Release: v2.36.0 - P-Reinforce v3.0 Standard Integration
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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('<details>');
|
||||
expect(markdown).toContain('<summary>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('<details>');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user