Version 2.46.0 Release: Autonomous Anti-Blocking Generation and Trace Relevance Refinement
This commit is contained in:
+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.43.0",
|
||||
"version": "2.46.0",
|
||||
"publisher": "connectailab",
|
||||
"license": "MIT",
|
||||
"icon": "assets/icon.png",
|
||||
|
||||
+96
-7
@@ -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 '';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user