@entelligentsia/forgecli 0.11.2 → 0.15.0

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 (89) hide show
  1. package/CHANGELOG.md +324 -0
  2. package/README.md +2 -1
  3. package/dist/CHANGELOG-forge-plugin.md +210 -0
  4. package/dist/bin/forge.js +20 -1
  5. package/dist/bin/forge.js.map +1 -1
  6. package/dist/extensions/forgecli/ask-user-tool.js +32 -20
  7. package/dist/extensions/forgecli/ask-user-tool.js.map +1 -1
  8. package/dist/extensions/forgecli/config-layer.d.ts +15 -0
  9. package/dist/extensions/forgecli/config-layer.js +4 -1
  10. package/dist/extensions/forgecli/config-layer.js.map +1 -1
  11. package/dist/extensions/forgecli/config-writer.js +4 -1
  12. package/dist/extensions/forgecli/config-writer.js.map +1 -1
  13. package/dist/extensions/forgecli/enhance.js +1 -1
  14. package/dist/extensions/forgecli/enhance.js.map +1 -1
  15. package/dist/extensions/forgecli/fix-bug.js +31 -1
  16. package/dist/extensions/forgecli/fix-bug.js.map +1 -1
  17. package/dist/extensions/forgecli/forge-cli-schema.json +19 -0
  18. package/dist/extensions/forgecli/forge-tools.js +80 -0
  19. package/dist/extensions/forgecli/forge-tools.js.map +1 -1
  20. package/dist/extensions/forgecli/forge-update-command.js +24 -18
  21. package/dist/extensions/forgecli/forge-update-command.js.map +1 -1
  22. package/dist/extensions/forgecli/friction-emit.d.ts +97 -0
  23. package/dist/extensions/forgecli/friction-emit.js +246 -0
  24. package/dist/extensions/forgecli/friction-emit.js.map +1 -0
  25. package/dist/extensions/forgecli/health-check.d.ts +10 -0
  26. package/dist/extensions/forgecli/health-check.js +160 -8
  27. package/dist/extensions/forgecli/health-check.js.map +1 -1
  28. package/dist/extensions/forgecli/hook-dispatcher.js +24 -2
  29. package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
  30. package/dist/extensions/forgecli/hooks/write-guard.js +5 -1
  31. package/dist/extensions/forgecli/hooks/write-guard.js.map +1 -1
  32. package/dist/extensions/forgecli/index.js +29 -5
  33. package/dist/extensions/forgecli/index.js.map +1 -1
  34. package/dist/extensions/forgecli/lib/store-error-remediation.d.ts +65 -0
  35. package/dist/extensions/forgecli/lib/store-error-remediation.js +298 -0
  36. package/dist/extensions/forgecli/lib/store-error-remediation.js.map +1 -0
  37. package/dist/extensions/forgecli/regenerate.d.ts +22 -0
  38. package/dist/extensions/forgecli/regenerate.js +133 -3
  39. package/dist/extensions/forgecli/regenerate.js.map +1 -1
  40. package/dist/extensions/forgecli/run-sprint.js +16 -1
  41. package/dist/extensions/forgecli/run-sprint.js.map +1 -1
  42. package/dist/extensions/forgecli/run-task.js +30 -8
  43. package/dist/extensions/forgecli/run-task.js.map +1 -1
  44. package/dist/extensions/forgecli/skill-curation-flag.d.ts +21 -0
  45. package/dist/extensions/forgecli/skill-curation-flag.js +71 -0
  46. package/dist/extensions/forgecli/skill-curation-flag.js.map +1 -0
  47. package/dist/extensions/forgecli/skill-curator-subagent.d.ts +101 -0
  48. package/dist/extensions/forgecli/skill-curator-subagent.js +342 -0
  49. package/dist/extensions/forgecli/skill-curator-subagent.js.map +1 -0
  50. package/dist/extensions/forgecli/skill-retriever.d.ts +84 -0
  51. package/dist/extensions/forgecli/skill-retriever.js +246 -0
  52. package/dist/extensions/forgecli/skill-retriever.js.map +1 -0
  53. package/dist/extensions/forgecli/skill-usage-tracker.d.ts +91 -0
  54. package/dist/extensions/forgecli/skill-usage-tracker.js +224 -0
  55. package/dist/extensions/forgecli/skill-usage-tracker.js.map +1 -0
  56. package/dist/extensions/forgecli/store-resolver.d.ts +18 -0
  57. package/dist/extensions/forgecli/store-resolver.js +44 -4
  58. package/dist/extensions/forgecli/store-resolver.js.map +1 -1
  59. package/dist/extensions/forgecli/store-validator.d.ts +3 -0
  60. package/dist/extensions/forgecli/store-validator.js +4 -2
  61. package/dist/extensions/forgecli/store-validator.js.map +1 -1
  62. package/dist/forge-payload/.base-pack/personas/supervisor.md +9 -0
  63. package/dist/forge-payload/.base-pack/workflows/enhance.md +344 -18
  64. package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
  65. package/dist/forge-payload/.schemas/event.schema.json +20 -2
  66. package/dist/forge-payload/.schemas/migrations.json +112 -0
  67. package/dist/forge-payload/.schemas/proposal.schema.json +40 -0
  68. package/dist/forge-payload/agents/store-query-validator.md +103 -0
  69. package/dist/forge-payload/agents/tomoshibi.md +185 -0
  70. package/dist/forge-payload/commands/regenerate.md +109 -20
  71. package/dist/forge-payload/hooks/check-update.js +378 -0
  72. package/dist/forge-payload/hooks/forge-permissions.js +158 -0
  73. package/dist/forge-payload/hooks/triage-error.js +71 -0
  74. package/dist/forge-payload/hooks/validate-write.js +236 -0
  75. package/dist/forge-payload/integrity.json +32 -0
  76. package/dist/forge-payload/meta/workflows/meta-enhance.md +344 -18
  77. package/dist/forge-payload/schemas/structure-manifest.json +511 -0
  78. package/dist/forge-payload/tools/build-persona-pack.cjs +120 -11
  79. package/dist/forge-payload/tools/compression-gate.cjs +192 -0
  80. package/dist/forge-payload/tools/delete-candidate-detector.cjs +114 -0
  81. package/dist/forge-payload/tools/judge-proposal.cjs +177 -0
  82. package/dist/forge-payload/tools/manage-versions.cjs +132 -4
  83. package/dist/forge-payload/tools/queue-drain.cjs +152 -0
  84. package/dist/forge-payload/tools/replay-scoring.cjs +117 -0
  85. package/node_modules/@mariozechner/clipboard/package.json +2 -1
  86. package/node_modules/@mariozechner/clipboard-linux-x64-musl/README.md +3 -0
  87. package/node_modules/@mariozechner/clipboard-linux-x64-musl/clipboard.linux-x64-musl.node +0 -0
  88. package/node_modules/@mariozechner/clipboard-linux-x64-musl/package.json +25 -0
  89. package/package.json +4 -2
package/CHANGELOG.md CHANGED
@@ -7,6 +7,330 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.15.0] — 2026-05-22
11
+
12
+ > **The SkillOS release.** Forge now treats its own skill library as a
13
+ > living, signal-driven asset. The model's actual frictions — what it
14
+ > reached for, what worked, what didn't — feed back into the project
15
+ > through a five-subkind taxonomy, an LLM-judge rubric, a compression
16
+ > gate, and a per-task curator queue drained as one batched review at
17
+ > sprint close. The result: your `.forge/skills/` tracks the codebase as
18
+ > it evolves, instead of calcifying into a directory of well-intentioned
19
+ > instructions nobody opens.
20
+ >
21
+ > This release also closes the packaging gaps that were silently
22
+ > shipping every Forge install: missing structural checks, missing
23
+ > SkillOS tools in the payload, a `/forge:update` that upgraded the
24
+ > CLI but skipped project migrations, a version banner that lied about
25
+ > the bundled plugin. The deterministic-check infrastructure
26
+ > (`check-structure.cjs`, `verify-integrity.cjs`) is finally live
27
+ > end-to-end and would now flag a missing skill file the moment it
28
+ > happens. The plugin payload is now coherent enough to drive its own
29
+ > auditing.
30
+
31
+ ### Added — SkillOS curation pipeline (FORGE-S24)
32
+
33
+ - **`skill-retriever.ts`** (T08, ac90659). New BM25 retriever using
34
+ `lunr.js` (pinned exact) over skill frontmatter + description fields.
35
+ Returns deterministic top-k for a known corpus and emits a
36
+ `skill_usage` event with `retrieved: true` and a populated
37
+ `retrieval_score` for every hit. The signal that tells Phase 2 which
38
+ skills the model actually saw.
39
+ - **`skill-usage-tracker.ts`** (T09, 394125c). Diffs tool-call trajectories
40
+ against retrieved skills; emits `skill_usage` events with `used: true`
41
+ and `tool_call_success_rate` populated. Three discriminator cases
42
+ (overlap → used, no-overlap → unused, reasoning-only → used) covered
43
+ by failing-first tests. Telemetry-actor split: the orchestrator owns
44
+ emission; subagents never call this module directly.
45
+ - **`skill-curator-subagent.ts`** (T10, ea29079). Per-task curator —
46
+ takes retrieval + usage signals, dispatches a `runForgeSubagent` with
47
+ the composed task body, parses proposals out, and writes them to the
48
+ append-only queue at
49
+ `.forge/enhancement-proposals/queue/<sprint>/<task>-<ts>.json`. One
50
+ curator run per task; Phase 2 collects them at sprint close.
51
+ - **`friction-emit.ts`** (T11, b4d3526). Auto-emits friction events with
52
+ five skill subkinds — `skill_unused` (retrieved but not used on success),
53
+ `skill_failed` (used on failure), `skill_missing` (not retrieved on
54
+ success with ≥2 tool errors), `skill_stale` (unused 3 consecutive
55
+ sprints), `skill_redundant` (>0.8 overlap pair). The diagnostic
56
+ vocabulary that lets a project tell you which of its skills are
57
+ carrying weight and which aren't.
58
+ - **`forgeCli.skillCuration.enabled` flag** (T12, 7904c4b). Gated rollout
59
+ — default OFF. Layered resolution: env override
60
+ `FORGE_CLI_SKILL_CURATION_ENABLED` → project
61
+ `<cwd>/.pi/forge-cli/config.json` → global
62
+ `~/.pi/forge-cli/config.json` → `false`. All four emission modules
63
+ no-op silently when the flag is off, so a flag-off install is
64
+ byte-identical to pre-FORGE-S24 behaviour. The pure classifier helpers
65
+ (`computeFrictionSignals`, `classifySkillUsage`, `buildSkillIndex`,
66
+ `retrieveTopK`, `composeCuratorTask`, `parseProposalsFromOutput`) are
67
+ **not** gated — they have no side effects and remain callable for
68
+ downstream consumers.
69
+
70
+ ### Added — bundled forge-plugin pipeline (paired with v0.46.1)
71
+
72
+ Forge-cli now bundles `forge-plugin@0.46.1` with the full FORGE-S24
73
+ machinery, regenerated into the installable base-pack:
74
+
75
+ - **`queue-drain.cjs`** (T07). Append-only queue under
76
+ `.forge/enhancement-proposals/queue/<sprint>/`; dedupe key is
77
+ `{op, target_path, sha256(diff_body)}`; drain at sprint close produces
78
+ a single batched review prompt instead of one prompt per task — the
79
+ SkillOS §3.2.1 grouped-reward pattern.
80
+ - **`compression-gate.cjs`** (T06). Rejects `update_skill` proposals that
81
+ grow a target skill by >20% unless backed by ≥3 supporting friction
82
+ events. Closes the unbounded-growth failure mode where every friction
83
+ pastes another paragraph of trajectory copy into a skill file. Strict
84
+ byte-wise (UTF-8) growth check; insert/delete pass through;
85
+ recurrence-supported growth is allowed.
86
+ - **`judge-proposal.cjs`** (T03). Sonnet-class LLM-judge rubric applied
87
+ to every surviving proposal; drops `<3/5`. Rejections persist to
88
+ `.forge/enhancement-proposals/queue/<sprint>/phase2-<ts>-rejections.json`
89
+ so every drop stays traceable.
90
+ - **`delete-candidate-detector.cjs`** (T05). Scans trailing 3 sprints
91
+ of `skill_usage` events. Any skill with zero retrieval AND zero
92
+ invocation across the window emits a `delete_skill` proposal at
93
+ Phase 2 entry. The only mechanism by which the skill repo shrinks.
94
+ - **`replay-scoring.cjs`** (T04). Cross-task recurrence — same
95
+ `{subkind, skillId}` across tasks t..N gets a `recurrence_count` boost
96
+ that the judge consumes as a specificity score. "This friction
97
+ happened three times" beats "this friction happened once."
98
+ - **Proposal schema with op classification** (T02). `proposal.schema.json`
99
+ enforces `op ∈ {insert_skill, update_skill, delete_skill}` with
100
+ required `target_path` and `diff_body`. Legacy inserts back-compat
101
+ via `proposal-normalize.cjs`. Strict `additionalProperties: false`.
102
+ - **`skill_usage` event variant** (T01). `event.schema.json` gained a
103
+ conditional `allOf` branch for `type: "skill_usage"` requiring
104
+ `{eventId, sprintId, taskId, skillId, retrieved, used,
105
+ tool_call_success_rate, retrieval_score}`. Strict.
106
+
107
+ ### Fixed — packaging gaps that broke deterministic checks
108
+
109
+ - **`/forge:update` skipped the migration step entirely** (fc40f4a,
110
+ closes forge-cli#32). The handler implemented the full 5-step guided
111
+ upgrade — detect install → fetch changelog → confirm → npm install →
112
+ apply migrations — but the registration in `index.ts` omitted the
113
+ migration-side options, so the handler returned early before reaching
114
+ the migration block. Projects upgrading the CLI never received
115
+ schema/workflow/template migrations. Fixed by wiring
116
+ `currentBundledForgeVersion` + `migrationProjectRoot` at registration.
117
+ - **`/forge:update` migration block unreachable when CLI was already at
118
+ latest** (31796d5, follow-up on #32). The "already at latest" branch
119
+ early-returned BEFORE the migration check at line 373. Decoupled the
120
+ two orthogonal gates — npm-upgrade and project-migration — so a
121
+ locally-built CLI ahead of npm's published version (or a fresh clone
122
+ with a stale drift cache) still fires the migration prompt when
123
+ `fromVersion !== toVersion`.
124
+ - **`structure-manifest.json` + `integrity.json` not bundled** (cd732d2).
125
+ Both files exist in plugin source (`forge/forge/schemas/`,
126
+ `forge/forge/integrity.json`) and the consuming tools
127
+ (`check-structure.cjs`, `verify-integrity.cjs`) shipped in
128
+ `TOOLS_TO_COPY`, but `build-payload.cjs` never copied the artifacts.
129
+ Result: `/forge:health`'s structural-check and integrity-verify
130
+ gates silently passed on every install ("× not found" → exit 0).
131
+ Bundle now ships `dist/forge-payload/schemas/structure-manifest.json`
132
+ (non-dot path matching what `check-structure.cjs` resolves) and
133
+ `dist/forge-payload/integrity.json` at bundle root. After install,
134
+ `verify-integrity.cjs` correctly reports `〇 Plugin integrity — all
135
+ 25 files unmodified`, and `check-structure.cjs` flags any missing
136
+ expected file in the user's `.forge/` tree.
137
+ - **`hooks/` and `agents/` not bundled** (625fbb1, continuation of
138
+ cd732d2). `integrity.json` tracks 25 files including 4 hooks
139
+ (`check-update.js`, `forge-permissions.js`, `triage-error.js`,
140
+ `validate-write.js`) and 2 agents (`tomoshibi.md`,
141
+ `store-query-validator.md`). None were copied. The TS ports
142
+ (`hook-dispatcher.ts`, `hooks/*.ts`) are the actual runtime path —
143
+ the `.js`/`.cjs` files are integrity ballast that satisfy plugin
144
+ tampering detection. Bundled to satisfy
145
+ `verify-integrity.cjs`. Hooks intentionally bundle only top-level
146
+ `*.js` (4 files); `lib/`, `__tests__/`, and `*.cjs` (post-init,
147
+ post-sprint) are excluded — none are integrity-tracked and the TS
148
+ ports own the runtime channel.
149
+ - **5 FORGE-S24 plugin tools missing from `TOOLS_TO_COPY`** (commit
150
+ in the build-payload cluster). `queue-drain.cjs`,
151
+ `compression-gate.cjs`, `judge-proposal.cjs`,
152
+ `delete-candidate-detector.cjs`, `replay-scoring.cjs` — the new Phase 2
153
+ workflow `require()`s them from `$FORGE_ROOT/tools/`. Without them
154
+ bundled, `/forge:enhance` would fail with
155
+ `Cannot find module './forge/tools/queue-drain.cjs'` (and four more).
156
+ Added to the allowlist with a comment block flagging the dependency.
157
+ - **Version banner reported stale `bundledVersion`** (ae520f8). Both
158
+ readers (`src/bin/forge.ts printVersion`, `src/extensions/forgecli/
159
+ index.ts readPkgVersions`) sourced the value from `package.json`
160
+ `forge.bundledVersion` — a hand-maintained mirror that nobody
161
+ updated when the plugin moved 0.45.0 → 0.46.0 → 0.46.1. Both readers
162
+ now prefer `dist/forge-payload/.claude-plugin/plugin.json` as the
163
+ single source of truth. After fix:
164
+ `@entelligentsia/forgecli@0.15.0 (forge-plugin@0.46.1, pi@…)` reads
165
+ the truth. The mirror in package.json is now informational only.
166
+
167
+ ### Changed
168
+
169
+ - **Bundled forge-plugin: v0.44.5 → v0.46.1.** Two minor bumps. v0.45.x
170
+ shipped the FORGE-S24 SKILL-CURATION machinery (event variant,
171
+ proposal schema, queue drain, compression gate, judge, recurrence,
172
+ delete-candidate); v0.46.0 was the sprint-completion marker; v0.46.1
173
+ is the build fix that regenerated `init/base-pack/workflows/
174
+ enhance.md` from the meta source so projects migrating to 0.46.x
175
+ actually receive the new Phase 2 algorithm.
176
+ - **`forge_verify_apply` MCP tool ships in the bundle** (b971596,
177
+ closes forge-cli#31). Detects hallucinated `Edit` calls — when an
178
+ assistant proposes an edit that doesn't match any file on disk, the
179
+ tool flags it at apply time instead of silently no-op'ing.
180
+
181
+ ### Documented
182
+
183
+ - **`hook-dispatcher.ts` scope clarification** (7d513ca). New header
184
+ comment block stating the write-guard is a soft fence for honest
185
+ mistakes, not a security boundary against adversarial bash escape.
186
+ See `doc/analysis/write-guard-scope-and-model-behavior.md` in the
187
+ forge-engineering repo for the testbench evidence (two pi-session
188
+ recordings of the same rogue-write prompt — one model surrendered on
189
+ the first block, another escalated three times until succeeding via
190
+ `echo > file`).
191
+
192
+ ### Migration notes
193
+
194
+ After upgrading via `npm i -g @entelligentsia/forgecli@0.15.0`:
195
+
196
+ 1. `4ge` from your project root.
197
+ 2. `/forge:update` will now actually apply project migrations (the
198
+ wire-up + decoupling fixes above). Accept the migration prompt.
199
+ 3. To enable SkillOS curation, set
200
+ `forgeCli.skillCuration.enabled: true` in
201
+ `<cwd>/.pi/forge-cli/config.json` (or
202
+ `FORGE_CLI_SKILL_CURATION_ENABLED=1` for one-shot use). Until you
203
+ enable it, the four emission modules no-op — the install is
204
+ byte-identical to pre-FORGE-S24 behaviour.
205
+ 4. `/forge:health` now reports through the bundled
206
+ `verify-integrity.cjs` and `check-structure.cjs` — expect cleaner
207
+ output than prior releases, and real signal if your `.forge/` tree
208
+ diverges from the manifest's expectations.
209
+
210
+ ## [0.14.0] — 2026-05-22
211
+
212
+ ### Added
213
+
214
+ - **`forgeCli.skillCuration.enabled` config flag — gated rollout for SKILL-CURATION pipeline (FORGE-S24-T12).** Adds `forgeCli.skillCuration.enabled` (boolean, **default `false`**) to `forge-cli-schema.json` under a new top-level `forgeCli` object (`additionalProperties: false` preserved). New module `src/extensions/forgecli/skill-curation-flag.ts` exporting `isSkillCurationEnabled(cwd?)` resolves the flag in priority order: env override `FORGE_CLI_SKILL_CURATION_ENABLED` (accepts `1`/`true`/`0`/`false` case-insensitive; unrecognised values fall through) → project config (`<cwd>/.pi/forge-cli/config.json`) → global config (`~/.pi/forge-cli/config.json`) → `false`. The four SKILL-CURATION modules from T08–T11 now check the flag at the top of their public emission/dispatch entry points and no-op when off — `emitSkillUsageEvents` (T08), `emitSkillUsageTrackingEvents` (T09), `emitFrictionEvents` (T11) return `{emitted: 0, failed: 0, stderrs: []}` and make **zero** `spawnSync` calls; `runSkillCurator` (T10) returns `{exitCode: 0, written: 0}` and dispatches **no** subagent, writes **no** queue file. The gate runs BEFORE TypeBox `Value.Parse`, so a flag-off run with malformed runtime input is also a silent no-op (matches the "default OFF ⇒ byte-identical to pre-FORGE-S24 behaviour" acceptance criterion). The orchestrator-side friction channel (non-skill subkinds emitted via `forge/tools/friction-emit.cjs`) is **not** gated by this flag — only the auto-emit module landed in T11 (the five skill subkinds: `skill_unused`, `skill_failed`, `skill_missing`, `skill_stale`, `skill_redundant`). Pure classifier helpers (`computeFrictionSignals`, `classifySkillUsage`, `buildSkillIndex`, `retrieveTopK`, `clampRetrievalScore`, `composeCuratorTask`, `parseProposalsFromOutput`) are **not** gated — they have no side effects and remain callable for downstream consumers that want to compute signals without emitting. Test-first per Iron Law 2: `skill-curation-flag.test.ts` (14 cases — `parseEnvFlag` accepts on/off variants and rejects unrecognised, `isSkillCurationEnabled` defaults to false with all layers empty (AC2), env override wins over both disk layers in both directions, project config wins over global, global used when project absent, malformed env falls through to disk; flag-off no-op guarantee for all four modules — zero `spawnSync` for emit modules, no `runForgeSubagent` dispatch for curator, no queue directory created (AC3); flag-on smoke for `emitSkillUsageEvents` — exactly one argv-array spawn per hit with `["emit", sprintId, json]` shape (IL6)). Pre-existing skill-pipeline tests (`skill-retriever.test.ts`, `skill-usage-tracker.test.ts`, `friction-emit.test.ts`, `skill-curator-subagent.test.ts`) now set `FORGE_CLI_SKILL_CURATION_ENABLED=1` in their `beforeEach` to exercise the flag-on path; the default-off contract is covered by the dedicated gate suite. Full forge-cli test surface unchanged in count: 1733 passed / 7 skipped (3 pre-existing failures in `test/bin/doctor.test.ts` are environmental and unrelated to T12). `npm run typecheck` green. **Operator action required** to enable: upgrade to ≥ 0.14.0 AND set `forgeCli.skillCuration.enabled: true` in `<cwd>/.pi/forge-cli/config.json` (or `FORGE_CLI_SKILL_CURATION_ENABLED=1` for one-shot use). Until both conditions hold, the pipeline is silent — `skill_usage` events are not emitted, friction events for the five skill subkinds are not emitted, and the per-task curator subagent is not dispatched. Pairs with plugin v0.46.0 (convergent sprint marker; no plugin source change in that bump). Closes FORGE-S24-T12 and the SKILL-CURATION sprint.
215
+
216
+ ### Changed
217
+
218
+ - **`config-layer.ts`** — `GlobalConfig` and `ProjectConfig` interfaces gain an optional `forgeCli?: ForgeCliFeatureFlags` field carrying the `skillCuration` sub-object. Schema-level addition (`additionalProperties: false` was already in force on the root); existing configs continue to validate unchanged.
219
+
220
+ ---
221
+
222
+ ## [0.13.4] — 2026-05-22
223
+
224
+ ### Added
225
+
226
+ - **`friction-emit.ts` — auto-emit friction events with 5 skill subkinds (FORGE-S24-T11).** New module under `src/extensions/forgecli/` that, at task close, classifies the orchestrator's per-task signals (retrieved skills with T08 retrieval verdicts + T09 usage verdicts, task success outcome, observed tool-error count, per-skill historical "consecutive unused sprints" counter, optional pairwise retrieval-overlap measurements) and emits one `friction` event per matching gate via `node <storeCli> emit <sprintId> <json>` (argv-array, Iron Law 6). Public surface: `computeFrictionSignals(inputs)` → pure `FrictionSignal[]` (no IO, no subprocess); `emitFrictionEvents(signals, runtime)` → `FrictionEmitResult` (`emitted`, `failed`, `stderrs`). Five gating rules per FORGE-S24-T11 acceptance criteria: `skill_unused` (retrieved && !used && task.success), `skill_failed` (retrieved && used && !task.success), `skill_missing` (!retrieved && task.success && tool_errors ≥ 2 — one signal per task, no skillId attribution), `skill_stale` (retrieved && !used across ≥ 3 consecutive sprints, per-skill history supplied by caller), `skill_redundant` (pairwise retrieval overlap strictly > 0.8, attributed to skillA, paired-with skillB recorded in evidence). Each emitted event carries the schema's required `{type: "friction", workflow, persona, issue}` trio plus `subkind`, `skillId` (when applicable), and an `evidence` blob (retrievalScore, toolErrorCount, consecutiveUnusedSprints, or `{pairedWith, overlap}` as appropriate per subkind). Telemetry-actor split (IL10) honoured — runtime attribution (`model`, `provider`, `startTimestamp`, `endTimestamp`, `durationMinutes`) is caller-supplied, never fabricated inside the module; subagents never invoke this emitter directly. Non-zero `store-cli emit` exits increment `failed` and capture stderr without throwing (IL7). Subkind enum matches the plugin event schema (forge v0.45.0+) — no plugin schema change required for this task. Wiring into `run-task.ts` / `fix-bug.ts` is gated on FORGE-S24-T12 feature flag.
227
+
228
+ ---
229
+
230
+ ## [0.13.3] — 2026-05-22
231
+
232
+ ### Added
233
+
234
+ - **`skill-curator-subagent.ts` — per-task curator dispatch (FORGE-S24-T10).** New module under `src/extensions/forgecli/` that, at task close, hands the task trajectory summary, the retrieved-skill bodies (from T08), and the task outcome verdict to a curator subagent, then writes the curator's enhancement proposals to the project-local enhancement-proposals queue at `.forge/enhancement-proposals/queue/<sprintId>/<taskId>-<ts>.json` (T07 layout — the sprint-close queue-drain consumes this path). Public surface: `runSkillCurator(input)` → typed `CuratorResult` (`exitCode`, `written`, `queueFile?`, `errorMessage?`); `parseProposalsFromOutput(text)` → `Proposal[]` (preferred: fenced ```json``` block, fallback: bare JSON array, items that fail TypeBox validation are dropped); `composeCuratorTask(input)` → first-user-message string carrying sprint/task header, trajectory summary, retrieved-skill bodies, task outcome, and the proposal schema. Dispatch goes through `runForgeSubagent({persona: defaultCuratorPersona(), task, cwd, exportTag})` — fresh in-process pi session, isolated context (Iron Law 10). The curator persona is **inline** (no new file under `forge/forge/meta/personas/` — Iron Law 1 forge-cli boundary respected, no plugin version bump, no security-scan trigger). Proposals are deduped by `{op, target_path, sha256(diff_body)}` (mirrors T07's `dedupeKey`) before write; the empty-array case writes NO file (zero-noise invariant); if the canonical path already exists, the curator retries with a `-N` suffix on the ts (append-only invariant — T07's queue-drain is read-only and never deletes). The subagent NEVER calls `store-cli emit`, writes to `forge/forge/meta/skills/`, or invokes write-mutating tools — these are forbidden in the persona system prompt and enforced by Slice 2 contract (orchestrator emits the canonical phase event AFTER `runSkillCurator` returns, using its own runtime telemetry). Failures (subagent `exitCode !== 0`, parse errors) surface as a typed result with `written: 0` and a non-empty `errorMessage` — no throw, no silent continuation (IL7). Wiring into `run-task.ts` / `fix-bug.ts` is gated on FORGE-S24-T12 feature flag.
235
+
236
+ ---
237
+
238
+ ## [0.13.2] — 2026-05-22
239
+
240
+ ### Added
241
+
242
+ - **`skill-usage-tracker.ts` — post-task usage classification + emission (FORGE-S24-T09).** New module under `src/extensions/forgecli/` that, after a task closes, diffs the observed tool-call trajectory against each retrieved skill's declared workflow steps and emits a follow-up `skill_usage` event per skill with `used: true | false` and a populated `tool_call_success_rate`. Public surface: `classifySkillUsage(skill, trajectory)` → pure `UsageVerdict` (`used`, `signal: "overlap" | "reasoning" | "none"`, `tool_call_success_rate ∈ [0,1]`); `emitSkillUsageTrackingEvents(retrieved, trajectory, runtime)` writes one event per retrieved skill via `node <storeCli> emit <sprintId> <json>` (argv-array, Iron Law 6). Heuristic per FORGE-S24-T09 acceptance criteria: skill name appears in agent reasoning → `used: true` (signal `reasoning`); ≥2 distinct workflow steps overlap with executed tool calls → `used: true` (signal `overlap`); otherwise → `used: false`. `tool_call_success_rate` is the fraction of declared steps observed in the trajectory (distinct overlaps / total steps), clamped to `[0,1]`. Step matching is case-insensitive substring against tool call names to tolerate qualified MCP-style names (e.g. `mcp__store__store-cli-query` matches workflow step `"store-cli query"`). Emitted events conform to the `skill_usage` schema variant landed in forge plugin v0.45.0 (FORGE-S24-T01) with `retrieval_score: 0` — the populated retrieval score was already emitted at retrieval time by T08; this is a tracking-time follow-up. Runs in **orchestrator context** (telemetry-actor split honoured — subagent never invokes the tracker; runtime attribution `model/provider/timestamps/durationMinutes` is caller-supplied, never fabricated). Module owns classification + emission only — assembling the retrieved-skill list, trajectory, and runtime attribution is the orchestrator handler's responsibility (wiring into `run-task.ts`/`fix-bug.ts` is gated on FORGE-S24-T12 feature flag).
243
+
244
+ ---
245
+
246
+ ## [0.13.1] — 2026-05-22
247
+
248
+ ### Added
249
+
250
+ - **`skill-retriever.ts` — BM25 skill retrieval (FORGE-S24-T08).** New module under `src/extensions/forgecli/` that ranks the project skill corpus by relevance to a task description using `lunr.js` Okapi-BM25 scoring. Public surface: `buildSkillIndex(corpus)` → opaque `SkillIndex` handle; `retrieveTopK(index, query, k)` → deterministic `RetrievalHit[]`; `emitSkillUsageEvents(hits, runtime)` writes one `skill_usage` event per hit via `node <storeCli> emit <sprintId> <json>` (argv-array, Iron Law 6). Indexed fields: skill `name` (boost 4), `description` (boost 2), and selected frontmatter (`name`, `tags`, `aliases`, boost 1). Raw BM25 scores are squashed via `s / (1 + s)` before being written to `retrieval_score` (schema constraint `[0,1]`). Emitted events conform to the `skill_usage` variant landed in forge plugin v0.45.0 (FORGE-S24-T01): required `eventId, sprintId, taskId, skillId, retrieved=true, used=false, tool_call_success_rate=0, retrieval_score`, plus the canonical attribution fields supplied by the orchestrator (`role, action, startTimestamp, endTimestamp, durationMinutes, model, provider`). Module owns ranking + emission only — corpus construction (parsing `SKILL.md` frontmatter) remains the caller's responsibility and is delegated to `loaders/persona-skill-loader.ts`. Behaviour is gated downstream by `forgeCli.skillCuration.enabled` (FORGE-S24-T12).
251
+
252
+ ### Dependencies
253
+
254
+ - **`lunr@2.3.9`** added as a runtime dependency, pinned exact (no `^`/`~`) per Iron Law 3 dependency discipline. Pure-JS, MIT-licensed, zero runtime dependencies — SBOM impact is a single direct entry. `@types/lunr@2.3.7` added as dev dependency.
255
+
256
+ ---
257
+
258
+ ## [0.13.0] — 2026-05-21
259
+
260
+ ### Added
261
+
262
+ - **`forge_verify_apply` MCP tool** detects hallucinated Edit calls. The agent is instructed to call it AFTER applying Phase 2 enhancement edits and BEFORE invoking `manage-versions add-snapshot`. The tool runs `generation-manifest.cjs check` on each claimed file path and returns structured buckets: `modified` (verified ✓), `unchanged` (Edit silently no-op'd — usually `old_string` mismatch), `untracked` (no manifest entry), `missing` (file gone). If `unchanged.length > 0`, the agent MUST re-apply via Edit/Write and re-verify before snapshotting. Closes [forge-cli#31](https://github.com/Entelligentsia/forge-cli/issues/31).
263
+
264
+ Background: hello testbench session 17:48 UTC showed the agent's UI claimed 5 file modifications; the snap-4 archive captured by add-snapshot afterwards contained pristine content for 4 of 5 files. The Edit tool had failed silently and the agent reported success regardless. The verification logic is now in forge-cli code (deterministic, testable) rather than relying on agent self-inspection.
265
+
266
+ ### Changed
267
+
268
+ - **`enhance.ts` Phase 2 kickoff** restructured: previous Step 5 (snapshot) split into Step 5 (VERIFY via `forge_verify_apply`) and Step 6 (snapshot after verify). Snapshot is now explicitly gated on verification completing with no `unchanged` files.
269
+
270
+ ---
271
+
272
+ ## [0.12.1] — 2026-05-21
273
+
274
+ ### Fixed
275
+
276
+ - **`enhance.ts` — explicit `add-snapshot` instruction in Phase 2 kickoff.** Previously the kickoff said nothing about snapshots; agents had to discover the requirement buried in `meta-enhance.md` step 8. In practice many agents applied edits but skipped the snapshot call, breaking the entire durability story — replay (v0.12.0) had nothing to restore from. Kickoff now explicitly instructs: after applying any approved edits, MUST call `node "$FORGE_ROOT/tools/manage-versions.cjs" add-snapshot --source post-sprint:<SPRINT_ID> --enhanced-elements <paths>`. Discovered via hello testbench transcript: `currentSnapshot` stayed at 3 after a Phase 2 apply session, leading to silent data loss on the next regenerate. Closes part 1 of [forge-cli#30](https://github.com/Entelligentsia/forge-cli/issues/30).
277
+ - **`regenerate.ts` — modification guard now fires on untracked files.** Previously only fired on `generation-manifest check` exit 1 (modified hash mismatch); exit 2 (untracked — no manifest entry) was ignored. After a prior regenerate ran `clear-namespace` without re-recording, files end up in untracked state — and the next regenerate silently overwrote them. Guard now surfaces both "modified" and "untracked" states with state-aware prompt text. `ModifiedFile` interface extended with a `state: "modified" | "untracked"` field. Closes part 2 of [forge-cli#30](https://github.com/Entelligentsia/forge-cli/issues/30).
278
+
279
+ ---
280
+
281
+ ## [0.12.0] — 2026-05-21
282
+
283
+ ### Added
284
+
285
+ - **`regenerate.ts` — snapshot replay (Approach A layer 3).** Mirrors the new plugin behaviour in forge v0.45.0. After `substitute-placeholders.cjs` writes fresh base-pack content over `.forge/{personas,skills,workflows,templates}/`, the handler now invokes `manage-versions replay --target <category>` for each of the four structural-element categories. Restores user-enhanced files captured by `/forge:enhance` Phase 2 snapshots from `.forge/archive/snap-N/`. Pure additive — no-op when no snapshots match. Skipped when `--force` is used (force == "I want pristine base-pack content, no overlay"). Closes [forge-cli#27](https://github.com/Entelligentsia/forge-cli/issues/27).
286
+
287
+ ### Changed
288
+
289
+ - **Modification guard text softened** to reflect the new replay behaviour. The pre-write prompt now states that snapshot-captured edits will be restored automatically post-regenerate, and only manual edits NOT captured in any snapshot are at risk. Guard remains as defence-in-depth.
290
+
291
+ - Bundled forge-plugin advanced to **0.45.0** (replay subcommand + regenerate replay step).
292
+
293
+ ---
294
+
295
+ ## [0.11.6] — 2026-05-21
296
+
297
+ ### Fixed
298
+
299
+ - **`enhance.ts` — Phase 2 kickoff no longer over-restricts friction discovery to `forge_store_query`.** The kickoff previously instructed agents to discover friction events "ONLY via the `forge_store_query` tool — do NOT raw-read `.forge/store/events/`". But the underlying NLP engine matches by keyword-on-title heuristics, not by `type === 'friction'` exactly — a testbench with 10 friction events on disk returned only 6 via NLP and zero in one agent's path, producing `frictionCount: 0` Phase 2 outputs despite events being present. Kickoff now: (a) keeps `forge_store_query` as **preferred** path, (b) explicitly instructs the agent to **fall back to the workflow body's filesystem walk** (`meta-enhance.md` Step 1's `e.type === 'friction'` filter) when MCP returns suspiciously low / zero, (c) zero-friction guard fires only when **both paths** are empty. Closes [forge-cli#29](https://github.com/Entelligentsia/forge-cli/issues/29).
300
+ - Bundled forge-plugin advanced to **0.44.10**, which includes the fix for `manage-versions add-snapshot` silently failing to archive files ([forge#108](https://github.com/Entelligentsia/forge/issues/108)) — layer 2 of the composition contract is now functional. Unblocks future Approach A work (forge#107 / forge-cli#27).
301
+
302
+ ---
303
+
304
+ ## [0.11.5] — 2026-05-21
305
+
306
+ ### Fixed
307
+
308
+ - **`index.ts` — `process.env.FORGE_ROOT` now exported on main-thread activation.** When forge-cli activates inside a Forge project, `process.env.FORGE_ROOT` is set to the absolute path resolved from `.forge/config.json`'s `paths.forgeRoot`, alongside the existing `before_agent_start` orientation injection. Subagent dispatch via `runForgeSubagent` already set this (forge-subagent.ts:200), but kickoff handlers (`/forge:enhance`, `/forge:health`, `/forge:calibrate`, etc.) bypass the subagent path — so `$FORGE_ROOT/...` in their workflow bodies' bash command literals previously substituted to an empty string, producing the symptom "Snapshot: not written (Forge manage-versions not available in this context)" during `/forge:enhance` Phase 2. Closes [forge-cli#28](https://github.com/Entelligentsia/forge-cli/issues/28). Phase 1 of the snapshot-replay durability story tracked in forge#107 / forge-cli#27.
309
+
310
+ ---
311
+
312
+ ## [0.11.4] — 2026-05-21
313
+
314
+ ### Fixed
315
+
316
+ - **`regenerate.ts` — pre-write modification guard for `/forge:regenerate`.** Companion to [forge#106](https://github.com/Entelligentsia/forge/issues/106) (FORGE-BUG-037) and the forge plugin v0.44.9 markdown fix. Before invoking `substitute-placeholders.cjs`, the handler now enumerates files in `.forge/{personas,skills,workflows,templates}/` and runs `generation-manifest.cjs check` on each. Any file whose hash no longer matches the recorded manifest — typically applied by `/forge:enhance` Phase 2 — triggers a `ctx.ui.confirm` prompt listing the modifications. On decline, regenerate is cancelled and the edits are preserved. New `--force` flag bypasses the prompt for CI / dogfood reset.
317
+
318
+ The plugin-level markdown fix (forge v0.44.9) covered the Claude Code plugin flow which executes the command markdown body. forge-cli has a self-contained TypeScript handler that bypasses the markdown body entirely; this commit closes that vector. See [forge-cli#26](https://github.com/Entelligentsia/forge-cli/issues/26).
319
+
320
+ - Bundled forge-plugin advanced to **0.44.9** (was 0.44.6 / 0.44.8 in 0.11.3 builds depending on rebuild timing).
321
+
322
+ ---
323
+
324
+ ## [0.11.3] — 2026-05-21
325
+
326
+ ### Fixed
327
+
328
+ - **`ask-user-tool.ts` — chip strip no longer steals arrow keys from `forge_ask_user` dialogs.** `ctx.ui.confirm`/`select`/`input` are pi's built-in overlay dialogs but `forge_ask_user` never called `pushOverlay`/`popOverlay` on the `ForgeInputRouter`. The thread-switcher's arrow-key handler (registered with `skipWhenOverlayActive`) was never suppressed because `overlayDepth` stayed at 0. All three dialog calls are now wrapped in `router.pushOverlay()` / `router.popOverlay()` (try/finally), matching the pattern in `config-command.ts`.
329
+
330
+ - **`run-task.ts` — chip strip no longer appears before task ID is resolved.** `registry.startSession(taskId)` was called with the raw user arg (e.g. `"T01"`) before `resolveToCanonicalId` had a chance to disambiguate ambiguous matches. The chip strip appeared showing `T01` before the user answered the `ctx.ui.select` choice dialog — and arrow keys were stolen by the strip. `startSession` is now deferred to after `resolveToCanonicalId` and all resume-detection `ctx.ui.confirm` prompts, right before pipeline delegation.
331
+
332
+ ---
333
+
10
334
  ## [0.11.2] — 2026-05-20
11
335
 
12
336
  ### Fixed
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![npm](https://img.shields.io/npm/v/@entelligentsia/forgecli?style=flat-square&color=000&label=npm)](https://www.npmjs.com/package/@entelligentsia/forgecli)
9
9
  [![node](https://img.shields.io/badge/node-%E2%89%A520-000?style=flat-square)](#)
10
10
  [![license](https://img.shields.io/badge/license-MIT-000?style=flat-square)](#)
11
- [![forge plugin](https://img.shields.io/badge/forge--plugin-v0.44.5-000?style=flat-square&labelColor=fafafa)](https://github.com/Entelligentsia/forge)
11
+ [![forge plugin](https://img.shields.io/badge/forge--plugin-v0.46.1-000?style=flat-square&labelColor=fafafa)](https://github.com/Entelligentsia/forge)
12
12
  [![pi runtime](https://img.shields.io/badge/runtime-pi--coding--agent-000?style=flat-square)](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
13
13
 
14
14
  </div>
@@ -22,6 +22,7 @@ forge-cli generates a project-specific engineering knowledge base, sprint workfl
22
22
 
23
23
  - **Structured SDLC, in any terminal.** Plan → implement → review → validate → commit chains, gated by your own personas and audience rules.
24
24
  - **Project memory that compounds.** Every sprint sharpens the knowledge base; the next one starts smarter.
25
+ - **Skills that stay relevant.** Forge ships a SkillOS-style curation loop (FORGE-S24). Five friction subkinds — `skill_unused`, `skill_failed`, `skill_missing`, `skill_stale`, `skill_redundant` — surface what the model actually reached for, what worked, and what didn't. Phase 2 enhancement classifies proposals as `insert_skill` / `update_skill` / `delete_skill`, scores them through an LLM-judge rubric, gates oversize edits behind a compression check, and drains a per-task curator queue into one batched review at sprint close. The result: your skill library tracks your codebase as it evolves, instead of calcifying.
25
26
  - **Bring your own model.** Anthropic, OpenAI, ollama, openrouter — anything pi resolves.
26
27
 
27
28
  ## Install
@@ -5,6 +5,216 @@ Format: newest first. Breaking changes are marked **△ Breaking**.
5
5
 
6
6
  ---
7
7
 
8
+ ## [0.46.1] — 2026-05-22
9
+
10
+ **Build fix: ship the FORGE-S24 SKILL-CURATION Phase 2 pipeline that 0.46.0 was supposed to.**
11
+
12
+ The 0.45.1–0.45.7 migration entries declared `workflows:enhance` in their `regenerate` lists, but `node tools/build-base-pack.cjs` was not re-run during the sprint. As a result the installable `init/base-pack/workflows/enhance.md` stayed at the pre-S24 algorithm, and projects migrating to 0.46.0 received a stale workflow that lacked queue-drain (step 1a), recurrence scoring, delete-candidate detection (step 5b), compression gate (step 5b.5), and the LLM-judge step (step 5c).
13
+
14
+ This patch regenerates the base-pack copy from `meta/workflows/meta-enhance.md` (280 → 600 lines, 20 S24 markers landed) and forces a re-copy on any project sitting at 0.46.0 by declaring `workflows:enhance` in this entry's regenerate list. No plugin source change beyond the regenerated workflow file. Additive, non-breaking.
15
+
16
+ Discovered during cartographer testbench smoke-test of `/forge:enhance` — Phase 2 ran the legacy algorithm (no op classification, no queue path, no judge rejections sidecar) because the workflow the migration copied into the project lacked the new steps.
17
+
18
+ **Regenerate:** `workflows:enhance`
19
+
20
+ > Manual: Run `/forge:update` to refresh `.forge/workflows/enhance.md` with the SKILL-CURATION Phase 2 pipeline.
21
+
22
+ ---
23
+
24
+ ## [0.46.0] — 2026-05-22
25
+
26
+ **FORGE-S24 SKILL-CURATION sprint completion — gated rollout marker (FORGE-S24-T12).**
27
+
28
+ No plugin source change in this bump. The seven preceding migration entries (0.45.1 → 0.45.7) carry the actual SKILL-CURATION machinery:
29
+
30
+ | Bump | Task | Adds |
31
+ |------|------|------|
32
+ | 0.45.0 → 0.45.1 | T01 | `skill_usage` event variant in `forge/schemas/event.schema.json` |
33
+ | 0.45.1 → 0.45.2 | T02 | Proposal op classification (`insert_skill`/`update_skill`/`delete_skill`) — `forge/schemas/proposal.schema.json` + `forge/tools/proposal-normalize.cjs` |
34
+ | 0.45.2 → 0.45.3 | T04 | Cross-task recurrence scoring — `forge/tools/replay-scoring.cjs` |
35
+ | 0.45.3 → 0.45.4 | T05 | Delete-candidate detection (3-sprint zero-use) — `forge/tools/delete-candidate-detector.cjs` |
36
+ | 0.45.4 → 0.45.5 | T03 | LLM-judge rubric (Sonnet, drop <3/5) — `forge/tools/judge-proposal.cjs` |
37
+ | 0.45.5 → 0.45.6 | T06 | Compression gate (reject >20% growth without 3+ frictions) — `forge/tools/compression-gate.cjs` |
38
+ | 0.45.6 → 0.45.7 | T07 | Queue drain at sprint close — `forge/tools/queue-drain.cjs` |
39
+
40
+ T12 lands the **gated-rollout contract on the forge-cli side** via the new `forgeCli.skillCuration.enabled` config flag (default OFF). The four forge-cli modules (T08 skill-retriever, T09 skill-usage-tracker, T10 skill-curator-subagent, T11 friction-emit) no-op at entry when the flag is off, so a flag-off run is byte-identical to pre-FORGE-S24 behaviour. This plugin-side minor bump (0.45.7 → 0.46.0) is the convergent terminal marker for the sprint: it signals to operators running `/forge:update` that the full SKILL-CURATION pipeline has shipped end-to-end, and pairs with the forge-cli 0.13.4 → 0.14.0 bump that lands the rollout flag.
41
+
42
+ **No regeneration required** (no manifest entry change, no workflow change, no schema change in this bump alone — those landed in 0.45.1–0.45.7).
43
+
44
+ **Operator action required to enable:** upgrade forge-cli to ≥ 0.14.0 **and** set `forgeCli.skillCuration.enabled: true` in `<cwd>/.pi/forge-cli/config.json` (or the env override `FORGE_CLI_SKILL_CURATION_ENABLED=1` for one-shot use). Until both conditions hold, the new event variants and friction subkinds remain emitter-silent — the plugin schema continues to accept them on receipt, so a delayed forge-cli upgrade is non-blocking.
45
+
46
+ Additive, non-breaking.
47
+
48
+ ---
49
+
50
+ ## [0.45.3] — 2026-05-22
51
+
52
+ Feature: **Cross-task replay scoring — recurrence boost** (FORGE-S24-T04 — SKILL-CURATION).
53
+
54
+ For each Phase 2 enrichment proposal synthesised from a friction event at
55
+ task `t`, the new `forge/tools/replay-scoring.cjs` helper scans friction
56
+ events in tasks `t+1..N` of the same sprint for matching
57
+ `(subkind, evidence.skillId)` pairs and stamps `recurrence_count` (>= 1,
58
+ includes the origin task) plus `recurrence_task_ids` (taskOrder-sorted)
59
+ onto the proposal. The downstream T03 judge can now boost specificity
60
+ when a friction signal recurred across multiple tasks instead of treating
61
+ every observation as a singleton.
62
+
63
+ `forge/meta/workflows/meta-enhance.md` Phase 2 gains step 5a invoking
64
+ `annotateProposals(proposals, frictionEvents, taskOrder)` between the
65
+ synthesis step (5) and the artifact write (6); step 5 now requires
66
+ `sourceFrictionIds` to carry every contributing `eventId` so the
67
+ recurrence scan can resolve provenance. `forge/schemas/proposal.schema.json`
68
+ gains two optional fields — `recurrence_count` (integer, minimum 1) and
69
+ `recurrence_task_ids` (array of strings) — both additive;
70
+ `additionalProperties: false` continues to reject unknown keys.
71
+
72
+ Forward-only scan: earlier tasks (before `fromTaskId` in `taskOrder`) are
73
+ excluded. Proposals without resolvable provenance receive a neutral
74
+ `recurrence_count: 1` and empty `recurrence_task_ids: []` — neutral
75
+ signal, not silent failure.
76
+
77
+ Test-first per Iron Law 2: `replay-scoring.test.cjs` (11 cases including
78
+ the AC3 three-task fixture, forward-only-direction guard,
79
+ subkind/skillId mismatch rejection, and the no-mutation invariant)
80
+ landed before the helper. Full suite 1364/1364 (was 1353; +11).
81
+
82
+ Additive, non-breaking. Users running an older version receive the
83
+ recurrence-augmented proposal schema on `/forge:update` and re-run
84
+ `/forge:enhance --phase 2` to take advantage of the new fields.
85
+
86
+ ---
87
+
88
+ ## [0.45.2] — 2026-05-22
89
+
90
+ Feature: **Phase 2 proposal op classification** (FORGE-S24-T02 — SKILL-CURATION).
91
+
92
+ Replaces the insert-biased Phase 2 enrichment-proposal vocabulary with an
93
+ explicit three-op classification. New `forge/schemas/proposal.schema.json`
94
+ enumerates `op` ∈ `{insert_skill, update_skill, delete_skill}` and requires
95
+ `target_path` + `diff_body` per record; `additionalProperties: false` rejects
96
+ unknown fields.
97
+
98
+ `meta/workflows/meta-enhance.md` Phase 2 (steps 5–6) now:
99
+
100
+ - Routes every friction event through the three-op classifier when synthesising proposals.
101
+ - Emits a machine-readable `phase2-<timestamp>.json` artifact alongside the existing markdown — each entry conforms to `proposal.schema.json`.
102
+ - References `proposal-normalize.cjs:normaliseProposal()` as the **explicit** read-side back-compat path for legacy (pre-0.45.2) proposals lacking `op`; missing `op` defaults to `insert_skill` — never silently coerced.
103
+
104
+ This is the foundation for the remaining S24 work: T03 (judge), T05
105
+ (delete-candidate detection), T06 (compression gate), T07 (queue drain).
106
+
107
+ Test-first per Iron Law 2: `proposal-schema.test.cjs` (6 cases — missing op,
108
+ unknown op, missing `target_path`, `additionalProperties:false`, and the legacy
109
+ normalisation contract) landed before the schema. Full suite 1353/1353.
110
+
111
+ Additive, non-breaking: existing proposals continue to validate via the
112
+ normaliser, and no existing schema or tool changes.
113
+
114
+ ---
115
+
116
+ ## [0.45.1] — 2026-05-22
117
+
118
+ Feature: **`skill_usage` event variant** (FORGE-S24-T01, plan 08 Phase B — SKILL-CURATION).
119
+
120
+ `event.schema.json` gains a new event type `skill_usage` with an `allOf`
121
+ branch requiring `{eventId, sprintId, taskId, skillId, retrieved, used,
122
+ tool_call_success_rate, retrieval_score}`. The numeric properties
123
+ `tool_call_success_rate` and `retrieval_score` carry declarative `[0, 1]`
124
+ bounds; `skillId` is `minLength: 1`. The root `additionalProperties: false`
125
+ gate continues to reject unknown fields on every variant.
126
+
127
+ This is the data foundation for Sprint S24 SKILL-CURATION:
128
+
129
+ - **T02** emits `skill_usage` records when a skill is retrieved into context or invoked.
130
+ - **T04** correlates the `retrieval_score` against tool-call outcomes.
131
+ - **T05** detects delete-candidate skills using rolling-window aggregates of `used` × `tool_call_success_rate`.
132
+ - **T08** replays historic events for retrospective scoring.
133
+
134
+ No `validate-store.cjs` code-path change — the existing `validateRecord()`
135
+ machinery handles the new `allOf` branch identically to the `friction`,
136
+ `sprint-complete`, and `sprint-halted` variants. Additive and non-breaking:
137
+ existing event records remain valid.
138
+
139
+ **Note on target version.** TASK_PROMPT requested `v0.45.0`, but that tag
140
+ already shipped (snapshot-replay, forge#107). Bumping to `0.45.1` is the
141
+ minimum forward step.
142
+
143
+ ---
144
+
145
+ ## [0.45.0] — 2026-05-21
146
+
147
+ Feature: **Approach A — snapshot replay** ([forge#107](https://github.com/Entelligentsia/forge/issues/107)). `manage-versions` gains a new `replay` subcommand that fulfills **layer 3** of the composition contract declared at `manage-versions.cjs:13` (`Working Artifact = base@pluginVersion + snapshot@currentSnapshot + user_enhancements`).
148
+
149
+ After `/forge:regenerate` writes fresh base-pack content over `.forge/{personas,skills,workflows,templates}/`, the replay step walks all snapshots in `.forge/structure-versions.json`, finds enhanced elements matching the target prefix, and restores them from the archive over the freshly-generated content. Later snapshots win on file collision; files not captured by any snapshot remain at the fresh base-pack version.
150
+
151
+ This closes the loop opened by the v0.44.9 modification guard (forge#106) and the v0.44.10 archive-path fix (forge#108): user enhancements applied by `/forge:enhance` Phase 2 now survive `/forge:regenerate` automatically. The pre-write modification guard remains as defence-in-depth for manual edits not captured in any snapshot.
152
+
153
+ **v1 semantics (overlay).** A user-enhanced file retains its captured content even when the base-pack version of that file has changed in a plugin update. Trade-off accepted for v1; future v2 may layer 3-way merge for richer plugin-update propagation.
154
+
155
+ New CLI:
156
+
157
+ ```sh
158
+ node "$FORGE_ROOT/tools/manage-versions.cjs" replay --target personas # restore all personas/* enhancements
159
+ node "$FORGE_ROOT/tools/manage-versions.cjs" replay --target personas/engineer.md # restore one file
160
+ node "$FORGE_ROOT/tools/manage-versions.cjs" replay --target skills [--dry-run]
161
+ ```
162
+
163
+ `regenerate.md` updated: each of personas, skills, workflows, templates fans out generation → calls `replay` → re-records manifest hashes so subsequent `check` operations reflect the restored content.
164
+
165
+ **Regenerate:** commands:regenerate, tools:manage-versions
166
+
167
+ > Closes [forge#107](https://github.com/Entelligentsia/forge/issues/107). Companion forge-cli mirror tracked in [forge-cli#27](https://github.com/Entelligentsia/forge-cli/issues/27).
168
+
169
+ ---
170
+
171
+ ## [0.44.10] — 2026-05-21
172
+
173
+ Fix `manage-versions add-snapshot` silently failing to archive files. The tool previously did `path.join(projectRoot, ".forge", relPath)` to locate source files. Workflow callers (`meta-enhance.md`, `base-pack/workflows/enhance.md`) have always passed `--enhanced-elements` with the full `.forge/` prefix (e.g. `.forge/personas/architect.md`), producing a double-prefixed source path `projectRoot/.forge/.forge/...` that never exists — `fs.existsSync` then silently skipped every file. Every archive directory created since basePackVersion 0.43.3 has been empty. Layer 2 of the composition contract declared at `manage-versions.cjs:13` (`Working Artifact = base + snapshot + user_enhancements`) was a no-op. Tool now strips a leading `.forge/` from each element path; both `.forge/-relative` and project-root-relative forms accepted. New test `forge#108 — tolerates leading ".forge/" prefix in --enhanced-elements`. Unblocks [forge#107](https://github.com/Entelligentsia/forge/issues/107) (Approach A — snapshot replay).
174
+
175
+ **Regenerate:** tools:manage-versions
176
+
177
+ > Closes [forge#108](https://github.com/Entelligentsia/forge/issues/108).
178
+
179
+ ---
180
+
181
+ ## [0.44.9] — 2026-05-21
182
+
183
+ Fix `/forge:regenerate personas` and `/forge:regenerate skills` silently overwriting manual modifications (typically applied by `/forge:enhance` Phase 2). Pre-write `generation-manifest check` + `△ ...has been manually modified. Overwriting will discard your changes. Proceed? (yes / no / show diff)` prompt added to both single-file and full-rebuild paths in `commands/regenerate.md`, mirroring the workflows and templates pattern. Closes the asymmetric modification-detection across the four structural-element categories. Markdown-only fix — no `.cjs` changes.
184
+
185
+ **Regenerate:** commands:regenerate
186
+
187
+ > Closes [forge#106](https://github.com/Entelligentsia/forge/issues/106) (FORGE-BUG-037).
188
+
189
+ ---
190
+
191
+ ## [0.44.8] — 2026-05-21
192
+
193
+ **#104 fix — /forge:enhance UX gaps.** Two workflow enhancements:
194
+
195
+ 1. **Phase-routing guidance** — the `--phase` flag table in both `commands/enhance.md` and the bundled `workflows/enhance.md` now includes a "when to run" one-liner for each phase: `--phase 1` (post-init placeholder fill), `--phase 2` (post-sprint friction scan), `--phase 3` (on-demand drift detection).
196
+
197
+ 2. **Zero-friction guard for Phase 2** — meta-enhance.md now guards Step 1: if the friction event list is empty, the workflow prints `No friction events queued for the active sprint — nothing to enhance.` and exits Phase 2 immediately (skips steps 3–10; emits the enhancement event with `frictionCount: 0`). Prevents silent no-op Phase 2 runs. Steps 2–9 in Phase 2 are renumbered 3–10 accordingly.
198
+
199
+ **Regenerate:** workflows:enhance
200
+
201
+ ---
202
+
203
+ ## [0.44.7] — 2026-05-21
204
+
205
+ **#105 fix — build-persona-pack.cjs schema mismatch.** Tool now accepts both:
206
+
207
+ - **Base-pack format** (no frontmatter, `init/base-pack/personas/`): derives `id`, `role`, `summary`, `responsibilities`, `outputs`, `file_ref` from content and filename. Previously threw "no frontmatter block found".
208
+ - **Meta format** (YAML frontmatter, `meta/personas/`): existing behavior preserved; `file_ref` must be in frontmatter.
209
+
210
+ Skills: `file_ref` is now derived from the file path when absent from frontmatter (base-pack skills). Previously threw "frontmatter missing required field 'file_ref'".
211
+
212
+ Guard: `buildPack()` throws when both dirs are empty, preventing silent empty-pack writes that caused `/forge:health` to report persona-pack permanently stale.
213
+
214
+ **Regenerate:** tools:build-persona-pack
215
+
216
+ ---
217
+
8
218
  ## [0.44.6] — 2026-05-20
9
219
 
10
220
  **P1 bug-fix batch (forge-cli#25 — defects C, D, E).** Three tool fixes to address defects discovered in the forge-cli 0.11.0 dogfooding session.
package/dist/bin/forge.js CHANGED
@@ -44,10 +44,29 @@ async function readPiVersion() {
44
44
  return "unknown";
45
45
  }
46
46
  }
47
+ function readBundledPluginVersion() {
48
+ // Authoritative source: dist/forge-payload/.claude-plugin/plugin.json,
49
+ // regenerated by scripts/build-payload.cjs from forge/forge/.claude-plugin/
50
+ // plugin.json on every build. The package.json forge.bundledVersion mirror
51
+ // (used as fallback below) is hand-maintained and tends to drift — until
52
+ // FORGE-S24 it sat at 0.45.0 across the entire 0.46.x line.
53
+ try {
54
+ const pluginJsonPath = path.resolve(__dirname, "..", "forge-payload", ".claude-plugin", "plugin.json");
55
+ const pluginPkg = JSON.parse(fs.readFileSync(pluginJsonPath, "utf8"));
56
+ if (typeof pluginPkg.version === "string" && pluginPkg.version.length > 0) {
57
+ return pluginPkg.version;
58
+ }
59
+ }
60
+ catch {
61
+ // Fall through to package.json mirror.
62
+ }
63
+ const pkg = readForgeCliPkg();
64
+ return pkg.forge?.bundledVersion ?? "unknown";
65
+ }
47
66
  async function printVersion() {
48
67
  const pkg = readForgeCliPkg();
49
68
  const forgeCliVersion = pkg.version ?? "unknown";
50
- const bundledVersion = pkg.forge?.bundledVersion ?? "unknown";
69
+ const bundledVersion = readBundledPluginVersion();
51
70
  const piVersion = await readPiVersion();
52
71
  process.stdout.write(`@entelligentsia/forgecli@${forgeCliVersion} (forge-plugin@${bundledVersion}, pi@${piVersion})\n`);
53
72
  }