diff --git a/.astra/project-context/architecture.md b/.astra/project-context/architecture.md index deac270..a7d0ee3 100644 --- a/.astra/project-context/architecture.md +++ b/.astra/project-context/architecture.md @@ -3,15 +3,15 @@ ## Snapshot -- **Workspace**: `connectai` `v2.2.193` _(absolute path varies by environment; resolved from the active VS Code workspace)_ +- **Workspace**: `connectai` `v2.2.194` _(absolute path varies by environment; resolved from the active VS Code workspace)_ - **Description**: The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making. - **Stack**: TypeScript, Node.js, VS Code Extension, LM Studio SDK, Test runner -- **Stats**: 416 source files, ~70,115 lines across 5 top-level modules. +- **Stats**: 419 source files, ~70,411 lines across 5 top-level modules. ## Last Refresh -- **Time**: 2026-05-29T06:58:50.818Z +- **Time**: 2026-05-29T07:30:42.899Z - **Files newly analysed**: 3 -- **Files reused from cache**: 413 +- **Files reused from cache**: 416 ## Directory Map ```mermaid @@ -40,11 +40,11 @@ mindmap > Arrows: which top-level module imports from which. ```mermaid flowchart LR - src["src/
262 files"] + src["src/
263 files"] media["media/
6 files"] tests["tests/
37 files"] core_py["core_py/
6 files"] - docs["docs/
105 files"] + docs["docs/
107 files"] tests --> src ``` @@ -67,13 +67,13 @@ flowchart LR ## Modules -### `src/` — 262 files, ~52,385 lines +### `src/` — 263 files, ~52,640 lines **Sub-directories** - `src/features/` (92) — Astra Office — public API. 다음 세션에서 추가될 OfficeSnapshot presenter / schema 도 같은 entry 로 노출 예정. 현재 노출: full webview panel H - `src/sidebar/` (35) — Brain profile lifecycle 의 pure helpers — sidebarProvider 의 add/edit/delete 흐름에서 modal UI 와 config 쓰기를 제외한 데이터 변환 만 격리. 현 - `src/lib/` (28) — Astra Mode Architecture Context Builder. 의도: 사용자가 Astra 자체의 mode 디자인 (Guard vs Multi-Agent 가 별도 모드여야 하는지) 을 묻는 메타 질문에 답할 -- `src/agent/` (26) — Post-hoc Self-Check — 답변 완료 후 LLM 한 번 호출로 3가지 평가. 사용자 제안: "[Self-Check] 단계 — 이 답변이 사용자 질문에 직접 답하는가 / 규칙 준수 / 논리 모순 없는가". +- `src/agent/` (27) — Post-hoc Self-Check — 답변 완료 후 LLM 한 번 호출로 3가지 평가. 사용자 제안: "[Self-Check] 단계 — 이 답변이 사용자 질문에 직접 답하는가 / 규칙 준수 / 논리 모순 없는가". - `src/retrieval/` (16) — Actionability Scoring — 검색 결과를 "현재 작업 상태" 신호로 재가중. 기존 TF-IDF (단어 매칭) + recency (시간) 만으로는 "지금 이 사용자가 하고 있는 작업과 직접 연결 된 문서 - `src/core/` (15) — Astra Path Resolver (경로 해결기) Astra의 모든 데이터 파일(.astra 디렉토리)의 경로를 중앙에서 관리합니다. 확장 프로그램의 설치 경로(extensionUri) 기반으로 .astra 디렉토 - `src/memory/` (9) — Distillation Loop — stale Episodic Memory → Long-Term "episode-digest" 승급. 배경: Episodic Memory 가 무한히 누적되면 검색 노이즈. 30일+ 지 @@ -85,8 +85,8 @@ flowchart LR **Key files** - `src/utils.ts` (471 lines) -- `src/config.ts` (550 lines) -- `src/agent.ts` (1568 lines) +- `src/config.ts` (557 lines) +- `src/agent.ts` (1589 lines) - `src/features/company/types.ts` (446 lines) — Type definitions for the 1인 기업 (One-Person Company) mode. The mode turns the user into a virtual CEO that dispatches work to a roster of specialist agents. Each turn produces a session directory conta - `src/core/services.ts` (176 lines) - `src/sidebarProvider.ts` (3186 lines) @@ -165,17 +165,17 @@ flowchart LR - `core_py/optimizer.py` (55 lines) - `core_py/queue_worker.py` (82 lines) -### `docs/` — 105 files, ~3,775 lines +### `docs/` — 107 files, ~3,816 lines **Sub-directories** -- `docs/records/` (92) — Bug: /Volumes/Data/project/Antigravity/ConnectAI 프로젝트 코드 리뷰 해줄 수 있어? 개선할 부분이 있는지, 그러고... +- `docs/records/` (94) — Bug: /Volumes/Data/project/Antigravity/ConnectAI 프로젝트 코드 리뷰 해줄 수 있어? 개선할 부분이 있는지, 그러고... - `docs/docs/` (5) — Bug: Viewed integrationretrieval.test.ts:1-59 integrationretrieval.test.ts를 통해 ... - `docs/Meeting/` (0) **Key files** - `docs/TELEGRAM_REMOTE_EXECUTION_PLAN.md` (452 lines) — Telegram Remote Execution 기획서 - `docs/AgentEngine_Architecture.md` (314 lines) — AgentEngine Architecture Document -- `docs/records/ConnectAI/timeline.md` (227 lines) — Project Timeline +- `docs/records/ConnectAI/timeline.md` (233 lines) — Project Timeline - `docs/ASTRA_OFFICE_REFACTOR.md` (198 lines) — Astra Office Refactor — Design Doc - `docs/EXPERIENCE_MEMORY_PLAN.md` (122 lines) — Experience Memory (Mistake / Lesson Loop) — Implementation Plan - `docs/records/ConnectAI/development/2026-05-02_connectai_project_knowledge_overview.md` (121 lines) — Astra Project Knowledge Overview @@ -232,7 +232,7 @@ flowchart LR - `g1nation.calendar.refresh` — Astra: Google Calendar 새로고침 📅 - `g1nation.calendar.connectOAuth` — Astra: Google Calendar OAuth 연결 (쓰기) 🔐 - `g1nation.devilAgent.toggle` — Astra: Toggle Devil Agent 🎭 -- **Configuration** (121 settings): +- **Configuration** (122 settings): - `g1nation.multiAgentEnabled` *(boolean)* _(default: `false`)_ — Enable Multi-Agent Workflow (Planner -> Researcher -> Writer) for complex tasks. - `g1nation.datacollectBridgeUrl` *(string)* _(default: `"http://127.0.0.1:3002"`)_ — Wiki/Datacollect MCP Bridge URL. /research, /benchmark, /youtube chat slash commands route here. The Bridge must be running (`npm run bridge` in the Datacollect project). - `g1nation.datacollectSavePath` *(string)* _(default: `""`)_ @@ -293,7 +293,7 @@ flowchart LR - `g1nation.embeddingBlendAlpha` *(number)* _(default: `0.5`)_ — Hybrid score blend: 0 = pure TF-IDF (sparse / keyword), 1 = pure embedding cosine (dense / semantic), 0.5 = balanced. Only used when g1nation.embeddingModel is set. Default 0.5. - `g1nation.conflictHighlightingEnabled` *(boolean)* _(default: `true`)_ — Conflict Surface — 검색된 출처에서 충돌/논란 신호 감지 시 [CONFLICT WARNINGS] 블록을 시스템 프롬프트에 주입. LLM 이 상충되는 관점을 명시하고 사용자 판단에 위임하도록. 기본 켜짐. - `g1nation.conflictSeverityThreshold` *(string)* _(default: `"medium"`)_ — Conflict 자기-신호 surface 시 최소 severity 임계. low=가장 민감(노이즈 가능), medium=균형(기본), high=강한 충돌만. - - _…and 61 more_ + - _…and 62 more_ ## Dependencies - **Runtime** (2): `@lmstudio/sdk`, `pdf-parse` @@ -341,7 +341,7 @@ Astra는 대표님의 명시적인 승인 하에 로컬 시스템의 강력한 **Designed for High-Performance Decision Making.** Copyright (C) **g1nation**. All rights reserved. -_Last auto-scan: 2026-05-29T06:58:50.818Z · signature `e7ea4eff`_ +_Last auto-scan: 2026-05-29T07:30:42.899Z · signature `246e4864`_ ## Purpose diff --git a/.astra/project-context/scan-cache.json b/.astra/project-context/scan-cache.json index 82a23bd..2c36c83 100644 --- a/.astra/project-context/scan-cache.json +++ b/.astra/project-context/scan-cache.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-05-29T06:58:50.872Z", + "generatedAt": "2026-05-29T07:30:42.949Z", "files": { "src/agent/actions/brainOps.ts": { "mtimeMs": 1779764602582.9768, @@ -316,10 +316,17 @@ "src/agent" ] }, + "src/agent/termValidator.ts": { + "mtimeMs": 1780039129011.7288, + "size": 8675, + "lines": 221, + "role": "Post-generation Term Validator — 답변 완료 후 정규식/사전 기반 결정론적 스캔. v2.2.192 의 Terminology Dictionary 가 instructional (LLM 에게 표준 표기 사용 지시) 이면, 이건 deterministic — LLM 이 지시를 안 따랐을 때 catch. Glossary 파싱 — 두 패턴 인식", + "imports": [] + }, "src/agent.ts": { - "mtimeMs": 1780033640914.1235, - "size": 85839, - "lines": 1568, + "mtimeMs": 1780039163639.0967, + "size": 87154, + "lines": 1589, "role": "", "imports": [ "src/utils", @@ -391,6 +398,7 @@ "src/agent/handlePrompt/computeBudgetedRequest", "src/agent/handlePrompt/processFinalAnswer", "src/agent/postHocSelfCheck", + "src/agent/termValidator", "src/agent/handlePrompt/applyAutoContinuation", "src/features/approval/approvalQueue", "src/features/providers", @@ -433,9 +441,9 @@ ] }, "src/config.ts": { - "mtimeMs": 1780033618147.1584, - "size": 30668, - "lines": 550, + "mtimeMs": 1780039183821.263, + "size": 31114, + "lines": 557, "role": "", "imports": [] }, @@ -1158,9 +1166,9 @@ "imports": [] }, "src/features/datacollect/slashRouter.ts": { - "mtimeMs": 1780035781878.5417, - "size": 226019, - "lines": 4090, + "mtimeMs": 1780039211177.6035, + "size": 226165, + "lines": 4096, "role": "", "imports": [ "src/utils", @@ -1170,6 +1178,7 @@ "src/memory", "src/config", "src/retrieval/terminologyBlock", + "src/agent/termValidator", "src/features/setup/datacollectSetup", "src/features/datacollect/prompts/synthesisPrompt", "src/features/datacollect/prompts/youtubePrompts", @@ -3348,7 +3357,7 @@ "imports": [] }, "docs/records/ConnectAI/chronicle.config.json": { - "mtimeMs": 1780037924615.7922, + "mtimeMs": 1780039836702.8801, "size": 371, "lines": 11, "role": "JSON configuration", @@ -3550,6 +3559,13 @@ "role": "ADR: 질문이 있어, 내가 당근이라는 중고 거래 사이트에서 8TB HDD를 구매했어. 안전거래 (Escrow)를 사용했어. 일단 물건을 어제 받았고, ...", "imports": [] }, + "docs/records/ConnectAI/decisions/ADR-0029-여전히-오타가-있어-그러고-어색한-답변도-있네-오타-및-문맥-수정-사항-s-m-a-t-s-m-a-r-t-하드.md": { + "mtimeMs": 1780038664272.424, + "size": 2211, + "lines": 19, + "role": "ADR: 여전히 오타가 있어. 그러고 어색한 답변도 있네. 오타 및 문맥 수정 사항 S.M.A.T -> S.M.A.R.T: 하드디스크의 자가 진단 기능을...", + "imports": [] + }, "docs/records/ConnectAI/development/2026-05-02_answer-format-readability-tuning.md": { "mtimeMs": 1778028987330.4185, "size": 1564, @@ -3837,6 +3853,13 @@ "role": "Discussion: E:\\Wiki\\connectai git에 커밋하고 푸쉬해줘.", "imports": [] }, + "docs/records/ConnectAI/discussions/2026-05-29_진행해.md": { + "mtimeMs": 1780039836692.6577, + "size": 892, + "lines": 16, + "role": "Discussion: 진행해", + "imports": [] + }, "docs/records/ConnectAI/planning/2026-05-02_project-chronicle-guard.md": { "mtimeMs": 1778028987339.2349, "size": 3004, @@ -3887,9 +3910,9 @@ "imports": [] }, "docs/records/ConnectAI/timeline.md": { - "mtimeMs": 1780037924607.6199, - "size": 14606, - "lines": 227, + "mtimeMs": 1780039836694.6646, + "size": 14881, + "lines": 233, "role": "Project Timeline", "imports": [] }, diff --git a/.astra/tests/engine/.astra/cache/7fa9e2c0ed212d5dbde1172e996cde86955f34dda22a6e02b95c9adc0a456927.json b/.astra/tests/engine/.astra/cache/7fa9e2c0ed212d5dbde1172e996cde86955f34dda22a6e02b95c9adc0a456927.json index 63014da..f482f2e 100644 --- a/.astra/tests/engine/.astra/cache/7fa9e2c0ed212d5dbde1172e996cde86955f34dda22a6e02b95c9adc0a456927.json +++ b/.astra/tests/engine/.astra/cache/7fa9e2c0ed212d5dbde1172e996cde86955f34dda22a6e02b95c9adc0a456927.json @@ -1,5 +1,5 @@ { "result": "직답 결과 — single-pass mock 응답입니다.", - "createdAt": 1780037632204, + "createdAt": 1780039528659, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/engine/.astra/cache/8c208151bed9108b665cd93e98fc10d377a9fef641dd359504b8d53aecd0a4c3.json b/.astra/tests/engine/.astra/cache/8c208151bed9108b665cd93e98fc10d377a9fef641dd359504b8d53aecd0a4c3.json index 07d894f..34013c4 100644 --- a/.astra/tests/engine/.astra/cache/8c208151bed9108b665cd93e98fc10d377a9fef641dd359504b8d53aecd0a4c3.json +++ b/.astra/tests/engine/.astra/cache/8c208151bed9108b665cd93e98fc10d377a9fef641dd359504b8d53aecd0a4c3.json @@ -1,5 +1,5 @@ { - "result": "---\nid: wiki_on\ndate: 2026-05-29T06:53:52.206Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\n직답 결과 — single-pass mock 응답입니다.\n\n직답 결과 — single-pass mock 응답입니다.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `0/100` | ✅ Low |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[DIRECT]** 답변 작성 중... (단일 호출 fast-path) (23ms)\n", - "createdAt": 1780037632207, + "result": "---\nid: wiki_on\ndate: 2026-05-29T07:25:28.661Z\ntype: knowledge_artifact\nstandard: P-Reinforce v3.0\ntags: [automated, connect_ai, brain_sync]\n---\n\n## 📌 Brief Summary\n직답 결과 — single-pass mock 응답입니다.\n\n직답 결과 — single-pass mock 응답입니다.\n---\n## 🛡️ Reliability & Audit Summary\n> [!NOTE]\n> 이 문서는 ConnectAI의 **Intelligent Resilience** 엔진에 의해 검증 및 정제되었습니다.\n\n| Metric | Value | Status |\n| :--- | :--- | :--- |\n| **Conflict Risk** | `0/100` | ✅ Low |\n| **Fallbacks Used** | `0` | ✅ None |\n| **Auto Retries** | `0` | ✅ Stable |\n| **Deduplication** | `0` | Standard |\n| **Processing Time** | `0.0s` | ✅ Fast |\n\n### 🔍 Decision Audit Trail\n- **[DIRECT]** 답변 작성 중... (단일 호출 fast-path) (22ms)\n", + "createdAt": 1780039528661, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/engine/.astra/missions/wiki_on.json b/.astra/tests/engine/.astra/missions/wiki_on.json index 17c8e91..589ebb5 100644 --- a/.astra/tests/engine/.astra/missions/wiki_on.json +++ b/.astra/tests/engine/.astra/missions/wiki_on.json @@ -1,8 +1,8 @@ { "missionId": "wiki_on", "status": "completed", - "startTime": "2026-05-29T06:53:52.180Z", - "totalElapsedMs": 28, + "startTime": "2026-05-29T07:25:28.636Z", + "totalElapsedMs": 26, "results": { "direct": "직답 결과 — single-pass mock 응답입니다." }, @@ -12,16 +12,16 @@ { "from": "idle", "to": "direct", - "durationMs": 23, + "durationMs": 22, "message": "답변 작성 중... (단일 호출 fast-path)", - "ts": "2026-05-29T06:53:52.203Z" + "ts": "2026-05-29T07:25:28.658Z" }, { "from": "direct", "to": "completed", "durationMs": 4, "message": "미션 완료", - "ts": "2026-05-29T06:53:52.207Z" + "ts": "2026-05-29T07:25:28.662Z" } ], "resilienceMetrics": { diff --git a/.astra/tests/stress/.astra/cache/21818066876cbf8515758bc351bb3d9d44f32b0e4cd024b2e055db3a0d34417e.json b/.astra/tests/stress/.astra/cache/21818066876cbf8515758bc351bb3d9d44f32b0e4cd024b2e055db3a0d34417e.json index c726d93..771d30f 100644 --- a/.astra/tests/stress/.astra/cache/21818066876cbf8515758bc351bb3d9d44f32b0e4cd024b2e055db3a0d34417e.json +++ b/.astra/tests/stress/.astra/cache/21818066876cbf8515758bc351bb3d9d44f32b0e4cd024b2e055db3a0d34417e.json @@ -1,5 +1,5 @@ { "result": "Final report with inconsistencies. This should be long enough to pass validation.", - "createdAt": 1780037639311, + "createdAt": 1780039534874, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/4fc755e372f1dd80d6bffc7b2ef973124fb64ba505f767c53a783833bbc3fa6a.json b/.astra/tests/stress/.astra/cache/4fc755e372f1dd80d6bffc7b2ef973124fb64ba505f767c53a783833bbc3fa6a.json index bdadd8e..771d30f 100644 --- a/.astra/tests/stress/.astra/cache/4fc755e372f1dd80d6bffc7b2ef973124fb64ba505f767c53a783833bbc3fa6a.json +++ b/.astra/tests/stress/.astra/cache/4fc755e372f1dd80d6bffc7b2ef973124fb64ba505f767c53a783833bbc3fa6a.json @@ -1,5 +1,5 @@ { "result": "Final report with inconsistencies. This should be long enough to pass validation.", - "createdAt": 1780037639310, + "createdAt": 1780039534874, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/6e559207c4542d959700ff14f360e6575e54853929e991e579e318f2f5a19030.json b/.astra/tests/stress/.astra/cache/6e559207c4542d959700ff14f360e6575e54853929e991e579e318f2f5a19030.json index 8284473..c218834 100644 --- a/.astra/tests/stress/.astra/cache/6e559207c4542d959700ff14f360e6575e54853929e991e579e318f2f5a19030.json +++ b/.astra/tests/stress/.astra/cache/6e559207c4542d959700ff14f360e6575e54853929e991e579e318f2f5a19030.json @@ -1,5 +1,5 @@ { "result": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]", - "createdAt": 1780037639305, + "createdAt": 1780039534870, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/cache/f65136cebc95448a7e93a45745cb73b3a5a01af5d82391ec29a25bd72b8239a5.json b/.astra/tests/stress/.astra/cache/f65136cebc95448a7e93a45745cb73b3a5a01af5d82391ec29a25bd72b8239a5.json index 7c150ba..9f73afc 100644 --- a/.astra/tests/stress/.astra/cache/f65136cebc95448a7e93a45745cb73b3a5a01af5d82391ec29a25bd72b8239a5.json +++ b/.astra/tests/stress/.astra/cache/f65136cebc95448a7e93a45745cb73b3a5a01af5d82391ec29a25bd72b8239a5.json @@ -1,5 +1,5 @@ { "result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.", - "createdAt": 1780037639308, + "createdAt": 1780039534872, "modelVersion": "unknown" } \ No newline at end of file diff --git a/.astra/tests/stress/.astra/missions/stress_conflict_1780037639285.json b/.astra/tests/stress/.astra/missions/stress_conflict_1780039534851.json similarity index 76% rename from .astra/tests/stress/.astra/missions/stress_conflict_1780037639285.json rename to .astra/tests/stress/.astra/missions/stress_conflict_1780039534851.json index f6c33de..0a8fde6 100644 --- a/.astra/tests/stress/.astra/missions/stress_conflict_1780037639285.json +++ b/.astra/tests/stress/.astra/missions/stress_conflict_1780039534851.json @@ -1,8 +1,8 @@ { - "missionId": "stress_conflict_1780037639285", + "missionId": "stress_conflict_1780039534851", "status": "completed", - "startTime": "2026-05-29T06:53:59.285Z", - "totalElapsedMs": 26, + "startTime": "2026-05-29T07:25:34.851Z", + "totalElapsedMs": 24, "results": { "outline": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]", "section_0": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.", @@ -14,30 +14,30 @@ { "from": "idle", "to": "outline", - "durationMs": 19, + "durationMs": 18, "message": "답변 구조 잡는 중...", - "ts": "2026-05-29T06:53:59.304Z" + "ts": "2026-05-29T07:25:34.869Z" }, { "from": "outline", "to": "section", - "durationMs": 3, + "durationMs": 2, "message": "본문 작성 중...", - "ts": "2026-05-29T06:53:59.307Z" + "ts": "2026-05-29T07:25:34.871Z" }, { "from": "section", "to": "polish", "durationMs": 2, "message": "최종 다듬기 중...", - "ts": "2026-05-29T06:53:59.309Z" + "ts": "2026-05-29T07:25:34.873Z" }, { "from": "polish", "to": "completed", "durationMs": 2, "message": "미션 완료", - "ts": "2026-05-29T06:53:59.311Z" + "ts": "2026-05-29T07:25:34.875Z" } ], "resilienceMetrics": { diff --git a/PATCHNOTES.md b/PATCHNOTES.md index a8a51d6..5805594 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,5 +1,64 @@ # Astra Patch Notes +## v2.2.194 (2026-05-29) +### 🔤 Post-gen Term Validator — 결정론적 글로서리 검증 (엔진 변경) +v2.2.192 의 Terminology Dictionary 가 *instructional* (LLM 에게 "표준 표기 사용" 지시) 였다면, 이건 *deterministic* — LLM 이 지시를 안 따랐을 때 결정론적 정규식 스캔으로 catch. + +**감사 결과 (적용 범위 좁힘):** +- 사용자 제안 4종 중 **#1a Post-gen Validator** 만 적용 (사용자 선택) +- #1b Contextual Refiner (rewrite) — Self-Check 와 중복, latency 비용 +- #2 Few-Shot Bad/Good 예시 — 글로서리 자유 markdown 이라 사용자가 직접 추가 가능 +- #3 CoVe 검증 질문 — v2.2.184 + v2.2.191 에서 완료 + +--- + +**신규 모듈: [termValidator.ts](src/agent/termValidator.ts)** + +- `parseGlossary(filePath)` — `.astra/glossary.md` 정규식 파싱 (mtime 캐시): + - **Pattern 1**: `- **Canonical** (X: typo1, typo2, ...)` — typo 등장 시 "→ Canonical 권장" + - **Pattern 2**: H2/H3 제목에 "금지/비추/forbidden/avoid/don't" 포함 섹션의 `- ❌ "phrase"` 또는 `- ❌ phrase` +- `validateTermUsage(answer, path)` — 정규식 스캔 + 발견 횟수 +- `formatTermValidatorFooter(result)` — markdown 한 줄 footer +- 토큰 필터 — 한글 1음절·영문 1자·공백 포함 토큰 제외 (false-positive 최소화) +- 영문 토큰: 단어 경계 매치, 한글: substring (한글은 word boundary 의미 없음) + +**비용·안전:** +- **LLM 호출 없음** — 정규식 1회, 수 ms +- 매 turn 자동 실행 (Self-Check 옆) +- 글로서리 없거나 비어 있으면 silent no-op +- 답변 자체엔 영향 없음 (Devil/Self-Check 와 같은 swallow 패턴) + +**Footer 형식:** +- 위반 없음: `🔤 Term validator: ✓ 사전 N개 항목, 위반 없음` +- 위반 있음: `🔤 Term validator: ⚠️ 위반 3건 — "astra" → "ASTRA" · "chronicle 외" → "Chronicle" · "p-rein동" → "P-Reinforce"` + +**Wiring:** +- [agent.ts](src/agent.ts) — `_maybeRunTermValidator` 메서드 추가, Self-Check 호출 직후 +- `/glossary reload` — Term Validator 캐시도 함께 비움 + +**신규 설정 1개:** +- `g1nation.termValidatorEnabled` (boolean, 기본 true) + +--- + +**ASTRA 답변 후 검증 누적 (footer 형식):** + +| 빌드 | Footer | 비용 | Default | +|---|---|---|---| +| v2.2.191 | `🔍 Self-check`: 답함=✓ · 근거=○ · 모순=없음 | LLM 호출 1회 | off (opt-in) | +| **v2.2.194** | **`🔤 Term validator`: ✓ 사전 N개, 위반 없음** | **정규식 1회 (cheap)** | **on** | + +**시너지:** +- Terminology Dictionary (v2.2.192) → LLM 에게 표준 표기 사용 지시 (작성 중) +- Term Validator (v2.2.194) → 그 결과를 *답변 후* 검증 + 위반 표시 (작성 후) +- 즉, *instructional + deterministic* 의 두 단계 방어선 + +**신규 패키징:** `astra-2.2.194.vsix`. + +--- + + + ## v2.2.193 (2026-05-29) ### 📚 /help — 슬래시 명령 카테고리 브라우저 + 엔진 상태 누적된 슬래시 명령이 25개+. 사용자 발견 부담을 줄이기 위해 카테고리별 그룹핑 + ASTRA 추론 엔진 상태(6종 verification 블록 on/off) 한 화면. diff --git a/docs/records/ConnectAI/chronicle.config.json b/docs/records/ConnectAI/chronicle.config.json index d5271ae..b0d6a99 100644 --- a/docs/records/ConnectAI/chronicle.config.json +++ b/docs/records/ConnectAI/chronicle.config.json @@ -7,5 +7,5 @@ "corePurpose": "", "detailLevel": "standard", "createdAt": "2026-05-20T09:42:40.003Z", - "updatedAt": "2026-05-29T06:58:44.615Z" + "updatedAt": "2026-05-29T07:30:36.702Z" } diff --git a/docs/records/ConnectAI/decisions/ADR-0029-여전히-오타가-있어-그러고-어색한-답변도-있네-오타-및-문맥-수정-사항-s-m-a-t-s-m-a-r-t-하드.md b/docs/records/ConnectAI/decisions/ADR-0029-여전히-오타가-있어-그러고-어색한-답변도-있네-오타-및-문맥-수정-사항-s-m-a-t-s-m-a-r-t-하드.md new file mode 100644 index 0000000..586d452 --- /dev/null +++ b/docs/records/ConnectAI/decisions/ADR-0029-여전히-오타가-있어-그러고-어색한-답변도-있네-오타-및-문맥-수정-사항-s-m-a-t-s-m-a-r-t-하드.md @@ -0,0 +1,19 @@ +# ADR: 여전히 오타가 있어. 그러고 어색한 답변도 있네. 오타 및 문맥 수정 사항 S.M.A.T -> S.M.A.R.T: 하드디스크의 자가 진단 기능을... + +## Status +accepted + +## Context +여전히 오타가 있어. 그러고 어색한 답변도 있네. 오타 및 문맥 수정 사항 S.M.A.T -> S.M.A.R.T: 하드디스크의 자가 진단 기능을 뜻하는 명칭은 'S.M.A.R.T(Smart)'가 올바른 표기입니다. 'R'이 빠져 있으니 수정이 필요합니다. 증거로 채점되기 -> 증거로 인정되기: 법적·절차적 분쟁에서는 '채점'보다는 '인정'이나 '채택'이라는 표현이 훨씬 자연스럽고 전문적으로 보입니다. 메시지 전한 상태 유지 -> 메시지 전송 후 대기: 체크리스트 항목으로서 행동의 완결성을 명확히 주기 위해 '전송 후 대기'나 '메시지 발송 완료'로 바꾸는 것이 더 깔끔합니다. 우리 connectai 시스템/로직적으로 어느 부분을 개선해야할까? + +## Decision +결론: 아스트라의 답변에서 발생하는 오타와 문맥적 어색함을 근본적으로 해결하려면, 단순히 '프롬프트 수정'에 그치지 말고 '출력물 검증 자동화(Automated Output Verification)'를 시스템 로직의 핵심 레이어로 통합해야 합니다. 사용자가 지적한 오류들은 모두 모델이 생성한 결과물이 '최종 확정'되기 전에 검토되지 않았기 때문에 발생한 문제입니다. 이를 개선하기 위해 `connectai` 시스템에 도입해야 할 3가지 기술적 강화 방안을 제안합니다. 1. **Post-Generation Validator (출력 후 검증 레이어) 구축 가장 시급한 부분입니다. 모델이 답변을 생성(Generation)한 직후, 사용자에게 전달하기 전 단계에서 '검증 로직'을 실행하는 프로세스를 추가해야 합니다. - 로직 개선: `LLM Response` $\rightarrow$ `Validator (Regex/Dictionary Check)` $\rightarrow$ `Final Out... + +## Reason +Captured automatically because the conversation contained decision-oriented language. + +## Alternatives +Not captured yet. + +## Consequences +- Future prompts should treat this as project context unless the user changes direction. diff --git a/docs/records/ConnectAI/discussions/2026-05-29_진행해.md b/docs/records/ConnectAI/discussions/2026-05-29_진행해.md new file mode 100644 index 0000000..457a43b --- /dev/null +++ b/docs/records/ConnectAI/discussions/2026-05-29_진행해.md @@ -0,0 +1,16 @@ +# Discussion: 진행해 + +## User Request Summary +진행해 + +## Interpreted Intent +Capture a meaningful project discussion automatically instead of requiring manual record selection. + +## Questions +No explicit question was captured. + +## Main Discussion +- 결론: `E:\Wiki\connectai\docs\records\ConnectAI` 경로의 파일들을 순차적으로 읽어, 각 기록의 맥락을 보존하면서도 유기적인 연결이 가능한 구조로 Wiki화 작업을 진행하겠습니다. 방금 확인한 파일 목록 중 분석이 필요한 파일부터 차례대로 내용을 파악하여 지식 베이스에 통합하겠습니다. 먼저 `project-profile.md` 파일을 읽어 이 기록의 근간이 되는 프로젝트의 기본 프로필과 목적을 파악하겠습니다. + +## Decisions +No decisions captured yet. diff --git a/docs/records/ConnectAI/timeline.md b/docs/records/ConnectAI/timeline.md index d48fab9..257ca5d 100644 --- a/docs/records/ConnectAI/timeline.md +++ b/docs/records/ConnectAI/timeline.md @@ -225,3 +225,9 @@ ## 2026-05-29 - Auto decision record created: decisions\ADR-0028-질문이-있어-내가-당근이라는-중고-거래-사이트에서-8tb-hdd를-구매했어-안전거래-escrow-를-사용했어.md + +## 2026-05-29 +- Auto decision record created: decisions\ADR-0029-여전히-오타가-있어-그러고-어색한-답변도-있네-오타-및-문맥-수정-사항-s-m-a-t-s-m-a-r-t-하드.md + +## 2026-05-29 +- Auto discussion record created: discussions\2026-05-29_진행해.md diff --git a/package-lock.json b/package-lock.json index 0861a7c..c2c918e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "astra", - "version": "2.2.193", + "version": "2.2.194", "license": "MIT", "dependencies": { "@lmstudio/sdk": "^1.5.0", diff --git a/package.json b/package.json index 87c2dfe..a28b522 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "astra", "displayName": "Astra", "description": "The personal intelligence layer for Antigravity and VS Code. A private cognitive partner for deep project context, memory, and proactive strategic decision-making.", - "version": "2.2.193", + "version": "2.2.194", "publisher": "g1nation", "license": "MIT", "icon": "assets/icon.png", @@ -698,6 +698,11 @@ "maximum": 20000, "description": "Glossary 본문 시스템 프롬프트 cap (chars). 너무 크면 토큰 비용↑. 초과분은 잘림. 기본 4000." }, + "g1nation.termValidatorEnabled": { + "type": "boolean", + "default": true, + "description": "Post-gen Term Validator — 답변 완료 후 글로서리 forbidden 단어 결정론적 정규식 스캔. Terminology Dictionary 의 instructional 지시를 deterministic 검증으로 보완. LLM 호출 없음, 매 turn 안전. footer 한 줄 표시. 기본 켜짐." + }, "g1nation.knowledgeMix.secondBrainWeight": { "type": "number", "default": 50, diff --git a/src/agent.ts b/src/agent.ts index bdaf6ba..a496f06 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -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; diff --git a/src/agent/termValidator.ts b/src/agent/termValidator.ts new file mode 100644 index 0000000..37ad281 --- /dev/null +++ b/src/agent/termValidator.ts @@ -0,0 +1,221 @@ +/** + * Post-generation Term Validator — 답변 *완료 후* 정규식/사전 기반 *결정론적* 스캔. + * + * v2.2.192 의 Terminology Dictionary 가 *instructional* (LLM 에게 표준 표기 사용 지시) 이면, + * 이건 *deterministic* — LLM 이 지시를 안 따랐을 때 catch. + * + * Glossary 파싱 — 두 패턴 인식: + * 1. **표준 표기**: `- **Canonical** (X: typo1, typo2, ...)` + * → typo1/typo2 가 답변에 등장하면 "→ Canonical 권장" flag + * 2. **금지 표현**: H2/H3 제목에 "금지"/"비추" 포함된 섹션의 `- ❌ "phrase"` 또는 `- ❌ phrase` + * → phrase 가 답변에 등장하면 "삭제/재작성 권장" flag + * + * Forbidden 후보 필터: + * - 1~30 chars + * - 공백/괄호/이모지 등 description 토큰 제외 + * - 빈 문자열, 한 자 (한글 1음절 제외) 등 false-positive 위험 토큰 제외 + * + * 비용: LLM 호출 없음, 정규식 1회. 매 turn 안전하게 실행 가능. + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +const DEFAULT_GLOSSARY_REL_PATH = '.astra/glossary.md'; + +interface ForbiddenEntry { + forbidden: string; + suggested?: string; // canonical 권장 표기 (X-marker 케이스만) + source: 'x-marker' | 'banned-section'; +} + +interface ValidatorCache { + mtime: number; + entries: ForbiddenEntry[]; +} + +const _cache = new Map(); + +export function clearTermValidatorCache(): void { + _cache.clear(); +} + +function getGlossaryFilePath(relPath: string): string | null { + const folders = vscode.workspace.workspaceFolders; + if (!folders || folders.length === 0) return null; + return path.join(folders[0].uri.fsPath, relPath); +} + +/** 토큰이 forbidden 후보로 적합한가 — false-positive 줄임. */ +function isValidForbiddenToken(token: string): boolean { + if (!token) return false; + const t = token.trim(); + if (t.length === 0 || t.length > 30) return false; + // 빈 괄호·이모지·문장 등 제외 — 영문/한글/숫자/하이픈/언더스코어만 허용 (+ 슬래시 — 명령어 지원) + if (!/^[\w가-힣\-./]+$/u.test(t)) return false; + // 한글 1음절은 false-positive 위험 (조사 등) — 제외 + if (/^[가-힣]$/.test(t)) return false; + // 영문 1글자는 거의 항상 false-positive — 제외 + if (/^[a-zA-Z]$/.test(t)) return false; + return true; +} + +/** + * Glossary markdown 파싱. mtime 캐시. + */ +function parseGlossary(filePath: string): ForbiddenEntry[] { + try { + if (!fs.existsSync(filePath)) return []; + const st = fs.statSync(filePath); + const cached = _cache.get(filePath); + if (cached && cached.mtime === st.mtimeMs) return cached.entries; + + const content = fs.readFileSync(filePath, 'utf-8'); + const entries: ForbiddenEntry[] = []; + + // ─── Pattern 1: **Canonical** (X: typo1, typo2, ...) ─── + // `**ASTRA** (X: astra, Astra 외)` — Astra 외 같은 description 은 후보 필터로 제거. + const xPattern = /\*\*([^*]+)\*\*\s*\(X:\s*([^)]+)\)/g; + let m: RegExpExecArray | null; + while ((m = xPattern.exec(content)) !== null) { + const canonical = m[1].trim(); + const variants = m[2].split(',').map((v) => v.trim()); + for (const v of variants) { + if (isValidForbiddenToken(v)) { + entries.push({ forbidden: v, suggested: canonical, source: 'x-marker' }); + } + } + } + + // ─── Pattern 2: 금지 섹션 내 `- ❌ "..."` 또는 `- ❌ ...` ─── + // H2/H3 제목에 "금지" 또는 "비추" 포함된 섹션 본문만 스캔. + const sectionRegex = /^(#{2,3})\s+(.+)$/gm; + const sections: { headerEnd: number; nextHeaderStart: number; title: string }[] = []; + let sm: RegExpExecArray | null; + let lastIdx = 0; + const matches: { idx: number; level: number; title: string }[] = []; + while ((sm = sectionRegex.exec(content)) !== null) { + matches.push({ idx: sm.index + sm[0].length, level: sm[1].length, title: sm[2].trim() }); + } + for (let i = 0; i < matches.length; i++) { + const cur = matches[i]; + const next = matches[i + 1]; + sections.push({ + headerEnd: cur.idx, + nextHeaderStart: next ? next.idx : content.length, + title: cur.title, + }); + } + for (const sec of sections) { + if (!/금지|비추|forbidden|avoid|don'?t/i.test(sec.title)) continue; + const body = content.slice(sec.headerEnd, sec.nextHeaderStart); + const itemRe = /^-\s*❌\s*(?:"([^"]+)"|'([^']+)'|([^\n—]+?))(?:\s*[—–-].*)?$/gm; + let im: RegExpExecArray | null; + while ((im = itemRe.exec(body)) !== null) { + const phrase = (im[1] || im[2] || im[3] || '').trim(); + if (isValidForbiddenToken(phrase)) { + entries.push({ forbidden: phrase, source: 'banned-section' }); + } + } + } + + // Dedup + const seen = new Set(); + const deduped = entries.filter((e) => { + const k = `${e.source}::${e.forbidden.toLowerCase()}`; + if (seen.has(k)) return false; + seen.add(k); + return true; + }); + + _cache.set(filePath, { mtime: st.mtimeMs, entries: deduped }); + lastIdx = 0; // suppress lint unused + void lastIdx; + return deduped; + } catch { + return []; + } +} + +export interface TermViolation { + forbidden: string; + suggested?: string; + source: 'x-marker' | 'banned-section'; + /** 답변 내 발견된 횟수. */ + occurrences: number; +} + +export interface TermValidationResult { + /** validator 실행 여부 — 글로서리 없거나 disabled 면 false. */ + ran: boolean; + /** 사전 entry 수 — 0 이면 글로서리 비어 있음. */ + dictionarySize: number; + violations: TermViolation[]; + /** 0 이면 위반 없음. */ + totalViolations: number; +} + +function escapeRegex(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * 답변 텍스트에서 forbidden 단어 발견 — 대소문자 무시, 단어 경계 매치 (영문), + * 한글은 substring (한글은 word boundary 가 의미 없음). + */ +function countMatches(text: string, forbidden: string): number { + const isAscii = /^[\w\-./]+$/.test(forbidden); + const re = isAscii + ? new RegExp(`\\b${escapeRegex(forbidden)}\\b`, 'gi') + : new RegExp(escapeRegex(forbidden), 'g'); + const matches = text.match(re); + return matches ? matches.length : 0; +} + +export function validateTermUsage( + answer: string, + glossaryRelPath: string = DEFAULT_GLOSSARY_REL_PATH, +): TermValidationResult { + const fp = getGlossaryFilePath(glossaryRelPath); + if (!fp || !fs.existsSync(fp)) { + return { ran: false, dictionarySize: 0, violations: [], totalViolations: 0 }; + } + const entries = parseGlossary(fp); + if (entries.length === 0) { + return { ran: true, dictionarySize: 0, violations: [], totalViolations: 0 }; + } + if (!answer || !answer.trim()) { + return { ran: true, dictionarySize: entries.length, violations: [], totalViolations: 0 }; + } + + const violations: TermViolation[] = []; + let total = 0; + for (const e of entries) { + const n = countMatches(answer, e.forbidden); + if (n > 0) { + violations.push({ ...e, occurrences: n }); + total += n; + } + } + violations.sort((a, b) => b.occurrences - a.occurrences); + return { ran: true, dictionarySize: entries.length, violations, totalViolations: total }; +} + +/** + * Footer 마크다운 한 줄. 위반 없으면 깔끔한 ✓, 있으면 상세. + * Self-Check footer 와 시각적으로 통일. + */ +export function formatTermValidatorFooter(result: TermValidationResult): string { + if (!result.ran) return ''; + if (result.dictionarySize === 0) return ''; + if (result.totalViolations === 0) { + return `\n_🔤 **Term validator**: ✓ 사전 ${result.dictionarySize}개 항목, 위반 없음_`; + } + const top = result.violations.slice(0, 4).map((v) => { + if (v.suggested) return `"${v.forbidden}" → "${v.suggested}"`; + return `"${v.forbidden}" (금지)`; + }).join(' · '); + const more = result.violations.length > 4 ? ` _+${result.violations.length - 4}건_` : ''; + return `\n_🔤 **Term validator**: ⚠️ 위반 ${result.totalViolations}건 — ${top}${more}_`; +} diff --git a/src/config.ts b/src/config.ts index d28ddb8..4519d41 100644 --- a/src/config.ts +++ b/src/config.ts @@ -167,6 +167,12 @@ export interface IAgentConfig { glossaryPath: string; /** Glossary 본문 시스템 프롬프트 cap (chars). 너무 크면 토큰 비용↑. 기본 4000. */ glossaryMaxBodyLength: number; + /** + * Post-gen Term Validator — 답변 완료 후 글로서리 forbidden 단어 결정론적 스캔. + * Terminology Dictionary (v2.2.192) 의 *instructional* 지시를 *deterministic* 검증으로 보완. + * LLM 호출 없음 (정규식), 매 turn 안전 실행. footer 한 줄 표시. 기본 true. + */ + termValidatorEnabled: boolean; /** * Global Knowledge Mix weight (0–100). Controls how much the assistant leans on * Second Brain evidence vs. model general knowledge when answering. @@ -439,6 +445,7 @@ export function getConfig(): IAgentConfig { glossaryEnabled: cfg.get('glossaryEnabled', true), glossaryPath: cfg.get('glossaryPath', '.astra/glossary.md') || '.astra/glossary.md', glossaryMaxBodyLength: Math.max(500, Math.min(20000, cfg.get('glossaryMaxBodyLength', 4000))), + termValidatorEnabled: cfg.get('termValidatorEnabled', true), knowledgeMixSecondBrainWeight: Math.max(0, Math.min(100, Math.round( cfg.get('knowledgeMix.secondBrainWeight', 50) ))), diff --git a/src/features/datacollect/slashRouter.ts b/src/features/datacollect/slashRouter.ts index 4858b36..2599ff7 100644 --- a/src/features/datacollect/slashRouter.ts +++ b/src/features/datacollect/slashRouter.ts @@ -19,6 +19,7 @@ import { GLOSSARY_TEMPLATE, clearGlossaryCache, } from '../../retrieval/terminologyBlock'; +import { clearTermValidatorCache } from '../../agent/termValidator'; /** * Datacollect "라디오" slash 명령 라우터. @@ -3982,7 +3983,8 @@ async function runGlossary(arg: string, view: any, _context?: vscode.ExtensionCo if (trimmed === 'reload') { clearGlossaryCache(); - chunk(view, '\n🔄 글로서리 캐시 비움. 다음 채팅 turn 에 파일 재읽기.\n'); + clearTermValidatorCache(); + chunk(view, '\n🔄 글로서리 캐시 비움 (system prompt + Term Validator 모두). 다음 채팅 turn 에 파일 재읽기.\n'); return true; }