Version 2.50.0 Release: Autonomous Turn-Based Recording and UI Streamlining
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "g1nation",
|
"name": "g1nation",
|
||||||
"displayName": "G1nation",
|
"displayName": "G1nation",
|
||||||
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
|
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
|
||||||
"version": "2.48.0",
|
"version": "2.50.0",
|
||||||
"publisher": "connectailab",
|
"publisher": "connectailab",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
|
|||||||
@@ -452,6 +452,12 @@ export class AgentExecutor {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prompt && recentProjectKnowledgeContext) {
|
||||||
|
assistantContent = this.ensureRecentProjectKnowledgeEvidence(assistantContent, recentProjectKnowledgeContext);
|
||||||
|
}
|
||||||
|
if (prompt && localPathContext) {
|
||||||
|
assistantContent = this.ensureLocalProjectPathEvidence(assistantContent, localPathContext);
|
||||||
|
}
|
||||||
const traceMarkdown = secondBrainTrace
|
const traceMarkdown = secondBrainTrace
|
||||||
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
|
? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug)
|
||||||
: '';
|
: '';
|
||||||
@@ -824,6 +830,74 @@ export class AgentExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ensureRecentProjectKnowledgeEvidence(content: string, recentProjectKnowledgeContext: string): string {
|
||||||
|
const recordPath = this.extractRecentProjectKnowledgeRecordPath(recentProjectKnowledgeContext);
|
||||||
|
if (!recordPath || content.includes(recordPath)) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const evidenceFiles = this.extractEvidenceFilesFromProjectKnowledge(recentProjectKnowledgeContext).slice(0, 8);
|
||||||
|
const evidenceSection = [
|
||||||
|
'## 근거',
|
||||||
|
`이번 답변은 최근 생성된 프로젝트 지식 기록과 로컬 프로젝트 구조를 기준으로 작성했습니다: \`${recordPath}\``,
|
||||||
|
evidenceFiles.length
|
||||||
|
? `확인된 근거 파일:\n${evidenceFiles.map((file) => `- \`${file}\``).join('\n')}`
|
||||||
|
: ''
|
||||||
|
].filter(Boolean).join('\n\n');
|
||||||
|
|
||||||
|
return [
|
||||||
|
content.trim(),
|
||||||
|
'',
|
||||||
|
evidenceSection
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureLocalProjectPathEvidence(content: string, localPathContext: string): string {
|
||||||
|
if (!localPathContext.includes('Access: succeeded') || content.includes('## 근거')) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathMatch = localPathContext.match(/Path:\s*(.+)/);
|
||||||
|
const projectPath = pathMatch?.[1]?.trim();
|
||||||
|
const evidenceFiles = this.extractPriorityPreviewFiles(localPathContext).slice(0, 10);
|
||||||
|
if (!projectPath && evidenceFiles.length === 0) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const evidenceSection = [
|
||||||
|
'## 근거',
|
||||||
|
projectPath
|
||||||
|
? `이번 답변은 로컬 프로젝트 경로 \`${projectPath}\`에서 확인한 파일 구조와 코드 프리뷰를 기준으로 작성했습니다.`
|
||||||
|
: '이번 답변은 확인된 로컬 프로젝트 파일 구조와 코드 프리뷰를 기준으로 작성했습니다.',
|
||||||
|
evidenceFiles.length
|
||||||
|
? `확인된 근거 파일:\n${evidenceFiles.map((file) => `- \`${file}\``).join('\n')}`
|
||||||
|
: ''
|
||||||
|
].filter(Boolean).join('\n\n');
|
||||||
|
|
||||||
|
return [
|
||||||
|
content.trim(),
|
||||||
|
'',
|
||||||
|
evidenceSection
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractRecentProjectKnowledgeRecordPath(recentProjectKnowledgeContext: string): string | null {
|
||||||
|
return recentProjectKnowledgeContext.match(/project evidence:\s*(\/Volumes\/Data\/project\/Antigravity\/[^\s`"'<>]+\.md)/i)?.[1] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractEvidenceFilesFromProjectKnowledge(recentProjectKnowledgeContext: string): string[] {
|
||||||
|
const evidenceBlock = recentProjectKnowledgeContext.match(/## Evidence Files\n([\s\S]*?)(?=\n## |\n# |$)/i)?.[1] || '';
|
||||||
|
const evidenceFiles = [...evidenceBlock.matchAll(/-\s+`([^`]+)`/g)].map((match) => match[1].trim());
|
||||||
|
if (evidenceFiles.length > 0) {
|
||||||
|
return Array.from(new Set(evidenceFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
const structureBlock = recentProjectKnowledgeContext.match(/## Confirmed Structure\n([\s\S]*?)(?=\n## |\n# |$)/i)?.[1] || '';
|
||||||
|
return Array.from(new Set([...structureBlock.matchAll(/`([^`]+)`/g)]
|
||||||
|
.map((match) => match[1].trim())
|
||||||
|
.filter((value) => /[\\/]/.test(value) || /\.[a-z0-9]+$/i.test(value))));
|
||||||
|
}
|
||||||
|
|
||||||
private findRecentProjectKnowledgeRecord(rootPath: string): string | null {
|
private findRecentProjectKnowledgeRecord(rootPath: string): string | null {
|
||||||
const fromHistory = [...this.chatHistory]
|
const fromHistory = [...this.chatHistory]
|
||||||
.reverse()
|
.reverse()
|
||||||
|
|||||||
+142
-20
@@ -45,6 +45,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
private static readonly lastAgentStateKey = 'g1nation.lastAgentPath';
|
private static readonly lastAgentStateKey = 'g1nation.lastAgentPath';
|
||||||
private static readonly chronicleProjectsStateKey = 'g1nation.chronicleProjects';
|
private static readonly chronicleProjectsStateKey = 'g1nation.chronicleProjects';
|
||||||
private static readonly activeChronicleProjectStateKey = 'g1nation.activeChronicleProjectId';
|
private static readonly activeChronicleProjectStateKey = 'g1nation.activeChronicleProjectId';
|
||||||
|
private static readonly lastAutoChronicleSignatureStateKey = 'g1nation.lastAutoChronicleSignature';
|
||||||
private _view?: vscode.WebviewView;
|
private _view?: vscode.WebviewView;
|
||||||
public brainEnabled = true;
|
public brainEnabled = true;
|
||||||
private _currentSessionBrainId: string | null = null;
|
private _currentSessionBrainId: string | null = null;
|
||||||
@@ -84,6 +85,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
case 'promptWithFile':
|
case 'promptWithFile':
|
||||||
await this._context.globalState.update(SidebarChatProvider.blankChatStateKey, false);
|
await this._context.globalState.update(SidebarChatProvider.blankChatStateKey, false);
|
||||||
await this._handlePrompt(data);
|
await this._handlePrompt(data);
|
||||||
|
await this._autoWriteChronicleAfterPrompt();
|
||||||
// After prompt, save the session automatically
|
// After prompt, save the session automatically
|
||||||
await this._saveCurrentSession();
|
await this._saveCurrentSession();
|
||||||
break;
|
break;
|
||||||
@@ -1480,6 +1482,136 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _autoWriteChronicleAfterPrompt() {
|
||||||
|
const profile = this._getActiveChronicleProject();
|
||||||
|
if (!profile) return;
|
||||||
|
|
||||||
|
const history = this._agent.getHistory();
|
||||||
|
const latestUser = [...history].reverse().find(message => message.role === 'user')?.content || '';
|
||||||
|
const latestAssistant = [...history].reverse().find(message => message.role === 'assistant')?.content || '';
|
||||||
|
const recordType = this._inferAutoChronicleRecordType(latestUser, latestAssistant);
|
||||||
|
if (!recordType) return;
|
||||||
|
|
||||||
|
const signature = [
|
||||||
|
profile.projectId,
|
||||||
|
recordType,
|
||||||
|
this._summarizeTextForWiki(latestUser).slice(0, 240),
|
||||||
|
this._summarizeTextForWiki(latestAssistant).slice(0, 240)
|
||||||
|
].join('|');
|
||||||
|
const lastSignature = this._context.globalState.get<string>(SidebarChatProvider.lastAutoChronicleSignatureStateKey, '');
|
||||||
|
if (signature === lastSignature) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._chronicle.ensureProject(profile);
|
||||||
|
const createdAt = new Date().toISOString();
|
||||||
|
const title = this._summarizeForTitle(latestUser || latestAssistant || 'Project Chronicle Auto Record');
|
||||||
|
const summary = this._summarizeTextForWiki(latestAssistant || latestUser);
|
||||||
|
let result;
|
||||||
|
|
||||||
|
if (recordType === 'bug') {
|
||||||
|
const bugNumber = this._chronicle.nextBugNumber(profile);
|
||||||
|
result = this._chronicle.writeBug(profile, {
|
||||||
|
title,
|
||||||
|
symptom: this._summarizeTextForWiki(latestUser || 'Issue was detected during the conversation.'),
|
||||||
|
cause: 'Captured automatically from the current conversation. Confirm root cause during follow-up review if needed.',
|
||||||
|
fix: summary,
|
||||||
|
prevention: 'Keep automatic records tied to the active project and verify the relevant test or reproduction path.',
|
||||||
|
createdAt
|
||||||
|
}, bugNumber);
|
||||||
|
} else if (recordType === 'planning') {
|
||||||
|
result = this._chronicle.writePlanning(profile, {
|
||||||
|
featureName: title,
|
||||||
|
purpose: 'Capture the current planning or architecture direction before implementation continues.',
|
||||||
|
background: summary,
|
||||||
|
userIntent: this._summarizeTextForWiki(latestUser),
|
||||||
|
sourceRequest: latestUser || 'No user request captured in the current chat.',
|
||||||
|
scope: ['Continue from the active project conversation.', 'Use the selected project record folder automatically.'],
|
||||||
|
outOfScope: ['Manual record type selection.', 'Blocking the user with record-writing prompts.'],
|
||||||
|
developmentDirection: summary,
|
||||||
|
dependencyStrategy: 'Prefer existing project modules and local Markdown records.',
|
||||||
|
expectedValue: 'Future work can resume with the latest project intent and reasoning preserved.',
|
||||||
|
successCriteria: ['The record is saved automatically after a meaningful project turn.', 'The record stays under the active project.'],
|
||||||
|
developerInstruction: 'Use this record as lightweight context for the next development or review pass.',
|
||||||
|
createdAt
|
||||||
|
});
|
||||||
|
} else if (recordType === 'decision') {
|
||||||
|
const adrNumber = this._chronicle.nextAdrNumber(profile);
|
||||||
|
result = this._chronicle.writeDecision(profile, {
|
||||||
|
title,
|
||||||
|
status: 'accepted',
|
||||||
|
context: this._summarizeTextForWiki(latestUser),
|
||||||
|
decision: summary,
|
||||||
|
reason: 'Captured automatically because the conversation contained decision-oriented language.',
|
||||||
|
alternatives: [],
|
||||||
|
consequences: ['Future prompts should treat this as project context unless the user changes direction.'],
|
||||||
|
createdAt
|
||||||
|
}, adrNumber);
|
||||||
|
} else if (recordType === 'development') {
|
||||||
|
result = this._chronicle.writeDevelopmentLog(profile, {
|
||||||
|
featureName: title,
|
||||||
|
purpose: 'Record the implementation or verification outcome from the current conversation.',
|
||||||
|
implementationSummary: summary,
|
||||||
|
architecture: 'Captured automatically from the assistant response and active project context.',
|
||||||
|
changedFiles: this._extractChangedFilesFromText(latestAssistant),
|
||||||
|
dependencyNotes: 'No new dependency note was captured automatically.',
|
||||||
|
bugs: [],
|
||||||
|
lessons: ['Automatic project records should be generated in the background when the turn contains durable project knowledge.'],
|
||||||
|
createdAt
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
result = this._chronicle.writeDiscussion(profile, {
|
||||||
|
title,
|
||||||
|
userRequest: this._summarizeTextForWiki(latestUser || 'No user request captured in the current chat.'),
|
||||||
|
interpretedIntent: 'Capture a meaningful project discussion automatically instead of requiring manual record selection.',
|
||||||
|
questions: [],
|
||||||
|
discussions: [summary],
|
||||||
|
decisions: [],
|
||||||
|
createdAt
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this._chronicle.appendTimeline(profile, [`Auto ${recordType} record created: ${result.relativePath}`], createdAt);
|
||||||
|
await this._context.globalState.update(SidebarChatProvider.lastAutoChronicleSignatureStateKey, signature);
|
||||||
|
await this._sendChronicleRecords();
|
||||||
|
this.injectSystemMessage(`**[Chronicle Auto Saved]** ${recordType} · \`${result.filePath}\``);
|
||||||
|
} catch (err: any) {
|
||||||
|
logError('Automatic Chronicle record write failed.', { error: err?.message || String(err), recordType });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _inferAutoChronicleRecordType(userText: string, assistantText: string): 'planning' | 'discussion' | 'decision' | 'development' | 'bug' | null {
|
||||||
|
const combined = `${userText}\n${assistantText}`;
|
||||||
|
if (!combined.trim()) return null;
|
||||||
|
if (/(기록하지마|저장하지마|no\s+record|do\s+not\s+record)/i.test(combined)) return null;
|
||||||
|
if (!/(프로젝트|코드|아키텍처|설계|개선|수정|구현|테스트|검증|이슈|문제|버그|오류|끊김|결정|방향|기록|지식|review|architecture|implement|fix|bug|issue|test|decision)/i.test(combined)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (/(버그|오류|에러|이슈|문제|끊김|안\s*됨|실패|bug|error|issue|failed|failure)/i.test(userText)) {
|
||||||
|
return 'bug';
|
||||||
|
}
|
||||||
|
if (/(수정 완료|개선 완료|구현 완료|패치|테스트.*통과|검증.*완료|변경.*파일|compile|jest|tsc|passed|implemented|fixed)/i.test(assistantText)) {
|
||||||
|
return 'development';
|
||||||
|
}
|
||||||
|
if (/(결정|확정|채택|방향은|하기로|하지 않기로|decision|decide|accepted)/i.test(combined)) {
|
||||||
|
return 'decision';
|
||||||
|
}
|
||||||
|
if (/(계획|설계|아키텍처|조사|방향|로드맵|mvp|planning|architecture|roadmap|design)/i.test(userText)) {
|
||||||
|
return 'planning';
|
||||||
|
}
|
||||||
|
if (/(개선|수정|구현|테스트|검증|패킹|compile|jest|tsc|implement|fix|test|verify)/i.test(combined)) {
|
||||||
|
return 'development';
|
||||||
|
}
|
||||||
|
return 'discussion';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _extractChangedFilesFromText(text: string): string[] {
|
||||||
|
const files = new Set<string>();
|
||||||
|
for (const match of text.matchAll(/`([^`\n]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|yml|yaml))`/gi)) {
|
||||||
|
files.add(match[1].trim());
|
||||||
|
}
|
||||||
|
return files.size > 0 ? Array.from(files).slice(0, 12) : ['No explicit changed file list was captured automatically.'];
|
||||||
|
}
|
||||||
|
|
||||||
private async _writeChronicleRecord(recordType: string) {
|
private async _writeChronicleRecord(recordType: string) {
|
||||||
switch (recordType) {
|
switch (recordType) {
|
||||||
case 'planning':
|
case 'planning':
|
||||||
@@ -1891,6 +2023,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.record-row {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
.brand { font-weight: 700; font-size: 14px; color: var(--text-bright); letter-spacing: 0; display: flex; align-items: center; gap: 8px; min-width: 0; }
|
.brand { font-weight: 700; font-size: 14px; color: var(--text-bright); letter-spacing: 0; display: flex; align-items: center; gap: 8px; min-width: 0; }
|
||||||
.logo { width: 22px; height: 22px; background: var(--accent); color: #fff; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 900; }
|
.logo { width: 22px; height: 22px; background: var(--accent); color: #fff; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 900; }
|
||||||
@@ -1917,6 +2052,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
box-shadow: 0 0 0 2px rgba(139, 148, 158, 0.12);
|
box-shadow: 0 0 0 2px rgba(139, 148, 158, 0.12);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.status-dot.ready {
|
||||||
|
background: #3fb950;
|
||||||
|
box-shadow: 0 0 0 2px rgba(63, 185, 80, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
.chat {
|
.chat {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -2420,22 +2559,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
<button class="icon-btn" id="openDesignerBtn" data-tooltip="Open Record Folder">Open</button>
|
<button class="icon-btn" id="openDesignerBtn" data-tooltip="Open Record Folder">Open</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-row">
|
<div class="control-row record-row">
|
||||||
<div class="select-wrap">
|
<div class="status-pill" id="chronicleAutoStatus" title="Project records are saved automatically after meaningful project turns.">
|
||||||
<select id="chronicleRecordTypeSel" title="Select Chronicle Record Type">
|
<span class="status-dot ready"></span><span>Auto Records</span>
|
||||||
<option value="planning">Planning</option>
|
|
||||||
<option value="discussion">Discussion</option>
|
|
||||||
<option value="decision">Decision</option>
|
|
||||||
<option value="development">Development</option>
|
|
||||||
<option value="bug">Bug</option>
|
|
||||||
<option value="retrospective">Retrospective</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-group" aria-label="Chronicle write actions">
|
|
||||||
<button class="icon-btn" id="writeChronicleBtn" data-tooltip="Write Selected Record">Write</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="control-row">
|
|
||||||
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
|
<div class="select-wrap"><select id="chronicleRecordSel" title="Select Chronicle Record"></select></div>
|
||||||
<div class="tool-group" aria-label="Chronicle record actions">
|
<div class="tool-group" aria-label="Chronicle record actions">
|
||||||
<button class="icon-btn" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Ref</button>
|
<button class="icon-btn" id="refreshChronicleRecordsBtn" data-tooltip="Refresh Records">Ref</button>
|
||||||
@@ -2652,7 +2779,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
const attachPreview = document.getElementById('attachPreview');
|
const attachPreview = document.getElementById('attachPreview');
|
||||||
const agentSel = document.getElementById('agentSel');
|
const agentSel = document.getElementById('agentSel');
|
||||||
const designerSel = document.getElementById('designerSel');
|
const designerSel = document.getElementById('designerSel');
|
||||||
const chronicleRecordTypeSel = document.getElementById('chronicleRecordTypeSel');
|
|
||||||
const chronicleRecordSel = document.getElementById('chronicleRecordSel');
|
const chronicleRecordSel = document.getElementById('chronicleRecordSel');
|
||||||
const editAgentBtn = document.getElementById('editAgentBtn');
|
const editAgentBtn = document.getElementById('editAgentBtn');
|
||||||
const addAgentBtn = document.getElementById('addAgentBtn');
|
const addAgentBtn = document.getElementById('addAgentBtn');
|
||||||
@@ -3228,10 +3354,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
document.getElementById('addDesignerBtn').onclick = () => vscode.postMessage({ type: 'createChronicleProject' });
|
document.getElementById('addDesignerBtn').onclick = () => vscode.postMessage({ type: 'createChronicleProject' });
|
||||||
document.getElementById('openDesignerBtn').onclick = () => vscode.postMessage({ type: 'openChronicleFolder' });
|
document.getElementById('openDesignerBtn').onclick = () => vscode.postMessage({ type: 'openChronicleFolder' });
|
||||||
document.getElementById('writeChronicleBtn').onclick = () => vscode.postMessage({
|
|
||||||
type: 'writeChronicleRecord',
|
|
||||||
recordType: chronicleRecordTypeSel.value
|
|
||||||
});
|
|
||||||
document.getElementById('refreshChronicleRecordsBtn').onclick = () => vscode.postMessage({ type: 'getChronicleRecords' });
|
document.getElementById('refreshChronicleRecordsBtn').onclick = () => vscode.postMessage({ type: 'getChronicleRecords' });
|
||||||
document.getElementById('openChronicleRecordBtn').onclick = () => {
|
document.getElementById('openChronicleRecordBtn').onclick = () => {
|
||||||
if (!chronicleRecordSel.value) return;
|
if (!chronicleRecordSel.value) return;
|
||||||
|
|||||||
@@ -235,4 +235,68 @@ describe('local project path preflight', () => {
|
|||||||
expect(requestHistory[1].content).not.toContain('Datacollector');
|
expect(requestHistory[1].content).not.toContain('Datacollector');
|
||||||
expect(requestHistory[1].content).not.toContain('2nd Brain Trace');
|
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}`,
|
||||||
|
'',
|
||||||
|
'# ConnectAI 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('로컬 프로젝트 경로');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user