208 lines
9.1 KiB
TypeScript
208 lines
9.1 KiB
TypeScript
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('코드를 업로드해 주시면');
|
|
});
|
|
|
|
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('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('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);
|
|
});
|
|
|
|
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');
|
|
});
|
|
});
|