Release: v2.36.9 - Integrate Blog Production Manual & Writing Patterns
This commit is contained in:
@@ -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
@@ -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",
|
||||
|
||||
+63
-12
@@ -416,9 +416,12 @@ export class AgentExecutor {
|
||||
|
||||
// 5. Execute Actions
|
||||
const rationale = this.parseRationale(aiResponseText);
|
||||
const assistantContent = enforceProjectClaimPolicyInAnswer(
|
||||
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 {
|
||||
|
||||
@@ -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('코드를 업로드해 주시면');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user