feat: v2.2.194 — Post-gen Term Validator (결정론적 글로서리 검증)

v2.2.192 Terminology Dictionary 가 *instructional* 지시 (LLM 에게 표준 표기 사용 권유)
였다면, 이번엔 *deterministic* 검증 — LLM 이 지시를 안 따랐을 때 결정론적 정규식 스캔.

신규 모듈: src/agent/termValidator.ts
- parseGlossary() — .astra/glossary.md 정규식 파싱 (mtime 캐시)
  Pattern 1: **Canonical** (X: typo1, typo2, ...) — typo 등장 시 "→ Canonical 권장"
  Pattern 2: H2/H3 "금지/비추/forbidden/avoid/don't" 섹션의 -  "phrase"
- validateTermUsage() — 정규식 스캔 + 발견 횟수
- formatTermValidatorFooter() — markdown 한 줄 footer

False-positive 필터:
- 한글 1음절·영문 1자·공백 포함 토큰 제외
- 영문 단어 경계 매치, 한글 substring

Wiring:
- agent.ts _maybeRunTermValidator — Self-Check 직후, swallow 패턴
- /glossary reload — Term Validator 캐시도 함께 비움

신규 설정: g1nation.termValidatorEnabled (기본 true)

Footer 누적:
- v2.2.191 🔍 Self-check (LLM 호출, opt-in)
- v2.2.194 🔤 Term validator (정규식, on by default)

시너지: Terminology Dictionary(instructional, 작성 중) + Term Validator(deterministic,
작성 후) → 사용자가 .astra/glossary.md 한 곳만 관리하면 2단 자동 동작.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 16:38:56 +09:00
parent 990ea0ae5f
commit 15a34e0889
21 changed files with 433 additions and 55 deletions
+20
View File
@@ -165,6 +165,7 @@ import { buildAstraModeSystemPrompt } from './agent/handlePrompt/buildAstraModeS
import { computeBudgetedRequest } from './agent/handlePrompt/computeBudgetedRequest';
import { processFinalAnswer } from './agent/handlePrompt/processFinalAnswer';
import { postHocSelfCheck, formatSelfCheckFooter, DEFAULT_SELF_CHECK_OPTIONS } from './agent/postHocSelfCheck';
import { validateTermUsage, formatTermValidatorFooter } from './agent/termValidator';
import { applyAutoContinuation } from './agent/handlePrompt/applyAutoContinuation';
export interface ChatMessage {
@@ -1236,6 +1237,9 @@ export class AgentExecutor {
userPrompt: prompt || '',
assistantAnswer: finalAssistantContent,
});
// ── Term Validator (v2.2.194) — 결정론적 정규식 스캔. LLM 호출 없음, 즉시 실행. ──
// 글로서리 forbidden 단어가 답변에 등장 시 footer flag. 위반 없으면 ✓.
this._maybeRunTermValidator(finalAssistantContent);
} else {
this.webview.postMessage({ type: 'streamChunk', value: finalAssistantContent });
}
@@ -1442,6 +1446,22 @@ export class AgentExecutor {
} catch { /* swallow — self-check never breaks the turn */ }
}
/**
* Post-gen Term Validator — 글로서리 forbidden 단어가 답변에 등장하는지 결정론적 스캔.
* LLM 호출 없음, 동기 실행 (수 ms). 글로서리 없거나 disabled 면 silent no-op.
*/
private _maybeRunTermValidator(answer: string): void {
try {
const cfg = getConfig();
if (cfg.termValidatorEnabled === false) return;
if (!answer || !answer.trim()) return;
const result = validateTermUsage(answer, cfg.glossaryPath || '.astra/glossary.md');
if (!result.ran || result.dictionarySize === 0) return; // 글로서리 없거나 비어 있음 → 표시 안 함
const footer = formatTermValidatorFooter(result);
if (footer) this.webview?.postMessage({ type: 'streamChunk', value: footer });
} catch { /* swallow — validator never breaks the turn */ }
}
private async callNonStreaming(params: {
baseUrl: string;
modelName: string;