[RAG] Implement visual conflict and density metadata tags for enhanced context intelligence
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# docs Chronicle Records
|
||||||
|
|
||||||
|
## Project
|
||||||
|
- ID: docs
|
||||||
|
- Root: /Volumes/Data/project/Antigravity/ConnectAI/docs
|
||||||
|
- Record root: /Volumes/Data/project/Antigravity/ConnectAI/docs/docs/records/docs
|
||||||
|
- Detail level: standard
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Capture project direction, architecture discussion, decisions, and development notes as Markdown.
|
||||||
|
|
||||||
|
## Folders
|
||||||
|
- `planning/`
|
||||||
|
- `discussions/`
|
||||||
|
- `decisions/`
|
||||||
|
- `development/`
|
||||||
|
- `bugs/`
|
||||||
|
- `retrospectives/`
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Bug: Viewed integration_retrieval.test.ts:1-59 `integration_retrieval.test.ts`를 통해 **...
|
||||||
|
|
||||||
|
## Date
|
||||||
|
2026-05-05
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
Viewed integration_retrieval.test.ts:1-59 `integration_retrieval.test.ts`를 통해 **ConnectAI**의 지능형 검색 엔진(Astra Engine)이 실전 수준의 **상충 감지(Conflict Detection)** 능력을 갖추고 있음을 확인했습니다. 특히 `doc2.md`와 같이 "충돌", "오류", "논란", "반대" 등 부정적 지표가 다수 포함된 문서에 대해 `conflictDetected: true`와 `HIGH` 심각도를 부여하는 로직은, 단순히 데이터를 수집하는 것을 넘어 **'수집된 지식의 위험도를 스스로 평가'**할 수 있음을 의미합니다. 이는 사용자께서 강조하신 "다시 검증할 수 있는 수집 흐름"의 핵심적인 기술적 근거가 됩니다. 이제 프로젝트의 고수준 운영 전략과 데이터 관리 정책을 파악하기 위해, `docs/records/src/chronicle.config.json`을 살펴보겠습니다. 이를 통해 수집 ...
|
||||||
|
|
||||||
|
## Cause
|
||||||
|
Captured automatically from the current conversation. Confirm root cause during follow-up review if needed.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
## 간단 요약 이 요청은 프로젝트 지식 생성이 아니라 코드리뷰와 제품 평가 요청입니다. 확인된 파일 구조 기준으로 보면, 이 프로젝트는 지식 수집 워크플로우를 앱 형태로 묶어 운영하려는 도구로 보이며, 먼저 데이터 수집 흐름의 안정성, 외부 연동 실패 처리, 수집 결과의 저장/재처리 가능성을 중심으로 평가해야 합니다. ## 확인된 근거 대상 경로: `/Volumes/Data/project/Antigravity/ConnectAI/src` 확인된 우선 파일: - `docs/records/src/README.md` - `docs/records/src/bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md` - `docs/records/src/chronicle.config.json` - `docs/records/src/project-profile.md` - `docs/records/src/timeline....
|
||||||
|
|
||||||
|
## Prevention
|
||||||
|
Keep automatic records tied to the active project and verify the relevant test or reproduction path.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"projectId": "docs",
|
||||||
|
"projectName": "docs",
|
||||||
|
"projectRoot": "/Volumes/Data/project/Antigravity/ConnectAI/docs",
|
||||||
|
"recordRoot": "/Volumes/Data/project/Antigravity/ConnectAI/docs/docs/records/docs",
|
||||||
|
"description": "Auto-detected from the local project path in the conversation.",
|
||||||
|
"corePurpose": "Capture project direction, architecture discussion, decisions, and development notes as Markdown.",
|
||||||
|
"detailLevel": "standard",
|
||||||
|
"createdAt": "2026-05-05T02:55:58.823Z",
|
||||||
|
"updatedAt": "2026-05-05T02:55:58.824Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Project Profile
|
||||||
|
|
||||||
|
## Project Name
|
||||||
|
docs
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Auto-detected from the local project path in the conversation.
|
||||||
|
|
||||||
|
## Project Root
|
||||||
|
/Volumes/Data/project/Antigravity/ConnectAI/docs
|
||||||
|
|
||||||
|
## Record Root
|
||||||
|
/Volumes/Data/project/Antigravity/ConnectAI/docs/docs/records/docs
|
||||||
|
|
||||||
|
## Core Purpose
|
||||||
|
Capture project direction, architecture discussion, decisions, and development notes as Markdown.
|
||||||
|
|
||||||
|
## Target Users
|
||||||
|
- Project developer
|
||||||
|
|
||||||
|
## Avoid Directions
|
||||||
|
- Do not mix records across projects.
|
||||||
|
|
||||||
|
## Record Detail Level
|
||||||
|
standard
|
||||||
|
|
||||||
|
## Created
|
||||||
|
2026-05-05T02:55:58.823Z
|
||||||
|
|
||||||
|
## Updated
|
||||||
|
2026-05-05T02:55:58.823Z
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Project Timeline
|
||||||
|
|
||||||
|
## 2026-05-05
|
||||||
|
- Project Chronicle record folder initialized for docs.
|
||||||
|
|
||||||
|
## 2026-05-05
|
||||||
|
- Auto bug record created: bugs/BUG-0001-viewed-integration-retrieval-test-ts-1-59-integration-retrie.md
|
||||||
+94
-92
@@ -1,8 +1,10 @@
|
|||||||
import * as vscode from 'vscode';
|
agent.ts 인데 코드 분석해주면 좋겠어.
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
// axios removed
|
// axios removed
|
||||||
import {
|
import {
|
||||||
findBrainFiles,
|
findBrainFiles,
|
||||||
getSystemPrompt,
|
getSystemPrompt,
|
||||||
shouldAutoPushBrain,
|
shouldAutoPushBrain,
|
||||||
@@ -21,10 +23,10 @@ import { PlannerAgent, ResearcherAgent, WriterAgent } from './agents/factory';
|
|||||||
import { AgentWorkflowManager } from './agents/AgentWorkflowManager';
|
import { AgentWorkflowManager } from './agents/AgentWorkflowManager';
|
||||||
import { ErrorTranslator } from './core/errorHandler';
|
import { ErrorTranslator } from './core/errorHandler';
|
||||||
import { agentEvents, AgentEventTypes } from './core/events';
|
import { agentEvents, AgentEventTypes } from './core/events';
|
||||||
import {
|
import {
|
||||||
AgentExecutionError,
|
AgentExecutionError,
|
||||||
FileSystemError,
|
FileSystemError,
|
||||||
APICommunicationError
|
APICommunicationError
|
||||||
} from './core/errors';
|
} from './core/errors';
|
||||||
import { StatusBarManager, AgentStatus } from './core/statusBar';
|
import { StatusBarManager, AgentStatus } from './core/statusBar';
|
||||||
import { lockManager } from './core/lock';
|
import { lockManager } from './core/lock';
|
||||||
@@ -109,7 +111,7 @@ export class AgentExecutor {
|
|||||||
private parseRationale(text: string) {
|
private parseRationale(text: string) {
|
||||||
const match = text.match(/<rationale>([\s\S]*?)<\/rationale>/);
|
const match = text.match(/<rationale>([\s\S]*?)<\/rationale>/);
|
||||||
if (!match) return undefined;
|
if (!match) return undefined;
|
||||||
|
|
||||||
const raw = match[1];
|
const raw = match[1];
|
||||||
const problem = raw.match(/\[PROBLEM\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || "";
|
const problem = raw.match(/\[PROBLEM\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || "";
|
||||||
const goal = raw.match(/\[GOAL\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || "";
|
const goal = raw.match(/\[GOAL\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || "";
|
||||||
@@ -202,8 +204,8 @@ export class AgentExecutor {
|
|||||||
public async handlePrompt(
|
public async handlePrompt(
|
||||||
prompt: string | null,
|
prompt: string | null,
|
||||||
modelName: string,
|
modelName: string,
|
||||||
options: {
|
options: {
|
||||||
internetEnabled?: boolean,
|
internetEnabled?: boolean,
|
||||||
brainEnabled?: boolean,
|
brainEnabled?: boolean,
|
||||||
loopDepth?: number,
|
loopDepth?: number,
|
||||||
visionContent?: any[],
|
visionContent?: any[],
|
||||||
@@ -218,17 +220,17 @@ export class AgentExecutor {
|
|||||||
brainProfileId?: string
|
brainProfileId?: string
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
internetEnabled = false,
|
internetEnabled = false,
|
||||||
brainEnabled = false,
|
brainEnabled = false,
|
||||||
loopDepth = 0,
|
loopDepth = 0,
|
||||||
visionContent,
|
visionContent,
|
||||||
temperature = 0.7,
|
temperature = 0.7,
|
||||||
systemPrompt = getSystemPrompt()
|
systemPrompt = getSystemPrompt()
|
||||||
} = options;
|
} = options;
|
||||||
const { ollamaUrl, defaultModel: configDefaultModel, timeout, multiAgentEnabled } = getConfig();
|
const { ollamaUrl, defaultModel: configDefaultModel, timeout, multiAgentEnabled } = getConfig();
|
||||||
const runId = options.runId ?? (loopDepth === 0 ? ++this.runSerial : this.activeRunId);
|
const runId = options.runId ?? (loopDepth === 0 ? ++this.runSerial : this.activeRunId);
|
||||||
|
|
||||||
// Decide whether to use Multi-Agent Workflow as an internal execution strategy.
|
// Decide whether to use Multi-Agent Workflow as an internal execution strategy.
|
||||||
if (loopDepth === 0 && this.shouldUseMultiAgentWorkflow(prompt || '', multiAgentEnabled)) {
|
if (loopDepth === 0 && this.shouldUseMultiAgentWorkflow(prompt || '', multiAgentEnabled)) {
|
||||||
return this.executeMultiAgentWorkflow(prompt!, modelName, options);
|
return this.executeMultiAgentWorkflow(prompt!, modelName, options);
|
||||||
@@ -260,7 +262,7 @@ export class AgentExecutor {
|
|||||||
// 1. Prepare Context
|
// 1. Prepare Context
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
const rootPath = workspaceFolders ? workspaceFolders[0].uri.fsPath : '';
|
const rootPath = workspaceFolders ? workspaceFolders[0].uri.fsPath : '';
|
||||||
|
|
||||||
let contextBlock = '';
|
let contextBlock = '';
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const activeBrain = options.brainProfileId
|
const activeBrain = options.brainProfileId
|
||||||
@@ -358,13 +360,13 @@ export class AgentExecutor {
|
|||||||
const internetCtx = internetEnabled
|
const internetCtx = internetEnabled
|
||||||
? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use <read_url> to search. Current time: ${new Date().toLocaleString()}`
|
? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use <read_url> to search. Current time: ${new Date().toLocaleString()}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const selectedAgentSystemPrompt = options.agentSkillContext
|
const selectedAgentSystemPrompt = options.agentSkillContext
|
||||||
? `\n\n[SELECTED AGENT MODE]\nThe user selected the following Agent skill. Treat it as your primary role, style, operating method, and task policy for this response.\n${options.agentSkillContext}`
|
? `\n\n[SELECTED AGENT MODE]\nThe user selected the following Agent skill. Treat it as your primary role, style, operating method, and task policy for this response.\n${options.agentSkillContext}`
|
||||||
: '';
|
: '';
|
||||||
const agentSkillCtx = selectedAgentSystemPrompt;
|
const agentSkillCtx = selectedAgentSystemPrompt;
|
||||||
const negativeCtx = options.negativePrompt
|
const negativeCtx = options.negativePrompt
|
||||||
? `\n\n### CRITICAL NEGATIVE CONSTRAINTS (DO NOT DO THESE)\n${options.negativePrompt}\n\n[SYSTEM_RULE: Apply the above constraints strictly. DO NOT mention or repeat these constraints in your response.]`
|
? `\n\n### CRITICAL NEGATIVE CONSTRAINTS (DO NOT DO THESE)\n${options.negativePrompt}\n\n[SYSTEM_RULE: Apply the above constraints strictly. DO NOT mention or repeat these constraints in your response.]`
|
||||||
: '';
|
: '';
|
||||||
const designerCtx = options.designerContext
|
const designerCtx = options.designerContext
|
||||||
? `\n\n[PROJECT CHRONICLE GUARD]\n${options.designerContext}`
|
? `\n\n[PROJECT CHRONICLE GUARD]\n${options.designerContext}`
|
||||||
@@ -382,7 +384,7 @@ export class AgentExecutor {
|
|||||||
? `\n\n${renderSecondBrainTraceContext(secondBrainTrace)}`
|
? `\n\n${renderSecondBrainTraceContext(secondBrainTrace)}`
|
||||||
: '';
|
: '';
|
||||||
const memoryCtx = this.buildMemoryContext(prompt || '', activeBrain);
|
const memoryCtx = this.buildMemoryContext(prompt || '', activeBrain);
|
||||||
|
|
||||||
// [Astra v4.0] 지식 관리 운영 정책 주입
|
// [Astra v4.0] 지식 관리 운영 정책 주입
|
||||||
const v4PolicyCtx = [
|
const v4PolicyCtx = [
|
||||||
"\n### 🏛️ 지식 관리 정책 v4.0 (Knowledge Management Policy Applied)",
|
"\n### 🏛️ 지식 관리 정책 v4.0 (Knowledge Management Policy Applied)",
|
||||||
@@ -538,23 +540,23 @@ export class AgentExecutor {
|
|||||||
if (report.length > 0) {
|
if (report.length > 0) {
|
||||||
this.emitHistoryChanged();
|
this.emitHistoryChanged();
|
||||||
logInfo('Agent actions executed.', { loopDepth: loopDepth + 1, report });
|
logInfo('Agent actions executed.', { loopDepth: loopDepth + 1, report });
|
||||||
|
|
||||||
// Continue loop if needed
|
// Continue loop if needed
|
||||||
if (loopDepth < config.maxAutoSteps) {
|
if (loopDepth < config.maxAutoSteps) {
|
||||||
const currentActionStr = report.join('|');
|
const currentActionStr = report.join('|');
|
||||||
const lastActionStr = this.context.workspaceState.get<string>('lastActionStr');
|
const lastActionStr = this.context.workspaceState.get<string>('lastActionStr');
|
||||||
|
|
||||||
if (currentActionStr === lastActionStr) {
|
if (currentActionStr === lastActionStr) {
|
||||||
this.webview.postMessage({ type: 'streamChunk', value: "\n⚠️ *Stopping to prevent infinite loop.*" });
|
this.webview.postMessage({ type: 'streamChunk', value: "\n⚠️ *Stopping to prevent infinite loop.*" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.context.workspaceState.update('lastActionStr', currentActionStr);
|
await this.context.workspaceState.update('lastActionStr', currentActionStr);
|
||||||
logInfo('Autonomous loop continuing after actions.', { loopDepth: loopDepth + 1, actions: report });
|
logInfo('Autonomous loop continuing after actions.', { loopDepth: loopDepth + 1, actions: report });
|
||||||
|
|
||||||
// Explicitly tell the AI to look at the results and continue
|
// Explicitly tell the AI to look at the results and continue
|
||||||
const continuationPrompt = "The requested local action has been executed. Use the action result messages already in the conversation to answer the user's original request directly, in the user's language. Do not say you are waiting for the next instruction.";
|
const continuationPrompt = "The requested local action has been executed. Use the action result messages already in the conversation to answer the user's original request directly, in the user's language. Do not say you are waiting for the next instruction.";
|
||||||
|
|
||||||
this.webview.postMessage({ type: 'autoContinue', value: `자료를 확인하고 답변을 정리하는 중입니다... (${loopDepth + 1}/${config.maxAutoSteps})` });
|
this.webview.postMessage({ type: 'autoContinue', value: `자료를 확인하고 답변을 정리하는 중입니다... (${loopDepth + 1}/${config.maxAutoSteps})` });
|
||||||
await new Promise(r => setTimeout(r, 800));
|
await new Promise(r => setTimeout(r, 800));
|
||||||
if (this.isStaleRun(runId)) return;
|
if (this.isStaleRun(runId)) return;
|
||||||
@@ -591,7 +593,7 @@ export class AgentExecutor {
|
|||||||
options: any
|
options: any
|
||||||
) {
|
) {
|
||||||
if (!this.webview) return;
|
if (!this.webview) return;
|
||||||
this.stop();
|
this.stop();
|
||||||
this.abortController = new AbortController();
|
this.abortController = new AbortController();
|
||||||
const signal = this.abortController.signal;
|
const signal = this.abortController.signal;
|
||||||
|
|
||||||
@@ -632,13 +634,13 @@ export class AgentExecutor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (signal.aborted || !this.webview) return;
|
if (signal.aborted || !this.webview) return;
|
||||||
|
|
||||||
this.webview.postMessage({ type: 'streamChunk', value: `\n\n--- \n\n${finalReport}` });
|
this.webview.postMessage({ type: 'streamChunk', value: `\n\n--- \n\n${finalReport}` });
|
||||||
this.webview.postMessage({ type: 'streamEnd' });
|
this.webview.postMessage({ type: 'streamEnd' });
|
||||||
|
|
||||||
this.chatHistory.push({ role: 'assistant', content: finalReport });
|
this.chatHistory.push({ role: 'assistant', content: finalReport });
|
||||||
this.emitHistoryChanged();
|
this.emitHistoryChanged();
|
||||||
|
|
||||||
this.statusBarManager.updateStatus(AgentStatus.Success, 'Workflow Complete');
|
this.statusBarManager.updateStatus(AgentStatus.Success, 'Workflow Complete');
|
||||||
this.webview.postMessage({ type: 'autoContinue', value: '✅ 모든 분석이 성공적으로 완료되었습니다.' });
|
this.webview.postMessage({ type: 'autoContinue', value: '✅ 모든 분석이 성공적으로 완료되었습니다.' });
|
||||||
|
|
||||||
@@ -649,11 +651,11 @@ export class AgentExecutor {
|
|||||||
}
|
}
|
||||||
const friendly = ErrorTranslator.translate(error);
|
const friendly = ErrorTranslator.translate(error);
|
||||||
logError('Workflow failed', error);
|
logError('Workflow failed', error);
|
||||||
|
|
||||||
this.webview.postMessage({ type: 'autoContinue', value: '' });
|
this.webview.postMessage({ type: 'autoContinue', value: '' });
|
||||||
this.webview.postMessage({
|
this.webview.postMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
value: `### ${friendly.title}\n\n**상태:** ${friendly.message}\n\n**해결 방법:** ${friendly.action}`
|
value: `### ${friendly.title}\n\n**상태:** ${friendly.message}\n\n**해결 방법:** ${friendly.action}`
|
||||||
});
|
});
|
||||||
this.statusBarManager.updateStatus(AgentStatus.Idle, 'Error occurred');
|
this.statusBarManager.updateStatus(AgentStatus.Idle, 'Error occurred');
|
||||||
}
|
}
|
||||||
@@ -662,7 +664,7 @@ export class AgentExecutor {
|
|||||||
private async callAgent(role: AgentRole, prompt: string, modelName: string, options: any): Promise<string> {
|
private async callAgent(role: AgentRole, prompt: string, modelName: string, options: any): Promise<string> {
|
||||||
const persona = AGENT_PROMPTS[role];
|
const persona = AGENT_PROMPTS[role];
|
||||||
const { ollamaUrl, timeout } = getConfig();
|
const { ollamaUrl, timeout } = getConfig();
|
||||||
|
|
||||||
const messages: ChatMessage[] = [
|
const messages: ChatMessage[] = [
|
||||||
{ role: 'system', content: persona },
|
{ role: 'system', content: persona },
|
||||||
{ role: 'user', content: prompt }
|
{ role: 'user', content: prompt }
|
||||||
@@ -692,7 +694,7 @@ export class AgentExecutor {
|
|||||||
const json = JSON.parse(trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed);
|
const json = JSON.parse(trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed);
|
||||||
const content = json.choices?.[0]?.delta?.content || json.message?.content || '';
|
const content = json.choices?.[0]?.delta?.content || json.message?.content || '';
|
||||||
responseText += content;
|
responseText += content;
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return responseText;
|
return responseText;
|
||||||
@@ -1821,8 +1823,8 @@ export class AgentExecutor {
|
|||||||
|
|
||||||
// Save session whenever history changes
|
// Save session whenever history changes
|
||||||
this.sessionManager.saveSession(
|
this.sessionManager.saveSession(
|
||||||
this.currentTaskId,
|
this.currentTaskId,
|
||||||
this.chatHistory,
|
this.chatHistory,
|
||||||
this.context.workspaceState.get<string>('lastActionStr')
|
this.context.workspaceState.get<string>('lastActionStr')
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1868,54 +1870,54 @@ export class AgentExecutor {
|
|||||||
const modelCandidates = this.buildModelCandidates(modelName, engine);
|
const modelCandidates = this.buildModelCandidates(modelName, engine);
|
||||||
|
|
||||||
for (const candidateModel of modelCandidates) {
|
for (const candidateModel of modelCandidates) {
|
||||||
for (const variant of messageVariants) {
|
for (const variant of messageVariants) {
|
||||||
const streamBody = {
|
const streamBody = {
|
||||||
model: candidateModel,
|
|
||||||
messages: variant.messages,
|
|
||||||
stream: true,
|
|
||||||
...(engine === 'lmstudio'
|
|
||||||
? { max_tokens: 4096, temperature }
|
|
||||||
: { options: { num_ctx: 32768, num_predict: 4096, temperature } }),
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
logInfo('AI streaming request started.', {
|
|
||||||
engine,
|
|
||||||
apiUrl,
|
|
||||||
model: candidateModel,
|
model: candidateModel,
|
||||||
variant: variant.name,
|
messages: variant.messages,
|
||||||
messageCount: variant.messages.length,
|
stream: true,
|
||||||
roles: variant.messages.map(message => message.role),
|
...(engine === 'lmstudio'
|
||||||
firstUserPreview: summarizeText(String(variant.messages.find(message => message.role === 'user')?.content || ''), 300)
|
? { max_tokens: 4096, temperature }
|
||||||
});
|
: { options: { num_ctx: 32768, num_predict: 4096, temperature } }),
|
||||||
const response = await fetch(apiUrl, {
|
};
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'text/event-stream',
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Connection': 'keep-alive'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(streamBody),
|
|
||||||
signal: this.abortController?.signal,
|
|
||||||
keepalive: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
try {
|
||||||
const errText = await response.text();
|
logInfo('AI streaming request started.', {
|
||||||
lastError = new Error(`AI Engine error (${engine}/${variant.name}): ${response.status} - ${summarizeText(errText, 300)}`);
|
engine,
|
||||||
logError('AI streaming request returned non-OK status.', { engine, variant: variant.name, apiUrl, status: response.status, body: summarizeText(errText, 500) });
|
apiUrl,
|
||||||
continue;
|
model: candidateModel,
|
||||||
|
variant: variant.name,
|
||||||
|
messageCount: variant.messages.length,
|
||||||
|
roles: variant.messages.map(message => message.role),
|
||||||
|
firstUserPreview: summarizeText(String(variant.messages.find(message => message.role === 'user')?.content || ''), 300)
|
||||||
|
});
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'text/event-stream',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(streamBody),
|
||||||
|
signal: this.abortController?.signal,
|
||||||
|
keepalive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errText = await response.text();
|
||||||
|
lastError = new Error(`AI Engine error (${engine}/${variant.name}): ${response.status} - ${summarizeText(errText, 300)}`);
|
||||||
|
logError('AI streaming request returned non-OK status.', { engine, variant: variant.name, apiUrl, status: response.status, body: summarizeText(errText, 500) });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo('AI streaming request connected.', { engine, variant: variant.name, apiUrl });
|
||||||
|
return { response, engine, apiUrl };
|
||||||
|
} catch (error: any) {
|
||||||
|
lastError = error instanceof Error ? error : new Error(String(error));
|
||||||
|
logError('AI streaming request failed.', { engine, variant: variant.name, apiUrl, model: candidateModel, error: lastError.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
logInfo('AI streaming request connected.', { engine, variant: variant.name, apiUrl });
|
|
||||||
return { response, engine, apiUrl };
|
|
||||||
} catch (error: any) {
|
|
||||||
lastError = error instanceof Error ? error : new Error(String(error));
|
|
||||||
logError('AI streaming request failed.', { engine, variant: variant.name, apiUrl, model: candidateModel, error: lastError.message });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw lastError || new Error('Unable to connect to AI engine.');
|
throw lastError || new Error('Unable to connect to AI engine.');
|
||||||
@@ -1986,14 +1988,14 @@ export class AgentExecutor {
|
|||||||
try {
|
try {
|
||||||
const absPath = validatePath(rootPath, relPath);
|
const absPath = validatePath(rootPath, relPath);
|
||||||
await this.transactionManager.record(absPath);
|
await this.transactionManager.record(absPath);
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
||||||
fs.writeFileSync(absPath, content, 'utf-8');
|
fs.writeFileSync(absPath, content, 'utf-8');
|
||||||
|
|
||||||
report.push(`✅ Created: ${relPath}`);
|
report.push(`✅ Created: ${relPath}`);
|
||||||
if (!firstCreatedFile) firstCreatedFile = absPath;
|
if (!firstCreatedFile) firstCreatedFile = absPath;
|
||||||
if (absPath.startsWith(activeBrainDir)) brainModified = true;
|
if (absPath.startsWith(activeBrainDir)) brainModified = true;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw new FileSystemError(`Failed to create file ${relPath}: ${err.message}`, relPath, err);
|
throw new FileSystemError(`Failed to create file ${relPath}: ${err.message}`, relPath, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2007,10 +2009,10 @@ export class AgentExecutor {
|
|||||||
const absPath = validatePath(rootPath, relPath);
|
const absPath = validatePath(rootPath, relPath);
|
||||||
if (fs.existsSync(absPath)) {
|
if (fs.existsSync(absPath)) {
|
||||||
await this.transactionManager.record(absPath);
|
await this.transactionManager.record(absPath);
|
||||||
|
|
||||||
let currentContent = fs.readFileSync(absPath, 'utf-8');
|
let currentContent = fs.readFileSync(absPath, 'utf-8');
|
||||||
const searchMatch = editContent.match(/<search>([\s\S]*?)<\/search>\s*<replace>([\s\S]*?)<\/replace>/i);
|
const searchMatch = editContent.match(/<search>([\s\S]*?)<\/search>\s*<replace>([\s\S]*?)<\/replace>/i);
|
||||||
|
|
||||||
if (searchMatch) {
|
if (searchMatch) {
|
||||||
const searchStr = searchMatch[1];
|
const searchStr = searchMatch[1];
|
||||||
const replaceStr = searchMatch[2];
|
const replaceStr = searchMatch[2];
|
||||||
@@ -2029,7 +2031,7 @@ export class AgentExecutor {
|
|||||||
} else {
|
} else {
|
||||||
report.push(`❌ File not found: ${relPath}`);
|
report.push(`❌ File not found: ${relPath}`);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw new FileSystemError(`Failed to edit file ${relPath}: ${err.message}`, relPath, err);
|
throw new FileSystemError(`Failed to edit file ${relPath}: ${err.message}`, relPath, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2047,7 +2049,7 @@ export class AgentExecutor {
|
|||||||
} else {
|
} else {
|
||||||
report.push(`⚠️ Delete failed: ${relPath} not found`);
|
report.push(`⚠️ Delete failed: ${relPath} not found`);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw new FileSystemError(`Failed to delete file ${relPath}: ${err.message}`, relPath, err);
|
throw new FileSystemError(`Failed to delete file ${relPath}: ${err.message}`, relPath, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2094,7 +2096,7 @@ export class AgentExecutor {
|
|||||||
.filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name))
|
.filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name))
|
||||||
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
if (listing.length > 5000) {
|
if (listing.length > 5000) {
|
||||||
listing = listing.slice(0, 5000) + "\n... (truncated for context)";
|
listing = listing.slice(0, 5000) + "\n... (truncated for context)";
|
||||||
}
|
}
|
||||||
@@ -2118,11 +2120,11 @@ export class AgentExecutor {
|
|||||||
.filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name))
|
.filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name))
|
||||||
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
.map(e => e.isDirectory() ? `${e.name}/` : e.name)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|
||||||
if (listing.length > 5000) {
|
if (listing.length > 5000) {
|
||||||
listing = listing.slice(0, 5000) + "\n... (truncated for context)";
|
listing = listing.slice(0, 5000) + "\n... (truncated for context)";
|
||||||
}
|
}
|
||||||
|
|
||||||
report.push(`🧠 Brain Listed: ${relPath}`);
|
report.push(`🧠 Brain Listed: ${relPath}`);
|
||||||
this.chatHistory.push({ role: 'system', content: `[Result of list_brain ${relPath}]\n${listing}`, internal: true });
|
this.chatHistory.push({ role: 'system', content: `[Result of list_brain ${relPath}]\n${listing}`, internal: true });
|
||||||
} else {
|
} else {
|
||||||
@@ -2138,7 +2140,7 @@ export class AgentExecutor {
|
|||||||
const brainDir = activeBrainDir;
|
const brainDir = activeBrainDir;
|
||||||
const files = findBrainFiles(brainDir);
|
const files = findBrainFiles(brainDir);
|
||||||
const targetFile = files.find((f: string) => path.basename(f) === fileName || f.endsWith(fileName));
|
const targetFile = files.find((f: string) => path.basename(f) === fileName || f.endsWith(fileName));
|
||||||
|
|
||||||
if (targetFile && fs.existsSync(targetFile)) {
|
if (targetFile && fs.existsSync(targetFile)) {
|
||||||
const content = fs.readFileSync(targetFile, 'utf-8');
|
const content = fs.readFileSync(targetFile, 'utf-8');
|
||||||
report.push(`🧠 Brain Read: ${fileName}`);
|
report.push(`🧠 Brain Read: ${fileName}`);
|
||||||
@@ -2201,4 +2203,4 @@ export class AgentExecutor {
|
|||||||
logError('Second Brain sync failed.', err);
|
logError('Second Brain sync failed.', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# src Chronicle Records
|
||||||
|
|
||||||
|
## Project
|
||||||
|
- ID: src
|
||||||
|
- Root: /Volumes/Data/project/Antigravity/ConnectAI/src
|
||||||
|
- Record root: /Volumes/Data/project/Antigravity/ConnectAI/src/docs/records/src
|
||||||
|
- Detail level: standard
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Capture project direction, architecture discussion, decisions, and development notes as Markdown.
|
||||||
|
|
||||||
|
## Folders
|
||||||
|
- `planning/`
|
||||||
|
- `discussions/`
|
||||||
|
- `decisions/`
|
||||||
|
- `development/`
|
||||||
|
- `bugs/`
|
||||||
|
- `retrospectives/`
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Bug: Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts ...
|
||||||
|
|
||||||
|
## Date
|
||||||
|
2026-05-05
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Edited agent.ts Viewed agent.ts:2159-2206 제공해주신 코드 리뷰 관점과 제품 평가 리포트를 정독했습니다. 현재 **ConnectAI**의 아키텍처는 단순한 챗봇을 넘어, **지식 수집 파이프라인의 안정성(Robustness)**과 **데이터의 무결성(Integr...
|
||||||
|
|
||||||
|
## Cause
|
||||||
|
Captured automatically from the current conversation. Confirm root cause during follow-up review if needed.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
## 간단 요약 이 요청은 프로젝트 지식 생성이 아니라 코드리뷰와 제품 평가 요청입니다. 확인된 파일 구조 기준으로 보면, 이 프로젝트는 지식 수집 워크플로우를 앱 형태로 묶어 운영하려는 도구로 보이며, 먼저 데이터 수집 흐름의 안정성, 외부 연동 실패 처리, 수집 결과의 저장/재처리 가능성을 중심으로 평가해야 합니다. ## 확인된 근거 대상 경로: `/Volumes/Data/project/Antigravity/ConnectAI/src` 확인된 우선 파일: - `core/astraPath.ts` - `core/conflict.ts` - `core/dataProcessor.ts` - `core/errorHandler.ts` - `core/errors.ts` - `core/events.ts` - `core/health.ts` - `core/lock.ts` - `core/queue.ts` - `core/services.ts` 확인된 구조 일부: ```text agents/ agents/A...
|
||||||
|
|
||||||
|
## Prevention
|
||||||
|
Keep automatic records tied to the active project and verify the relevant test or reproduction path.
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Bug: Viewed agent.ts:2159-2206 제공해주신 세부 파일 구조와 **지식 수집 워크플로우** 관점의 코드리뷰 요청을 정독했습니다. 이...
|
||||||
|
|
||||||
|
## Date
|
||||||
|
2026-05-05
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
Viewed agent.ts:2159-2206 제공해주신 세부 파일 구조와 **지식 수집 워크플로우** 관점의 코드리뷰 요청을 정독했습니다. 이전 분석에서 `engine.ts`의 상태 영속화와 `diagnostics.ts`의 무결성 검증을 확인했다면, 이번에는 사용자께서 우려하신 **'외부 연동 실패 처리'**의 실체인 `src/lib/api.ts`와 `src/lib/gemini.ts`, 그리고 UI 결합도를 결정짓는 `src/components/AgentDashboard.tsx`를 집중 분석하겠습니다. 특히 외부 API 호출 시의 타임아웃 처리, 속도 제한(Rate Limiting) 대응, 그리고 UI가 엔진의 상태를 어떻게 관측하는지 확인하여 리포트를 보완하겠습니다.따르겠습니다. `src/lib/api.ts` 파일이 존재하지 않는 것으로 확인되었습니다. 사용자께서 언급하신 **'다음에 깊게 볼 파일'** 리스트 중 일부가 현재 파일 시스템 구조와 차이가 있을 수 있으므로, 정확한...
|
||||||
|
|
||||||
|
## Cause
|
||||||
|
Captured automatically from the current conversation. Confirm root cause during follow-up review if needed.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
## 간단 요약 이 요청은 프로젝트 지식 생성이 아니라 코드리뷰와 제품 평가 요청입니다. 확인된 파일 구조 기준으로 보면, 이 프로젝트는 지식 수집 워크플로우를 앱 형태로 묶어 운영하려는 도구로 보이며, 먼저 데이터 수집 흐름의 안정성, 외부 연동 실패 처리, 수집 결과의 저장/재처리 가능성을 중심으로 평가해야 합니다. ## 확인된 근거 대상 경로: `/Volumes/Data/project/Antigravity/ConnectAI/src` 확인된 우선 파일: - `docs/records/src/README.md` - `docs/records/src/bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md` - `docs/records/src/chronicle.config.json` - `docs/records/src/project-profile.md` - `docs/records/src/timeline....
|
||||||
|
|
||||||
|
## Prevention
|
||||||
|
Keep automatic records tied to the active project and verify the relevant test or reproduction path.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"projectId": "src",
|
||||||
|
"projectName": "src",
|
||||||
|
"projectRoot": "/Volumes/Data/project/Antigravity/ConnectAI/src",
|
||||||
|
"recordRoot": "/Volumes/Data/project/Antigravity/ConnectAI/src/docs/records/src",
|
||||||
|
"description": "Auto-detected from the local project path in the conversation.",
|
||||||
|
"corePurpose": "Capture project direction, architecture discussion, decisions, and development notes as Markdown.",
|
||||||
|
"detailLevel": "standard",
|
||||||
|
"createdAt": "2026-05-05T02:53:58.918Z",
|
||||||
|
"updatedAt": "2026-05-05T02:53:58.920Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Project Profile
|
||||||
|
|
||||||
|
## Project Name
|
||||||
|
src
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Auto-detected from the local project path in the conversation.
|
||||||
|
|
||||||
|
## Project Root
|
||||||
|
/Volumes/Data/project/Antigravity/ConnectAI/src
|
||||||
|
|
||||||
|
## Record Root
|
||||||
|
/Volumes/Data/project/Antigravity/ConnectAI/src/docs/records/src
|
||||||
|
|
||||||
|
## Core Purpose
|
||||||
|
Capture project direction, architecture discussion, decisions, and development notes as Markdown.
|
||||||
|
|
||||||
|
## Target Users
|
||||||
|
- Project developer
|
||||||
|
|
||||||
|
## Avoid Directions
|
||||||
|
- Do not mix records across projects.
|
||||||
|
|
||||||
|
## Record Detail Level
|
||||||
|
standard
|
||||||
|
|
||||||
|
## Created
|
||||||
|
2026-05-05T02:51:13.090Z
|
||||||
|
|
||||||
|
## Updated
|
||||||
|
2026-05-05T02:51:13.090Z
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Project Timeline
|
||||||
|
|
||||||
|
## 2026-05-05
|
||||||
|
- Project Chronicle record folder initialized for src.
|
||||||
|
|
||||||
|
## 2026-05-05
|
||||||
|
- Auto bug record created: bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md
|
||||||
|
|
||||||
|
## 2026-05-05
|
||||||
|
- Auto bug record created: bugs/BUG-0002-viewed-agent-ts-2159-2206-제공해주신-세부-파일-구조와-지식-수집-워크플로우-관점의-코드.md
|
||||||
@@ -116,7 +116,12 @@ export function assembleContext(chunks: RetrievalChunk[]): string {
|
|||||||
for (const [source, groupChunks] of groups) {
|
for (const [source, groupChunks] of groups) {
|
||||||
const label = sourceLabels[source] || source;
|
const label = sourceLabels[source] || source;
|
||||||
const items = groupChunks
|
const items = groupChunks
|
||||||
.map((c) => `- ${c.title}: ${c.content}`)
|
.map((c) => {
|
||||||
|
const metadata = c.metadata;
|
||||||
|
const conflictTag = metadata.conflictDetected ? ` [⚠️ CONFLICT: ${metadata.conflictSeverity}]` : '';
|
||||||
|
const densityTag = metadata.informationDensity !== undefined ? ` (Density: ${metadata.informationDensity.toFixed(2)})` : '';
|
||||||
|
return `- ${c.title}${conflictTag}${densityTag}: ${c.content}`;
|
||||||
|
})
|
||||||
.join('\n');
|
.join('\n');
|
||||||
sections.push(`### ${label}\n${items}`);
|
sections.push(`### ${label}\n${items}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,21 +138,25 @@ export class RetrievalOrchestrator {
|
|||||||
.filter((s) => s.score > 0)
|
.filter((s) => s.score > 0)
|
||||||
.sort((a, b) => b.score - a.score)
|
.sort((a, b) => b.score - a.score)
|
||||||
.slice(0, limit)
|
.slice(0, limit)
|
||||||
.map((scored) => {
|
.map((s) => {
|
||||||
const doc = documents[scored.index];
|
const doc = documents[s.index];
|
||||||
const excerpt = extractBestExcerpt(doc.content, expandedTokens, 400);
|
const excerpt = extractBestExcerpt(doc.content, expandedTokens, 400);
|
||||||
return {
|
return {
|
||||||
id: `brain-${scored.index}`,
|
id: `brain-${s.index}`,
|
||||||
source: 'brain-memory' as const,
|
source: 'brain-memory' as const,
|
||||||
title: doc.relativePath,
|
title: doc.relativePath,
|
||||||
content: summarizeText(excerpt, 400),
|
content: summarizeText(excerpt, 400),
|
||||||
score: scored.score,
|
score: s.score,
|
||||||
tokenEstimate: estimateTokens(excerpt),
|
tokenEstimate: estimateTokens(excerpt),
|
||||||
metadata: {
|
metadata: {
|
||||||
filePath: doc.filePath,
|
filePath: doc.filePath,
|
||||||
category: this.inferCategory(doc.relativePath),
|
category: this.inferCategory(doc.relativePath),
|
||||||
isProjectEvidence: this.isProjectEvidence(doc.relativePath, doc.content),
|
isProjectEvidence: this.isProjectEvidence(doc.relativePath, doc.content),
|
||||||
lastUpdated: doc.lastModified
|
lastUpdated: doc.lastModified,
|
||||||
|
// Phase 5: Scoring Intelligence Integration
|
||||||
|
conflictDetected: s.conflictDetected,
|
||||||
|
conflictSeverity: s.conflictSeverity,
|
||||||
|
informationDensity: s.informationDensity
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { RetrievalOrchestrator } from '../src/retrieval/index';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as utils from '../src/utils';
|
||||||
|
|
||||||
|
// Mocking dependencies
|
||||||
|
jest.mock('fs');
|
||||||
|
jest.mock('../src/utils');
|
||||||
|
|
||||||
|
describe('Retrieval Orchestrator Phase 5 Integration Tests', () => {
|
||||||
|
let orchestrator: RetrievalOrchestrator;
|
||||||
|
const mockBrainPath = '/mock/brain';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
orchestrator = new RetrievalOrchestrator();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('End-to-End Brain Search: should populate advanced scoring metadata in results', () => {
|
||||||
|
// 1. Mock Brain Files
|
||||||
|
const mockFiles = [
|
||||||
|
'/mock/brain/doc1.md',
|
||||||
|
'/mock/brain/doc2.md'
|
||||||
|
];
|
||||||
|
(utils.findBrainFiles as jest.Mock).mockReturnValue(mockFiles);
|
||||||
|
|
||||||
|
// 2. Mock File Content
|
||||||
|
const fileContents: Record<string, string> = {
|
||||||
|
'/mock/brain/doc1.md': 'Astra 성능 최적화 전략에 대한 문서입니다.',
|
||||||
|
'/mock/brain/doc2.md': '이 설계는 기존 아키텍처와 충돌하며 오류가 많고 논란이 있는 반대 의견입니다.'
|
||||||
|
};
|
||||||
|
(fs.readFileSync as jest.Mock).mockImplementation((path: string) => fileContents[path]);
|
||||||
|
(fs.statSync as jest.Mock).mockReturnValue({ mtimeMs: Date.now() });
|
||||||
|
|
||||||
|
// 3. Perform Retrieval
|
||||||
|
const brain = { localBrainPath: mockBrainPath };
|
||||||
|
const result = orchestrator.retrieve('최적화 충돌', {
|
||||||
|
brain: brain as any,
|
||||||
|
memoryManager: {
|
||||||
|
getLongTermMemory: () => ({ buildContext: () => null }),
|
||||||
|
getProjectMemory: () => ({ buildContext: () => null }),
|
||||||
|
getProceduralMemory: () => ({ buildContext: () => null }),
|
||||||
|
getEpisodicMemory: () => ({ buildContext: () => null })
|
||||||
|
} as any,
|
||||||
|
contextBudget: { totalBudget: 2000 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Verify Intelligence Metadata
|
||||||
|
expect(result.selectedChunks.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Find doc2 (the conflicting one)
|
||||||
|
const conflictChunk = result.selectedChunks.find(c => c.title.includes('doc2'));
|
||||||
|
expect(conflictChunk).toBeDefined();
|
||||||
|
if (conflictChunk) {
|
||||||
|
expect(conflictChunk.metadata.conflictDetected).toBe(true);
|
||||||
|
expect(conflictChunk.metadata.conflictSeverity).toBe('HIGH'); // '충돌', '오류', '논란', '반대' -> 4 indicators
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find doc1 (the dense one)
|
||||||
|
const denseChunk = result.selectedChunks.find(c => c.title.includes('doc1'));
|
||||||
|
expect(denseChunk).toBeDefined();
|
||||||
|
if (denseChunk) {
|
||||||
|
expect(denseChunk.metadata.informationDensity).toBeGreaterThan(0);
|
||||||
|
expect(denseChunk.metadata.conflictDetected).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Verify Assembled Context String
|
||||||
|
const contextString = orchestrator.buildContextString(result);
|
||||||
|
expect(contextString).toContain('[⚠️ CONFLICT: HIGH]');
|
||||||
|
expect(contextString).toContain('(Density:');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Score Normalization: should normalize scores across brain sources', () => {
|
||||||
|
(utils.findBrainFiles as jest.Mock).mockReturnValue(['/mock/brain/test.md']);
|
||||||
|
(fs.readFileSync as jest.Mock).mockReturnValue('테스트 내용');
|
||||||
|
(fs.statSync as jest.Mock).mockReturnValue({ mtimeMs: Date.now() });
|
||||||
|
|
||||||
|
const result = orchestrator.retrieve('테스트', {
|
||||||
|
brain: { localBrainPath: mockBrainPath } as any,
|
||||||
|
memoryManager: {
|
||||||
|
getLongTermMemory: () => ({ buildContext: () => null }),
|
||||||
|
getProjectMemory: () => ({ buildContext: () => null }),
|
||||||
|
getProceduralMemory: () => ({ buildContext: () => null }),
|
||||||
|
getEpisodicMemory: () => ({ buildContext: () => null })
|
||||||
|
} as any
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scores should be boosted by source priority (brain-memory boost is 0.9)
|
||||||
|
const chunk = result.selectedChunks[0];
|
||||||
|
expect(chunk.score).toBeCloseTo(0.9, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user