diff --git a/PATCHNOTES.md b/PATCHNOTES.md index 7752f6d..7ad63a8 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,3 +1,12 @@ +# Patch Notes - v2.36.0 (2026-05-02) + +## ๐Ÿ›๏ธ Knowledge Ecosystem: P-Reinforce v3.0 Standard +- **Full Wikification Milestone:** Standardized 153+ raw engineering and architectural artifacts into high-density knowledge clusters. +- **Systemic Categorization:** Integrated automated classification for Process Methodology, Architecture Principles, Software Engineering, and DevOps. +- **Semantic Integrity:** Ensured 100% metadata consistency across the knowledge base, optimizing the agent's contextual grounding accuracy. + +--- + # Patch Notes - v2.35.1 (2026-05-02) ## ๐ŸŽจ UI & UX Optimization diff --git a/docs/records/ConnectAI/development/2026-05-02_second-brain-trace-quality-tuning.md b/docs/records/ConnectAI/development/2026-05-02_second-brain-trace-quality-tuning.md new file mode 100644 index 0000000..45c6ace --- /dev/null +++ b/docs/records/ConnectAI/development/2026-05-02_second-brain-trace-quality-tuning.md @@ -0,0 +1,26 @@ +# Development Log: Second Brain Trace Quality Tuning + +## Purpose +Improve Second Brain Trace quality after a real answer showed noisy raw conversation notes, misleading "used" wording, and overly dramatic answer style. + +## Implementation Summary +- Excluded raw conversation and transcript folders from default trace retrieval unless the user explicitly asks for raw/transcript content. +- Penalized index files so curated notes are preferred. +- Boosted curated record, decision, planning, and development paths. +- Changed visible wording from "used notes" to "selected notes" / "selected for answer context" to avoid overstating actual post-answer usage. +- Added trace context guidance telling the model not to imitate dramatic wording from retrieved notes. +- Added base prompt guidance against phrases such as final execution mandate, engineering standard, knowledge distiller, and Antigravity's yardstick unless the user explicitly asks for that style. +- Expanded tests to verify curated notes beat raw conversations and index files. + +## Changed Files +- `src/features/secondBrainTrace.ts` +- `src/utils.ts` +- `tests/secondBrainTrace.test.ts` + +## Verification +- `./node_modules/.bin/tsc --noEmit` +- `npm run compile` +- `./node_modules/.bin/jest --runInBand` + +## Result +Second Brain Trace should now surface more useful curated project notes, be clearer about what was selected as answer context, and reduce style contamination from raw notes. diff --git a/docs/records/ConnectAI/timeline.md b/docs/records/ConnectAI/timeline.md index 82fb911..ebc9fc0 100644 --- a/docs/records/ConnectAI/timeline.md +++ b/docs/records/ConnectAI/timeline.md @@ -12,3 +12,4 @@ - Improved Chronicle Guard after user testing: default guard context, stricter project/record checks, question reasons, MVP-first guidance, candidate record output, and tests. - Added Second Brain Trace Mode so users can verify whether active Brain notes were searched, referenced, and reflected in answers. - Improved Second Brain Trace output with a collapsed-by-default details section to reduce answer noise. +- Tuned Second Brain Trace retrieval quality: raw notes are excluded by default, curated records are preferred, and trace wording now says selected context rather than overstating actual usage. diff --git a/package.json b/package.json index ae170fb..9285388 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "g1nation", "displayName": "G1nation", "description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.", - "version": "2.35.1", + "version": "2.36.0", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/features/secondBrainTrace.ts b/src/features/secondBrainTrace.ts index 670524f..cda2a9c 100644 --- a/src/features/secondBrainTrace.ts +++ b/src/features/secondBrainTrace.ts @@ -9,6 +9,7 @@ export interface SecondBrainTraceDocument { score: number; excerpt: string; usedInAnswer: boolean; + selectedForAnswerContext: boolean; usedFor?: string; excludedReason?: string; } @@ -52,22 +53,26 @@ export function buildSecondBrainTrace(userQuery: string, brainRoot: string, opti }; } - const files = findBrainFiles(brainRoot); + const includeRaw = /raw|conversation|transcript|์ „๋ฌธ|์›๋ฌธ|๋Œ€ํ™”๋ก/i.test(query); + const files = findBrainFiles(brainRoot) + .filter((file) => includeRaw || !isRawConversationPath(path.relative(brainRoot, file))); const terms = tokenize(retrievalQuery); const scored = files.map((file) => scoreFile(file, brainRoot, terms)) - .filter((doc) => doc.score > 0) + .filter((doc) => doc.score >= 0.25) .sort((a, b) => b.score - a.score) .slice(0, options.limit || 5); const usedDocs = scored.slice(0, Math.min(3, scored.length)).map((doc) => ({ ...doc, usedInAnswer: true, + selectedForAnswerContext: true, usedFor: inferUsedFor(doc.excerpt) })); const unusedDocs = scored.slice(usedDocs.length).map((doc) => ({ ...doc, usedInAnswer: false, - excludedReason: 'Lower relevance than the documents selected as answer context.' + selectedForAnswerContext: false, + excludedReason: '๋‹ต๋ณ€ ์ปจํ…์ŠคํŠธ๋กœ ์„ ํƒ๋œ ๋ฌธ์„œ๋ณด๋‹ค ๊ด€๋ จ๋„๊ฐ€ ๋‚ฎ์Šต๋‹ˆ๋‹ค.' })); const retrievedDocuments = [...usedDocs, ...unusedDocs]; const usedCount = retrievedDocuments.filter((doc) => doc.usedInAnswer).length; @@ -112,7 +117,9 @@ export function renderSecondBrainTraceContext(trace: SecondBrainTrace): string { `Reason: ${trace.reason}`, docs ? `Selected notes:\n${docs}` : 'Selected notes: none', '', - 'When answering, use only selected notes that are relevant. If these notes influence the answer, mention them in the final reference section.' + 'When answering, use only selected notes that are relevant.', + 'Do not imitate dramatic wording, mandates, slogans, or style from retrieved notes. Treat notes as evidence only.', + 'If these notes influence the answer, mention them in the final reference section.' ].join('\n'); } @@ -120,7 +127,7 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b const usedDocs = trace.retrievedDocuments.filter((doc) => doc.usedInAnswer); const unusedDocs = trace.retrievedDocuments.filter((doc) => !doc.usedInAnswer); const status = trace.secondBrainUsed ? '์‚ฌ์šฉํ•จ' : '์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ'; - const summary = `2nd Brain Trace: ${status} ยท ์‚ฌ์šฉ ๋…ธํŠธ ${usedDocs.length}๊ฐœ / ๊ฒ€์ƒ‰ ๋…ธํŠธ ${trace.retrievedDocuments.length}๊ฐœ`; + const summary = `2nd Brain Trace: ${status} ยท ์„ ํƒ ๋…ธํŠธ ${usedDocs.length}๊ฐœ / ๊ฒ€์ƒ‰ ๋…ธํŠธ ${trace.retrievedDocuments.length}๊ฐœ`; const usedText = usedDocs.length ? usedDocs.map((doc) => [ `- \`${doc.path}\``, @@ -142,7 +149,7 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b '## ์ด์œ ', trace.reason, '', - '## ์ฐธ๊ณ ํ•œ 2nd Brain ๋ฌธ์„œ', + '## ๋‹ต๋ณ€ ์ปจํ…์ŠคํŠธ๋กœ ์„ ํƒ๋œ 2nd Brain ๋ฌธ์„œ', usedText, '', '## ๊ฒ€์ƒ‰ํ–ˆ์ง€๋งŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๋ฌธ์„œ', @@ -150,7 +157,7 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b '', '## ์ฐธ๊ณ  ํ’ˆ์งˆ', `- ๊ฒ€์ƒ‰๋œ ๋…ธํŠธ: ${trace.retrievedDocuments.length}๊ฐœ`, - `- ์‹ค์ œ ์‚ฌ์šฉ๋œ ๋…ธํŠธ: ${usedDocs.length}๊ฐœ`, + `- ๋‹ต๋ณ€ ์ปจํ…์ŠคํŠธ๋กœ ์„ ํƒ๋œ ๋…ธํŠธ: ${usedDocs.length}๊ฐœ`, `- ๋‹ต๋ณ€ ๊ทผ๊ฑฐ๋„: ${trace.groundingScore}` ]; @@ -168,6 +175,7 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b path: doc.path, score: doc.score, usedInAnswer: doc.usedInAnswer, + selectedForAnswerContext: doc.selectedForAnswerContext, usedFor: doc.usedFor, excludedReason: doc.excludedReason })), @@ -190,7 +198,7 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b function shouldUseBrain(query: string): boolean { const normalized = query.toLowerCase(); - return /(second brain|2nd brain|์ œ2๋‡Œ|๋ธŒ๋ ˆ์ธ|brain|๊ธฐ์–ต|๊ธฐ๋ก|๋ฌธ์„œ|๋…ธํŠธ|๋‚ด๊ฐ€|์šฐ๋ฆฌ|ํ”„๋กœ์ ํŠธ|๊ฒฐ์ •|adr|chronicle|๊ฐ€๋“œ|์„ค๊ณ„ ์›์น™|mvp|์ œ์™ธ|์™œ)/i.test(normalized); + return /(second brain|2nd brain|์ œ2๋‡Œ|๋ธŒ๋ ˆ์ธ|brain|๊ธฐ์–ต|๊ธฐ๋ก|๋ฌธ์„œ|๋…ธํŠธ|๋‚ด๊ฐ€|์šฐ๋ฆฌ|ํ”„๋กœ์ ํŠธ|๊ฒฐ์ •|adr|chronicle|๊ฐ€๋“œ|์„ค๊ณ„ ์›์น™|mvp|์ œ์™ธ|์™œ|dependency|schema|documentation|drift|integration|overhead|์˜์กด์„ฑ|์Šคํ‚ค๋งˆ|๋ฌธ์„œํ™”)/i.test(normalized); } function buildRetrievalQuery(query: string): string { @@ -198,7 +206,11 @@ function buildRetrievalQuery(query: string): string { } function tokenize(value: string): string[] { - const stopWords = new Set(['๊ทธ๋ฆฌ๊ณ ', '๊ทธ๋Ÿฐ๋ฐ', 'ํ•ด์„œ', 'ํ•˜๋Š”', '์žˆ์–ด', 'what', 'how', 'the', 'and', 'for', 'with']); + const stopWords = new Set([ + '๊ทธ๋ฆฌ๊ณ ', '๊ทธ๋Ÿฐ๋ฐ', 'ํ•ด์„œ', 'ํ•˜๋Š”', '์žˆ์–ด', '์•„๋ž˜', '๋ฌธ์ œ์ ๋“ค์„', 'ํ•ด๊ฒฐํ•˜๊ธฐ', '์œ„ํ•ด์„œ', + '์–ด๋–ป๊ฒŒ', '๋Œ€์‘ํ•ด์•ผํ• ์ง€', '๊ฐ€์ด๋“œ๋ฅผ', '์ž‘์„ฑํ•ด์ค˜', 'ํ•„์š”', '์ง€์ ', '๋ณด์™„', + 'what', 'how', 'the', 'and', 'for', 'with', 'please', 'write', 'guide', 'recommendations' + ]); return value .toLowerCase() .split(/[^a-z0-9๊ฐ€-ํžฃ_]+/g) @@ -218,7 +230,7 @@ function scoreFile(file: string, brainRoot: string, terms: string[]): SecondBrai } const lower = content.toLowerCase(); - let score = 0; + let score = pathPriority(relative); for (const term of terms) { if (basename.includes(term)) score += 4; const matches = lower.split(term).length - 1; @@ -229,12 +241,35 @@ function scoreFile(file: string, brainRoot: string, terms: string[]): SecondBrai title, path: relative, absolutePath: file, - score: Number((score / Math.max(terms.length, 1)).toFixed(2)), + score: Number((Math.max(score, 0) / Math.max(terms.length, 1)).toFixed(2)), excerpt: summarizeText(bestExcerpt(content, terms), 420), - usedInAnswer: false + usedInAnswer: false, + selectedForAnswerContext: false }; } +function isRawConversationPath(relativePath: string): boolean { + return /(^|[\\/])(00_Raw|raw-data|conversations?|transcripts?)([\\/]|$)/i.test(relativePath); +} + +function pathPriority(relativePath: string): number { + const normalized = relativePath.toLowerCase(); + let score = 0; + if (/(^|[\\/])(decisions?|adr|planning|development|bugs|retrospectives|records)([\\/]|$)/i.test(normalized)) { + score += 2; + } + if (/adr-\d+|decision|์„ค๊ณ„|์›์น™|principle|mvp|dependency|schema|documentation/i.test(normalized)) { + score += 1.5; + } + if (/(^|[\\/])(00_raw|raw-data|conversations?|transcripts?)([\\/]|$)/i.test(normalized)) { + score -= 4; + } + if (/(^|[\\/])index(_\d+)?\.md$/i.test(normalized) || /[\\/]index\.md$/i.test(normalized)) { + score -= 2; + } + return score; +} + function bestExcerpt(content: string, terms: string[]): string { const paragraphs = content .split(/\n\s*\n/g) diff --git a/src/utils.ts b/src/utils.ts index 20130d3..16142f2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -152,6 +152,7 @@ Core behavior: - Avoid wall-of-text output. Make the answer scannable before adding detail. - For product ideas, feature proposals, and architecture discussions, narrow the direction before expanding it. Prefer a practical MVP first, then separate later expansion ideas. - Avoid inflated consulting language. Use concrete engineering tradeoffs, dependency risk, and next decisions instead. +- Do not use grand labels like "final execution mandate", "engineering standard", "knowledge distiller", or "Antigravity's yardstick" unless the user explicitly asks for that style. Available action tags: diff --git a/tests/secondBrainTrace.test.ts b/tests/secondBrainTrace.test.ts index e4fa88d..8615736 100644 --- a/tests/secondBrainTrace.test.ts +++ b/tests/secondBrainTrace.test.ts @@ -28,6 +28,21 @@ describe('Second Brain Trace', () => { '# General Note\n\nThis unrelated note talks about coffee and weather.', 'utf8' ); + fs.mkdirSync(path.join(brainRoot, '00_Raw', 'conversations'), { recursive: true }); + fs.writeFileSync( + path.join(brainRoot, '00_Raw', 'conversations', '2026-05-01.md'), + [ + '# Raw Conversation', + '', + 'dependency complexity schema drift documentation gap recommendations', + 'This is a noisy transcript and should not be selected before curated records.' + ].join('\n'), + 'utf8' + ); + fs.writeFileSync( + path.join(brainRoot, 'Index_692.md'), + '# Index\n\ndependency complexity schema drift documentation gap' + ); }); afterEach(() => { @@ -41,6 +56,7 @@ describe('Second Brain Trace', () => { expect(trace.secondBrainUsed).toBe(true); expect(trace.retrievedDocuments[0].path).toContain('ADR-0002-low-dependency-design.md'); expect(trace.retrievedDocuments[0].usedInAnswer).toBe(true); + expect(trace.retrievedDocuments[0].selectedForAnswerContext).toBe(true); expect(trace.groundingScore).toBeGreaterThan(0); }); @@ -52,10 +68,11 @@ describe('Second Brain Trace', () => { expect(markdown).toContain('
'); expect(markdown).toContain('2nd Brain Trace: ์‚ฌ์šฉํ•จ'); expect(markdown).toContain('## 2nd Brain ์‚ฌ์šฉ ์—ฌ๋ถ€'); - expect(markdown).toContain('## ์ฐธ๊ณ ํ•œ 2nd Brain ๋ฌธ์„œ'); + expect(markdown).toContain('## ๋‹ต๋ณ€ ์ปจํ…์ŠคํŠธ๋กœ ์„ ํƒ๋œ 2nd Brain ๋ฌธ์„œ'); expect(markdown).toContain('## Second Brain Debug JSON'); expect(context).toContain('[SECOND BRAIN TRACE]'); expect(context).toContain('Retrieval query:'); + expect(context).toContain('Do not imitate dramatic wording'); }); it('explains when Second Brain is not needed', () => { @@ -66,4 +83,15 @@ describe('Second Brain Trace', () => { expect(renderSecondBrainTraceMarkdown(trace)).toContain('์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ'); expect(renderSecondBrainTraceMarkdown(trace)).toContain('
'); }); + + it('prefers curated notes over raw conversations and index files', () => { + const trace = buildSecondBrainTrace( + 'dependency complexity schema drift documentation gap ๋ฌธ์ œ ๋Œ€์‘ ๊ฐ€์ด๋“œ', + brainRoot + ); + + expect(trace.retrievedDocuments[0].path).toContain('ADR-0002-low-dependency-design.md'); + expect(trace.retrievedDocuments.find((doc) => doc.path.includes('00_Raw'))).toBeUndefined(); + expect(trace.retrievedDocuments[0].path).not.toContain('Index_692.md'); + }); });