Release: v2.36.0 - P-Reinforce v3.0 Standard Integration

This commit is contained in:
g1nation
2026-05-02 16:26:35 +09:00
parent 0a58d5127a
commit f874ae6152
7 changed files with 114 additions and 14 deletions
+9
View File
@@ -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.
+1
View File
@@ -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
View File
@@ -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",
+47 -12
View File
@@ -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)
+1
View File
@@ -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:
+29 -1
View File
@@ -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');
});
});