From d333042e7c5e8acf1503061f7cda24c2e67f5eaa Mon Sep 17 00:00:00 2001 From: g1nation Date: Tue, 5 May 2026 15:29:16 +0900 Subject: [PATCH] [RAG] Implement visual conflict and density metadata tags for enhanced context intelligence --- docs/docs/records/docs/README.md | 18 ++ ...trieval-test-ts-1-59-integration-retrie.md | 16 ++ docs/docs/records/docs/chronicle.config.json | 11 ++ docs/docs/records/docs/project-profile.md | 31 +++ docs/docs/records/docs/timeline.md | 7 + src/agent.ts | 186 +++++++++--------- src/docs/records/src/README.md | 18 ++ ...d-agent-ts-edited-agent-ts-edited-agent.md | 16 ++ ...€-파일-ꡬ쑰와-지식-μˆ˜μ§‘-μ›Œν¬ν”Œλ‘œμš°-κ΄€μ μ˜-μ½”λ“œ.md | 16 ++ src/docs/records/src/chronicle.config.json | 11 ++ src/docs/records/src/project-profile.md | 31 +++ src/docs/records/src/timeline.md | 10 + src/retrieval/contextBudget.ts | 7 +- src/retrieval/index.ts | 14 +- tests/integration_retrieval.test.ts | 91 +++++++++ 15 files changed, 385 insertions(+), 98 deletions(-) create mode 100644 docs/docs/records/docs/README.md create mode 100644 docs/docs/records/docs/bugs/BUG-0001-viewed-integration-retrieval-test-ts-1-59-integration-retrie.md create mode 100644 docs/docs/records/docs/chronicle.config.json create mode 100644 docs/docs/records/docs/project-profile.md create mode 100644 docs/docs/records/docs/timeline.md create mode 100644 src/docs/records/src/README.md create mode 100644 src/docs/records/src/bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md create mode 100644 src/docs/records/src/bugs/BUG-0002-viewed-agent-ts-2159-2206-μ œκ³΅ν•΄μ£Όμ‹ -μ„ΈλΆ€-파일-ꡬ쑰와-지식-μˆ˜μ§‘-μ›Œν¬ν”Œλ‘œμš°-κ΄€μ μ˜-μ½”λ“œ.md create mode 100644 src/docs/records/src/chronicle.config.json create mode 100644 src/docs/records/src/project-profile.md create mode 100644 src/docs/records/src/timeline.md create mode 100644 tests/integration_retrieval.test.ts diff --git a/docs/docs/records/docs/README.md b/docs/docs/records/docs/README.md new file mode 100644 index 0000000..db7fd9a --- /dev/null +++ b/docs/docs/records/docs/README.md @@ -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/` diff --git a/docs/docs/records/docs/bugs/BUG-0001-viewed-integration-retrieval-test-ts-1-59-integration-retrie.md b/docs/docs/records/docs/bugs/BUG-0001-viewed-integration-retrieval-test-ts-1-59-integration-retrie.md new file mode 100644 index 0000000..c3eb412 --- /dev/null +++ b/docs/docs/records/docs/bugs/BUG-0001-viewed-integration-retrieval-test-ts-1-59-integration-retrie.md @@ -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. diff --git a/docs/docs/records/docs/chronicle.config.json b/docs/docs/records/docs/chronicle.config.json new file mode 100644 index 0000000..4668f79 --- /dev/null +++ b/docs/docs/records/docs/chronicle.config.json @@ -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" +} diff --git a/docs/docs/records/docs/project-profile.md b/docs/docs/records/docs/project-profile.md new file mode 100644 index 0000000..57cc79f --- /dev/null +++ b/docs/docs/records/docs/project-profile.md @@ -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 diff --git a/docs/docs/records/docs/timeline.md b/docs/docs/records/docs/timeline.md new file mode 100644 index 0000000..9c8ce58 --- /dev/null +++ b/docs/docs/records/docs/timeline.md @@ -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 diff --git a/src/agent.ts b/src/agent.ts index 3be4b2d..423d0e6 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -1,8 +1,10 @@ -import * as vscode from 'vscode'; +agent.ts 인데 μ½”λ“œ 뢄석해주면 μ’‹κ² μ–΄. + + import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; // axios removed -import { +import { findBrainFiles, getSystemPrompt, shouldAutoPushBrain, @@ -21,10 +23,10 @@ import { PlannerAgent, ResearcherAgent, WriterAgent } from './agents/factory'; import { AgentWorkflowManager } from './agents/AgentWorkflowManager'; import { ErrorTranslator } from './core/errorHandler'; import { agentEvents, AgentEventTypes } from './core/events'; -import { - AgentExecutionError, - FileSystemError, - APICommunicationError +import { + AgentExecutionError, + FileSystemError, + APICommunicationError } from './core/errors'; import { StatusBarManager, AgentStatus } from './core/statusBar'; import { lockManager } from './core/lock'; @@ -109,7 +111,7 @@ export class AgentExecutor { private parseRationale(text: string) { const match = text.match(/([\s\S]*?)<\/rationale>/); if (!match) return undefined; - + const raw = match[1]; const problem = raw.match(/\[PROBLEM\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || ""; const goal = raw.match(/\[GOAL\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || ""; @@ -202,8 +204,8 @@ export class AgentExecutor { public async handlePrompt( prompt: string | null, modelName: string, - options: { - internetEnabled?: boolean, + options: { + internetEnabled?: boolean, brainEnabled?: boolean, loopDepth?: number, visionContent?: any[], @@ -218,17 +220,17 @@ export class AgentExecutor { brainProfileId?: string } ) { - const { - internetEnabled = false, - brainEnabled = false, - loopDepth = 0, - visionContent, + const { + internetEnabled = false, + brainEnabled = false, + loopDepth = 0, + visionContent, temperature = 0.7, systemPrompt = getSystemPrompt() } = options; const { ollamaUrl, defaultModel: configDefaultModel, timeout, multiAgentEnabled } = getConfig(); const runId = options.runId ?? (loopDepth === 0 ? ++this.runSerial : this.activeRunId); - + // Decide whether to use Multi-Agent Workflow as an internal execution strategy. if (loopDepth === 0 && this.shouldUseMultiAgentWorkflow(prompt || '', multiAgentEnabled)) { return this.executeMultiAgentWorkflow(prompt!, modelName, options); @@ -260,7 +262,7 @@ export class AgentExecutor { // 1. Prepare Context const workspaceFolders = vscode.workspace.workspaceFolders; const rootPath = workspaceFolders ? workspaceFolders[0].uri.fsPath : ''; - + let contextBlock = ''; const config = getConfig(); const activeBrain = options.brainProfileId @@ -358,13 +360,13 @@ export class AgentExecutor { const internetCtx = internetEnabled ? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use to search. Current time: ${new Date().toLocaleString()}` : ''; - + 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}` : ''; const agentSkillCtx = selectedAgentSystemPrompt; - 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.]` + 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.]` : ''; const designerCtx = options.designerContext ? `\n\n[PROJECT CHRONICLE GUARD]\n${options.designerContext}` @@ -382,7 +384,7 @@ export class AgentExecutor { ? `\n\n${renderSecondBrainTraceContext(secondBrainTrace)}` : ''; const memoryCtx = this.buildMemoryContext(prompt || '', activeBrain); - + // [Astra v4.0] 지식 관리 운영 μ •μ±… μ£Όμž… const v4PolicyCtx = [ "\n### πŸ›οΈ 지식 관리 μ •μ±… v4.0 (Knowledge Management Policy Applied)", @@ -538,23 +540,23 @@ export class AgentExecutor { if (report.length > 0) { this.emitHistoryChanged(); logInfo('Agent actions executed.', { loopDepth: loopDepth + 1, report }); - + // Continue loop if needed if (loopDepth < config.maxAutoSteps) { const currentActionStr = report.join('|'); const lastActionStr = this.context.workspaceState.get('lastActionStr'); - + if (currentActionStr === lastActionStr) { this.webview.postMessage({ type: 'streamChunk', value: "\n⚠️ *Stopping to prevent infinite loop.*" }); return; } - + await this.context.workspaceState.update('lastActionStr', currentActionStr); logInfo('Autonomous loop continuing after actions.', { loopDepth: loopDepth + 1, actions: report }); - + // 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."; - + this.webview.postMessage({ type: 'autoContinue', value: `자료λ₯Ό ν™•μΈν•˜κ³  닡변을 μ •λ¦¬ν•˜λŠ” μ€‘μž…λ‹ˆλ‹€... (${loopDepth + 1}/${config.maxAutoSteps})` }); await new Promise(r => setTimeout(r, 800)); if (this.isStaleRun(runId)) return; @@ -591,7 +593,7 @@ export class AgentExecutor { options: any ) { if (!this.webview) return; - this.stop(); + this.stop(); this.abortController = new AbortController(); const signal = this.abortController.signal; @@ -632,13 +634,13 @@ export class AgentExecutor { ); if (signal.aborted || !this.webview) return; - + this.webview.postMessage({ type: 'streamChunk', value: `\n\n--- \n\n${finalReport}` }); this.webview.postMessage({ type: 'streamEnd' }); - + this.chatHistory.push({ role: 'assistant', content: finalReport }); this.emitHistoryChanged(); - + this.statusBarManager.updateStatus(AgentStatus.Success, 'Workflow Complete'); this.webview.postMessage({ type: 'autoContinue', value: 'βœ… λͺ¨λ“  뢄석이 μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.' }); @@ -649,11 +651,11 @@ export class AgentExecutor { } const friendly = ErrorTranslator.translate(error); logError('Workflow failed', error); - + this.webview.postMessage({ type: 'autoContinue', value: '' }); - this.webview.postMessage({ - type: 'error', - value: `### ${friendly.title}\n\n**μƒνƒœ:** ${friendly.message}\n\n**ν•΄κ²° 방법:** ${friendly.action}` + this.webview.postMessage({ + type: 'error', + value: `### ${friendly.title}\n\n**μƒνƒœ:** ${friendly.message}\n\n**ν•΄κ²° 방법:** ${friendly.action}` }); 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 { const persona = AGENT_PROMPTS[role]; const { ollamaUrl, timeout } = getConfig(); - + const messages: ChatMessage[] = [ { role: 'system', content: persona }, { role: 'user', content: prompt } @@ -692,7 +694,7 @@ export class AgentExecutor { const json = JSON.parse(trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed); const content = json.choices?.[0]?.delta?.content || json.message?.content || ''; responseText += content; - } catch (e) {} + } catch (e) { } } } return responseText; @@ -1821,8 +1823,8 @@ export class AgentExecutor { // Save session whenever history changes this.sessionManager.saveSession( - this.currentTaskId, - this.chatHistory, + this.currentTaskId, + this.chatHistory, this.context.workspaceState.get('lastActionStr') ); @@ -1868,54 +1870,54 @@ export class AgentExecutor { const modelCandidates = this.buildModelCandidates(modelName, engine); for (const candidateModel of modelCandidates) { - for (const variant of messageVariants) { - 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, + for (const variant of messageVariants) { + const streamBody = { 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 - }); + messages: variant.messages, + stream: true, + ...(engine === 'lmstudio' + ? { max_tokens: 4096, temperature } + : { options: { num_ctx: 32768, num_predict: 4096, temperature } }), + }; - 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; + try { + logInfo('AI streaming request started.', { + engine, + apiUrl, + 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.'); @@ -1986,14 +1988,14 @@ export class AgentExecutor { try { const absPath = validatePath(rootPath, relPath); await this.transactionManager.record(absPath); - + fs.mkdirSync(path.dirname(absPath), { recursive: true }); fs.writeFileSync(absPath, content, 'utf-8'); - + report.push(`βœ… Created: ${relPath}`); if (!firstCreatedFile) firstCreatedFile = absPath; if (absPath.startsWith(activeBrainDir)) brainModified = true; - } catch (err: any) { + } catch (err: any) { throw new FileSystemError(`Failed to create file ${relPath}: ${err.message}`, relPath, err); } } @@ -2007,10 +2009,10 @@ export class AgentExecutor { const absPath = validatePath(rootPath, relPath); if (fs.existsSync(absPath)) { await this.transactionManager.record(absPath); - + let currentContent = fs.readFileSync(absPath, 'utf-8'); const searchMatch = editContent.match(/([\s\S]*?)<\/search>\s*([\s\S]*?)<\/replace>/i); - + if (searchMatch) { const searchStr = searchMatch[1]; const replaceStr = searchMatch[2]; @@ -2029,7 +2031,7 @@ export class AgentExecutor { } else { report.push(`❌ File not found: ${relPath}`); } - } catch (err: any) { + } catch (err: any) { throw new FileSystemError(`Failed to edit file ${relPath}: ${err.message}`, relPath, err); } } @@ -2047,7 +2049,7 @@ export class AgentExecutor { } else { 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); } } @@ -2094,7 +2096,7 @@ export class AgentExecutor { .filter(e => !e.name.startsWith('.') && !EXCLUDED_DIRS.has(e.name)) .map(e => e.isDirectory() ? `${e.name}/` : e.name) .join('\n'); - + if (listing.length > 5000) { 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)) .map(e => e.isDirectory() ? `${e.name}/` : e.name) .join('\n'); - + if (listing.length > 5000) { listing = listing.slice(0, 5000) + "\n... (truncated for context)"; } - + report.push(`🧠 Brain Listed: ${relPath}`); this.chatHistory.push({ role: 'system', content: `[Result of list_brain ${relPath}]\n${listing}`, internal: true }); } else { @@ -2138,7 +2140,7 @@ export class AgentExecutor { const brainDir = activeBrainDir; const files = findBrainFiles(brainDir); const targetFile = files.find((f: string) => path.basename(f) === fileName || f.endsWith(fileName)); - + if (targetFile && fs.existsSync(targetFile)) { const content = fs.readFileSync(targetFile, 'utf-8'); report.push(`🧠 Brain Read: ${fileName}`); @@ -2201,4 +2203,4 @@ export class AgentExecutor { logError('Second Brain sync failed.', err); } } -} +} \ No newline at end of file diff --git a/src/docs/records/src/README.md b/src/docs/records/src/README.md new file mode 100644 index 0000000..607e4ff --- /dev/null +++ b/src/docs/records/src/README.md @@ -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/` diff --git a/src/docs/records/src/bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md b/src/docs/records/src/bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md new file mode 100644 index 0000000..e069304 --- /dev/null +++ b/src/docs/records/src/bugs/BUG-0001-edited-agent-ts-edited-agent-ts-edited-agent-ts-edited-agent.md @@ -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. diff --git a/src/docs/records/src/bugs/BUG-0002-viewed-agent-ts-2159-2206-μ œκ³΅ν•΄μ£Όμ‹ -μ„ΈλΆ€-파일-ꡬ쑰와-지식-μˆ˜μ§‘-μ›Œν¬ν”Œλ‘œμš°-κ΄€μ μ˜-μ½”λ“œ.md b/src/docs/records/src/bugs/BUG-0002-viewed-agent-ts-2159-2206-μ œκ³΅ν•΄μ£Όμ‹ -μ„ΈλΆ€-파일-ꡬ쑰와-지식-μˆ˜μ§‘-μ›Œν¬ν”Œλ‘œμš°-κ΄€μ μ˜-μ½”λ“œ.md new file mode 100644 index 0000000..bb10820 --- /dev/null +++ b/src/docs/records/src/bugs/BUG-0002-viewed-agent-ts-2159-2206-μ œκ³΅ν•΄μ£Όμ‹ -μ„ΈλΆ€-파일-ꡬ쑰와-지식-μˆ˜μ§‘-μ›Œν¬ν”Œλ‘œμš°-κ΄€μ μ˜-μ½”λ“œ.md @@ -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. diff --git a/src/docs/records/src/chronicle.config.json b/src/docs/records/src/chronicle.config.json new file mode 100644 index 0000000..be4ba75 --- /dev/null +++ b/src/docs/records/src/chronicle.config.json @@ -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" +} diff --git a/src/docs/records/src/project-profile.md b/src/docs/records/src/project-profile.md new file mode 100644 index 0000000..44e84e2 --- /dev/null +++ b/src/docs/records/src/project-profile.md @@ -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 diff --git a/src/docs/records/src/timeline.md b/src/docs/records/src/timeline.md new file mode 100644 index 0000000..fe85191 --- /dev/null +++ b/src/docs/records/src/timeline.md @@ -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 diff --git a/src/retrieval/contextBudget.ts b/src/retrieval/contextBudget.ts index 73e8bd3..2f3b71e 100644 --- a/src/retrieval/contextBudget.ts +++ b/src/retrieval/contextBudget.ts @@ -116,7 +116,12 @@ export function assembleContext(chunks: RetrievalChunk[]): string { for (const [source, groupChunks] of groups) { const label = sourceLabels[source] || source; 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'); sections.push(`### ${label}\n${items}`); } diff --git a/src/retrieval/index.ts b/src/retrieval/index.ts index f021ec9..8053e32 100644 --- a/src/retrieval/index.ts +++ b/src/retrieval/index.ts @@ -138,21 +138,25 @@ export class RetrievalOrchestrator { .filter((s) => s.score > 0) .sort((a, b) => b.score - a.score) .slice(0, limit) - .map((scored) => { - const doc = documents[scored.index]; + .map((s) => { + const doc = documents[s.index]; const excerpt = extractBestExcerpt(doc.content, expandedTokens, 400); return { - id: `brain-${scored.index}`, + id: `brain-${s.index}`, source: 'brain-memory' as const, title: doc.relativePath, content: summarizeText(excerpt, 400), - score: scored.score, + score: s.score, tokenEstimate: estimateTokens(excerpt), metadata: { filePath: doc.filePath, category: this.inferCategory(doc.relativePath), 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 } }; }); diff --git a/tests/integration_retrieval.test.ts b/tests/integration_retrieval.test.ts new file mode 100644 index 0000000..b76e6ed --- /dev/null +++ b/tests/integration_retrieval.test.ts @@ -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 = { + '/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); + }); +});