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)
|
# Patch Notes - v2.36.8 (2026-05-02)
|
||||||
|
|
||||||
## 🔍 Intelligence & Stability: Review Preflight
|
## 🔍 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
|
- previews priority project files
|
||||||
- injects the result into the model context before the answer is generated
|
- injects the result into the model context before the answer is generated
|
||||||
- If access fails, the injected context includes the concrete failure reason.
|
- 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
|
## Changed Files
|
||||||
- `src/agent.ts`
|
- `src/agent.ts`
|
||||||
- `src/utils.ts`
|
- `src/utils.ts`
|
||||||
- `tests/systemPrompt.test.ts`
|
- `tests/systemPrompt.test.ts`
|
||||||
|
- `tests/localPathPreflight.test.ts`
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
- `./node_modules/.bin/tsc --noEmit`
|
- `./node_modules/.bin/tsc --noEmit`
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "g1nation",
|
"name": "g1nation",
|
||||||
"displayName": "G1nation",
|
"displayName": "G1nation",
|
||||||
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
|
"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",
|
"publisher": "connectailab",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
|
|||||||
+65
-14
@@ -416,9 +416,12 @@ export class AgentExecutor {
|
|||||||
|
|
||||||
// 5. Execute Actions
|
// 5. Execute Actions
|
||||||
const rationale = this.parseRationale(aiResponseText);
|
const rationale = this.parseRationale(aiResponseText);
|
||||||
const assistantContent = enforceProjectClaimPolicyInAnswer(
|
const assistantContent = this.enforceLocalPathReviewAnswer(
|
||||||
this.sanitizeAssistantContent(aiResponseText),
|
enforceProjectClaimPolicyInAnswer(
|
||||||
secondBrainTrace
|
this.sanitizeAssistantContent(aiResponseText),
|
||||||
|
secondBrainTrace
|
||||||
|
),
|
||||||
|
localPathContext
|
||||||
);
|
);
|
||||||
const traceMarkdown = secondBrainTrace
|
const traceMarkdown = secondBrainTrace
|
||||||
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
|
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
|
||||||
@@ -623,7 +626,8 @@ export class AgentExecutor {
|
|||||||
const sections: string[] = [
|
const sections: string[] = [
|
||||||
'[LOCAL PROJECT PATH PREFLIGHT]',
|
'[LOCAL PROJECT PATH PREFLIGHT]',
|
||||||
'The user provided a local project path for review or analysis. Use this inspected context before asking for uploads.',
|
'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)) {
|
for (const candidate of candidates.slice(0, 2)) {
|
||||||
@@ -665,14 +669,14 @@ export class AgentExecutor {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tree = this.listProjectTree(absPath, absPath, 0, 2, 80);
|
const tree = this.listProjectTree(absPath, absPath, 0, 4, 140);
|
||||||
const priorityFiles = this.findPriorityProjectFiles(absPath).slice(0, 8);
|
const priorityFiles = this.findPriorityProjectFiles(absPath).slice(0, 12);
|
||||||
const previews = priorityFiles.map((file) => {
|
const previews = priorityFiles.map((file) => {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(file, 'utf8');
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
return [
|
return [
|
||||||
`### ${path.relative(absPath, file)}`,
|
`### ${path.relative(absPath, file)}`,
|
||||||
summarizeText(content, 1800)
|
summarizeText(content, 2200)
|
||||||
].join('\n');
|
].join('\n');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return `### ${path.relative(absPath, file)}\nRead failed: ${error.message}`;
|
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 {
|
private listProjectTree(root: string, current: string, depth: number, maxDepth: number, limit: number): string {
|
||||||
if (limit <= 0 || depth > maxDepth) {
|
if (limit <= 0 || depth > maxDepth) {
|
||||||
return '';
|
return '';
|
||||||
@@ -741,8 +774,8 @@ export class AgentExecutor {
|
|||||||
'webpack.config.js'
|
'webpack.config.js'
|
||||||
]);
|
]);
|
||||||
const results: string[] = [];
|
const results: string[] = [];
|
||||||
const visit = (dir: string, depth: number) => {
|
const visit = (dir: string, depth: number, inSourceArea: boolean) => {
|
||||||
if (depth > 3 || results.length >= 14) return;
|
if (depth > 6 || results.length >= 24) return;
|
||||||
let entries: fs.Dirent[] = [];
|
let entries: fs.Dirent[] = [];
|
||||||
try {
|
try {
|
||||||
entries = fs.readdirSync(dir, { withFileTypes: true })
|
entries = fs.readdirSync(dir, { withFileTypes: true })
|
||||||
@@ -754,16 +787,19 @@ export class AgentExecutor {
|
|||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const fullPath = path.join(dir, entry.name);
|
const fullPath = path.join(dir, entry.name);
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
if (/^(src|app|pages|components|docs|lib|server|backend|frontend|config)$/i.test(entry.name)) {
|
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);
|
||||||
visit(fullPath, depth + 1);
|
if (nextInSourceArea) {
|
||||||
|
visit(fullPath, depth + 1, nextInSourceArea);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const relative = path.relative(root, fullPath);
|
const relative = path.relative(root, fullPath);
|
||||||
|
const isSourceCode = /\.(ts|tsx|js|jsx)$/i.test(entry.name);
|
||||||
if (
|
if (
|
||||||
exactNames.has(entry.name)
|
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)
|
|| /\.(config|rc)\.(js|ts|json)$/i.test(entry.name)
|
||||||
) {
|
) {
|
||||||
results.push(fullPath);
|
results.push(fullPath);
|
||||||
@@ -771,8 +807,23 @@ export class AgentExecutor {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
visit(root, 0);
|
visit(root, 0, false);
|
||||||
return Array.from(new Set(results));
|
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 {
|
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