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
+16 -16
View File
@@ -3,15 +3,15 @@
<!-- ASTRA:AUTO-START -->
## 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/<br/>262 files"]
src["src/<br/>263 files"]
media["media/<br/>6 files"]
tests["tests/<br/>37 files"]
core_py["core_py/<br/>6 files"]
docs["docs/<br/>105 files"]
docs["docs/<br/>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`_
<!-- ASTRA:AUTO-END -->
## Purpose
+37 -14
View File
@@ -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": []
},
@@ -1,5 +1,5 @@
{
"result": "직답 결과 — single-pass mock 응답입니다.",
"createdAt": 1780037632204,
"createdAt": 1780039528659,
"modelVersion": "unknown"
}
@@ -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"
}
@@ -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": {
@@ -1,5 +1,5 @@
{
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
"createdAt": 1780037639311,
"createdAt": 1780039534874,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "Final report with inconsistencies. This should be long enough to pass validation.",
"createdAt": 1780037639310,
"createdAt": 1780039534874,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "[{\"heading\":\"본문\",\"scope\":\"전체 답변\"}]",
"createdAt": 1780037639305,
"createdAt": 1780039534870,
"modelVersion": "unknown"
}
@@ -1,5 +1,5 @@
{
"result": "[CONFLICT WARNING] 성능이 200% 증가했습니다. vs 그러나 동시에 50% 감소했습니다. 최적화와 성능 저하가 동시에 발견됨.",
"createdAt": 1780037639308,
"createdAt": 1780039534872,
"modelVersion": "unknown"
}
@@ -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": {
+59
View File
@@ -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) 한 화면.
+1 -1
View File
@@ -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"
}
@@ -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.
@@ -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` 파일을 읽어 이 기록의 근간이 되는 프로젝트의 기본 프로필과 목적을 파악하겠습니다. <read_file path="E:\Wiki\connectint\docs\records\ConnectAI\project-profile.md"/>
## Decisions
No decisions captured yet.
+6
View File
@@ -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
+1 -1
View File
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "astra",
"version": "2.2.193",
"version": "2.2.194",
"license": "MIT",
"dependencies": {
"@lmstudio/sdk": "^1.5.0",
+6 -1
View File
@@ -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,
+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;
+221
View File
@@ -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<string, ValidatorCache>();
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<string>();
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}_`;
}
+7
View File
@@ -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 (0100). 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<boolean>('glossaryEnabled', true),
glossaryPath: cfg.get<string>('glossaryPath', '.astra/glossary.md') || '.astra/glossary.md',
glossaryMaxBodyLength: Math.max(500, Math.min(20000, cfg.get<number>('glossaryMaxBodyLength', 4000))),
termValidatorEnabled: cfg.get<boolean>('termValidatorEnabled', true),
knowledgeMixSecondBrainWeight: Math.max(0, Math.min(100, Math.round(
cfg.get<number>('knowledgeMix.secondBrainWeight', 50)
))),
+3 -1
View File
@@ -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;
}