Feat: Enhance query intent search and answer format readability

This commit is contained in:
g1nation
2026-05-02 18:20:22 +09:00
parent 26bbb22c7e
commit da4ebe3942
9 changed files with 173 additions and 12 deletions
+6 -1
View File
@@ -21,7 +21,7 @@ export function buildProjectChronicleGuardContext(project: ProjectProfile | null
'',
'Required response order for new ideas or feature requests:',
'1. Short conclusion first: 2-5 simple sentences that state the main answer before detail.',
'2. Brief summary: 3-5 bullets or a compact paragraph that previews the full answer.',
'2. Brief summary: one compact paragraph by default. Avoid bullet-heavy formatting.',
'3. Detailed answer.',
'4. Request summary.',
'5. Inferred user intent.',
@@ -46,10 +46,15 @@ export function buildProjectChronicleGuardContext(project: ProjectProfile | null
'- If only general/reference notes are available, avoid phrases like "the current architecture has..." or "the project is prepared for..." and use "if implemented" or "needs verification" wording.',
'- If only general/reference notes are available, explicitly say: "현재 정보만으로는 기술 구조를 판단할 수 없습니다."',
'- Without project evidence, never say the architecture is flexible, technically stable, scalable, gateway-based, microservice-ready, layered, or structurally prepared.',
'- When the user asks about customer evaluation, approval likelihood, development direction fit, business value, UX, customer journey, product discovery, or purchase conversion, treat the answer as a UX/business inference unless explicit approval criteria are provided.',
'- For approval/evaluation questions, prefer this structure: confirmed facts, inference, general UX/business principle, needs verification, recommended direction.',
'- Be realistic about approval: if the user has not shown that the experience connects to product discovery or purchase conversion, say that revision or clarification requests are likely rather than saying it may be positive.',
'',
'Tone and scope:',
'- Be practical and plain-spoken.',
'- Keep the top conclusion calm and short so the user can understand the answer before reading the long version.',
'- Prefer short paragraphs with blank lines between numbered sections. Avoid starting most lines with `*` or `-` bullets.',
'- Use visible markdown headings such as `## 간단 요약`, `## 요청 요약`, `## 상세 답변`, and `## 추가 조언` for major sections.',
'- Avoid grand phrases like advanced cognitive architecture, compounding knowledge, perfect graph, or ultimate knowledge distiller.',
'- When the user wants low dependency, keep the first proposal to Markdown, JSON, local files, and explicit user save actions.',
'- Do not jump directly to large architectures. Narrow direction before expanding.',
+58 -8
View File
@@ -3,6 +3,7 @@ import * as path from 'path';
import { findBrainFiles, summarizeText } from '../utils';
export type SecondBrainSourceType = 'Project Evidence' | 'User Decision' | 'General Knowledge' | 'Reference Only';
export type SecondBrainQueryIntent = 'technical' | 'ux-business' | 'governance' | 'general';
export interface SecondBrainTraceDocument {
title: string;
@@ -21,6 +22,7 @@ export interface SecondBrainTraceDocument {
export interface SecondBrainTrace {
userQuery: string;
queryIntent: SecondBrainQueryIntent;
shouldUseSecondBrain: boolean;
secondBrainUsed: boolean;
reason: string;
@@ -38,9 +40,11 @@ export function buildSecondBrainTrace(userQuery: string, brainRoot: string, opti
} = {}): SecondBrainTrace {
const query = userQuery.trim();
const shouldUseSecondBrain = !!options.force || shouldUseBrain(query);
const retrievalQuery = buildRetrievalQuery(query);
const queryIntent = classifyQueryIntent(query);
const retrievalQuery = buildRetrievalQuery(query, queryIntent);
const baseTrace: SecondBrainTrace = {
userQuery: query,
queryIntent,
shouldUseSecondBrain,
secondBrainUsed: false,
reason: shouldUseSecondBrain
@@ -66,7 +70,7 @@ export function buildSecondBrainTrace(userQuery: string, brainRoot: string, opti
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))
const scored = files.map((file) => scoreFile(file, brainRoot, terms, queryIntent))
.filter((doc) => doc.score >= 0.25)
.sort((a, b) => b.score - a.score)
.slice(0, options.limit || 5);
@@ -134,6 +138,7 @@ export function renderSecondBrainTraceContext(trace: SecondBrainTrace): string {
return [
'[SECOND BRAIN TRACE]',
`Second Brain used: ${trace.secondBrainUsed ? 'yes' : 'no'}`,
`Query intent: ${trace.queryIntent}`,
`Retrieval query: ${trace.retrievalQuery}`,
`Reason: ${trace.reason}`,
docs ? `Selected notes:\n${docs}` : 'Selected notes: none',
@@ -143,6 +148,12 @@ export function renderSecondBrainTraceContext(trace: SecondBrainTrace): string {
'No Evidence, No Project Claim: do not state that the current project has a technical structure unless it is supported by user-provided facts, source code, design docs, project docs, or project records.',
'General Knowledge notes can explain concepts, but cannot prove the current project actually implements those concepts.',
'Classify major claims as Confirmed, Inference, General Knowledge, or Needs Verification when the answer discusses a project.',
trace.queryIntent === 'ux-business'
? 'This is a UX/business/approval-fit question. Prefer customer journey, product discovery, requirement fit, business value, stakeholder approval, acceptance criteria, and conversion-flow reasoning over technical architecture reasoning.'
: '',
trace.queryIntent === 'ux-business'
? 'Approval likelihood is an inference unless explicit approval criteria are provided. Use wording such as "보완 요청 가능성이 높습니다", "승인 가능성을 높일 수 있습니다", and "확인 필요".'
: '',
hasProjectEvidence
? 'At least one selected note can support project-specific claims.'
: 'No selected note can support project-specific implementation claims.',
@@ -189,6 +200,9 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b
'## 2nd Brain 사용 여부',
status,
'',
'## 질문 의도',
trace.queryIntent,
'',
'## 이유',
trace.reason,
'',
@@ -214,6 +228,7 @@ export function renderSecondBrainTraceMarkdown(trace: SecondBrainTrace, debug: b
JSON.stringify({
secondBrainUsed: trace.secondBrainUsed,
shouldUseSecondBrain: trace.shouldUseSecondBrain,
queryIntent: trace.queryIntent,
retrievalQuery: trace.retrievalQuery,
searchedCollections: trace.searchedCollections,
retrievedDocuments: trace.retrievedDocuments.map((doc) => ({
@@ -311,11 +326,35 @@ function deriveProjectClaimPolicy(
function shouldUseBrain(query: string): boolean {
const normalized = query.toLowerCase();
return /(second brain|2nd brain|제2뇌|브레인|brain|기억|기록|문서|노트|내가|우리|프로젝트|결정|adr|chronicle|가드|설계 원칙|mvp|제외|왜|dependency|schema|documentation|drift|integration|overhead|의존성|스키마|문서화)/i.test(normalized);
return /(second brain|2nd brain|제2뇌|브레인|brain|기억|기록|문서|노트|내가|우리|프로젝트|결정|adr|chronicle|가드|설계 원칙|mvp|제외|왜|dependency|schema|documentation|drift|integration|overhead|의존성|스키마|문서화|고객|사용자|ux|경험|구매|전환|상품|공간|요구사항|승인|평가|비즈니스|가치|stakeholder|approval|customer|journey|conversion|requirement)/i.test(normalized);
}
function buildRetrievalQuery(query: string): string {
return tokenize(query).slice(0, 16).join(' ');
function classifyQueryIntent(query: string): SecondBrainQueryIntent {
const normalized = query.toLowerCase();
if (/(고객|사용자|ux|경험|구매|전환|상품|공간|요구사항|승인|평가|비즈니스|가치|웹스토어|virtual store|stakeholder|approval|customer|journey|conversion|requirement|acceptance|product discovery|business value)/i.test(normalized)) {
return 'ux-business';
}
if (/(dependency|schema|documentation|drift|integration|overhead|의존성|스키마|문서화|보안|검증|리스크|governance|reliability)/i.test(normalized)) {
return 'governance';
}
if (/(api|gateway|architecture|microservice|monolith|database|backend|frontend|routing|아키텍처|기술|라우팅|데이터|서버|클라이언트)/i.test(normalized)) {
return 'technical';
}
return 'general';
}
function buildRetrievalQuery(query: string, intent: SecondBrainQueryIntent): string {
const intentTerms: Record<SecondBrainQueryIntent, string[]> = {
'ux-business': [
'ux', 'customer journey', 'product discovery', 'virtual store', 'stakeholder approval',
'requirement fit', 'business value proposition', 'acceptance criteria', 'conversion flow',
'고객 경험', '상품 탐색', '구매 전환', '요구사항 적합성', '승인 기준', '비즈니스 가치'
],
technical: ['architecture', 'routing', 'api', 'implementation', 'source code', 'design document'],
governance: ['dependency', 'schema drift', 'documentation', 'validation', 'risk', 'decision'],
general: []
};
return [...tokenize(query), ...intentTerms[intent]].slice(0, 28).join(' ');
}
function tokenize(value: string): string[] {
@@ -331,7 +370,7 @@ function tokenize(value: string): string[] {
.filter((term) => term.length >= 2 && !stopWords.has(term));
}
function scoreFile(file: string, brainRoot: string, terms: string[]): SecondBrainTraceDocument {
function scoreFile(file: string, brainRoot: string, terms: string[], intent: SecondBrainQueryIntent): SecondBrainTraceDocument {
const relative = path.relative(brainRoot, file);
const title = path.basename(file, path.extname(file));
const basename = relative.toLowerCase();
@@ -345,7 +384,7 @@ function scoreFile(file: string, brainRoot: string, terms: string[]): SecondBrai
const canSupportProjectClaim = sourceType === 'Project Evidence' || sourceType === 'User Decision';
const lower = content.toLowerCase();
let score = pathPriority(relative);
let score = pathPriority(relative, intent);
for (const term of terms) {
if (basename.includes(term)) score += 4;
const matches = lower.split(term).length - 1;
@@ -388,7 +427,7 @@ function isRawConversationPath(relativePath: string): boolean {
return /(^|[\\/])(00_Raw|raw-data|conversations?|transcripts?)([\\/]|$)/i.test(relativePath);
}
function pathPriority(relativePath: string): number {
function pathPriority(relativePath: string, intent: SecondBrainQueryIntent): number {
const normalized = relativePath.toLowerCase();
let score = 0;
if (/(^|[\\/])(decisions?|adr|planning|development|bugs|retrospectives|records)([\\/]|$)/i.test(normalized)) {
@@ -403,6 +442,17 @@ function pathPriority(relativePath: string): number {
if (/(^|[\\/])index(_\d+)?\.md$/i.test(normalized) || /[\\/]index\.md$/i.test(normalized)) {
score -= 2;
}
if (intent === 'ux-business') {
if (/(ux|customer|journey|product|discovery|virtual|store|stakeholder|approval|requirement|business|value|acceptance|conversion|commerce|webstore|고객|사용자|경험|상품|구매|전환|공간|요구사항|승인|평가|비즈니스|가치)/i.test(normalized)) {
score += 5;
}
if (/(api|gateway|microservice|monolithic|backend|database|routing|architecture_principles|programming)/i.test(normalized)) {
score -= 3;
}
}
if (intent === 'technical' && /(api|gateway|microservice|architecture|routing|backend|database)/i.test(normalized)) {
score += 2;
}
return score;
}
+6 -1
View File
@@ -1992,7 +1992,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
}
.markdown-body h1 { font-size: 1.5em; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; color: var(--accent); }
.markdown-body h2 { font-size: 1.3em; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }
.markdown-body h2 { font-size: 1.45em; border-bottom: 1px solid var(--border); padding-bottom: 0.3em; }
.markdown-body h3 { font-size: 1.2em; }
.markdown-body p { margin: 0.75em 0 1.1em; }
.markdown-body ol, .markdown-body ul { margin: 0.7em 0 1.1em; padding-left: 1.45em; }
.markdown-body li { margin: 0.35em 0 0.65em; }
.markdown-body li + li { margin-top: 0.55em; }
.markdown-body table {
width: 100%;
border-collapse: collapse;
+5 -2
View File
@@ -150,8 +150,10 @@ Core behavior:
- Do not output hidden reasoning labels such as [PROBLEM], [GOAL], [REASONING], Phase 0, Fidelity Lock-in, or process manifestos.
- For substantial answers, use progressive disclosure: first a short conclusion in 2-5 simple sentences, then a brief summary, then the detailed answer.
- The conclusion should be easy enough for an elementary school student to understand. Put the main point before nuance.
- Keep the brief summary compact: 3-5 bullets or a short paragraph.
- Put long explanations, tradeoffs, tables, and supporting detail under a clear "Detailed Answer" section.
- Prefer paragraph-style formatting over bullet-heavy formatting. Do not put asterisks or dash bullets at the start of most lines.
- Keep the brief summary compact as 1 short paragraph by default. Use a numbered list only when sequence matters, and keep a blank line between numbered sections.
- Use visible markdown headings such as "## 간단 요약", "## 요청 요약", and "## 상세 답변" for major sections so the UI can render them larger.
- Put long explanations, tradeoffs, tables, and supporting detail under a clear "## 상세 답변" section.
- Avoid wall-of-text output. Make the answer understandable before adding detail.
- Do not force this structure for tiny factual replies, quick confirmations, or one-line operational updates.
- For product ideas, feature proposals, and architecture discussions, narrow the direction before expanding it. Prefer a practical MVP first, then separate later expansion ideas.
@@ -161,6 +163,7 @@ Core behavior:
- Even if Second Brain provides a general concept note, do not describe that concept as actually implemented in the current project. General concept notes are not project evidence.
- For project opinions, separate claims into confirmed facts, inferences, general knowledge, and items that need verification.
- If available evidence is only general knowledge, never say the project architecture is flexible, technically stable, scalable, gateway-based, microservice-ready, separated into layers, or structurally prepared. Say the technical structure cannot be judged from the current information.
- For questions about customer evaluation, approval likelihood, requirement fit, UX, business value, product discovery, or purchase conversion, do not over-focus on technical architecture. Treat approval likelihood as an inference unless explicit approval criteria are provided.
Available action tags: