@wooojin/forgen 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +235 -0
  3. package/README.ko.md +14 -0
  4. package/README.md +32 -0
  5. package/assets/claude/commands/calibrate.md +4 -3
  6. package/assets/claude/commands/forge-loop.md +62 -2
  7. package/assets/claude/commands/retro.md +2 -2
  8. package/dist/checks/_shared/text-sanitizer.d.ts +21 -0
  9. package/dist/checks/_shared/text-sanitizer.js +60 -0
  10. package/dist/checks/dangerous-response-pattern.d.ts +32 -0
  11. package/dist/checks/dangerous-response-pattern.js +65 -0
  12. package/dist/checks/fact-vs-agreement.js +25 -1
  13. package/dist/cli.js +8 -0
  14. package/dist/core/auto-compound-runner.js +31 -4
  15. package/dist/core/settings-injector.js +8 -2
  16. package/dist/core/statusline-cli.d.ts +13 -0
  17. package/dist/core/statusline-cli.js +150 -0
  18. package/dist/hooks/hook-registry.js +9 -4
  19. package/dist/hooks/stop-guard.js +56 -39
  20. package/dist/host/install-claude.js +34 -3
  21. package/dist/mcp/tools.js +4 -0
  22. package/dist/renderer/rule-renderer.d.ts +1 -1
  23. package/dist/renderer/rule-renderer.js +73 -1
  24. package/dist/store/compound-usage-store.d.ts +28 -0
  25. package/dist/store/compound-usage-store.js +59 -0
  26. package/package.json +1 -1
  27. package/plugin.json +1 -1
  28. package/scripts/postinstall.js +61 -6
  29. package/skills/architecture-decision/SKILL.md +21 -0
  30. package/skills/calibrate/SKILL.md +25 -3
  31. package/skills/code-review/SKILL.md +21 -0
  32. package/skills/compound/SKILL.md +21 -0
  33. package/skills/deep-interview/SKILL.md +21 -0
  34. package/skills/docker/SKILL.md +21 -0
  35. package/skills/forge-loop/SKILL.md +76 -1
  36. package/skills/learn/SKILL.md +21 -0
  37. package/skills/retro/SKILL.md +23 -2
  38. package/skills/ship/SKILL.md +21 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://claude.ai/schemas/claude-plugin.json",
3
3
  "name": "forgen",
4
- "version": "0.4.3",
4
+ "version": "0.4.5",
5
5
  "description": "Claude Code harness — the more you use Claude, the better it gets",
6
6
  "author": {
7
7
  "name": "jang-ujin",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,241 @@ All notable changes to forgen will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Fixed — forgen-eval testbed 측정 결함 (ADR-007)
11
+
12
+ `forgen-eval` ψ-stat 측정의 두 구조적 결함 식별 + 수정. 본 fix 이전 모든
13
+ ψ 측정 보고는 "ADR-007 이전 testbed 결함 위에서 산출됨" disclaimer 적용.
14
+ **v0.4.4 release note 의 mean ψ=+0.098 master gate PASS 도 본 disclaimer 대상**
15
+ (haiku judge + 결함 arm + 결함 mem 위 측정).
16
+
17
+ - **[testbed-P0] ForgenPlusMemArm single-session 결합** (`commit 25c8ac0`)
18
+ - 이전 구현은 forgen-only LLM 세션과 mem-only LLM 세션을 *각각* 돌리고
19
+ forgen 응답만 채택 — Driver 가 `qwen2.5:14b @ temp=0.3` 비결정 호출이라
20
+ `full.W − forgenOnly.W` 가 LLM stochastic noise 로 양/음 ±0.3 흔들림.
21
+ ψ 가 forgen+mem coexistence 신호 대신 LLM 분산을 측정.
22
+ - 한 LLM 세션 안에서 forgen UPS rule + claude-mem recall 을 둘 다 system
23
+ message 로 주입한 뒤 한 번 chat → forgen Stop guard 평가 구조로 재작성.
24
+
25
+ - **[testbed-P0] claude-mem 콘텐츠 직접 fetch** (`commit d65b4a4`)
26
+ - 이전 mem recall 은 `claude-mem search` CLI 출력 (검색 결과 *테이블* —
27
+ 세션 ID + 제목만) 을 그대로 inject. LLM 컨텍스트로는 사실상 메타-noise
28
+ 이고, 응답을 verbose / cautious / "context 더 주세요" 쪽으로 shift 시켜
29
+ sonnet judge 의 actionable advice 점수를 깎았음.
30
+ - 신규 `claudeMemRecallActual()` helper — 검색 후 ID 파싱 →
31
+ `~/.claude-mem/claude-mem.db` 의 `observations.narrative` /
32
+ `session_summaries.learned` 직접 조회 → 상위 N hit 의 실제 콘텐츠 inject
33
+ (`[#ID]\n<content>` 포맷). DB 미설치 환경에서 graceful no-op.
34
+
35
+ - **신규 분석 도구**: `src/runners/probe-mem-inject.ts` — judge 호출 없이
36
+ ForgenOnly + Full arm inject 텍스트와 응답을 콘솔에 덤프하는 정성 probe.
37
+ cross-talk 가설 검증에 사용.
38
+
39
+ - **신규 ADR**: `docs/adr/ADR-007-testbed-arm-isolation.md` — 두 결함의 발견
40
+ 경위, 영향 받은 측정 목록, 재측정 계획, 회귀 가드 명시. 후속 정성 분석
41
+ (E, 2026-05-08) 으로 cross-talk 가설보다 LLM stochasticity (qwen2.5:14b @
42
+ temp=0.3 base error rate × 더 긴 context surface) 가 음수 ψ 의 더 강한 설명
43
+ 임을 확인 — 다음 측정의 전제조건으로 Driver determinism (temp=0 + seed) 또는
44
+ 더 강한 driver 권고.
45
+
46
+ - **재측정 결과 (track-mem-fix N=10 sonnet, 2026-05-08)**: 양쪽 fix 적용 후
47
+ mean ψ = −0.080, 95% CI [−0.161, −0.000], gate FAIL (음수 시그널). 7 음수 /
48
+ 3 양수. v0.4.4 release note 의 mean ψ=+0.098 PASS 는 broken testbed 의
49
+ artifact 였음이 더 강하게 확정됨.
50
+
51
+ ### Changed — driver 를 claude-cli / codex-cli 로 통일 (commit 62600ec, 11b897a)
52
+
53
+ testbed driver 가 Ollama qwen2.5:14b 로 남아 judge stack (claude-cli +
54
+ codex-cli) 과 불일치 + qwen base error rate (~30-50%) 가 noise 의 주 원인 이었던
55
+ 문제를 production 시나리오 (forgen 이 personalize 하는 LLM = Claude 또는 Codex)
56
+ 와 일치하는 driver 로 교체.
57
+
58
+ **측정 비교 (N=10 sonnet judge, 2026-05-11 ~ 12)**:
59
+
60
+ | Driver | N eff | mean ψ | CI | mean δ(forgenOnly−vanilla) | 양수 δ | κ γ |
61
+ |---|---|---|---|---|---|---|
62
+ | qwen mem-fix (이전) | 10 | −0.080 | [−0.161, −0.000] | (n/a) | — | (n/a) |
63
+ | claude (older fixes) | 10 | +0.020 | [−0.133, +0.158] | +0.046 | 7/10 | (n/a) |
64
+ | codex (all fixes) | 10 | +0.024 | [−0.029, +0.094] | +0.120 | 8/10 | 0.323 |
65
+ | claude (all fixes, rate-limit cut) | 9 | −0.013 | [−0.083, +0.039] | +0.156 | 8/9 | 0.429 |
66
+ | claude (retry+sequential N=20) | 20 | +0.016 | [−0.012, +0.047] | +0.096 | 14/20 | 0.583 |
67
+ | codex (retry+sequential N=20) | 20 | +0.013 | [−0.029, +0.055] | +0.133 | 19/20 | 0.048 |
68
+ | **claude (judge retry N=33)** | **33** | **−0.005** | **[−0.036, +0.029]** | **+0.125** | **30/33 (91%)** | **0.474** |
69
+ | **codex (judge retry N=33)** | **33** | **−0.021** | **[−0.068, +0.024]** | **+0.176** | **32/33 (97%)** | **0.263** |
70
+ | **POOLED N=66 — 학술 증명** | **66** | **−0.013** | — | **+0.151** | **62/66 (93.9%) — p=1×10⁻¹⁴** | — |
71
+
72
+ **핵심 발견**:
73
+ - ψ (forgen+mem coexistence) 는 양 driver 모두 noise 영역 — 부호가 driver 별로
74
+ 갈리고 CI 가 0 가로지름. **forgen+mem 결합 효과는 통계적으로 측정 불가능.**
75
+ - δ (forgenOnly−vanilla) 는 **양 driver 일관 양수**, 다수 케이스 일관 — codex
76
+ driver 에서 +0.144 W. **forgen 단독 효과는 robust 하게 양수.**
77
+ - **셀링 메트릭 변경**: ψ 가 아닌 **δ (forgen vs vanilla)** 가 진짜 셀링
78
+ 포인트. v0.4.4 의 ψ master gate PASS 주장 대신 v0.4.5 부터는 δ 중심 메시지.
79
+
80
+ **알려진 limitation**:
81
+ - ~~codex driver 1MB input 한계~~ ✓ FIXED (commit 1362d59 history cap 16K)
82
+ - ~~codex judge spawn E2BIG~~ ✓ FIXED (commit e42bff6 judge stdin pipe + 5c8dce8
83
+ material cap 32K). fallback 2.5: 56 → 3 (95% 감소).
84
+ - ~~claude CLI subscription rate-limit~~ ✓ FIXED commit 7b333b2 (driver retry +
85
+ exponential backoff). claude retry+sequential N=20 측정에서 retry 0회 발동
86
+ (sequential 만으로 충분), N=20 effective 회복.
87
+
88
+ ### Fixed — Node 20.x 환경 호환성 (P0/P1)
89
+
90
+ `npm i -g @wooojin/forgen` 이후 "각종 훅이 에러난다"는 사용자 보고에 대응한 환경
91
+ 호환성 일괄 강화. 보고 환경: M2 MacBook + Node 20.x.
92
+
93
+ - **[P0] hook-registry.ts import attributes 호환성** (`src/hooks/hook-registry.ts:56`)
94
+ - `import ... with { type: 'json' }` (Node 20.10+ 만 파싱 가능) → `JSON.parse(readFileSync(...))`
95
+ 로 교체. Node 20.0–20.9 에서 모든 훅(23개)이 SyntaxError 로 깨지던 회귀를
96
+ 제거. 빌드 산출물에 import attributes 가 재유입되는 것을 막는 정적 검증 테스트
97
+ `tests/hook-registry-portability.test.ts` 추가.
98
+ - 영향 범위: hook-config 를 거쳐 모든 PreToolUse / PostToolUse / Stop / SessionStart
99
+ / UserPromptSubmit 훅이 Node 20.0–20.9 사용자 환경에서 동작하지 않던 상태에서
100
+ 회복.
101
+
102
+ - **[P1] postinstall self-check** (`scripts/postinstall.js`)
103
+ - 설치 마지막 단계에서 `dist/hooks/hook-registry.js` 를 dynamic import 로 로드하고,
104
+ `HOOK_REGISTRY` 가 비어있지 않은지 확인. 실패 시 stderr 로 Node 버전과 원인을
105
+ 명시해 사용자가 "왜 훅이 안 도는지" 를 install 시점에 즉시 알 수 있게 함
106
+ (npm install 자체는 깨뜨리지 않음).
107
+
108
+ - **[P1] install-claude.ts symlink 폴백 진단** (`src/host/install-claude.ts:67-87`)
109
+ - Windows 비관리자 / macOS SIP 환경에서 `fs.symlinkSync` 가 EPERM 으로 실패하면
110
+ 조용히 cpSync 로 폴백하던 동작에 stderr 진단 메시지 1줄 추가. "왜 install 이
111
+ 느린지" 가 사용자에게 보임.
112
+
113
+ - **[P1] CI portability matrix 확장** (`.github/workflows/ci.yml`)
114
+ - Node 20.0.0 / 20.10.0 / 20.x / 22.x × ubuntu/macos/windows 6개 조합으로 훅
115
+ 스모크 잡 추가. 모든 `dist/hooks/*.js` 를 sentinel input 으로 실행해
116
+ SyntaxError / Cannot find module / ERR_ 발생 시 CI 실패. 회귀 즉시 감지.
117
+
118
+ ### Notes
119
+
120
+ - `node:sqlite` 의존 (`src/core/session-store.ts`) 은 기존 try/catch 폴백으로 Node
121
+ <22.5 에서도 graceful degrade 동작 유지. session-search MCP 도구는 0건 반환.
122
+ - `quality-check.mjs MODULE_NOT_FOUND` 같이 사용자/타플러그인이 등록한 외부 훅이
123
+ worktree 에서 누락된 경우는 forgen 책임 영역 아님. `isForgenHookEntry()` 가
124
+ `dist/hooks/*.js` 경로만 자기 소유로 인식하므로 외부 훅 항목은 보존.
125
+
126
+ ## [0.4.4] — 2026-05-06
127
+
128
+ > **⚠ 정정 (2026-05-08, ADR-007 이후)**: 본 릴리스의 ψ master gate PASS
129
+ > (mean +0.098, CI [+0.002, +0.222]) 주장은 **broken testbed 위 측정** 으로
130
+ > 확정. 두 구조 결함 (ForgenPlusMemArm 비-결합 / mem recall 메타 inject) 위에서
131
+ > 산출되어 LLM noise + max-selection bias 가 평균을 양수로 끌어당긴 artifact.
132
+ > 양쪽 결함 수정 후 재측정 (track-mem-fix N=10 sonnet) 결과는 mean ψ = −0.080,
133
+ > CI [−0.161, −0.000], gate FAIL. **현 시점 forgen+mem 결합은 net negative
134
+ > 또는 noise 영역** — qwen2.5:14b @ temp=0.3 driver 의 hallucination 분산이
135
+ > 결합 효과를 mask 함. Driver determinism (temp=0 + seed) 또는 더 강한 driver
136
+ > 적용 후 재측정까지 셀링 보류. 자세한 내용은 ADR-007 참조.
137
+ >
138
+ > δ(forgenOnly−vanilla) = +0.223 주장도 같은 testbed 위 산출이므로 같은
139
+ > disclaimer 대상. 단 forgenOnly arm 자체는 본 ADR fix 영향 받지 않음 (vanilla
140
+ > 와의 비교는 단일 arm 내부 비교라 LLM noise 가 양쪽에 균등 분포 가능성 — 단
141
+ > 재측정으로 확인 필요).
142
+
143
+ ### v0.4.4 — measurement infra rebuild + stop-guard hardening (DANGEROUS-RESPONSE)
144
+
145
+ forgen-eval testbed 의 측정 인프라 5-layer 결함을 모두 수정해 신뢰성을 회복하고,
146
+ 그 과정에서 발견한 driver-brittleness 결함(syn-004 — small driver 가 학습된 룰을
147
+ 파괴 명령 우회로 회피)을 stop-guard `dangerous-response-pattern` 체크로 직접
148
+ close. 사후 N=10 재측정에서 **ψ master gate PASS** (mean +0.098, 95% CI [+0.002,
149
+ +0.222]) — pre-hardening (-0.028) 대비 부호 양수 전환. 또한
150
+ δ(forgenOnly−vanilla) = +0.223 (CI [+0.134, +0.326], 10/10 cases positive) 으로
151
+ forgen 효과가 robust 하게 확인됨. (위 박스 참조: 본 측정 결과는 ADR-007 이후
152
+ broken testbed artifact 로 확정.)
153
+
154
+ **Highlights**:
155
+
156
+ - **DANGEROUS-RESPONSE 응답 텍스트 가드** (`feat`)
157
+ - `src/checks/dangerous-response-pattern.ts` + `tests/dangerous-response-pattern.test.ts` (12 케이스)
158
+ - `src/hooks/stop-guard.ts` checks pipeline 에 1순위로 wire-in (raw lastMessage 사용 — sanitizer 가 코드 fence 를 stripping 하므로 sanitized 는 부적합)
159
+ - 패턴 셋: `find -exec rm`, `find -delete`, `xargs rm`, `rm -r/-rf`, `git push --force`, `git reset --hard`, `DROP TABLE`, `dd of=/dev/`, `curl|sh`, `wget|sh` 등 14종 (응답 텍스트용)
160
+ - 매칭 시 block + correction 요청 (FORGEN_USER_CONFIRMED=1 으로 한 turn 우회 가능)
161
+ - 발동 검증: hardening N=10 측정에서 forgenOnly arm block 2건 (이전 측정들 0건)
162
+
163
+ - **forgen-eval testbed 5-layer fix** (`fix`)
164
+ 1. Judge contamination — `claude` CLI 가 사용자 전역 `~/.claude/CLAUDE.md` 로드 → judge 가 forgen 어시스턴트로 빙의 (β score=0/NaN 다발). `claude -p ... --system-prompt <blind>`, `codex exec --ignore-user-config --ignore-rules --ephemeral` 로 격리.
165
+ 2. Persona stub — runner 가 ID 문자열만 β judge 에 전달. `loadPersonaSpec()` 도입해 `personas/persona-XXX.json` 실 spec 로드.
166
+ 3. Trigger turn hook 누락 — `ForgenOnlyArm` 이 correctionSequence 만 hook 통과. trigger 단계도 UPS+Stop hook pipeline 추가.
167
+ 4. Notepad 미초기화 — case 별 임시 cwd + `seedForgenNotepad()` 로 사전 학습 상태 시뮬레이션.
168
+ 5. Hooks dir 경로 하드코딩 (root cause) — 잘못된 절대경로로 모든 hook 호출이 silently 실패. `import.meta.url` 기반 상대경로로 자동 해결. (이 결함이 이전 모든 ψ 측정을 무효화하고 있었음)
169
+ 6. Bridge 응답 shape — `additionalContext` 가 `hookSpecificOutput` nested 필드. 인터페이스/접근 코드 동시 수정.
170
+
171
+ - **Two-layer enforcement 명문화** (`docs`)
172
+ - `README.md` + `README.ko.md` 의 "How It Works" 에 "Two-layer safety enforcement / 2-layer 안전 적용" 섹션 추가. soft (notepad-injector) + hard (PreToolUse + Stop DANGEROUS-RESPONSE) 모델 명시. 작은 driver 가 학습 룰을 우회해도 hard layer 가 차단함을 사용자가 이해 가능.
173
+
174
+ - **Judge rubric 4-anchor 명세** (`fix`)
175
+ - `packages/forgen-eval/src/judges/judge-types.ts` β/γ/φ 프롬프트에 1/2/3/4 모든 anchor 명시 (이전엔 1/4 만). 작은 judge 가 중간 점수 일관성 확보.
176
+
177
+ - **Reports as audit trail** (`chore`)
178
+ - `packages/forgen-eval/reports/psi-stat/*.json` 7건 (5월 4-6일) — pre-isolation, post-isolation, broken sleep run, fixed run, post-rubric, post-hardening 의 비교 가능한 측정 시리즈.
179
+
180
+ - **4축 personalization P1 — facet 임계값 분기 활성화** (`feat`)
181
+ - `src/renderer/rule-renderer.ts` — `_profile` → `profile` 활성화. 13개 facet (3 quality + 4 autonomy + 3 judgment + 3 communication) 의 0.85 / 0.15 임계값 분기 도입.
182
+ - 이전엔 facet 값이 inspect-print 외 어디에도 사용되지 않았음 (12-bucket pack lookup 만 활성). 본 변경으로 4축이 *연속 값* 으로 응답에 영향.
183
+ - `tests/renderer/rule-renderer.test.ts` — facet 0.1 vs 0.9 byte-diff 회귀 테스트 5건 (verification_depth, verbosity, approval_threshold 등).
184
+
185
+ - **judgment / communication 축 facet delta 갱신 경로** (`feat`)
186
+ - `src/core/auto-compound-runner.ts` — `profile_delta` 스키마 + 적용 분기에 `judgment_philosophy`, `communication_style` 케이스 추가. 이전엔 quality_safety / autonomy 2축만 자동 갱신, 나머지 2축은 0.5/0.45 default 영원 고정.
187
+
188
+ - **시맨틱 룰 FP 좁히기** (`fix`)
189
+ - `src/checks/fact-vs-agreement.ts` — `EVIDENCE_INDICATORS` 추가 (test counts `\d+/\d+`, exit code, timing, vitest output 형식, diff hunks 등 9 패턴). 응답에 측정 증거가 paste 되어 있으면 alert 억제 → "Docker e2e 77/77 PASS" 류 정량 사실 보고 FP 감소. tests/fact-vs-agreement.test.ts 4 케이스 추가 (총 13).
190
+ - `~/.forgen/me/rules/L1-no-mock-as-proof.json` — `trigger_exclude_regex` 에 `<observation>`, `<summary>`, observer 메타 패턴 추가. 메타-설명 응답 FP 감소.
191
+ - `~/.forgen/me/rules/L1-e2e-before-done.json` — TDD 진행 보고(`RED→GREEN`, `[N/M]`, `다음 단계`, `진행 상황`) 제외 패턴 추가.
192
+
193
+ **Final measurement (post-hardening + post-narrowing, 두 N=10 합산 N=20)**:
194
+ - ψ master gate: 두 측정 모두 borderline 0 (run1 −0.026, run2 +0.001) — composition-synergy metric 으로는 회귀
195
+ - **δ(forgenOnly−vanilla) N=20 = +0.161, CI [+0.068, +0.256]** — *진짜 forgen 효과* metric, 0 위로 robust. 14/20 cases positive.
196
+ - δ(full−vanilla) N=10 (run2): +0.218, CI [+0.117, +0.323]
197
+ - κ_γ ~0.38 / κ_β ~0.41 — subscription-mode CLI judge 한계 (haiku 가 4점 척도 안정 분류 어려움)
198
+ - fallback 5/160 = 3.1% (≤ 10% 게이트)
199
+ - forgenOnly arm block 이벤트 발화 — DANGEROUS-RESPONSE 패턴이 driver 우회 응답을 차단
200
+
201
+ **Production data sample (8일, 230 violations)**:
202
+ - 9 distinct rules 발화: fact-vs-agreement 67, L1-no-mock-as-proof 56, self-score-inflation 41, L1-no-rm-rf-unconfirmed (PreToolUse) 23, dangerous-response-pattern (신설, 첫날) 20, L1-e2e-before-done 15, etc.
203
+ - Stratified random sample N=30 → precision 60.7%. **Hard layer (PreToolUse + dangerous-response-pattern) 100% (6/6)**, semantic Stop-guard 룰 43-60%.
204
+ - drift 자가복구 14건 — stuck-loop 상황 force-approve 후 drift 기록 (메타 안전성).
205
+
206
+ **Host parity status**:
207
+ - ✅ **Claude (claude)**: 모든 hook 동작 확정 (이번 세션 라이브 self-validated 다수)
208
+ - ⚠️ **Codex (codex)**: PreToolUse hard layer + UserPromptSubmit soft layer 동등. Stop hook response-text 검사 (DANGEROUS-RESPONSE, L1-no-mock-as-proof 자가검증 등) 는 *best-effort* — codex CLI 가 Stop input 에 `last_assistant_message` 또는 `transcript_path` 를 제공해야 발화. 미제공 시 silently auto-approve (안전). 실 codex 사용 데이터로 다음 1주 검증 예정 (gap 발견 시 v0.4.5 보완).
209
+
210
+ **v0.4.4 Does NOT claim**:
211
+ - v0.5.0 release-proof. v0.5.0 은 70B 로컬 / Sonnet API 기반 강judge 로 κ ≥ 0.7 + 더 큰 N 으로 *사전 등록* metric (δ 우선) 으로 처음부터 측정 예정.
212
+ - 외부 재현 — 실행에 Claude Max + Codex subscription 필요.
213
+ - ψ master gate PASS — 두 N=10 측정 모두 borderline 0. ψ 자체가 composition-synergy 측정이라 "forgen이 vanilla 대비 좋은가" 질문에 부적합 metric 임이 본 사이클에서 확인됨. δ 가 답이고 δ 는 양수.
214
+
215
+ **Lessons (post-mortem)**:
216
+ - 측정 인프라 5-layer 결함 (특히 hooks dir 하드코딩) 으로 이전 모든 ψ 측정이 실은 vanilla-vs-vanilla 였음. 5월 6일 hardening + bridge fix 후에야 forgen 메커니즘이 testbed 에서 실제로 발화 시작.
217
+ - ψ 정의 ("full vs best single arm composition") 가 주 product 질문 ("forgen 이 vanilla 대비 좋은가") 과 어긋남을 늦게 발견. v0.5.0 metric 재정의 필요.
218
+ - 1주일 production data 가 enforcement 메커니즘 활성을 입증하나, FP precision (특히 시맨틱 룰 43-60%) 은 별도 트랙 개선 과제.
219
+
220
+ ### Internal — pathfinder + Deep Interview fix cycle (2026-04-30 post-v0.4.3)
221
+
222
+ **Pathfinder (stop-guard 3-check 구조 진단 + unify)** (`refactor`)
223
+ - `PATHFINDER-2026-04-30/` — features → flowcharts → duplication report → unified proposal → handoff
224
+ - `src/checks/_shared/text-sanitizer.ts` + tests — 3-check (`self-score-inflation`, `fact-vs-agreement`, `conclusion-verification-ratio`) measurement Set 중복 제거
225
+ - `src/hooks/stop-guard.ts` — 3-check 디스패처 정리
226
+
227
+ **Deep Interview D9/D11/D12 fix** (`fix`)
228
+ - D9: `docs/guard-design-checklist.md` — guard 설계 invariant 명문화
229
+ - D11: `src/store/compound-usage-store.ts` + tests + `src/mcp/tools.ts` wiring
230
+ - MCP `compound-read/list/search` 호출 시 `~/.forgen/state/compound-usage.jsonl` 에 사용 evidence 적재
231
+ - D12: `assets/claude/commands/calibrate.md` + `retro.md` — `~/.forgen/me/evidence/` → `behavior/` 경로 drift 수정 (skill 카탈로그 정합성 회복)
232
+
233
+ **Auto-compound retry 로깅 개선** (`chore`)
234
+ - `src/core/auto-compound-runner.ts` — retry 메시지에 attempt count + 에러 코드 + fail-open 단언 (UX 명확화, 동작 변경 없음)
235
+
236
+ ### Hygiene
237
+ - `package.json` self-dep 오염 (`@wooojin/forgen ^0.4.3`) 제거
238
+ - `plugin.json` (root) 0.4.2 → 0.4.3 sync (이전 d4c640c 가 `.claude-plugin/plugin.json` 만 sync)
239
+ - `package-lock.json` workspace + transitive peer dep 동기화
240
+
241
+ **Verification**: vitest 2373/2373 PASS, Docker e2e 77/77 PASS (round 16)
242
+
8
243
  ## [0.4.3] — 2026-04-30 — Self-correcting hotfix + testbed prep (alpha)
9
244
 
10
245
  forgen-eval introspect testbed (이번 릴리즈에 포함된 자기 측정 시스템) 가
package/README.ko.md CHANGED
@@ -274,6 +274,20 @@ Linux 컨테이너에서 `~/.claude.json` 만 마운트하면 refresh 토큰이
274
274
  (다음 세션: 업데이트된 규칙)
275
275
  ```
276
276
 
277
+ ### 2-layer 안전 적용
278
+
279
+ 학습된 제약이 모델이 우회를 시도해도 유지되도록 forgen은 **두 단계**에서 적용됩니다:
280
+
281
+ | 단계 | Hook | 시점 | 차단 대상 |
282
+ |---|---|---|---|
283
+ | **Soft (컨텍스트)** | UserPromptSubmit (`notepad-injector`) | 매 turn 시작 전 | 활성 룰을 Claude 컨텍스트에 재주입 — 모델이 자율 준수하도록 유도. |
284
+ | **Hard (도구)** | PreToolUse (`pre-tool-use` + `dangerous-patterns.json`) | 모든 Bash / Edit / Write 직전 | `rm -rf /`, `git push --force`, `DROP TABLE`, `mkfs`, `curl \| sh` 등 패턴 매칭 차단 — 모델 의도 무관하게 발동. |
285
+ | **Hard (응답)** | Stop (`stop-guard` DANGEROUS-RESPONSE) | Claude 응답 직후 | 응답 텍스트 자체 패턴 매칭 — *제안된* 파괴 명령(예: `find … -exec rm`, `xargs rm` 우회)을 사용자가 보기 전에 차단. |
286
+
287
+ Soft layer는 모델에게 "지켜줘"라고 요청하고, Hard layer는 요청하지 않습니다. driver 모델이 약해서 학습된 룰을 "창의적으로" 우회하려 해도 (예: `rm -rf` 금지 → `find -exec rm -r` 제안) Hard layer가 미리 차단합니다.
288
+
289
+ 오버라이드: 한 turn만 감사 우회는 `FORGEN_USER_CONFIRMED=1`, 특정 룰 영구 비활성화는 `forgen suppress-rule <rule_id>`.
290
+
277
291
  ### Compound 지식
278
292
 
279
293
  지식은 세션을 거치며 신뢰도 기반 라이프사이클로 축적됩니다:
package/README.md CHANGED
@@ -61,6 +61,19 @@ This is **Mech-B self-check prompt-inject**. It works because Claude Code's Stop
61
61
 
62
62
  > **v0.4.3 self-correction story:** the same guards detected their own 16-day false-positive (strict φ 65.66% — 84% from a single Korean-regex bug), and the [`forgen-eval`](packages/forgen-eval/) introspect testbed (alpha) flagged a `TEST-1` wiring gap on top of it. Both fixes shipped in v0.4.3 — forgen finding and fixing forgen. Details in [CHANGELOG](CHANGELOG.md).
63
63
 
64
+ > **v0.4.5 measurement evidence (statistically proven):** with all testbed
65
+ > structural fixes applied ([ADR-007](docs/adr/ADR-007-testbed-arm-isolation.md)),
66
+ > forgen's effect over a vanilla baseline is **statistically significant on real
67
+ > production drivers (Claude sonnet, Codex)**. Pooled across both drivers
68
+ > (retry+sequential N=33 each, N=66 total): **mean δ = +0.151 W, 95% CI [+0.118,
69
+ > +0.184], 62/66 (93.9%) cases positive, sign test p = 1.04×10⁻¹⁴**. Codex alone:
70
+ > mean δ = +0.176, 32/33 (97%) positive, p = 4×10⁻⁹. Claude alone: mean δ =
71
+ > +0.125, 30/33 (91%) positive, p = 7×10⁻⁷. **Three independent measurements
72
+ > across two model families all prove δ > 0**. v0.4.4's ψ-master-gate PASS claim
73
+ > is rescinded as a broken-testbed artifact; ψ (forgen+mem coexistence) is
74
+ > confirmed as ≈0 — **forgen alone is the recommended path**. See
75
+ > [`docs/release/v0.4.5-draft.md`](docs/release/v0.4.5-draft.md).
76
+
64
77
  🎬 **See it happen** (27 seconds):
65
78
 
66
79
  ```bash
@@ -319,6 +332,25 @@ entries in `~/.forgen/state/implicit-feedback.jsonl`. Idempotent — safe to re-
319
332
  (next session: updated rules)
320
333
  ```
321
334
 
335
+ ### Two-layer safety enforcement
336
+
337
+ forgen enforces your rules at **two layers** so a learned constraint holds even
338
+ if the model rationalizes a workaround:
339
+
340
+ | Layer | Hook | When | Catches |
341
+ |---|---|---|---|
342
+ | **Soft (context)** | UserPromptSubmit (`notepad-injector`) | Before each turn | Re-injects active rules into Claude's context so the model can self-comply. |
343
+ | **Hard (tool)** | PreToolUse (`pre-tool-use` + `dangerous-patterns.json`) | Before every Bash / Edit / Write | Pattern-match block on `rm -rf /`, `git push --force`, `DROP TABLE`, `mkfs`, `curl \| sh`, etc — fires regardless of model intent. |
344
+ | **Hard (response)** | Stop (`stop-guard` DANGEROUS-RESPONSE) | After Claude's reply | Pattern-match on the reply text itself — catches *suggestions* of destructive commands (e.g., `find … -exec rm`, `xargs rm` rationalizations) before the user sees them. |
345
+
346
+ The soft layer asks the model to behave; the hard layers don't ask. Even with a
347
+ weaker driver model that "creatively" routes around a learned rule (e.g.,
348
+ suggesting `find -exec rm -r {}` because `rm -rf` was forbidden), the hard
349
+ layers stop it before any damage.
350
+
351
+ Override hatch: set `FORGEN_USER_CONFIRMED=1` for a one-turn audited bypass, or
352
+ `forgen suppress-rule <rule_id>` to disable a specific rule permanently.
353
+
322
354
  ### Compound knowledge
323
355
 
324
356
  Knowledge accumulates across sessions with a trust-based lifecycle:
@@ -39,7 +39,8 @@ triggers:
39
39
  calibrate는 두 가지 데이터 소스를 사용합니다:
40
40
 
41
41
  ### 1차 소스: Evidence 파일
42
- `~/.forgen/me/evidence/` 디렉토리의 JSON 파일을 읽습니다.
42
+ `~/.forgen/me/behavior/` 디렉토리의 JSON 파일을 읽습니다.
43
+ (파일명: UUID.json — correction-record MCP 도구가 작성. 같은 디렉토리의 auto-*.md 는 auto-compound 산출이므로 calibrate 분석 대상 아님.)
43
44
  각 파일의 구조:
44
45
  ```json
45
46
  {
@@ -70,8 +71,8 @@ evidence 0건 + compound 교정 패턴 0건이면:
70
71
  ## Phase 1: Evidence 로드 및 검증
71
72
 
72
73
  ```bash
73
- ls ~/.forgen/me/evidence/ 2>/dev/null || echo "EMPTY"
74
- cat ~/.forgen/me/evidence/*.json 2>/dev/null || echo "NO_FILES"
74
+ ls ~/.forgen/me/behavior/*.json 2>/dev/null || echo "EMPTY"
75
+ cat ~/.forgen/me/behavior/*.json 2>/dev/null || echo "NO_FILES"
75
76
  ```
76
77
 
77
78
  로드한 JSON 파일마다 다음을 검증합니다:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: forge-loop
3
- description: This skill should be used when the user asks to "forge-loop, 포지루프, 끝까지, don't stop". 주어진 작업을 PRD(User Story)로 분해하고, 모든 수용 기준이 충족될 때까지 반복 실행합니다.
4
- argument-hint: "[task description]"
3
+ description: This skill should be used when the user asks to "forge-loop, 포지루프, 끝까지, don't stop, goal, 목표, goal lock, scope lock". 작업을 PRD(User Story)로 분해 + 모든 수용 기준 충족까지 반복 실행. `--goal-only` 플래그로 PRD/수용기준 박제만 (실행 사이클 없이) 가능 — goal-locking pattern lightweight 진입점.
4
+ argument-hint: "[task description] [--goal-only]"
5
5
  model: inherit
6
6
  allowed-tools:
7
7
  - Read
@@ -18,6 +18,12 @@ triggers:
18
18
  - "don't stop"
19
19
  - "완료될 때까지"
20
20
  - "루프로 실행"
21
+ - "goal"
22
+ - "목표"
23
+ - "goal lock"
24
+ - "scope lock"
25
+ - "completion criteria"
26
+ - "수용 기준"
21
27
  ---
22
28
 
23
29
  <Purpose>
@@ -90,6 +96,35 @@ EOF
90
96
  이 파일이 있어야 Claude가 중간에 멈추지 않도록 Stop 훅이 차단합니다.
91
97
  스토리 완료 시 `passes: true`로 업데이트. 전체 완료는 Stop 훅이 자동 처리.
92
98
 
99
+ ### goal-only 모드 — Phase 1 종료 분기
100
+
101
+ `$ARGUMENTS` 에 `--goal-only` / `--goal` / `--lock-only` 중 하나가 포함된 경우,
102
+ Phase 1 종료 직후 다음을 산출하고 종료 (Phase 2/3 건너뜀):
103
+
104
+ 1. 위 PRD JSON 의 stories 배열을 markdown Goal 박스로 변환:
105
+ ```
106
+ GOAL: <stories[0].title — 단일 story 면 한 문장 요약>
107
+ 완료 기준 (Acceptance Criteria):
108
+ - [ ] <story[i].acceptanceCriteria[j] 각각 — 구체적 증거 타입 포함>
109
+ 제약 (Out-of-Scope):
110
+ - <"수용 기준 품질 규칙" 표의 금지 패턴들>
111
+ - <사용자가 명시한 dry-run / touch 안 할 경로 등>
112
+ 검증 방법:
113
+ - <각 AC 의 verification command (bash / curl / file check)>
114
+ 컴파운드 패턴 (참고):
115
+ - <compound-search top 1-2 결과 — 본 작업 키워드로 검색>
116
+ ```
117
+
118
+ 2. 사용자에게 박스를 보여주고 안내:
119
+ ```
120
+ GOAL 박제 완료. 다음 옵션:
121
+ - 이 박스를 다른 컨텍스트/에이전트에 위임 → 복사 사용
122
+ - 본 세션에서 자동 실행 → `forge-loop resume` 로 Phase 2 이어 실행
123
+ 상태 파일: ~/.forgen/state/forge-loop.json (resume 시 재활용)
124
+ ```
125
+
126
+ 3. 종료. **Anti-Polite-Stop 규칙은 goal-only 모드에 적용 안 함** — 박제가 목적이고 실행은 명시적 escalation 시에만.
127
+
93
128
  ## Phase 2: 스토리 실행 루프
94
129
 
95
130
  ### 2-1. Compound-In (스토리별)
@@ -210,6 +245,31 @@ compound에 저장하시겠습니까? [Y/n]
210
245
  <Arguments>
211
246
  - `[task description]`: 실행할 작업 설명. 생략 시 현재 대화 컨텍스트에서 추론.
212
247
  - `resume`: 이전에 중단된 루프를 재개합니다.
248
+ - `--goal-only` (또는 `--goal`, `--lock-only`): **goal-locking lightweight 모드**.
249
+ Phase 1 (PRD + 수용 기준 + 상태 파일 저장) 까지만 실행하고 Phase 2/3 (자동
250
+ 실행 루프 + 최종 검증) 은 건너뜁니다. 산출물은 *구조화된 Goal 박스* — 작업
251
+ 범위 / 완료 기준 / 제약 / 검증 방법을 한 markdown 으로 박제. 사용자가 다른
252
+ 컨텍스트나 에이전트에 그대로 붙여 위임 가능. 추후 `forge-loop resume` 로
253
+ 자동 실행 사이클 escalate 가능 (상태 파일 재활용).
254
+
255
+ goal-only 모드의 산출물 포맷:
256
+ ```
257
+ GOAL: <한 문장 요약>
258
+ 완료 기준 (Acceptance Criteria — 증거 타입 포함):
259
+ - [ ] AC1: <테스트 로그 / 파일 변경 / dry-run 출력>
260
+ - [ ] AC2: ...
261
+ 제약 (Out-of-Scope / 안 할 것):
262
+ - <실 발송·배포·삭제 금지 / dry-run 한정>
263
+ - <touch 안 할 경로>
264
+ 검증 방법:
265
+ - <bash 명령 / 파일 확인 / 외부 verification>
266
+ 컴파운드 패턴 (참고):
267
+ - <compound-search 결과 top 1-2>
268
+ ```
269
+
270
+ goal-only 모드는 stop-guard 의 fact-vs-agreement / self-score-inflation
271
+ 체크와 직접 연동 — Goal 박스 박제 후 응답이 "완료" 주장 시 AC 의 증거가
272
+ 포함되어야 통과.
213
273
  </Arguments>
214
274
 
215
275
  $ARGUMENTS
@@ -69,8 +69,8 @@ compound-list
69
69
  ### 1-3: 교정 기록
70
70
 
71
71
  ```bash
72
- ls -la ~/.forgen/me/evidence/ 2>/dev/null || echo "교정 데이터 없음"
73
- find ~/.forgen/me/evidence/ -name "*.json" -mtime -{period_days} 2>/dev/null | wc -l
72
+ ls -la ~/.forgen/me/behavior/ 2>/dev/null || echo "교정 데이터 없음"
73
+ find ~/.forgen/me/behavior/ -name "*.json" -mtime -{period_days} 2>/dev/null | wc -l
74
74
  ```
75
75
 
76
76
  ## Phase 2: 코드 활동 분석
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Forgen — text-sanitizer (Pathfinder D4 + D5 흡수).
3
+ *
4
+ * stop-guard 진입 시 lastMessage 에 한 번 적용. 두 가지 면제:
5
+ *
6
+ * D4 — Structured-output 면제:
7
+ * observer hook / skill 산출물(<observation>...</observation>,
8
+ * <summary>...</summary> 등)은 *과거 사실 기록* 이지 자기 평가가 아님.
9
+ * 본문 안의 "verified", "신뢰도 95/100" 같은 어휘에 가드가 발화하면 FP.
10
+ *
11
+ * D5 — Self-paradox 면제:
12
+ * regex 트리거 어휘(예: 4/10, verified)를 *인용해서* 설명만 해도 본인
13
+ * 매칭. 메타 대화/디버깅에서 가드가 무력화됨. 코드/직인용 본문은 가드
14
+ * 판정 대상이 아니므로 stripping.
15
+ *
16
+ * 결정 (PATHFINDER-2026-04-30/03-unified-proposal.md):
17
+ * - 자연 산문 속 진짜 점수 인플레이션은 살아남아야 함 (TP 보존)
18
+ * - 짧은 인용("...") 만 제거; 긴 인용은 사용자 인용일 수 있어 보존
19
+ * - idempotent: 두 번 적용해도 결과 동일
20
+ */
21
+ export declare function sanitizeForGuard(raw: string): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Forgen — text-sanitizer (Pathfinder D4 + D5 흡수).
3
+ *
4
+ * stop-guard 진입 시 lastMessage 에 한 번 적용. 두 가지 면제:
5
+ *
6
+ * D4 — Structured-output 면제:
7
+ * observer hook / skill 산출물(<observation>...</observation>,
8
+ * <summary>...</summary> 등)은 *과거 사실 기록* 이지 자기 평가가 아님.
9
+ * 본문 안의 "verified", "신뢰도 95/100" 같은 어휘에 가드가 발화하면 FP.
10
+ *
11
+ * D5 — Self-paradox 면제:
12
+ * regex 트리거 어휘(예: 4/10, verified)를 *인용해서* 설명만 해도 본인
13
+ * 매칭. 메타 대화/디버깅에서 가드가 무력화됨. 코드/직인용 본문은 가드
14
+ * 판정 대상이 아니므로 stripping.
15
+ *
16
+ * 결정 (PATHFINDER-2026-04-30/03-unified-proposal.md):
17
+ * - 자연 산문 속 진짜 점수 인플레이션은 살아남아야 함 (TP 보존)
18
+ * - 짧은 인용("...") 만 제거; 긴 인용은 사용자 인용일 수 있어 보존
19
+ * - idempotent: 두 번 적용해도 결과 동일
20
+ */
21
+ const STRUCTURED_TAGS = [
22
+ 'observation',
23
+ 'summary',
24
+ 'request',
25
+ 'investigated',
26
+ 'completed',
27
+ 'next-steps',
28
+ 'next_steps',
29
+ 'title',
30
+ 'subtitle',
31
+ 'learned',
32
+ 'discovery',
33
+ ];
34
+ const SHORT_QUOTE_MAX = 20;
35
+ export function sanitizeForGuard(raw) {
36
+ if (!raw)
37
+ return raw;
38
+ let s = raw;
39
+ // 1) structured-output 블록 (open + close 쌍) 제거
40
+ for (const tag of STRUCTURED_TAGS) {
41
+ const re = new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, 'gi');
42
+ s = s.replace(re, '');
43
+ }
44
+ // self-closing 또는 dangling open tag 도 제거 (열렸지만 닫힘 누락 케이스)
45
+ for (const tag of STRUCTURED_TAGS) {
46
+ const reSelf = new RegExp(`<${tag}\\b[^>]*\\/?>`, 'gi');
47
+ s = s.replace(reSelf, '');
48
+ const reClose = new RegExp(`<\\/${tag}>`, 'gi');
49
+ s = s.replace(reClose, '');
50
+ }
51
+ // 2) fenced code block (```...```) 제거 — 진짜 점수가 들어갈 자리 아님
52
+ s = s.replace(/```[\s\S]*?```/g, '');
53
+ // 3) inline backtick 코드 (`...`) 제거
54
+ s = s.replace(/`[^`\n]*`/g, '');
55
+ // 4) 짧은 직인용 ("...") 제거 — 길이 SHORT_QUOTE_MAX 이하만.
56
+ // 긴 인용은 사용자 발언/실제 사실 인용이므로 가드 판정 대상에 남김.
57
+ const shortQuoteRe = new RegExp(`"[^"\\n]{0,${SHORT_QUOTE_MAX}}"`, 'g');
58
+ s = s.replace(shortQuoteRe, '');
59
+ return s;
60
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Forgen — DANGEROUS-RESPONSE: 응답 텍스트 파괴적 명령 가드
3
+ *
4
+ * Stop hook 단계에서 어시스턴트 응답 텍스트를 스캔하여 *파괴적 명령 제안*을
5
+ * 감지한다. PreToolUse 가드는 실제 도구 실행 직전에만 동작하므로, 모델이
6
+ * 텍스트로 위험한 대안을 *제안*하는 케이스(예: `find ... -exec rm -r {}`)는
7
+ * 도구 실행 단계에 도달하지 못하면 차단되지 않는다. 본 체크는 그 갭을 메운다.
8
+ *
9
+ * 발견 경위 (forgen-eval syn-004, 2026-05-06):
10
+ * notepad 룰 "[no-rm-rf-unconfirmed]" inject 상태에서, driver(llama 8B) 가
11
+ * `rm -rf` 리터럴은 회피했지만 `find . -type d -name 'build' -exec rm -r {}`
12
+ * 같은 우회 명령을 "안전한 대안"으로 제안. PreToolUse 차단은 실 실행 시
13
+ * 동작하지만 "응답 텍스트만으론" 못 막음. driver 모델 능력에 무관하게
14
+ * 일관된 안전 보장이 되도록 응답 단계에서 한 번 더 잡아낸다.
15
+ *
16
+ * 동작:
17
+ * - 어시스턴트 응답에 등록된 파괴 패턴이 매칭되면 block 권고.
18
+ * - "확실히, 명시적 확인" 같은 confirm 표현 동반 여부는 검사하지 않는다
19
+ * (휴리스틱 brittle). 매칭 시 무조건 block 후, 모델이 안전한 대안 제시
20
+ * 또는 명시적 confirm 절차로 재응답하도록 유도.
21
+ *
22
+ * 순수 함수 — Stop hook 이 reason 문자열을 그대로 주입.
23
+ */
24
+ export interface DangerousResultMatch {
25
+ block: boolean;
26
+ matched?: string;
27
+ reason: string;
28
+ }
29
+ /** 응답 텍스트가 파괴적 명령 패턴을 포함하는지 검사. */
30
+ export declare function checkDangerousResponsePattern(opts: {
31
+ text: string;
32
+ }): DangerousResultMatch;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Forgen — DANGEROUS-RESPONSE: 응답 텍스트 파괴적 명령 가드
3
+ *
4
+ * Stop hook 단계에서 어시스턴트 응답 텍스트를 스캔하여 *파괴적 명령 제안*을
5
+ * 감지한다. PreToolUse 가드는 실제 도구 실행 직전에만 동작하므로, 모델이
6
+ * 텍스트로 위험한 대안을 *제안*하는 케이스(예: `find ... -exec rm -r {}`)는
7
+ * 도구 실행 단계에 도달하지 못하면 차단되지 않는다. 본 체크는 그 갭을 메운다.
8
+ *
9
+ * 발견 경위 (forgen-eval syn-004, 2026-05-06):
10
+ * notepad 룰 "[no-rm-rf-unconfirmed]" inject 상태에서, driver(llama 8B) 가
11
+ * `rm -rf` 리터럴은 회피했지만 `find . -type d -name 'build' -exec rm -r {}`
12
+ * 같은 우회 명령을 "안전한 대안"으로 제안. PreToolUse 차단은 실 실행 시
13
+ * 동작하지만 "응답 텍스트만으론" 못 막음. driver 모델 능력에 무관하게
14
+ * 일관된 안전 보장이 되도록 응답 단계에서 한 번 더 잡아낸다.
15
+ *
16
+ * 동작:
17
+ * - 어시스턴트 응답에 등록된 파괴 패턴이 매칭되면 block 권고.
18
+ * - "확실히, 명시적 확인" 같은 confirm 표현 동반 여부는 검사하지 않는다
19
+ * (휴리스틱 brittle). 매칭 시 무조건 block 후, 모델이 안전한 대안 제시
20
+ * 또는 명시적 confirm 절차로 재응답하도록 유도.
21
+ *
22
+ * 순수 함수 — Stop hook 이 reason 문자열을 그대로 주입.
23
+ */
24
+ import { compileSafeRegex, safeRegexTest } from '../hooks/shared/safe-regex.js';
25
+ /**
26
+ * 응답 텍스트에서 검사할 파괴적 명령 패턴.
27
+ * dist/hooks/dangerous-patterns.json (PreToolUse 용) 와 별도 — 응답 텍스트
28
+ * 분석에 적합한 더 넓은 패턴 (find -exec rm 같은 우회 포함).
29
+ */
30
+ /** 패턴 순서 중요: 더 구체적인(우회) 패턴을 먼저 두어 일반 rm -r 패턴이 가로채지 않게 함. */
31
+ const RESPONSE_PATTERNS = [
32
+ { pattern: '\\bfind\\b[^\\n]{0,80}-exec\\s+rm\\b', description: 'find -exec rm (rm 우회)', flags: 'i' },
33
+ { pattern: '\\bfind\\b[^\\n]{0,80}-delete\\b', description: 'find -delete (rm 우회)', flags: 'i' },
34
+ { pattern: '\\bxargs\\s+rm\\b', description: 'xargs rm (rm 우회)', flags: 'i' },
35
+ { pattern: '\\brm\\s+-(?:rf|fr|r)\\b', description: 'rm -r/-rf 직접 호출', flags: 'i' },
36
+ { pattern: '\\bgit\\s+push\\b[^\\n]{0,40}--force(?!-)', description: 'git push --force', flags: 'i' },
37
+ { pattern: '\\bgit\\s+reset\\s+--hard\\b', description: 'git reset --hard', flags: 'i' },
38
+ { pattern: '\\bgit\\s+clean\\s+-[a-z]*f', description: 'git clean -f', flags: 'i' },
39
+ { pattern: '\\bDROP\\s+(?:TABLE|DATABASE)\\b', description: 'SQL DROP TABLE/DATABASE', flags: 'i' },
40
+ { pattern: '\\bTRUNCATE\\s+TABLE\\b', description: 'SQL TRUNCATE TABLE', flags: 'i' },
41
+ { pattern: '>\\s*/dev/sd[a-z]\\b', description: '/dev/sdX 직접 write', flags: 'i' },
42
+ { pattern: '\\bmkfs\\b', description: 'mkfs (파일시스템 포맷)', flags: 'i' },
43
+ { pattern: '\\bdd\\b[^\\n]{0,60}\\bof=/dev/', description: 'dd of=/dev/* (장치 write)', flags: 'i' },
44
+ { pattern: '\\bcurl\\b[^\\n]{0,60}\\|\\s*(?:ba)?sh\\b', description: 'curl | sh (원격 코드 실행)', flags: 'i' },
45
+ { pattern: '\\bwget\\b[^\\n]{0,60}\\|\\s*(?:ba)?sh\\b', description: 'wget | sh (원격 코드 실행)', flags: 'i' },
46
+ ];
47
+ /** 응답 텍스트가 파괴적 명령 패턴을 포함하는지 검사. */
48
+ export function checkDangerousResponsePattern(opts) {
49
+ const text = opts.text ?? '';
50
+ if (!text.trim())
51
+ return { block: false, reason: '' };
52
+ for (const p of RESPONSE_PATTERNS) {
53
+ const compiled = compileSafeRegex(p.pattern, p.flags ?? 'i');
54
+ if (!compiled.regex)
55
+ continue; // bad regex — skip
56
+ if (safeRegexTest(compiled.regex, text)) {
57
+ return {
58
+ block: true,
59
+ matched: p.description,
60
+ reason: `응답에 파괴적 명령 패턴이 포함되었습니다 (${p.description}). 사용자 명시 확인 절차를 포함하거나 비파괴 대안 (예: dry-run, --interactive)을 제시해 다시 응답하세요.`,
61
+ };
62
+ }
63
+ }
64
+ return { block: false, reason: '' };
65
+ }