From da4ebe3942730a9709b86465fd02d4c46ea5d882 Mon Sep 17 00:00:00 2001 From: g1nation Date: Sat, 2 May 2026 18:20:22 +0900 Subject: [PATCH] Feat: Enhance query intent search and answer format readability --- ...-05-02_answer-format-readability-tuning.md | 30 +++++++++ .../2026-05-02_query-intent-search-tuning.md | 35 ++++++++++ docs/records/ConnectAI/timeline.md | 2 + src/features/projectChronicle/guardPrompt.ts | 7 +- src/features/secondBrainTrace.ts | 66 ++++++++++++++++--- src/sidebarProvider.ts | 7 +- src/utils.ts | 7 +- tests/projectChronicleGuardPrompt.test.ts | 5 ++ tests/secondBrainTrace.test.ts | 26 ++++++++ 9 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 docs/records/ConnectAI/development/2026-05-02_answer-format-readability-tuning.md create mode 100644 docs/records/ConnectAI/development/2026-05-02_query-intent-search-tuning.md diff --git a/docs/records/ConnectAI/development/2026-05-02_answer-format-readability-tuning.md b/docs/records/ConnectAI/development/2026-05-02_answer-format-readability-tuning.md new file mode 100644 index 0000000..ef1044c --- /dev/null +++ b/docs/records/ConnectAI/development/2026-05-02_answer-format-readability-tuning.md @@ -0,0 +1,30 @@ +# Development Log: Answer Format Readability Tuning + +## Purpose +Reduce visual clutter in assistant answers by discouraging bullet-heavy output and improving markdown heading readability in the chat UI. + +## User Feedback +The answer content was useful, but many lines started with bullet dots, making the whole response feel visually messy. The user also wanted major sections such as simple summary, request summary, and additional advice to stand out more clearly. + +## Implementation Summary +- Updated the base system prompt to prefer short paragraphs over bullet-heavy formatting. +- Changed brief summary guidance from bullets to a compact paragraph by default. +- Added guidance to separate numbered sections with blank lines. +- Added guidance to use visible markdown headings such as `## 간단 요약`, `## 요청 요약`, and `## 상세 답변`. +- Updated Project Chronicle Guard prompt with the same paragraph-first and heading guidance. +- Increased markdown heading visibility in the sidebar UI. +- Added paragraph and list spacing styles so markdown output is less cramped. + +## Changed Files +- `src/utils.ts` +- `src/features/projectChronicle/guardPrompt.ts` +- `src/sidebarProvider.ts` +- `tests/projectChronicleGuardPrompt.test.ts` + +## Verification +- `./node_modules/.bin/tsc --noEmit` +- `npm run compile` +- `./node_modules/.bin/jest --runInBand` + +## Result +Future answers should start with a short conclusion, use fewer bullet dots, separate numbered sections more clearly, and show major headings with better visual hierarchy. diff --git a/docs/records/ConnectAI/development/2026-05-02_query-intent-search-tuning.md b/docs/records/ConnectAI/development/2026-05-02_query-intent-search-tuning.md new file mode 100644 index 0000000..18f21ca --- /dev/null +++ b/docs/records/ConnectAI/development/2026-05-02_query-intent-search-tuning.md @@ -0,0 +1,35 @@ +# Development Log: Query Intent Search Tuning + +## Purpose +Improve Second Brain search relevance and answer framing for questions about customer evaluation, approval likelihood, UX, business value, and requirement fit. + +## User Feedback +The answer became safer, but Second Brain still retrieved architecture documents for a question that was really about customer experience, approval risk, and business direction. Approval likelihood should also be labeled as inference, not fact. + +## Implementation Summary +- Added `queryIntent` classification to Second Brain Trace: + - `technical` + - `ux-business` + - `governance` + - `general` +- Expanded retrieval query terms based on intent. +- For `ux-business`, boosted UX, customer journey, product discovery, virtual store, stakeholder approval, requirement fit, business value, acceptance criteria, and conversion-flow documents. +- For `ux-business`, penalized API Gateway, microservice, monolithic, backend, database, routing, and generic architecture-principles paths. +- Added Trace context guidance that approval likelihood is inference unless explicit approval criteria are provided. +- Added base and Guard prompt guidance to prefer UX/business framing over technical architecture for these questions. +- Added tests to verify UX/business docs outrank API Gateway docs for approval/customer-experience questions. + +## Changed Files +- `src/features/secondBrainTrace.ts` +- `src/features/projectChronicle/guardPrompt.ts` +- `src/utils.ts` +- `tests/secondBrainTrace.test.ts` +- `tests/projectChronicleGuardPrompt.test.ts` + +## Verification +- `./node_modules/.bin/tsc --noEmit` +- `npm run compile` +- `./node_modules/.bin/jest --runInBand` + +## Result +Second Brain Trace should now better match the user's question intent. Customer approval and UX/business-fit questions should retrieve customer journey and business-value notes before generic technical architecture notes. diff --git a/docs/records/ConnectAI/timeline.md b/docs/records/ConnectAI/timeline.md index fe92a7e..ddadf3a 100644 --- a/docs/records/ConnectAI/timeline.md +++ b/docs/records/ConnectAI/timeline.md @@ -18,3 +18,5 @@ - Added No Evidence, No Project Claim rules and Second Brain source type classification to prevent general notes from being treated as project implementation evidence. - Added project claim policy enforcement so main answers must treat general-only or mixed evidence as cautious and avoid unsupported technical structure claims. - Added a deterministic output brake that removes unsupported technical structure claims from final answers when Trace policy is `general-only`. +- Tuned Second Brain retrieval by query intent so UX/business/approval questions prioritize customer journey, requirement fit, and business value notes over generic architecture notes. +- Tuned answer readability: paragraph-first summaries, fewer bullet-heavy sections, clearer numbered-section spacing, and larger markdown headings in the sidebar. diff --git a/src/features/projectChronicle/guardPrompt.ts b/src/features/projectChronicle/guardPrompt.ts index 3d57e4b..4c6847c 100644 --- a/src/features/projectChronicle/guardPrompt.ts +++ b/src/features/projectChronicle/guardPrompt.ts @@ -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.', diff --git a/src/features/secondBrainTrace.ts b/src/features/secondBrainTrace.ts index be105aa..80f8e2e 100644 --- a/src/features/secondBrainTrace.ts +++ b/src/features/secondBrainTrace.ts @@ -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 = { + '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; } diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 170b87a..79d5c7f 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -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; diff --git a/src/utils.ts b/src/utils.ts index f37d611..06f2988 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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: diff --git a/tests/projectChronicleGuardPrompt.test.ts b/tests/projectChronicleGuardPrompt.test.ts index c12718d..546eb39 100644 --- a/tests/projectChronicleGuardPrompt.test.ts +++ b/tests/projectChronicleGuardPrompt.test.ts @@ -19,11 +19,16 @@ describe('buildProjectChronicleGuardContext', () => { expect(context).toContain('Project selection status: selected'); expect(context).toContain('Short conclusion first'); expect(context).toContain('Brief summary'); + expect(context).toContain('one compact paragraph by default'); expect(context).toContain('Detailed answer'); + expect(context).toContain('Avoid bullet-heavy formatting'); + expect(context).toContain('Use visible markdown headings'); expect(context).toContain('No Evidence, No Project Claim'); expect(context).toContain('confirmed facts, inferences, general knowledge, and needs verification'); expect(context).toContain('현재 정보만으로는 기술 구조를 판단할 수 없습니다'); expect(context).toContain('technically stable'); + expect(context).toContain('approval likelihood'); + expect(context).toContain('confirmed facts, inference, general UX/business principle, needs verification, recommended direction'); expect(context).toContain('Project record target check'); expect(context).toContain('Record path check'); expect(context).toContain('Question reason'); diff --git a/tests/secondBrainTrace.test.ts b/tests/secondBrainTrace.test.ts index b2e3784..837c787 100644 --- a/tests/secondBrainTrace.test.ts +++ b/tests/secondBrainTrace.test.ts @@ -40,6 +40,17 @@ describe('Second Brain Trace', () => { ].join('\n'), 'utf8' ); + fs.mkdirSync(path.join(brainRoot, 'UX'), { recursive: true }); + fs.writeFileSync( + path.join(brainRoot, 'UX', 'Customer Journey Virtual Store.md'), + [ + '# Customer Journey Virtual Store', + '', + 'Customer-facing virtual stores should connect spatial experience to product discovery, product understanding, and purchase conversion.', + 'Stakeholder approval often depends on requirement fit, business value, and acceptance criteria rather than visual novelty alone.' + ].join('\n'), + 'utf8' + ); fs.mkdirSync(path.join(brainRoot, '00_Raw', 'conversations'), { recursive: true }); fs.writeFileSync( path.join(brainRoot, '00_Raw', 'conversations', '2026-05-01.md'), @@ -189,4 +200,19 @@ describe('Second Brain Trace', () => { fs.rmSync(root, { recursive: true, force: true }); } }); + + it('prioritizes UX and business documents for approval and customer-experience questions', () => { + const trace = buildSecondBrainTrace( + '롯데 이노베이트가 고객 대상 버추얼 웹스토어에서 상품 중심이 아니라 공간 중심 개발 방향을 승인할 가능성이 있을까?', + brainRoot, + { force: true } + ); + + expect(trace.queryIntent).toBe('ux-business'); + expect(trace.retrievalQuery).toContain('customer journey'); + expect(trace.retrievalQuery).toContain('approval'); + expect(trace.retrievedDocuments[0].path).toContain('Customer Journey Virtual Store.md'); + expect(trace.retrievedDocuments[0].path).not.toContain('API Gateway.md'); + expect(renderSecondBrainTraceContext(trace)).toContain('Approval likelihood is an inference'); + }); });