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(); 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(path.join('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('코드를 업로드해 주시면'); }); it('treats project knowledge creation requests with local paths as inspectable work', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const prompt = '그러면 지금 /Volumes/Data/project/Antigravity/ConnectAI 이 프로젝트에 대한 지식을 만들면 되는거 아니야?'; expect(agent.shouldPreflightLocalProjectPath(prompt)).toBe(true); }); it('does not mistake a knowledge collection tool review for project knowledge creation', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const prompt = '/Volumes/Data/project/Antigravity/Datacollector_MAC 이 프로젝트는 재가 지식을 수집하는데 사용하는 프로그램이야. 이 프로그램을 너가 코드리뷰를 하고 이 프로그램에 대한 너의 평가를 듣고 싶어. 장점과 단점, 앞으로의 확장성은 어떻게 잡아야할지.'; expect(agent.shouldPreflightLocalProjectPath(prompt)).toBe(true); expect(agent.isProjectReviewEvaluationRequest(prompt)).toBe(true); expect(agent.isProjectKnowledgeCreationRequest(prompt)).toBe(false); }); it('classifies local project intent by requested task instead of subject words', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const projectPath = '/Volumes/Data/project/Antigravity/Datacollector_MAC'; expect(agent.classifyLocalProjectIntent(`${projectPath} 지식 수집용 앱인데 한번 봐줘. 앞으로 확장성은?`)).toBe('review-evaluation'); expect(agent.classifyLocalProjectIntent(`${projectPath} 데이터 수집 프로그램이야. 장단점과 리스크 평가해줘.`)).toBe('review-evaluation'); expect(agent.classifyLocalProjectIntent(`${projectPath} 이 프로젝트에 대한 지식을 만들어줘.`)).toBe('knowledge-creation'); expect(agent.classifyLocalProjectIntent(`${projectPath} 이 프로젝트 README와 사용 가이드를 문서로 정리해줘.`)).toBe('documentation'); expect(agent.classifyLocalProjectIntent(`${projectPath} 이 프로젝트의 아키텍처 방향은 어떻게 생각해?`)).toBe('thinking'); }); it('adds deep review lenses for local project review intent', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const guidance = agent.buildLocalProjectIntentGuidance('review-evaluation'); expect(guidance).toContain('purpose fit'); expect(guidance).toContain('architecture shape'); expect(guidance).toContain('data/control flow'); expect(guidance).toContain('failure recovery'); expect(guidance).toContain('operability/observability'); expect(guidance).toContain('extensibility'); }); it('adds an Astra stance layer for opinionated project collaboration', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const prompt = '/Volumes/Data/project/Antigravity/Datacollector_MAC 이 지식 수집 앱을 평가해줘. 장점 단점 확장성도 보고 싶어.'; const localPathContext = [ 'Path: /Volumes/Data/project/Antigravity/Datacollector_MAC', 'Access: succeeded' ].join('\n'); const stance = agent.buildAstraStanceContext(prompt, localPathContext); expect(stance).toContain('[ASTRA STANCE LAYER]'); expect(stance).toContain('not a template'); expect(stance).toContain('State the real bet'); expect(stance).toContain('Review stance'); expect(stance).toContain('would rely on this project today'); expect(stance).toContain('Local project intent for tone: review-evaluation'); }); it('quality-gates template-like review answers into an Astra verdict', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const prompt = '/Volumes/Data/project/Antigravity/Datacollector_MAC 이 지식 수집 앱을 평가해줘. 장점 단점 확장성도 보고 싶어.'; const localPathContext = [ 'Path: /Volumes/Data/project/Antigravity/Datacollector_MAC', 'Access: succeeded' ].join('\n'); const templateAnswer = [ '## 간단 요약', '좋은 프로젝트입니다.', '## 요청 요약', '프로젝트 평가 요청입니다.', '## 추론된 사용자 의도', '방향성을 알고 싶어합니다.' ].join('\n'); const gated = agent.applyAstraQualityGate(templateAnswer, prompt, localPathContext); expect(gated).toContain('## Astra 판단'); expect(gated).toContain('## 내가 보는 위험'); expect(gated).toContain('## 다음 한 수'); expect(gated).toContain('실제로 의존해도 되는 도구'); }); it('does not quality-gate tiny non-project replies', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const answer = '좋아요. 바로 진행할게요.'; expect(agent.applyAstraQualityGate(answer, '좋아', '')).toBe(answer); }); it('treats architecture opinion requests with local paths as inspectable work', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const prompt = '프로젝트 경로는 /Volumes/Data/project/Antigravity/ConnectAI 이야. 그럼 이 프로젝트에 대한 설계, 아키텍처는 어떤거 같아?'; expect(agent.shouldPreflightLocalProjectPath(prompt)).toBe(true); expect(agent.isThinkingPartnerRequest(prompt)).toBe(true); }); it('adds concrete Astra mode architecture context for Guard and MA design questions', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const prompt = '지금 우리는 guard 모드가 있고 MA 모드가 있는데 굳이 이렇게 모드를 분리해서 사용하는게 좋을까?'; expect(agent.isAstraModeArchitectureQuestion(prompt)).toBe(true); const modeContext = agent.buildAstraModeArchitectureContext(prompt); expect(modeContext).toContain('Confirmed implementation facts'); expect(modeContext).toContain('Guard should be an always-on policy/context layer'); expect(modeContext).toContain('MA should be an optional execution strategy'); expect(modeContext).toContain('richer context assembly'); }); it('keeps Guard and MA out of the visible sidebar controls', () => { const sidebarSource = fs.readFileSync(path.join(__dirname, '..', 'src', 'sidebarProvider.ts'), 'utf8'); expect(sidebarSource).not.toContain('id="designerGuardBtn"'); expect(sidebarSource).not.toContain('id="multiAgentBtn"'); }); it('routes multi-agent automatically for report-style tasks but not local project preflight', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; expect(agent.shouldUseMultiAgentWorkflow('시장 조사 기반으로 긴 보고서를 작성해줘', false)).toBe(true); expect(agent.shouldUseMultiAgentWorkflow('프로젝트 경로는 /Volumes/Data/project/Antigravity/ConnectAI 이야. 아키텍처 봐줘', false)).toBe(false); expect(agent.shouldUseMultiAgentWorkflow('guard 모드와 MA 모드를 분리하는게 좋을까?', true)).toBe(false); }); it('removes file-structure requests when knowledge creation path 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('핵심 파일이나 구조를 저에게 제공'); expect(fixed).not.toContain('실제 구현 근거 없이는'); }); it('replaces blocking scope questions for local 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 = '## 블로킹 질문\n1. 어떤 기능 영역을 가장 먼저 분석하고 싶으신가요? (Question reason: 범위 조정)'; const localPathContext = [ 'Path: /Volumes/Data/project/Antigravity/ConnectAI', 'Access: succeeded', 'Type: directory', 'Scanned tree:', 'src/', 'src/agent.ts', 'Priority file previews:', 'File: package.json', '{"name":"g1nation"}', 'File: src/agent.ts', 'export class AgentExecutor {}' ].join('\n'); expect(agent.isBlockingProjectKnowledgeAnswer(answer)).toBe(true); const fallback = agent.buildProjectKnowledgeFallbackAnswer(localPathContext); expect(fallback).toContain('추가 질문으로 멈출 필요 없이'); expect(fallback).toContain('Astra Project Knowledge Overview'); expect(fallback).not.toContain('어떤 기능 영역을 가장 먼저'); }); it('replaces misrouted project-knowledge answers for code review requests', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const localPathContext = [ 'Path: /Volumes/Data/project/Antigravity/Datacollector_MAC', 'Access: succeeded', 'Type: directory', 'Scanned tree:', 'src/App.tsx', 'src/lib/engine.ts', 'src/lib/api.ts', 'Priority file previews:', 'File: src/lib/engine.ts', 'export async function runMission() {}', 'File: src/lib/api.ts', 'export async function fetchSources() {}' ].join('\n'); const wrongAnswer = '## 기본 지식 생성 방향\n## 바로 만들 지식 초안\n# Datacollector_MAC Project Knowledge Overview'; expect(agent.isMisroutedProjectKnowledgeAnswer(wrongAnswer)).toBe(true); const fixed = agent.buildProjectReviewFallbackAnswer(localPathContext); expect(fixed).toContain('코드리뷰와 제품 평가 요청'); expect(fixed).toContain('## 코드리뷰 관점 평가'); expect(fixed).toContain('목적 적합성'); expect(fixed).toContain('데이터/제어 흐름'); expect(fixed).toContain('실패 복구'); expect(fixed).toContain('운영성/관측성'); expect(fixed).toContain('## 확장성 방향'); expect(fixed).not.toContain('바로 만들 지식 초안'); }); it('detects shallow project review answers', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; expect(agent.isShallowProjectReviewAnswer('좋은 프로젝트입니다. 장점은 구조가 좋고 단점은 테스트가 필요합니다.')).toBe(true); expect(agent.isShallowProjectReviewAnswer([ '## 코드리뷰 관점 평가', '목적 적합성, 아키텍처 구조, 데이터 흐름, 실패 복구, 운영 로그, 확장성을 기준으로 봅니다.' ].join('\n'))).toBe(false); }); 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); }); 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'); }); it('uses the latest project knowledge record for architecture follow-up questions without a path', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const recordDir = path.join(root, 'docs', 'records', path.basename(root), 'development'); fs.mkdirSync(recordDir, { recursive: true }); const recordPath = path.join(recordDir, '2026-05-02_project_knowledge_overview.md'); fs.writeFileSync( recordPath, [ '# Project Knowledge Overview', '', '## Confirmed Structure', '- `src/agent.ts`: 에이전트 실행 흐름의 중심.', '- `src/sidebarProvider.ts`: Webview UI를 담당.' ].join('\n'), 'utf8' ); const followup = agent.buildRecentProjectKnowledgeContext('이젠 아키텍처에 대한 조사도 해주면 좋을 것 같아.', root); expect(followup).toContain('[RECENT LOCAL PROJECT KNOWLEDGE]'); expect(followup).toContain(recordPath); expect(followup).toContain('src/agent.ts'); expect(followup).toContain('recently generated project knowledge record'); }); it('removes old Second Brain trace details from assistant history before model requests', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const history = [ { role: 'user', content: 'ConnectAI 지식 만들어줘' }, { role: 'assistant', content: [ '## 간단 요약', 'ConnectAI 프로젝트 지식을 만들었습니다.', '
', '2nd Brain Trace: 사용함', 'Project_Logs/2026-04-25-Datacollector_Fix.md', '## Second Brain Debug JSON', '{"path":"Datacollector"}', '
' ].join('\n') } ]; const requestHistory = agent.buildRequestHistory(history); expect(requestHistory[1].content).toContain('ConnectAI 프로젝트 지식을 만들었습니다'); expect(requestHistory[1].content).not.toContain('Datacollector'); expect(requestHistory[1].content).not.toContain('2nd Brain Trace'); }); it('adds visible evidence when answering from recent project knowledge context', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const recordPath = '/Volumes/Data/project/Antigravity/ConnectAI/docs/records/ConnectAI/development/2026-05-02_connectai_project_knowledge_overview.md'; const recentContext = [ '[RECENT LOCAL PROJECT KNOWLEDGE]', `Use this recently generated project knowledge record as project evidence: ${recordPath}`, '', '# Astra Project Knowledge Overview', '', '## Evidence Files', '- `package.json`', '- `src/agent.ts`', '- `src/sidebarProvider.ts`' ].join('\n'); const answer = '## 간단 요약\nConnectAI 아키텍처는 실행 흐름 분리를 중심으로 구성됩니다.'; const fixed = agent.ensureRecentProjectKnowledgeEvidence(answer, recentContext); expect(fixed).toContain('## 근거'); expect(fixed).toContain(recordPath); expect(fixed).toContain('`src/agent.ts`'); expect(fixed).toContain('최근 생성된 프로젝트 지식 기록'); }); it('adds visible evidence when answering from an explicit local project path', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const localPathContext = [ 'Path: /Volumes/Data/project/Antigravity/ConnectAI', 'Access: succeeded', 'Type: directory', 'Scanned tree:', 'package.json', 'src/agent.ts', 'Priority file previews:', 'File: package.json', '```json', '{"name":"connectai"}', '```', 'File: src/agent.ts', '```ts', 'export class AgentExecutor {}', '```' ].join('\n'); const answer = '## 간단 요약\nConnectAI 아키텍처는 실행 흐름 분리를 중심으로 구성됩니다.'; const fixed = agent.ensureLocalProjectPathEvidence(answer, localPathContext); expect(fixed).toContain('## 근거'); expect(fixed).toContain('/Volumes/Data/project/Antigravity/ConnectAI'); expect(fixed).toContain('`package.json`'); expect(fixed).toContain('`src/agent.ts`'); expect(fixed).toContain('로컬 프로젝트 경로'); }); it('builds a Jarvis project brief for architecture thinking-partner questions', () => { const context: any = { globalStorageUri: { fsPath: path.join(root, '.storage') }, workspaceState: stateStore(), globalState: stateStore() }; const agent = new AgentExecutor(context) as any; const localPathContext = [ 'Path: /Volumes/Data/project/Antigravity/ConnectAI', 'Access: succeeded', 'Type: directory', 'Scanned tree:', 'package.json', 'src/agent.ts', 'src/features/secondBrainTrace.ts', 'Priority file previews:', 'File: package.json', '{"name":"connectai"}', 'File: src/agent.ts', 'export class AgentExecutor {}' ].join('\n'); const brief = agent.buildJarvisProjectBriefContext( '그럼 이 프로젝트에 대한 설계, 아키텍처는 어떤거 같아?', localPathContext, '' ); expect(brief).toContain('[JARVIS PROJECT BRIEF]'); expect(brief).toContain('/Volumes/Data/project/Antigravity/ConnectAI'); expect(brief).toContain('src/agent.ts'); expect(brief).toContain('Thinking partner response contract'); expect(brief).toContain('direct verdict'); expect(brief).toContain('decision fork'); }); });