From 134d59ddb43a161f8f063459c49db8f6ee98bb7e Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 2 May 2026 12:17:17 +0900 Subject: [PATCH] =?UTF-8?q?feat(tag-vocab):=20prompt.ts=20=E2=80=94=20PROM?= =?UTF-8?q?PT=5FVERSION=204=20+=20vocab=20parameter=20(#3=20v0.2.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PROMPT_VERSION 3 → 4 (marker bump, retry 트리거 X) - buildPrompt 4번째 param vocab: string[] = [] - vocab.length > 0 시 "Existing vocabulary tags" + "Prefer reusing" 라인 추가 - vocab=[] 시 라인 자체 생략 (Q3=B 결정) - 단위 +4 cases (신규 prompt.test.ts) Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/ai/prompt.ts | 11 ++++++++--- tests/unit/prompt.test.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/unit/prompt.test.ts diff --git a/src/main/ai/prompt.ts b/src/main/ai/prompt.ts index 8ad65d0..147fc8f 100644 --- a/src/main/ai/prompt.ts +++ b/src/main/ai/prompt.ts @@ -1,21 +1,26 @@ import type { ParseResult } from '../services/dueDateParser.js'; -export const PROMPT_VERSION = 3; +export const PROMPT_VERSION = 4; export function buildPrompt( rawText: string, todayKst: string, - candidates: ParseResult[] = [] + candidates: ParseResult[] = [], + vocab: string[] = [] ): string { const candidateBlock = candidates.length > 0 ? `\nDate candidates extracted by a Korean rule parser (these are HINTS — you decide which is correct, or pick null): ${candidates.map((c, i) => ` ${i + 1}. ${c.iso ?? '(ambiguous)'} — matched token: "${c.matchedToken ?? '?'}" (confidence: ${c.confidence ?? 'low'})`).join('\n')}\n` : ''; + const vocabBlock = vocab.length > 0 + ? `\nExisting vocabulary tags (most-used first): ${vocab.join(', ')}\nPrefer reusing a vocabulary tag when the meaning matches; create new tags only when the meaning is genuinely new.\n` + : ''; + return `You organize raw personal notes into structured metadata. Today's date in Korea Standard Time (KST): ${todayKst} -${candidateBlock} +${candidateBlock}${vocabBlock} Input note (raw text, may be fragmented, any language): --- ${rawText} diff --git a/tests/unit/prompt.test.ts b/tests/unit/prompt.test.ts new file mode 100644 index 0000000..e19cee6 --- /dev/null +++ b/tests/unit/prompt.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { buildPrompt, PROMPT_VERSION } from '@main/ai/prompt.js'; + +describe('prompt', () => { + it('PROMPT_VERSION is 4', () => { + expect(PROMPT_VERSION).toBe(4); + }); + + it('buildPrompt with empty vocab omits vocabulary line entirely', () => { + const out = buildPrompt('hello', '2026-05-02', [], []); + expect(out).not.toContain('vocabulary'); + expect(out).not.toContain('Prefer reusing'); + }); + + it('buildPrompt with vocab includes Prefer instruction + comma-separated list', () => { + const out = buildPrompt('hello', '2026-05-02', [], ['design', 'meeting', 'qa']); + expect(out).toContain('Existing vocabulary tags'); + expect(out).toContain('design, meeting, qa'); + expect(out).toContain('Prefer reusing'); + }); + + it('vocab block appears between candidate block and JSON rules', () => { + const out = buildPrompt('hello', '2026-05-02', [], ['design']); + const candidateIdx = out.indexOf("Today's date"); + const vocabIdx = out.indexOf('Existing vocabulary'); + const jsonRulesIdx = out.indexOf('Return a JSON object'); + expect(candidateIdx).toBeGreaterThan(-1); + expect(vocabIdx).toBeGreaterThan(candidateIdx); + expect(jsonRulesIdx).toBeGreaterThan(vocabIdx); + }); +});