Version 2.46.0 Release: Autonomous Anti-Blocking Generation and Trace Relevance Refinement

This commit is contained in:
g1nation
2026-05-03 01:13:33 +09:00
parent d2c9f624b8
commit fadf867978
5 changed files with 180 additions and 17 deletions
+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.43.0",
"version": "2.46.0",
"publisher": "connectailab",
"license": "MIT",
"icon": "assets/icon.png",
+96 -7
View File
@@ -433,8 +433,18 @@ export class AgentExecutor {
if (prompt && this.isSecondBrainInventoryRequest(prompt) && brainFiles.length > 0 && this.isNoBrainDataRefusal(assistantContent)) {
assistantContent = this.buildSecondBrainInventoryFallbackAnswer(activeBrain, brainFiles, secondBrainTrace);
}
if (prompt && localPathContext && this.isProjectKnowledgeCreationRequest(prompt) && this.isBlockingProjectKnowledgeAnswer(assistantContent)) {
assistantContent = this.buildProjectKnowledgeFallbackAnswer(localPathContext);
if (prompt && localPathContext && this.isProjectKnowledgeCreationRequest(prompt)) {
const record = this.writeProjectKnowledgeRecord(localPathContext);
if (this.isBlockingProjectKnowledgeAnswer(assistantContent)) {
assistantContent = this.buildProjectKnowledgeFallbackAnswer(localPathContext, record);
} else if (record && !assistantContent.includes(record.filePath)) {
assistantContent = [
assistantContent,
'',
'## 생성된 기록',
`프로젝트 지식 기록을 생성했습니다: \`${record.filePath}\``
].join('\n');
}
}
const traceMarkdown = secondBrainTrace
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
@@ -786,11 +796,11 @@ export class AgentExecutor {
try {
const content = fs.readFileSync(file, 'utf8');
return [
`### ${path.relative(absPath, file)}`,
`File: ${path.relative(absPath, file)}`,
summarizeText(content, 2200)
].join('\n');
} catch (error: any) {
return `### ${path.relative(absPath, file)}\nRead failed: ${error.message}`;
return `File: ${path.relative(absPath, file)}\nRead failed: ${error.message}`;
}
}).join('\n\n');
@@ -846,12 +856,12 @@ export class AgentExecutor {
return /(블로킹 질문|어떤 기능 영역|어떤 부분.*먼저|어떤 기능이나 아키텍처|구체적인 방향|방향 설정이 필요|명확히 알려주시면|우선적으로 정리|최종 사용 목적|Question reason|별도의 파일 기록.*생성되지|파일 기록이 생성되지|더 깊이 있는 분석.*지정|해당 기능.*지정하여 요청)/i.test(content);
}
private buildProjectKnowledgeFallbackAnswer(localPathContext: string): string {
private buildProjectKnowledgeFallbackAnswer(localPathContext: string, record?: { filePath: string; relativePath: string } | null): string {
const pathMatch = localPathContext.match(/Path:\s*(.+)/);
const projectPath = pathMatch?.[1]?.trim() || '제공된 로컬 프로젝트 경로';
const treeMatch = localPathContext.match(/Scanned tree:\n([\s\S]*?)(?:\nPriority file previews:|$)/);
const treePreview = treeMatch?.[1]?.trim().split('\n').slice(0, 18).join('\n') || '';
const priorityMatches = [...localPathContext.matchAll(/###\s+(.+)/g)].map((match) => match[1].trim()).slice(0, 10);
const priorityMatches = this.extractPriorityPreviewFiles(localPathContext).slice(0, 10);
const priorityText = priorityMatches.length
? priorityMatches.map((file) => `- ${file}`).join('\n')
: '- package.json, src, docs, config 계열 파일을 우선 확인';
@@ -891,10 +901,89 @@ export class AgentExecutor {
'```',
'',
'## 다음 액션',
'기본값으로는 위 초안을 프로젝트 지식 1번 문서로 저장하고, 그 다음 `agent.ts` 실행 흐름 지식을 별도 문서로 쪼개는 것이 좋습니다.'
record
? `프로젝트 지식 1번 문서를 생성했습니다: \`${record.filePath}\``
: '기본값으로는 위 초안을 프로젝트 지식 1번 문서로 저장하고, 그 다음 `agent.ts` 실행 흐름 지식을 별도 문서로 쪼개는 것이 좋습니다.'
].filter(Boolean).join('\n');
}
private extractPriorityPreviewFiles(localPathContext: string): string[] {
const fileMarkerMatches = [...localPathContext.matchAll(/^File:\s*(.+)$/gmi)]
.map((match) => match[1].trim());
if (fileMarkerMatches.length > 0) {
return Array.from(new Set(fileMarkerMatches));
}
const previewBlock = localPathContext.match(/Priority file previews:\n([\s\S]*)/)?.[1] || '';
return Array.from(new Set([...previewBlock.matchAll(/^###\s+(.+)$/gmi)]
.map((match) => match[1].trim())
.filter((value) => /[\\/]/.test(value) || /\.[a-z0-9]+$/i.test(value))));
}
private writeProjectKnowledgeRecord(localPathContext: string): { filePath: string; relativePath: string } | null {
const pathMatch = localPathContext.match(/Path:\s*(.+)/);
const projectPath = pathMatch?.[1]?.trim();
if (!projectPath || !localPathContext.includes('Access: succeeded')) return null;
try {
const projectName = path.basename(projectPath);
const today = new Date().toISOString().slice(0, 10);
const slug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'project';
const relativePath = path.join('docs', 'records', projectName, 'development', `${today}_${slug}_project_knowledge_overview.md`);
const filePath = path.join(projectPath, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, this.buildProjectKnowledgeMarkdown(localPathContext), 'utf8');
return { filePath, relativePath };
} catch (error: any) {
logError('Failed to write project knowledge record.', { error: error?.message || String(error) });
return null;
}
}
private buildProjectKnowledgeMarkdown(localPathContext: string): string {
const pathMatch = localPathContext.match(/Path:\s*(.+)/);
const projectPath = pathMatch?.[1]?.trim() || 'Unknown project path';
const projectName = path.basename(projectPath);
const treeMatch = localPathContext.match(/Scanned tree:\n([\s\S]*?)(?:\nPriority file previews:|$)/);
const treePreview = treeMatch?.[1]?.trim().split('\n').slice(0, 80).join('\n') || '';
const priorityFiles = this.extractPriorityPreviewFiles(localPathContext);
return [
`# ${projectName} Project Knowledge Overview`,
'',
`Date: ${new Date().toISOString()}`,
`Project: ${projectName}`,
`Repository: \`${projectPath}\``,
'',
'## Purpose',
`${projectName}는 VS Code 안에서 로컬 AI 에이전트, Second Brain, 프로젝트 기록, 에이전트 스킬을 연결하는 개발 보조 프로젝트다.`,
'',
'## Confirmed Structure',
'- `src/agent.ts`: 에이전트 실행, 로컬 경로 프리플라이트, Second Brain Trace, 액션 실행 흐름의 중심.',
'- `src/sidebarProvider.ts`: Webview UI, 브레인/모델/프로젝트 선택, 프롬프트 전달, 기록 UI를 담당.',
'- `src/features/secondBrainTrace.ts`: Second Brain 검색 결과와 근거 정책을 구성.',
'- `src/features/projectChronicle/`: 프로젝트 기록을 Markdown으로 관리하는 Chronicle 기능.',
'- `src/core/`: 큐, 이벤트, 트랜잭션, 오류 처리 등 실행 안정성 계층.',
'- `tests/`: Second Brain, 로컬 경로 프리플라이트, Chronicle, 보안/트랜잭션 회귀 테스트.',
'',
'## Evidence Files',
...(priorityFiles.length ? priorityFiles.map((file) => `- \`${file}\``) : ['- 확인된 우선 파일 없음']),
'',
'## Scanned Tree Excerpt',
'```text',
treePreview || '(no scanned tree captured)',
'```',
'',
'## Current Knowledge Gap',
'- 전체 아키텍처는 파일 구조와 일부 프리뷰 기준으로 파악 가능하지만, 세부 동작 지식은 `src/agent.ts`, `src/sidebarProvider.ts`, `secondBrainTrace.ts`, `projectChronicle` 순서로 심화 분석해 보강해야 한다.',
'',
'## Next Records',
'- `agent.ts` 실행 흐름 상세 분석',
'- Second Brain Trace 검색 및 근거 정책 분석',
'- Project Chronicle 기록 생성 흐름 분석'
].join('\n');
}
private listProjectTree(root: string, current: string, depth: number, maxDepth: number, limit: number): string {
if (limit <= 0 || depth > maxDepth) {
return '';
+17 -7
View File
@@ -390,12 +390,14 @@ function scoreFile(file: string, brainRoot: string, terms: string[], intent: Sec
content = '';
}
const sourceType = classifySourceType(relative, content);
const canSupportProjectClaim = sourceType === 'Project Evidence' || sourceType === 'User Decision';
const lower = content.toLowerCase();
const documentProject = inferDocumentProject(relative, lower);
const projectMatchesTarget = !targetProject || !documentProject || documentProject === targetProject;
const canSupportProjectClaim = projectMatchesTarget && (sourceType === 'Project Evidence' || sourceType === 'User Decision');
let score = pathPriority(relative, intent);
if (targetProject) {
score += projectRelevanceScore(relative, lower, targetProject);
score += projectRelevanceScore(relative, lower, targetProject, documentProject);
}
for (const term of terms) {
if (basename.includes(term)) score += 4;
@@ -417,17 +419,25 @@ function scoreFile(file: string, brainRoot: string, terms: string[], intent: Sec
};
}
function projectRelevanceScore(relativePath: string, lowerContent: string, targetProject: string): number {
function inferDocumentProject(relativePath: string, lowerContent: string): string | undefined {
const normalized = relativePath.toLowerCase();
const pathProject = `${normalized}\n${lowerContent}`.match(/\/volumes\/data\/project\/antigravity\/([a-z0-9_-]+)/i)
|| `${normalized}\n${lowerContent}`.match(/(?:^|[\\/])(connectai|datacollector|skybound)(?:[\\/]|_|-|\b)/i);
if (pathProject?.[1]) return pathProject[1].toLowerCase();
const labeledProject = lowerContent.match(/(?:project|프로젝트)\s*[:]\s*`?([a-z0-9_-]+)/i);
return labeledProject?.[1]?.toLowerCase();
}
function projectRelevanceScore(relativePath: string, lowerContent: string, targetProject: string, documentProject?: string): number {
const normalized = relativePath.toLowerCase();
let score = 0;
if (normalized.includes(targetProject)) score += 12;
const targetMatches = lowerContent.split(targetProject).length - 1;
if (targetMatches > 0) score += Math.min(targetMatches * 4, 20);
const otherProjectMatch = lowerContent.match(/(?:project|프로젝트|repository|repo)\s*[:]?\s*`?\/?volumes\/data\/project\/antigravity\/([a-z0-9_-]+)/i)
|| lowerContent.match(/(?:project|프로젝트)\s*[:]\s*([a-z0-9_-]+)/i);
const otherProject = otherProjectMatch?.[1]?.toLowerCase();
if (otherProject && otherProject !== targetProject) score -= 24;
const otherProject = documentProject && documentProject !== targetProject ? documentProject : undefined;
if (otherProject) score -= 32;
if (normalized.includes('project_logs') && otherProject && otherProject !== targetProject) score -= 8;
return score;
+51 -2
View File
@@ -101,9 +101,9 @@ describe('local project path preflight', () => {
'src/',
'src/agent.ts',
'Priority file previews:',
'### package.json',
'File: package.json',
'{"name":"g1nation"}',
'### src/agent.ts',
'File: src/agent.ts',
'export class AgentExecutor {}'
].join('\n');
@@ -125,4 +125,53 @@ describe('local project path preflight', () => {
expect(agent.isBlockingProjectKnowledgeAnswer(answer)).toBe(true);
});
it('extracts only preview file markers, not markdown headings inside previews', () => {
const context: any = {
globalStorageUri: { fsPath: path.join(root, '.storage') },
workspaceState: stateStore(),
globalState: stateStore()
};
const agent = new AgentExecutor(context) as any;
const localPathContext = [
'Priority file previews:',
'File: README.md',
'### 1. 벡터화된 고성능 추론 엔진',
'File: src/agent.ts',
'export class AgentExecutor {}'
].join('\n');
expect(agent.extractPriorityPreviewFiles(localPathContext)).toEqual(['README.md', 'src/agent.ts']);
});
it('writes a project knowledge record for accessible local project context', () => {
const context: any = {
globalStorageUri: { fsPath: path.join(root, '.storage') },
workspaceState: stateStore(),
globalState: stateStore()
};
const agent = new AgentExecutor(context) as any;
const localPathContext = [
`Path: ${root}`,
'Access: succeeded',
'Type: directory',
'Scanned tree:',
'package.json',
'src/',
'Priority file previews:',
'File: package.json',
'{"name":"g1nation"}',
'File: src/features/game/systems/CombatSystem.ts',
'export class CombatSystem {}'
].join('\n');
const record = agent.writeProjectKnowledgeRecord(localPathContext);
expect(record?.filePath).toBeTruthy();
expect(fs.existsSync(record.filePath)).toBe(true);
const content = fs.readFileSync(record.filePath, 'utf8');
expect(content).toContain('Project Knowledge Overview');
expect(content).toContain('package.json');
expect(content).toContain('src/features/game/systems/CombatSystem.ts');
});
});
+15
View File
@@ -242,6 +242,18 @@ describe('Second Brain Trace', () => {
].join('\n'),
'utf8'
);
fs.writeFileSync(
path.join(root, 'Project_Logs', '2026-04-26-Skybound_User_Decision.md'),
[
'# Skybound User Decision',
'',
'## Decision',
'Reward card clarity was improved.',
'- `/Volumes/Data/project/Antigravity/Skybound/src/features/game/hooks/useGameEngine.ts`',
'project antigravity connectai knowledge documentation'
].join('\n'),
'utf8'
);
const trace = buildSecondBrainTrace(
'그러면 지금 /Volumes/Data/project/Antigravity/ConnectAI 프로젝트에 대한 지식을 만들어',
@@ -251,6 +263,9 @@ describe('Second Brain Trace', () => {
expect(trace.retrievedDocuments[0].path).toContain('ConnectAI_Project_Knowledge.md');
expect(trace.retrievedDocuments[0].path).not.toContain('Datacollector');
const skybound = trace.retrievedDocuments.find((doc) => doc.path.includes('Skybound'));
expect(skybound?.canSupportProjectClaim).not.toBe(true);
expect(trace.projectClaimPolicy).not.toBe('cautious');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}