From d2c9f624b88a2aee1d271f659ce4698fb5648910 Mon Sep 17 00:00:00 2001 From: g1nation Date: Sun, 3 May 2026 00:59:38 +0900 Subject: [PATCH] Version 2.43.0 Release: Contextual Project Trace & Overrides --- package.json | 2 +- src/agent.ts | 2 +- src/features/secondBrainTrace.ts | 32 +++++++++++++++++++++++-- tests/localPathPreflight.test.ts | 12 ++++++++++ tests/secondBrainTrace.test.ts | 40 ++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 36a4b43..992a2bc 100644 --- a/package.json +++ b/package.json @@ -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.42.0", + "version": "2.43.0", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/agent.ts b/src/agent.ts index 945f944..f790c64 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -843,7 +843,7 @@ export class AgentExecutor { } private isBlockingProjectKnowledgeAnswer(content: string): boolean { - return /(블로킹 질문|어떤 기능 영역|어떤 부분.*먼저|어떤 기능이나 아키텍처|구체적인 방향|방향 설정이 필요|명확히 알려주시면|우선적으로 정리|최종 사용 목적|Question reason)/i.test(content); + return /(블로킹 질문|어떤 기능 영역|어떤 부분.*먼저|어떤 기능이나 아키텍처|구체적인 방향|방향 설정이 필요|명확히 알려주시면|우선적으로 정리|최종 사용 목적|Question reason|별도의 파일 기록.*생성되지|파일 기록이 생성되지|더 깊이 있는 분석.*지정|해당 기능.*지정하여 요청)/i.test(content); } private buildProjectKnowledgeFallbackAnswer(localPathContext: string): string { diff --git a/src/features/secondBrainTrace.ts b/src/features/secondBrainTrace.ts index 80f8e2e..c396341 100644 --- a/src/features/secondBrainTrace.ts +++ b/src/features/secondBrainTrace.ts @@ -70,7 +70,8 @@ export function buildSecondBrainTrace(userQuery: string, brainRoot: string, opti const files = findBrainFiles(brainRoot) .filter((file) => includeRaw || !isRawConversationPath(path.relative(brainRoot, file))); const terms = tokenize(retrievalQuery); - const scored = files.map((file) => scoreFile(file, brainRoot, terms, queryIntent)) + const targetProject = inferTargetProject(query); + const scored = files.map((file) => scoreFile(file, brainRoot, terms, queryIntent, targetProject)) .filter((doc) => doc.score >= 0.25) .sort((a, b) => b.score - a.score) .slice(0, options.limit || 5); @@ -370,7 +371,15 @@ function tokenize(value: string): string[] { .filter((term) => term.length >= 2 && !stopWords.has(term)); } -function scoreFile(file: string, brainRoot: string, terms: string[], intent: SecondBrainQueryIntent): SecondBrainTraceDocument { +function inferTargetProject(query: string): string | undefined { + const pathMatch = query.match(/\/Volumes\/Data\/project\/Antigravity\/([^\s`"'<>/]+)/i); + if (pathMatch?.[1]) return pathMatch[1].toLowerCase(); + + const namedProject = query.match(/\b(connectai|datacollector|skybound)\b/i); + return namedProject?.[1]?.toLowerCase(); +} + +function scoreFile(file: string, brainRoot: string, terms: string[], intent: SecondBrainQueryIntent, targetProject?: string): SecondBrainTraceDocument { const relative = path.relative(brainRoot, file); const title = path.basename(file, path.extname(file)); const basename = relative.toLowerCase(); @@ -385,6 +394,9 @@ function scoreFile(file: string, brainRoot: string, terms: string[], intent: Sec const lower = content.toLowerCase(); let score = pathPriority(relative, intent); + if (targetProject) { + score += projectRelevanceScore(relative, lower, targetProject); + } for (const term of terms) { if (basename.includes(term)) score += 4; const matches = lower.split(term).length - 1; @@ -405,6 +417,22 @@ function scoreFile(file: string, brainRoot: string, terms: string[], intent: Sec }; } +function projectRelevanceScore(relativePath: string, lowerContent: string, targetProject: 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; + + if (normalized.includes('project_logs') && otherProject && otherProject !== targetProject) score -= 8; + return score; +} + function classifySourceType(relativePath: string, content: string): SecondBrainSourceType { const normalized = relativePath.toLowerCase(); const lower = content.toLowerCase(); diff --git a/tests/localPathPreflight.test.ts b/tests/localPathPreflight.test.ts index 270645a..9c8a925 100644 --- a/tests/localPathPreflight.test.ts +++ b/tests/localPathPreflight.test.ts @@ -113,4 +113,16 @@ describe('local project path preflight', () => { expect(fallback).toContain('ConnectAI Project Knowledge Overview'); expect(fallback).not.toContain('어떤 기능 영역을 가장 먼저'); }); + + it('treats no-record-created answers as incomplete project knowledge creation', () => { + const context: any = { + globalStorageUri: { fsPath: path.join(root, '.storage') }, + workspaceState: stateStore(), + globalState: stateStore() + }; + const agent = new AgentExecutor(context) as any; + const answer = 'Record Path Check: 요청하신 지식 생성 작업은 파일 목록 검토를 통해 완료되었으며, 별도의 파일 기록이 생성되지 않았습니다.'; + + expect(agent.isBlockingProjectKnowledgeAnswer(answer)).toBe(true); + }); }); diff --git a/tests/secondBrainTrace.test.ts b/tests/secondBrainTrace.test.ts index 837c787..38a4749 100644 --- a/tests/secondBrainTrace.test.ts +++ b/tests/secondBrainTrace.test.ts @@ -215,4 +215,44 @@ describe('Second Brain Trace', () => { expect(trace.retrievedDocuments[0].path).not.toContain('API Gateway.md'); expect(renderSecondBrainTraceContext(trace)).toContain('Approval likelihood is an inference'); }); + + it('prioritizes notes for the project named in a local Antigravity path', () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'second-brain-project-target-')); + try { + fs.mkdirSync(path.join(root, 'Project_Logs'), { recursive: true }); + fs.writeFileSync( + path.join(root, 'Project_Logs', '2026-04-25-Datacollector_Fix.md'), + [ + '# Datacollector Fix', + '', + 'Project: Datacollector', + 'Repository: `/Volumes/Data/project/Antigravity/Datacollector`', + 'project antigravity repository documentation knowledge' + ].join('\n'), + 'utf8' + ); + fs.writeFileSync( + path.join(root, 'Project_Logs', '2026-05-02-ConnectAI_Project_Knowledge.md'), + [ + '# ConnectAI Project Knowledge', + '', + 'Project: ConnectAI', + 'Repository: `/Volumes/Data/project/Antigravity/ConnectAI`', + 'ConnectAI project knowledge for local AI assistant, Second Brain Trace, and Project Chronicle.' + ].join('\n'), + 'utf8' + ); + + const trace = buildSecondBrainTrace( + '그러면 지금 /Volumes/Data/project/Antigravity/ConnectAI 프로젝트에 대한 지식을 만들어', + root, + { force: true } + ); + + expect(trace.retrievedDocuments[0].path).toContain('ConnectAI_Project_Knowledge.md'); + expect(trace.retrievedDocuments[0].path).not.toContain('Datacollector'); + } finally { + fs.rmSync(root, { recursive: true, force: true }); + } + }); });