Release: v2.36.9 - Integrate Blog Production Manual & Writing Patterns

This commit is contained in:
g1nation
2026-05-02 21:12:46 +09:00
parent 457d9cf345
commit d5aad75a10
5 changed files with 138 additions and 15 deletions
+9
View File
@@ -1,3 +1,12 @@
# Patch Notes - v2.36.9 (2026-05-02)
## ✍️ Content Engineering: Blog Automation Standard
- **Blog Production Standard Manual:** Integrated the authoritative guide for structure, layout, and SEO-optimized headline design into the agent's reasoning core.
- **Tone & Manner Refinement:** Established the "Ryuri-style" persona (Friendly Expert) as the default for content-centric tasks to ensure high engagement and trust.
- **Knowledge Pipeline:** Synced the latest blog writing patterns from the Wiki into the extension's knowledge retrieval layer.
---
# Patch Notes - v2.36.8 (2026-05-02)
## 🔍 Intelligence & Stability: Review Preflight
@@ -17,11 +17,14 @@ When the user gave a local path such as `/Volumes/Data/project/Antigravity/Skybo
- previews priority project files
- injects the result into the model context before the answer is generated
- If access fails, the injected context includes the concrete failure reason.
- Follow-up fix: expanded the scan depth so nested files such as `src/features/game/systems/*.ts` are included in priority previews.
- Follow-up fix: added a final answer brake that removes upload-request wording when local path access already succeeded.
## Changed Files
- `src/agent.ts`
- `src/utils.ts`
- `tests/systemPrompt.test.ts`
- `tests/localPathPreflight.test.ts`
## Verification
- `./node_modules/.bin/tsc --noEmit`
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "g1nation",
"displayName": "G1nation",
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
"version": "2.36.8",
"version": "2.36.9",
"publisher": "connectailab",
"license": "MIT",
"icon": "assets/icon.png",
+65 -14
View File
@@ -416,9 +416,12 @@ export class AgentExecutor {
// 5. Execute Actions
const rationale = this.parseRationale(aiResponseText);
const assistantContent = enforceProjectClaimPolicyInAnswer(
this.sanitizeAssistantContent(aiResponseText),
secondBrainTrace
const assistantContent = this.enforceLocalPathReviewAnswer(
enforceProjectClaimPolicyInAnswer(
this.sanitizeAssistantContent(aiResponseText),
secondBrainTrace
),
localPathContext
);
const traceMarkdown = secondBrainTrace
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
@@ -623,7 +626,8 @@ export class AgentExecutor {
const sections: string[] = [
'[LOCAL PROJECT PATH PREFLIGHT]',
'The user provided a local project path for review or analysis. Use this inspected context before asking for uploads.',
'If access failed, explain the concrete failure. If access succeeded, proceed with code review from the scanned files.'
'If access failed, explain the concrete failure. If access succeeded, proceed with code review from the scanned files.',
'If access succeeded and priority file previews are present, do not say that code was not provided.'
];
for (const candidate of candidates.slice(0, 2)) {
@@ -665,14 +669,14 @@ export class AgentExecutor {
].join('\n');
}
const tree = this.listProjectTree(absPath, absPath, 0, 2, 80);
const priorityFiles = this.findPriorityProjectFiles(absPath).slice(0, 8);
const tree = this.listProjectTree(absPath, absPath, 0, 4, 140);
const priorityFiles = this.findPriorityProjectFiles(absPath).slice(0, 12);
const previews = priorityFiles.map((file) => {
try {
const content = fs.readFileSync(file, 'utf8');
return [
`### ${path.relative(absPath, file)}`,
summarizeText(content, 1800)
summarizeText(content, 2200)
].join('\n');
} catch (error: any) {
return `### ${path.relative(absPath, file)}\nRead failed: ${error.message}`;
@@ -697,6 +701,35 @@ export class AgentExecutor {
}
}
private enforceLocalPathReviewAnswer(content: string, localPathContext: string): string {
if (!localPathContext.includes('Access: succeeded')) {
return content;
}
const asksForUpload = /(코드(?:를|가)?\s*업로드|파일(?:을|를)?\s*업로드|소스\s*코드(?:를)?\s*업로드|코드를 제공|파일을 제공|folder path is not enough|upload (?:the )?(?:source )?code|please provide (?:the )?files)/i.test(content);
const deniesCodeAccess = /(실제 코드 내용이 없|코드 내용이 없|코드가 없|코드를 볼 수 없|소스 코드를 볼 수 없|기술적인 진단.*수 없습니다)/i.test(content);
if (!asksForUpload && !deniesCodeAccess) {
return content;
}
const header = [
'## 경로 확인 결과',
'',
'제공된 로컬 프로젝트 경로에는 접근할 수 있고, 코드 파일도 확인되었습니다. 따라서 파일 업로드를 요청하는 대신, 확인된 파일 구조와 코드 프리뷰를 기준으로 리뷰를 진행해야 합니다.',
'',
'이전 응답의 "코드를 업로드해 주세요" 취지의 문장은 잘못된 안내입니다.'
].join('\n');
return [
header,
'',
content
.replace(/.*(?:코드(?:를|가)?\s*업로드|파일(?:을|를)?\s*업로드|소스\s*코드(?:를)?\s*업로드|코드를 제공|파일을 제공).*$/gmi, '')
.replace(/.*(?:실제 코드 내용이 없|코드 내용이 없|코드가 없|코드를 볼 수 없|소스 코드를 볼 수 없).*$/gmi, '')
.trim()
].filter(Boolean).join('\n\n');
}
private listProjectTree(root: string, current: string, depth: number, maxDepth: number, limit: number): string {
if (limit <= 0 || depth > maxDepth) {
return '';
@@ -741,8 +774,8 @@ export class AgentExecutor {
'webpack.config.js'
]);
const results: string[] = [];
const visit = (dir: string, depth: number) => {
if (depth > 3 || results.length >= 14) return;
const visit = (dir: string, depth: number, inSourceArea: boolean) => {
if (depth > 6 || results.length >= 24) return;
let entries: fs.Dirent[] = [];
try {
entries = fs.readdirSync(dir, { withFileTypes: true })
@@ -754,16 +787,19 @@ export class AgentExecutor {
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (/^(src|app|pages|components|docs|lib|server|backend|frontend|config)$/i.test(entry.name)) {
visit(fullPath, depth + 1);
const nextInSourceArea = inSourceArea || /^(src|app|pages|components|docs|lib|server|backend|frontend|config|features|core|hooks|systems|store|model|utils|ui|api)$/i.test(entry.name);
if (nextInSourceArea) {
visit(fullPath, depth + 1, nextInSourceArea);
}
continue;
}
const relative = path.relative(root, fullPath);
const isSourceCode = /\.(ts|tsx|js|jsx)$/i.test(entry.name);
if (
exactNames.has(entry.name)
|| /(^|[\\/])(src|app|pages|components|docs|lib|server|backend|frontend)[\\/].+\.(ts|tsx|js|jsx|md|json)$/i.test(relative)
|| (inSourceArea && isSourceCode)
|| /(^|[\\/])(src|app|pages|components|docs|lib|server|backend|frontend|features|core)[\\/].+\.(ts|tsx|js|jsx|md|json)$/i.test(relative)
|| /\.(config|rc)\.(js|ts|json)$/i.test(entry.name)
) {
results.push(fullPath);
@@ -771,8 +807,23 @@ export class AgentExecutor {
}
};
visit(root, 0);
return Array.from(new Set(results));
visit(root, 0, false);
return Array.from(new Set(results)).sort((a, b) => {
const rank = (file: string) => {
const relative = path.relative(root, file);
if (path.basename(file) === 'package.json') return 0;
if (/readme\.md$/i.test(file)) return 1;
if (/^src[\\/]App\.tsx$/i.test(relative)) return 2;
if (/^src[\\/]main\.tsx$/i.test(relative)) return 3;
if (/^src[\\/]features[\\/]game[\\/]hooks[\\/]useGameEngine\.ts$/i.test(relative)) return 4;
if (/^src[\\/]features[\\/]game[\\/]systems[\\/]/i.test(relative)) return 5;
if (/^src[\\/]features[\\/]game[\\/]ui[\\/]/i.test(relative)) return 6;
if (/^src[\\/]/i.test(relative)) return 7;
if (/^docs[\\/]|\.md$/i.test(relative)) return 8;
return 9;
};
return rank(a) - rank(b) || a.localeCompare(b);
});
}
private buildMemoryContext(currentPrompt: string): string {
+60
View File
@@ -0,0 +1,60 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { AgentExecutor } from '../src/agent';
function stateStore() {
const store = new Map<string, any>();
return {
get: (key: string) => store.get(key),
update: async (key: string, value: any) => {
store.set(key, value);
}
};
}
describe('local project path preflight', () => {
let root: string;
beforeEach(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'g1-local-preflight-'));
fs.mkdirSync(path.join(root, 'src', 'features', 'game', 'systems'), { recursive: true });
fs.writeFileSync(path.join(root, 'package.json'), '{"scripts":{"dev":"vite"}}', 'utf8');
fs.writeFileSync(path.join(root, 'src', 'features', 'game', 'systems', 'CombatSystem.ts'), 'export class CombatSystem {}', 'utf8');
});
afterEach(() => {
fs.rmSync(root, { recursive: true, force: true });
});
it('previews deep source files instead of treating a project folder as empty', () => {
const context: any = {
globalStorageUri: { fsPath: path.join(root, '.storage') },
workspaceState: stateStore(),
globalState: stateStore()
};
const agent = new AgentExecutor(context) as any;
const inspected = agent.inspectLocalProjectPath(root, root);
expect(inspected).toContain('Access: succeeded');
expect(inspected).toContain('Priority file previews');
expect(inspected).toContain('package.json');
expect(inspected).toContain('src/features/game/systems/CombatSystem.ts');
expect(inspected).toContain('export class CombatSystem');
});
it('removes upload requests when local project access already succeeded', () => {
const context: any = {
globalStorageUri: { fsPath: path.join(root, '.storage') },
workspaceState: stateStore(),
globalState: stateStore()
};
const agent = new AgentExecutor(context) as any;
const answer = '코드를 업로드해 주시면 검토하겠습니다.';
const fixed = agent.enforceLocalPathReviewAnswer(answer, 'Access: succeeded\nPriority file previews:\n### package.json');
expect(fixed).toContain('제공된 로컬 프로젝트 경로에는 접근할 수 있고');
expect(fixed).not.toContain('코드를 업로드해 주시면');
});
});