mindforge-cc 11.9.3 → 11.9.4

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "11.9.3",
2
+ "version": "11.9.4",
3
3
  "environment": "development",
4
4
  "governance": {
5
5
  "drift_threshold": 0.75,
@@ -47,7 +47,14 @@ Typical GitHub Action setup:
47
47
  steps:
48
48
  - uses: actions/checkout@v4
49
49
  - name: Run MindForge headless
50
- run: npx mindforge headless --phase 3
50
+ # `npx --package=mindforge-cc` PINS the package. Bare `npx mindforge` resolves to an UNRELATED
51
+ # third-party package on the public registry (mindforge@1.0.21, maintainer william@mindforge.ai) —
52
+ # not this project, whose npm name is mindforge-cc. In a fresh runner there is no local bin to
53
+ # shadow it, so npx fetches and unpacks that package, runs any install scripts it has, and then
54
+ # fails with "could not determine executable to run" because it exposes no `mindforge` bin.
55
+ # Measured both forms: the pinned one exits 0 with mindforge-cc in the npx cache; the bare one
56
+ # exits 1 after downloading someone else's package into a job holding MINDFORGE_TOKEN.
57
+ run: npx --yes --package=mindforge-cc mindforge headless --phase 3
51
58
  env:
52
59
  MINDFORGE_TOKEN: ${{ secrets.MINDFORGE_TOKEN }}
53
60
  AUTO_PUSH_ON_WAVE_COMPLETE: true
package/CHANGELOG.md CHANGED
@@ -1,5 +1,159 @@
1
1
  # Changelog
2
2
 
3
+ ## [11.9.4] — 2026-08-22 — Delivery: the gates register, the tarball matches its tag, three packages attested
4
+
5
+ Patch release. 11.9.3 argued that an instrument must not report success while doing
6
+ nothing. 11.9.4 is what an adversarial audit of the **published** 11.9.3 artifact found
7
+ when that standard was applied to the delivery itself: **enforcement that installed and
8
+ then declined to register, a tarball that could not be reproduced from its own tag, and a
9
+ README that understated what shipped.**
10
+
11
+ Every finding here came from measuring the published package in a confined environment —
12
+ not from reading the repository. That distinction is the whole content of this release.
13
+
14
+ ### BREAKING
15
+
16
+ - **The installer now registers hooks on projects where it previously declined.** If you
17
+ install into a project that has an ancestor directory containing a `.claude`, MindForge
18
+ now writes `.claude/settings.json` (merging append-only into any existing file) instead
19
+ of skipping. On a machine that has ever run Claude Code, `~/.claude` makes that
20
+ essentially every project — so most installs go from **0 registered hooks to 8**. Three
21
+ of them can block a tool call. If you were relying on the installer being inert here,
22
+ it no longer is; `.mindforge/hook-registration.json` records exactly what was written,
23
+ and the previous settings file is backed up under `.mindforge/backups/`. (#224)
24
+
25
+ ### Fixed
26
+
27
+ **Hook registration — the gates shipped installed but inert**
28
+
29
+ - **`register()` skipped whenever any ancestor held a `.claude` directory.** Measured
30
+ against the published 11.9.3 tarball, confined HOME, `~/.claude` as the only ancestor:
31
+ **11 hook scripts installed, 0 registered, no settings file written.** The gates that
32
+ the shipped `CLAUDE.md` calls MANDATORY were copied in and left unreachable. Same
33
+ sandbox on 11.9.4: **8 registered, installer preflight executed 7 of 8 and verified all
34
+ 3 deny-class hooks returning exit 2** before keeping the file. (#224)
35
+
36
+ The reason it printed — *"the harness will read `<ancestor>/.claude/settings.json`, not
37
+ this directory"* — was wrong three separate ways:
38
+
39
+ 1. `~/.claude/settings.json` is the **user tier**, applied in addition to the project
40
+ tier. Its existence carries no information about whether a project file is read, so
41
+ the condition that suppressed the gates was satisfied by an ordinary laptop.
42
+ 2. For a genuine project ancestor the claim is false too — that file is not read
43
+ either. Verified with a natural experiment: an ancestor two levels up carried a
44
+ `PreToolUse` Bash hook appending a marker to a log; across a dozen tool calls with
45
+ the inner directory as the project root, the log was never created. Skipping did not
46
+ deliver the gates elsewhere. It delivered them nowhere.
47
+ 3. The git-boundary guard was **dead code**. `stop` was the git toplevel while the walk
48
+ began at `dirname(projectRoot)`, so when toplevel equalled projectRoot the stop
49
+ condition could never be true and the walk ran to the filesystem root every time.
50
+ The boundary meant to keep the check local is why it reached `$HOME`.
51
+
52
+ It now warns and registers anyway: a registration that turns out inert costs nothing and
53
+ becomes live when the harness is launched there, whereas a skip is guaranteed inert. The
54
+ check additionally requires a real `settings.json` **file** — the old one accepted any
55
+ directory named `.claude`, and the one it hit in practice held only markdown. (#224)
56
+
57
+ - **The installer's only failure-path pointer led nowhere.** It told anyone whose hooks
58
+ were not registered to "see `docs/troubleshooting.md`", where the word *hook* appeared
59
+ **0** times. That file now carries the section the message names, separating "not
60
+ registered" from "registered but not live", and including a copy-paste payload that
61
+ drives a hook directly so a broken hook can be told apart from an unwired one. (#224)
62
+
63
+ **Release artifacts**
64
+
65
+ - **The published tarball could not be reproduced from its tag.**
66
+ `.mindforge/memory/sync-manifest.json` — gitignored, written at runtime by
67
+ `bin/memory/semantic-hub.js` — was **1 of 1979** shipped files not tracked at
68
+ `v11.9.3`, so provenance attested to a tree containing a file the repository does not
69
+ contain. With a `files[]` allowlist, a **directory** entry ships its contents regardless
70
+ of `.gitignore`; `files[]` already carried a negation for `pattern-library.jsonl` for
71
+ exactly this reason, and the manifest's entire content is the sync record *for that
72
+ already-excluded file*. (#225)
73
+
74
+ - **`mindforge-sdk` is published again, with provenance.** It sat at **11.8.0** on npm
75
+ while `sync-version.js` kept `sdk/package.json` at canonical — seven releases of
76
+ disagreement (11.8.1 through 11.9.3, none published) that nothing detected, because
77
+ `version:check` verifies the tracked file and not what the registry serves. It was also
78
+ the only one of the three packages with **no attestation**. The release workflow now
79
+ publishes it with `--provenance`, after the two proven publishes and before the GitHub
80
+ Release, so the newest step cannot cost the others their artifacts.
81
+
82
+ - **The Homebrew formula carries the real 11.9.3 digest.** Verified against an independent
83
+ measurement rather than the tool's own output, and explicitly confirmed not to be the
84
+ SHA-256 of npm's 21-byte `{"error":"Not found"}` body — the constant #203 used to write
85
+ for every unpublished version. (#223)
86
+
87
+ **Honesty about what is enforced**
88
+
89
+ - **The README understated the product.** "What is actually enforced" still declared that
90
+ **no install channel registers hooks**, with a `No / No` table, and stated that the
91
+ plugin channel's dispatcher crashes on every fire. Measured: the plugin's
92
+ `scripts/lib/` exists, all **14** path tokens in `plugins/mindforge/hooks/hooks.json`
93
+ resolve under the plugin root, and driving the dispatcher by hand returns **exit 2** for
94
+ `mindforge-block-no-verify` and `mindforge-config-protection`. A document that
95
+ under-claims a security capability is the same defect as one that over-claims it —
96
+ either way it describes a system that is not the one shipped. Now stated per channel,
97
+ with the four cases that remain deliberately unenforced and the three liveness
98
+ preconditions outside MindForge's control. (#225)
99
+
100
+ **Defects the published-artifact audit found (#222)**
101
+
102
+ - The **v11.9.3 release page shipped empty**: `changelog-fetcher.js --latest` exits 0
103
+ while writing zero bytes, so the `||` fallback never fired and a 195-line changelog
104
+ reached nobody. The step now prefers the in-tree changelog and fails on an empty body.
105
+ - A **shipped CI snippet told users to `npx` a package we do not own**.
106
+ `npx mindforge …` resolves to an unrelated third-party package; in a fresh runner it is
107
+ fetched and unpacked, install scripts and all, inside a job holding `MINDFORGE_TOKEN`.
108
+ Now pinned with `--package=mindforge-cc`.
109
+ - The **version-source gate missed a live defect twice**. `mindforge health` printed
110
+ `RELEASE v0.4.2` and `Current : v11.9.3` twenty-six lines apart in a project declaring
111
+ 0.4.2, and produced `Unexpected end of JSON input` with no manifest at all — the verb
112
+ whose job is verifying installation integrity misreporting the installation. Widening
113
+ the gate surfaced two more of the same shape, one of which bound the **wrong** version
114
+ silently into approval records, which is worse than no binding because the check still
115
+ returns a verdict.
116
+ - `mindforge approve` died with ENOENT on any non-Node project. Absent values are now
117
+ recorded as `null` rather than invented.
118
+
119
+ **Test infrastructure**
120
+
121
+ - `tests/production.test.js` carried the uncommitted diff into its clone with
122
+ `git diff HEAD` (which includes staged files) but committed it with `commit -a` (which
123
+ stages only modified and deleted **tracked** files). So any commit **adding** a file
124
+ under `bin/` or `tests/` left the clone dirty and failed the gate's own cleanliness
125
+ assertion — reintroducing precisely the `--no-verify` pressure that carry exists to
126
+ avoid, for the one case it did not cover.
127
+
128
+ ### Added
129
+
130
+ New regression gates, each falsified by reinstating the exact defect and confirming RED,
131
+ with every touched file restored byte-exact afterwards:
132
+
133
+ - `tests/hook-registration-ancestor.test.js` — six properties, the first of which asserts
134
+ the ancestor detector fires at all, so the other five cannot pass by never triggering.
135
+ - `every shipped file is tracked in git` — property-based, no name list. It cannot fail on
136
+ a clean clone, which is how the real leak survived CI, so it is paired with a second
137
+ test that plants the runtime state in a throwaway clone and then packs.
138
+ - `all three published packages publish with provenance` — counts the `npm publish`
139
+ invocations rather than matching one, because a single-match regex was satisfied by
140
+ either of the two packages that already had an attestation while the SDK had none. Also
141
+ asserts the SDK step is idempotent and ordered after the proven publishes.
142
+
143
+ ### Notes for operators
144
+
145
+ - **These fixes change installer behaviour on almost every project.** After upgrading,
146
+ check `.mindforge/hook-registration.json` to see what was registered, and restart the
147
+ harness — hooks are snapshotted at session start.
148
+ - A registered hook is only *live* if the harness has been restarted, the project is
149
+ trusted in the harness, and `CLAUDE_PROJECT_DIR` is set with `node` on the hook PATH.
150
+ None of those three are in MindForge's control; the last is a deliberate trade against a
151
+ fail-closed shell tail that was measured denying benign commands on a fresh clone.
152
+ - Two gaps remain that require repository settings rather than code, and are recorded
153
+ rather than claimed fixed: there is no `v*` **tag ruleset** restricting who may create
154
+ the ref that triggers publishing, and `NPM_TOKEN` is a long-lived repository secret with
155
+ no GitHub environment in front of it.
156
+
3
157
  ## [11.9.3] — 2026-08-21 — Honesty: gates that can fail, commands that run, a release path that is checked
4
158
 
5
159
  Patch release. No new features. Twenty-one fixes, and they all turned out to be the
@@ -12,6 +166,14 @@ Contains behaviour changes under a patch bump — several of the things being fi
12
166
  bugs that a consumer could have been relying on. Read BREAKING before upgrading if you
13
167
  script against the CLI or the installer.
14
168
 
169
+ > **Corrected after release.** Three measured numbers in this entry were wrong and are fixed above:
170
+ > the count of places `--status`/`--stop` were documented (removed rather than re-guessed — it reads 4,
171
+ > 9 or 15 depending on how you count, which is the argument against stating it); the eslint total, which
172
+ > was 199 on the author's machine and **190** on a clean clone, because 9 problems came from an
173
+ > untracked local directory; and "four releases behind", which is **three** (11.8.3 → 11.9.0 → 11.9.1 →
174
+ > 11.9.2). Found by an adversarial audit of this changelog against the published artifact. A release
175
+ > arguing that measured numbers should be reproducible has to hold its own notes to that standard.
176
+
15
177
  ### BREAKING
16
178
 
17
179
  Each of these is a bug fix whose correct behaviour differs from the shipped behaviour.
@@ -113,15 +275,16 @@ Each of these is a bug fix whose correct behaviour differs from the shipped beha
113
275
  exactly one event — a `v*` tag push — and the repository's only ruleset targets
114
276
  branches, so its six required checks applied to nothing on the path that ships. GitHub
115
277
  cannot attach required status checks to a tag. A `preflight` job now gates it. (#216)
116
- - The `stable` npm dist-tag was moved by hand, or not at all — it sat four releases
117
- behind `latest` (11.8.3 against 11.9.2), so `npm i mindforge-cc@stable` delivered a
278
+ - The `stable` npm dist-tag was moved by hand, or not at all — it sat three releases
279
+ behind `latest` (11.8.3 against 11.9.2, via 11.9.0 and 11.9.1), so `npm i mindforge-cc@stable` delivered a
118
280
  build with none of the 11.9.x fixes. The release workflow now moves it as its final
119
281
  step: forward-only, prereleases skipped, and verified against npm's uncached dist-tags
120
282
  endpoint rather than the CDN-cached packument. (#216)
121
283
 
122
284
  **Dashboard**
123
285
 
124
- - `--status` and `--stop` were documented in nine places and implemented in none; both
286
+ - `--status` and `--stop` were documented across the harness roots and the docs and implemented
287
+ nowhere; both
125
288
  printed nothing and exited 0. Now implemented, before `express` is required, so they
126
289
  work without the dependency installed. (#206)
127
290
  - `--stop` identified the target by the SHAPE of its command line, which matched any
@@ -141,8 +304,8 @@ Each of these is a bug fix whose correct behaviour differs from the shipped beha
141
304
  **Verification**
142
305
 
143
306
  - `mindforge verify`'s lint stage used `--max-warnings=0`, which made it impossible to
144
- pass in the repository it ships from: `npx eslint .` reports 199 problems / 0 errors /
145
- 199 warnings, so `verify` reported a lint FAILURE on a tree that is green by the
307
+ pass in the repository it ships from: on a clean clone `npx eslint .` reports 190 problems /
308
+ 0 errors / 190 warnings, so `verify` reported a lint FAILURE on a tree that is green by the
146
309
  project's own contract. Aligned with the project's definition; errors still fail. (#204)
147
310
  - `temporal cleanup` printed "🧹 Cleaning up old temporal snapshots..." and
148
311
  "✅ Cleanup complete." with no cleanup between them. Now wired to
package/MINDFORGE.md CHANGED
@@ -1,9 +1,9 @@
1
- # MINDFORGE.md — Parameter Registry (v11.9.3)
1
+ # MINDFORGE.md — Parameter Registry (v11.9.4)
2
2
 
3
3
  ## 1. IDENTITY & VERSIONING
4
4
 
5
5
  [NAME] = MindForge
6
- [VERSION] = 11.9.3
6
+ [VERSION] = 11.9.4
7
7
  [STABLE] = true
8
8
  [MODE] = "Platform Sovereign"
9
9
  [REQUIRED_CORE_VERSION] = 11.9.1
package/README.md CHANGED
@@ -6,11 +6,14 @@
6
6
 
7
7
  ## Latest release
8
8
 
9
- **v11.9.2** (2026-08-16) — Correctness: the config gate can now fail, the audit chain no longer
10
- forks under concurrent writers, trace retrieval works, and the cost ledger has one record shape.
11
- **Contains a breaking change to the dashboard HTTP surface** (loopback-only) see the BREAKING
12
- section in [CHANGELOG.md](./CHANGELOG.md), or [RELEASENOTES.md](./RELEASENOTES.md) for
13
- human-readable notes.
9
+ **v11.9.3** (2026-08-21) — Honesty: gates that can fail, commands that run, a release path that is
10
+ checked. Twenty-one fixes sharing one defect an instrument reporting success while doing nothing:
11
+ a self-install that printed "skipping" and overwrote 149 tracked files, 11 of 27 routed CLI verbs
12
+ dying on `MODULE_NOT_FOUND` in a real install, `--fetch-sha` hashing npm's 404 body into the Homebrew
13
+ formula, no version channel covering any document a user receives, and a publish path no check ever
14
+ touched. **Contains behaviour changes under a patch bump** — several fixed bugs whose correct
15
+ behaviour differs from what shipped. See the BREAKING section in
16
+ [CHANGELOG.md](./CHANGELOG.md), or [RELEASENOTES.md](./RELEASENOTES.md) for human-readable notes.
14
17
 
15
18
  ---
16
19
 
@@ -19,7 +22,8 @@ human-readable notes.
19
22
  Read this before the install instructions. MindForge ships a large corpus of agent
20
23
  instructions — commands, skills, personas, protocols — and those are advisory: they work by
21
24
  being in the model's context, and a model can decline them. The parts that would *block* an
22
- action are hooks, and **no install channel currently registers them.**
25
+ action are hooks. Through 11.9.2 **no channel registered them**; as of 11.9.3 both channels
26
+ register and execute them **on Claude Code**, and nowhere else.
23
27
 
24
28
  | Capability | Plugin channel | `npx` channel |
25
29
  |---|---|---|
@@ -27,34 +31,48 @@ action are hooks, and **no install channel currently registers them.**
27
31
  | Skills / personas / protocol docs | Yes | Yes |
28
32
  | Subagents | Yes | Yes |
29
33
  | Audit hash-chain (`bin/verify-audit.js`) | Yes | Yes |
30
- | **Hooks enforced (can block a tool call)** | **No** | **No** |
31
-
32
- Why, specifically:
33
-
34
- - **No hook configuration ships, and nothing generates one.** `package.json` `files[]` has 48
35
- entries and none contains `settings`, so neither `.claude/settings.json` nor
36
- `.agent/settings.json` is published. All references to those paths in `bin/` are reads or
37
- metadata strings there is no code that writes or merges one. `bin/harness-audit.js:335`
38
- even offers "wire trust-gate + block-no-verify into both …" as a *fix suggestion*, auditing a
39
- wiring nothing creates.
40
- - **The plugin channel's hooks additionally crash when fired.**
41
- `plugins/mindforge/scripts/run-with-flags.js:24` requires `./lib/hook-flags`, and
42
- `plugins/mindforge/scripts/lib/` does not exist in the published plugin. Running the
43
- dispatcher gives `Error: Cannot find module './lib/hook-flags'` and exit 1. The module it
44
- needs does exist at `.agent/hooks/lib/hook-flags.js`; it was never copied in.
45
-
46
- So treat MindForge as **governance-by-convention plus a tamper-evident audit log**, not as a
47
- policy enforcement point. Installing it also expands your repository's trust boundary by a large
48
- volume of agent instructions review what you install. Making hook registration real per
49
- harness is the headline goal of v12; the audit chain is genuinely verifiable today
34
+ | **Hooks enforced (can block a tool call)** | **Claude Code only** | **Claude Code + `--local` only** |
35
+
36
+ What that means, measured rather than asserted:
37
+
38
+ - **The `npx` channel generates the config it never used to ship.** `files[]` has 49 entries and
39
+ none of them contains `settings`, so no settings file is *published* — instead
40
+ `bin/installer/hook-registration.js` writes one at install time, merging append-only into any
41
+ file you already have. Measured on a confined install: **8 hooks registered** into
42
+ `.claude/settings.json`, of which the installer's own preflight **executed 7 and verified all 3
43
+ deny-class hooks returning exit 2** before keeping the file. A preflight failure rolls the
44
+ registration back rather than leaving a config whose commands do not run.
45
+ - **The plugin channel's dispatcher runs.** It previously crashed on every fire —
46
+ `run-with-flags.js` requires `./lib/hook-flags` and `plugins/mindforge/scripts/lib/` was not
47
+ copied in. That directory now exists, all **14 path tokens** in
48
+ `plugins/mindforge/hooks/hooks.json` resolve under the plugin root, and driving the dispatcher by
49
+ hand returns **exit 2** for `mindforge-block-no-verify` and `mindforge-config-protection`.
50
+
51
+ Still **not** enforced, deliberately and with a printed reason for each: any runtime other than
52
+ Claude Code (Cursor, Copilot, Gemini/Antigravity, OpenCode), `--global` scope, a self-install
53
+ inside a MindForge checkout, and Windows. Writing a Claude-schema config into `.cursor/` without an
54
+ execution-verified hook contract would be decorative. Every outcome, including "not registered", is
55
+ printed by the installer and written to `.mindforge/hook-registration.json`.
56
+
57
+ Three things gate whether a registered hook is *live*, none of them in MindForge's control: the
58
+ harness must be **restarted** (hooks are snapshotted at session start), the project must be
59
+ **trusted** in the harness, and `CLAUDE_PROJECT_DIR` must be set with `node` on the hook PATH —
60
+ if it is not, the commands exit 1 and the gate is simply absent, which is a deliberate trade
61
+ against a fail-closed tail that was measured denying benign commands on a fresh clone. See
62
+ *Hooks are installed but nothing is blocked* in `docs/troubleshooting.md`.
63
+
64
+ So: on Claude Code, treat MindForge as a policy enforcement point for the 8 registered hooks plus
65
+ a tamper-evident audit log; on every other harness, as **governance-by-convention** plus that same
66
+ audit log. Installing it also expands your repository's trust boundary by a large volume of agent
67
+ instructions — review what you install. The audit chain is verifiable today
50
68
  (`node bin/verify-audit.js`).
51
69
 
52
70
  ---
53
71
 
54
72
  ## Install
55
73
 
56
- Claude Code plugin marketplace (no project files written). **Note:** the plugin's hooks do not
57
- fire — see *What is actually enforced* above. Slash commands, skills and subagents do work.
74
+ Claude Code plugin marketplace (no project files written). The plugin's hooks now fire — see
75
+ *What is actually enforced* above for what that does and does not cover.
58
76
 
59
77
  ```bash
60
78
  /plugin marketplace add sairam0424/MindForge
package/RELEASENOTES.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # Release Notes
2
2
 
3
+ ## v11.9.3 — 2026-08-21 — Honesty: gates that can fail, commands that run, a release path that is checked
4
+
5
+ ### What's New
6
+ Nothing. Twenty-one fixes sharing a single defect: **an instrument reported success while doing
7
+ nothing.** Gates that could not fail, tests a comment satisfied, docs naming scripts that do not
8
+ exist, commands printing success while performing no action, and a publish path no check ever
9
+ touched.
10
+
11
+ ### ⚠️ Breaking (under a patch bump)
12
+ Six behaviour changes, each a bug fix whose correct behaviour differs from what shipped. Read these
13
+ if you script against the CLI or the installer.
14
+
15
+ - **Routed CLI commands now act on YOUR project, not MindForge's checkout.** The router passed
16
+ `cwd: ROOT`, so `mindforge classify` diffed MindForge's repository instead of yours and
17
+ `mindforge health` inspected MindForge's `node_modules`. Of 27 routed commands, 7 changed
18
+ behaviour — all in the correct direction.
19
+ - **`npx mindforge-cc install`, and any other positional argument, now exits 1.** The installer
20
+ takes flags only and used to ignore stray words silently, so that command appeared to work while
21
+ configuring nothing. Use `npx mindforge-cc --claude --local`.
22
+ - **`mindforge verify` now SKIPS unavailable stages instead of failing them.** A project with no
23
+ ESLint config or no test script was reported as FAILING those stages rather than as not having
24
+ them. CI that relied on a non-zero exit there will now pass. A run where every stage skipped
25
+ prints a "NOTHING WAS VERIFIED" banner rather than a clean bill of health.
26
+ - **`scripts/sync-version.js` now exits non-zero when the plugin build artifacts are stale.** A bump
27
+ used to report success and exit 0 while leaving `npm test` red.
28
+ - **A self-install no longer writes over your tracked files.** Running the installer inside a
29
+ MindForge checkout printed that it was skipping and then overwrote 149 tracked files, including
30
+ `CLAUDE.md`, `.claude/**`, `.agent/**` and `.mindforge/**`.
31
+ - **Releases must be tagged on a commit reachable from `main`.**
32
+
33
+ ### Fixes
34
+ - **11 of 27 routed CLI verbs died on `MODULE_NOT_FOUND` in a real install.** The router shipped;
35
+ six of the scripts it dispatches to did not.
36
+ - **Every `--global` install reported failure on a correct run.** Verification demanded six `bin/**`
37
+ paths regardless of scope, so a global install — which writes 389 files to `$HOME/.claude` and,
38
+ deliberately, zero to `bin/` — ended `6 of 12 required file(s) missing` and exit 1, with a
39
+ `--force` retry that could not help.
40
+ - **`--fetch-sha` hashed npm's 404 error body into the Homebrew formula.** For an unpublished
41
+ version the registry answers `{"error":"Not found"}` and `curl -sL` exits 0, so the digest written
42
+ was the SHA-256 of that error text — the same constant for every unpublished version — and
43
+ `--check` then passed. It now fails closed on both status and gzip magic bytes.
44
+ - **No version channel covered a document a user receives.** `SECURITY.md` — the security policy at
45
+ the root of the published package — said "Current version: 11.9.0", and five docs titled
46
+ themselves v11.9.0: three releases stale while every npm manifest was correct. A channel that does
47
+ not exist cannot drift, so `--check` was green the whole time.
48
+ - **The tag push that publishes was exempt from every gate.** Publishing is triggered by exactly one
49
+ event — a `v*` tag push — and the repository's only ruleset targets branches, so its six required
50
+ checks applied to nothing on the path that ships. GitHub cannot attach required status checks to a
51
+ tag, so this had to be fixed in the workflow itself.
52
+ - **The `stable` dist-tag sat three releases behind `latest`** (11.8.3 against 11.9.2), so
53
+ `npm i mindforge-cc@stable` delivered a build with none of the 11.9.x fixes. The release workflow
54
+ now moves it as its final step — forward-only, prereleases skipped.
55
+ - **`mindforge dashboard --status` and `--stop` were documented and implemented nowhere.** Both
56
+ printed nothing useful. `--stop` also identified its target by the SHAPE of a command line, which
57
+ matched any `node <anything>/dashboard/server.js` — verified against an unrelated app's dashboard.
58
+ - **`temporal cleanup` printed "Cleaning up…" and "Cleanup complete." with no cleanup between them.**
59
+ - **The protocol files instructed the agent to run scripts that do not exist** (`soul-engine.js`,
60
+ `shard-controller.js`), and 15 phantom `/mindforge:` slash commands appeared in shipped docs. A
61
+ reader following the user guide typed `/mindforge:personas --list` and got nothing.
62
+ - **Every abandoned exit left a full copy of the database on disk** — 1.8 GB of orphaned `.tmp` files.
63
+
64
+ Full detail, including the measured evidence for each: [changelogs/v11.9.3.md](./changelogs/v11.9.3.md).
65
+
3
66
  ## v11.9.2 — 2026-08-16 — Correctness: config gate, audit chain, retrieval, cost ledger
4
67
 
5
68
  ### What's New
package/SECURITY.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Security Policy
2
2
 
3
- > **Current version:** 11.9.3 | **npm audit:** 0 vulnerabilities across root, sdk, mcp-server
3
+ > **Current version:** 11.9.4 | **npm audit:** 0 vulnerabilities across root, sdk, mcp-server
4
4
 
5
5
  ## Supported Versions
6
6
 
@@ -210,7 +210,12 @@ function getApprovals() {
210
210
  const { verifyRecord } = require('../governance/approval-record');
211
211
  let currentVersion = null;
212
212
  try {
213
- currentVersion = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8')).version;
213
+ // MindForge's version, resolved by package NAME. `path.join(__dirname, '..', '..',
214
+ // 'package.json')` reached <project>/package.json in an install — the CONSUMER's manifest — so
215
+ // verifyRecord() below compared an approval record against the host app's version (0.4.2, say)
216
+ // instead of MindForge's. The try/catch stopped it crashing, which is why it went unnoticed:
217
+ // a wrong binding is quieter than a missing one.
218
+ currentVersion = require('../utils/mindforge-version').resolveMindforgeVersion(process.cwd()).version;
214
219
  } catch { /* version binding is skipped if the manifest is unreadable */ }
215
220
 
216
221
  const now = Date.now();
@@ -80,7 +80,18 @@ function approve(opts = {}) {
80
80
  const root = opts.root || ROOT;
81
81
  const approvalsDir = opts.approvalsDir || APPROVALS_DIR;
82
82
 
83
- const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
83
+ // TOLERATES A PROJECT WITH NO package.json. This was an unguarded readFileSync, so `mindforge approve`
84
+ // died with `ENOENT: no such file or directory, open '<project>/package.json'` on any repo that is not
85
+ // Node — Python, Go, Rust, or any polyglot root. Measured on the published 11.9.3 tarball.
86
+ //
87
+ // The FIELDS stay the consumer's on purpose: this record documents a governance decision in the
88
+ // user's project, so `project` and `version` describing that project is correct. Only the crash was
89
+ // wrong. Absent values are recorded as null rather than invented — an approval record that guesses
90
+ // what it is approving is worse than one that admits it does not know.
91
+ const pkg = (() => {
92
+ try { return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); }
93
+ catch { return { name: path.basename(root) || null, version: null }; }
94
+ })();
84
95
 
85
96
  const id = `MF-AUTH-${Date.now().toString(36).toUpperCase()}`;
86
97
  const timestamp = new Date().toISOString();
@@ -72,7 +72,19 @@ function summary(lines) {
72
72
 
73
73
  function main(argv = process.argv.slice(2)) {
74
74
  const args = parseArgs(argv);
75
- const currentVersion = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version;
75
+ // MindForge's version, by package NAME. This read `path.join(ROOT, 'package.json')`, which in an
76
+ // install is the CONSUMER's manifest — so every approval record was verified against the host
77
+ // app's version rather than MindForge's, and the command died with ENOENT on any project without
78
+ // a package.json at all. Version binding is the point of the record; binding it to the wrong
79
+ // version is worse than not binding it, because the check still reports a verdict.
80
+ //
81
+ // Degrades to null rather than throwing: an unresolvable version should make the binding
82
+ // unverifiable, not make the whole verification unrunnable. verifyRecord treats a null
83
+ // currentVersion as "do not check the binding".
84
+ let currentVersion = null;
85
+ try {
86
+ currentVersion = require('../utils/mindforge-version').resolveMindforgeVersion(process.cwd()).version;
87
+ } catch { /* binding unverifiable, reported as such rather than crashing the run */ }
76
88
 
77
89
  let files = [];
78
90
  try {
@@ -60,6 +60,7 @@
60
60
  */
61
61
 
62
62
  const fs = require('node:fs');
63
+ const os = require('node:os');
63
64
  const path = require('node:path');
64
65
  const { spawnSync } = require('node:child_process');
65
66
 
@@ -366,14 +367,48 @@ function probeInner(projectRoot, failures) {
366
367
 
367
368
  // ── register ─────────────────────────────────────────────────────────────────
368
369
 
369
- /** Would the harness read a DIFFERENT .claude/settings.json than the one we are about to write? */
370
- function ancestorClaudeDir(projectRoot) {
371
- const top = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd: projectRoot, encoding: 'utf8' });
372
- const stop = top.status === 0 ? path.resolve(top.stdout.trim()) : path.parse(projectRoot).root;
370
+ /**
371
+ * An ancestor project whose OWN .claude/settings.json a harness launched there would read instead of
372
+ * ours. Advisory only see the three corrections below. Returns the directory, or null.
373
+ *
374
+ * THIS USED TO SKIP REGISTRATION ENTIRELY, and the reason it printed was wrong in three ways:
375
+ *
376
+ * "the harness will read <ancestor>/.claude/settings.json, not this directory"
377
+ *
378
+ * 1. IT TRIPPED ON $HOME, so it tripped for essentially every project. ~/.claude/settings.json is the
379
+ * USER TIER: Claude Code applies it to every session IN ADDITION TO the project tier, not instead
380
+ * of it. Its existence says nothing about whether a project file is read. Measured on the author's
381
+ * machine: the user-tier hooks fire on every Bash call while a project settings.json sits below
382
+ * them. So the condition that suppressed the gates was satisfied by the normal state of a laptop.
383
+ *
384
+ * 2. FOR A REAL PROJECT ANCESTOR THE CLAIM IS ALSO FALSE — the ancestor's file is not read either.
385
+ * Measured with a natural experiment: an ancestor two levels up carried a PreToolUse Bash hook
386
+ * appending a marker to a log file. Across a dozen Bash calls with the inner directory as the
387
+ * project root, that log was never even created. Claude Code reads the project tier from the
388
+ * directory it treats as the project root; it does not walk up for settings. So skipping did not
389
+ * deliver the gates "over there", it delivered them nowhere.
390
+ *
391
+ * 3. THE GIT-BOUNDARY GUARD WAS DEAD CODE. `stop` was the git toplevel, but the walk started at
392
+ * path.dirname(projectRoot) — so when toplevel === projectRoot (the normal case) `dir === stop`
393
+ * could never be true and the walk ran to the filesystem root every time. The boundary that was
394
+ * supposed to keep this local is why it reached $HOME.
395
+ *
396
+ * So: WARN, NEVER SKIP. A registration that turns out to be inert costs nothing and becomes live the
397
+ * moment the harness is launched here; a skip is guaranteed inert. Two narrowings keep the warning
398
+ * meaningful rather than universal: $HOME is excluded because it is a tier and not a shadow, and an
399
+ * actual settings.json FILE must exist — the old check accepted any directory named .claude, of which
400
+ * a docs folder is a perfectly ordinary example.
401
+ *
402
+ * The git boundary is deliberately not repaired, just removed: a git toplevel ABOVE projectRoot means
403
+ * this project is nested inside another repo, which is exactly the case worth warning about, so
404
+ * stopping the walk there would suppress the one signal this function exists to produce.
405
+ */
406
+ function shadowingProjectSettings(projectRoot) {
407
+ const home = os.homedir();
373
408
  let dir = path.dirname(path.resolve(projectRoot));
374
409
  for (let i = 0; i < 64; i++) {
375
- if (fs.existsSync(path.join(dir, '.claude'))) return dir;
376
- if (dir === stop || dir === path.dirname(dir)) break;
410
+ if (dir !== home && fs.existsSync(path.join(dir, SETTINGS_REL))) return dir;
411
+ if (dir === path.dirname(dir)) break;
377
412
  dir = path.dirname(dir);
378
413
  }
379
414
  return null;
@@ -391,10 +426,15 @@ function register(options = {}) {
391
426
  if (selfInstall) return skip('self-install: the repo maintains its own tracked .claude/settings.json');
392
427
  if (process.platform === 'win32') return skip('win32 is unverified for the emitted command shape');
393
428
 
394
- const ancestor = ancestorClaudeDir(projectRoot);
395
- if (ancestor) {
396
- return skip(`the harness will read ${path.join(ancestor, '.claude', 'settings.json')}, not this directory — re-run the installer there`);
397
- }
429
+ // Advisory, deliberately not a skip — see shadowingProjectSettings for the three measurements that
430
+ // demoted it from one. The operator is told what to do about it; the gates still get installed.
431
+ const shadow = shadowingProjectSettings(projectRoot);
432
+ const warnings = shadow
433
+ ? [`${path.join(shadow, SETTINGS_REL)} exists in an ancestor project. These hooks are registered `
434
+ + 'for THIS directory and are live when the harness runs with it as the project root. If you '
435
+ + `launch the harness in ${shadow} instead, run the installer there too — its project settings `
436
+ + 'are read from where it starts, not walked up from.']
437
+ : [];
398
438
 
399
439
  const copy = applyCopyManifest(projectRoot, repoRoot);
400
440
  if (!copy.ok) return skip(copy.reason);
@@ -424,7 +464,7 @@ function register(options = {}) {
424
464
 
425
465
  const text = `${JSON.stringify(merged.next, null, 2)}\n`;
426
466
  if (dryRun) {
427
- return { status: 'dry-run', reason: `would write ${HOOK_SPEC.length} hooks to ${SETTINGS_REL}`, registered: false, preview: text };
467
+ return { status: 'dry-run', reason: `would write ${HOOK_SPEC.length} hooks to ${SETTINGS_REL}`, registered: false, preview: text, warnings };
428
468
  }
429
469
 
430
470
  const existedBefore = fs.existsSync(absSettings);
@@ -452,6 +492,7 @@ function register(options = {}) {
452
492
  preflight_skipped: verified.skipped,
453
493
  preflight_skip_reason: 'registered but not executed during preflight: spawns a background '
454
494
  + 'process and writes under $HOME. Advisory, so a failure there would not have blocked.',
495
+ warnings,
455
496
  residual_risk: 'CLAUDE_PROJECT_DIR unset, or node off the hook PATH, yields exit 1 and the gate is '
456
497
  + 'absent (identical to not installing). No fail-closed shell tail is used: measured, it denies '
457
498
  + 'benign commands on a fresh clone.',
@@ -466,6 +507,7 @@ function register(options = {}) {
466
507
  target: SETTINGS_REL,
467
508
  backup: receipt.backup,
468
509
  receipt: RECEIPT_REL,
510
+ warnings,
469
511
  };
470
512
  }
471
513
 
@@ -500,5 +542,6 @@ module.exports = {
500
542
  DENY_CLASS, OWNED_RE, KNOWN_IDS,
501
543
  commandFor, profilesFor, isOwned,
502
544
  applyCopyManifest, mergeSettings, assertNoLoss, probe,
545
+ shadowingProjectSettings,
503
546
  register, unregister,
504
547
  };
@@ -1152,7 +1152,15 @@ async function install(runtime, scope, options = {}) {
1152
1152
  } else {
1153
1153
  Theme.printStatus(c.yellow(`Hooks NOT registered (${hookRegistration.status}): ${hookRegistration.reason}`), 'warn');
1154
1154
  Theme.printStatus(c.dim('The hook scripts are installed but nothing invokes them, so no tool call '
1155
- + 'is gated. This is stated rather than implied — see docs/troubleshooting.md.'), 'info');
1155
+ + 'is gated. This is stated rather than implied — see "Hooks are installed but nothing is '
1156
+ + 'blocked" in docs/troubleshooting.md.'), 'info');
1157
+ }
1158
+
1159
+ // Non-fatal advisories. Printed for BOTH outcomes, because the case that produces one today —
1160
+ // an ancestor project with its own settings.json — used to CANCEL registration outright, and the
1161
+ // whole point of demoting it to a warning is that the operator hears it without losing the gates.
1162
+ for (const w of hookRegistration.warnings || []) {
1163
+ Theme.printStatus(c.yellow(w), 'warn');
1156
1164
  }
1157
1165
  }
1158
1166
 
@@ -1297,11 +1305,31 @@ async function run(args) {
1297
1305
  const options = { dryRun, force, verbose, withUtils, minimal };
1298
1306
 
1299
1307
  // Get package.json for version
1300
- const pJSON = JSON.parse(fsu.read(path.join(SOURCE_ROOT, 'package.json')));
1308
+ // MINDFORGE'S VERSION, NOT THE HOST PROJECT'S — and it must not crash when there is no manifest.
1309
+ //
1310
+ // This read was `JSON.parse(fsu.read(path.join(SOURCE_ROOT, 'package.json')))`, and SOURCE_ROOT is
1311
+ // path.resolve(__dirname, '..'). In an install that lands at <project>/bin/, so it read the CONSUMER's
1312
+ // package.json. Measured on the published 11.9.3 tarball: in a project declaring version 0.4.2,
1313
+ // `mindforge health` printed the banner `RELEASE v0.4.2` and then, 26 lines later, `Current : v11.9.3`
1314
+ // — one command, two contradictory versions, exit 0. In a project with NO package.json the whole
1315
+ // command was `Unexpected end of JSON input`, exit 1, no report at all: fsu.read returns empty for a
1316
+ // missing file and JSON.parse throws.
1317
+ //
1318
+ // This is the same defect 11.9.3 fixed for `--version`, in the one verb whose advertised job is
1319
+ // "verify project health and installation integrity". bin/utils/mindforge-version.js already resolves
1320
+ // correctly — by package NAME, which is what distinguishes our manifest from a consumer's — and it
1321
+ // shipped in the same release. It simply was not used here.
1322
+ //
1323
+ // It THROWS rather than guessing, which is right for a version check and wrong for a banner, so the
1324
+ // banner degrades to 'unknown' instead of taking the whole health report down with it.
1325
+ let bannerVersion = 'unknown';
1326
+ try {
1327
+ bannerVersion = require('./utils/mindforge-version').resolveMindforgeVersion(process.cwd()).version;
1328
+ } catch { /* a banner must never be the reason health cannot run */ }
1301
1329
 
1302
1330
  // Print header and brand manifest
1303
1331
  // Print header and brand manifest
1304
- Theme.printHeader(pJSON.version);
1332
+ Theme.printHeader(bannerVersion);
1305
1333
  Theme.printBrandManifest();
1306
1334
  // Check for updates only
1307
1335
  if (isCheck) {
@@ -12,6 +12,14 @@ Contains behaviour changes under a patch bump — several of the things being fi
12
12
  bugs that a consumer could have been relying on. Read BREAKING before upgrading if you
13
13
  script against the CLI or the installer.
14
14
 
15
+ > **Corrected after release.** Three measured numbers in this entry were wrong and are fixed above:
16
+ > the count of places `--status`/`--stop` were documented (removed rather than re-guessed — it reads 4,
17
+ > 9 or 15 depending on how you count, which is the argument against stating it); the eslint total, which
18
+ > was 199 on the author's machine and **190** on a clean clone, because 9 problems came from an
19
+ > untracked local directory; and "four releases behind", which is **three** (11.8.3 → 11.9.0 → 11.9.1 →
20
+ > 11.9.2). Found by an adversarial audit of this changelog against the published artifact. A release
21
+ > arguing that measured numbers should be reproducible has to hold its own notes to that standard.
22
+
15
23
  ### BREAKING
16
24
 
17
25
  Each of these is a bug fix whose correct behaviour differs from the shipped behaviour.
@@ -113,15 +121,16 @@ Each of these is a bug fix whose correct behaviour differs from the shipped beha
113
121
  exactly one event — a `v*` tag push — and the repository's only ruleset targets
114
122
  branches, so its six required checks applied to nothing on the path that ships. GitHub
115
123
  cannot attach required status checks to a tag. A `preflight` job now gates it. (#216)
116
- - The `stable` npm dist-tag was moved by hand, or not at all — it sat four releases
117
- behind `latest` (11.8.3 against 11.9.2), so `npm i mindforge-cc@stable` delivered a
124
+ - The `stable` npm dist-tag was moved by hand, or not at all — it sat three releases
125
+ behind `latest` (11.8.3 against 11.9.2, via 11.9.0 and 11.9.1), so `npm i mindforge-cc@stable` delivered a
118
126
  build with none of the 11.9.x fixes. The release workflow now moves it as its final
119
127
  step: forward-only, prereleases skipped, and verified against npm's uncached dist-tags
120
128
  endpoint rather than the CDN-cached packument. (#216)
121
129
 
122
130
  **Dashboard**
123
131
 
124
- - `--status` and `--stop` were documented in nine places and implemented in none; both
132
+ - `--status` and `--stop` were documented across the harness roots and the docs and implemented
133
+ nowhere; both
125
134
  printed nothing and exited 0. Now implemented, before `express` is required, so they
126
135
  work without the dependency installed. (#206)
127
136
  - `--stop` identified the target by the SHAPE of its command line, which matched any
@@ -141,8 +150,8 @@ Each of these is a bug fix whose correct behaviour differs from the shipped beha
141
150
  **Verification**
142
151
 
143
152
  - `mindforge verify`'s lint stage used `--max-warnings=0`, which made it impossible to
144
- pass in the repository it ships from: `npx eslint .` reports 199 problems / 0 errors /
145
- 199 warnings, so `verify` reported a lint FAILURE on a tree that is green by the
153
+ pass in the repository it ships from: on a clean clone `npx eslint .` reports 190 problems /
154
+ 0 errors / 190 warnings, so `verify` reported a lint FAILURE on a tree that is green by the
146
155
  project's own contract. Aligned with the project's definition; errors still fail. (#204)
147
156
  - `temporal cleanup` printed "🧹 Cleaning up old temporal snapshots..." and
148
157
  "✅ Cleanup complete." with no cleanup between them. Now wired to
@@ -0,0 +1,155 @@
1
+ # Changelog
2
+
3
+ ## [11.9.4] — 2026-08-22 — Delivery: the gates register, the tarball matches its tag, three packages attested
4
+
5
+ Patch release. 11.9.3 argued that an instrument must not report success while doing
6
+ nothing. 11.9.4 is what an adversarial audit of the **published** 11.9.3 artifact found
7
+ when that standard was applied to the delivery itself: **enforcement that installed and
8
+ then declined to register, a tarball that could not be reproduced from its own tag, and a
9
+ README that understated what shipped.**
10
+
11
+ Every finding here came from measuring the published package in a confined environment —
12
+ not from reading the repository. That distinction is the whole content of this release.
13
+
14
+ ### BREAKING
15
+
16
+ - **The installer now registers hooks on projects where it previously declined.** If you
17
+ install into a project that has an ancestor directory containing a `.claude`, MindForge
18
+ now writes `.claude/settings.json` (merging append-only into any existing file) instead
19
+ of skipping. On a machine that has ever run Claude Code, `~/.claude` makes that
20
+ essentially every project — so most installs go from **0 registered hooks to 8**. Three
21
+ of them can block a tool call. If you were relying on the installer being inert here,
22
+ it no longer is; `.mindforge/hook-registration.json` records exactly what was written,
23
+ and the previous settings file is backed up under `.mindforge/backups/`. (#224)
24
+
25
+ ### Fixed
26
+
27
+ **Hook registration — the gates shipped installed but inert**
28
+
29
+ - **`register()` skipped whenever any ancestor held a `.claude` directory.** Measured
30
+ against the published 11.9.3 tarball, confined HOME, `~/.claude` as the only ancestor:
31
+ **11 hook scripts installed, 0 registered, no settings file written.** The gates that
32
+ the shipped `CLAUDE.md` calls MANDATORY were copied in and left unreachable. Same
33
+ sandbox on 11.9.4: **8 registered, installer preflight executed 7 of 8 and verified all
34
+ 3 deny-class hooks returning exit 2** before keeping the file. (#224)
35
+
36
+ The reason it printed — *"the harness will read `<ancestor>/.claude/settings.json`, not
37
+ this directory"* — was wrong three separate ways:
38
+
39
+ 1. `~/.claude/settings.json` is the **user tier**, applied in addition to the project
40
+ tier. Its existence carries no information about whether a project file is read, so
41
+ the condition that suppressed the gates was satisfied by an ordinary laptop.
42
+ 2. For a genuine project ancestor the claim is false too — that file is not read
43
+ either. Verified with a natural experiment: an ancestor two levels up carried a
44
+ `PreToolUse` Bash hook appending a marker to a log; across a dozen tool calls with
45
+ the inner directory as the project root, the log was never created. Skipping did not
46
+ deliver the gates elsewhere. It delivered them nowhere.
47
+ 3. The git-boundary guard was **dead code**. `stop` was the git toplevel while the walk
48
+ began at `dirname(projectRoot)`, so when toplevel equalled projectRoot the stop
49
+ condition could never be true and the walk ran to the filesystem root every time.
50
+ The boundary meant to keep the check local is why it reached `$HOME`.
51
+
52
+ It now warns and registers anyway: a registration that turns out inert costs nothing and
53
+ becomes live when the harness is launched there, whereas a skip is guaranteed inert. The
54
+ check additionally requires a real `settings.json` **file** — the old one accepted any
55
+ directory named `.claude`, and the one it hit in practice held only markdown. (#224)
56
+
57
+ - **The installer's only failure-path pointer led nowhere.** It told anyone whose hooks
58
+ were not registered to "see `docs/troubleshooting.md`", where the word *hook* appeared
59
+ **0** times. That file now carries the section the message names, separating "not
60
+ registered" from "registered but not live", and including a copy-paste payload that
61
+ drives a hook directly so a broken hook can be told apart from an unwired one. (#224)
62
+
63
+ **Release artifacts**
64
+
65
+ - **The published tarball could not be reproduced from its tag.**
66
+ `.mindforge/memory/sync-manifest.json` — gitignored, written at runtime by
67
+ `bin/memory/semantic-hub.js` — was **1 of 1979** shipped files not tracked at
68
+ `v11.9.3`, so provenance attested to a tree containing a file the repository does not
69
+ contain. With a `files[]` allowlist, a **directory** entry ships its contents regardless
70
+ of `.gitignore`; `files[]` already carried a negation for `pattern-library.jsonl` for
71
+ exactly this reason, and the manifest's entire content is the sync record *for that
72
+ already-excluded file*. (#225)
73
+
74
+ - **`mindforge-sdk` is published again, with provenance.** It sat at **11.8.0** on npm
75
+ while `sync-version.js` kept `sdk/package.json` at canonical — seven releases of
76
+ disagreement (11.8.1 through 11.9.3, none published) that nothing detected, because
77
+ `version:check` verifies the tracked file and not what the registry serves. It was also
78
+ the only one of the three packages with **no attestation**. The release workflow now
79
+ publishes it with `--provenance`, after the two proven publishes and before the GitHub
80
+ Release, so the newest step cannot cost the others their artifacts.
81
+
82
+ - **The Homebrew formula carries the real 11.9.3 digest.** Verified against an independent
83
+ measurement rather than the tool's own output, and explicitly confirmed not to be the
84
+ SHA-256 of npm's 21-byte `{"error":"Not found"}` body — the constant #203 used to write
85
+ for every unpublished version. (#223)
86
+
87
+ **Honesty about what is enforced**
88
+
89
+ - **The README understated the product.** "What is actually enforced" still declared that
90
+ **no install channel registers hooks**, with a `No / No` table, and stated that the
91
+ plugin channel's dispatcher crashes on every fire. Measured: the plugin's
92
+ `scripts/lib/` exists, all **14** path tokens in `plugins/mindforge/hooks/hooks.json`
93
+ resolve under the plugin root, and driving the dispatcher by hand returns **exit 2** for
94
+ `mindforge-block-no-verify` and `mindforge-config-protection`. A document that
95
+ under-claims a security capability is the same defect as one that over-claims it —
96
+ either way it describes a system that is not the one shipped. Now stated per channel,
97
+ with the four cases that remain deliberately unenforced and the three liveness
98
+ preconditions outside MindForge's control. (#225)
99
+
100
+ **Defects the published-artifact audit found (#222)**
101
+
102
+ - The **v11.9.3 release page shipped empty**: `changelog-fetcher.js --latest` exits 0
103
+ while writing zero bytes, so the `||` fallback never fired and a 195-line changelog
104
+ reached nobody. The step now prefers the in-tree changelog and fails on an empty body.
105
+ - A **shipped CI snippet told users to `npx` a package we do not own**.
106
+ `npx mindforge …` resolves to an unrelated third-party package; in a fresh runner it is
107
+ fetched and unpacked, install scripts and all, inside a job holding `MINDFORGE_TOKEN`.
108
+ Now pinned with `--package=mindforge-cc`.
109
+ - The **version-source gate missed a live defect twice**. `mindforge health` printed
110
+ `RELEASE v0.4.2` and `Current : v11.9.3` twenty-six lines apart in a project declaring
111
+ 0.4.2, and produced `Unexpected end of JSON input` with no manifest at all — the verb
112
+ whose job is verifying installation integrity misreporting the installation. Widening
113
+ the gate surfaced two more of the same shape, one of which bound the **wrong** version
114
+ silently into approval records, which is worse than no binding because the check still
115
+ returns a verdict.
116
+ - `mindforge approve` died with ENOENT on any non-Node project. Absent values are now
117
+ recorded as `null` rather than invented.
118
+
119
+ **Test infrastructure**
120
+
121
+ - `tests/production.test.js` carried the uncommitted diff into its clone with
122
+ `git diff HEAD` (which includes staged files) but committed it with `commit -a` (which
123
+ stages only modified and deleted **tracked** files). So any commit **adding** a file
124
+ under `bin/` or `tests/` left the clone dirty and failed the gate's own cleanliness
125
+ assertion — reintroducing precisely the `--no-verify` pressure that carry exists to
126
+ avoid, for the one case it did not cover.
127
+
128
+ ### Added
129
+
130
+ New regression gates, each falsified by reinstating the exact defect and confirming RED,
131
+ with every touched file restored byte-exact afterwards:
132
+
133
+ - `tests/hook-registration-ancestor.test.js` — six properties, the first of which asserts
134
+ the ancestor detector fires at all, so the other five cannot pass by never triggering.
135
+ - `every shipped file is tracked in git` — property-based, no name list. It cannot fail on
136
+ a clean clone, which is how the real leak survived CI, so it is paired with a second
137
+ test that plants the runtime state in a throwaway clone and then packs.
138
+ - `all three published packages publish with provenance` — counts the `npm publish`
139
+ invocations rather than matching one, because a single-match regex was satisfied by
140
+ either of the two packages that already had an attestation while the SDK had none. Also
141
+ asserts the SDK step is idempotent and ordered after the proven publishes.
142
+
143
+ ### Notes for operators
144
+
145
+ - **These fixes change installer behaviour on almost every project.** After upgrading,
146
+ check `.mindforge/hook-registration.json` to see what was registered, and restart the
147
+ harness — hooks are snapshotted at session start.
148
+ - A registered hook is only *live* if the harness has been restarted, the project is
149
+ trusted in the harness, and `CLAUDE_PROJECT_DIR` is set with `node` on the hook PATH.
150
+ None of those three are in MindForge's control; the last is a deliberate trade against a
151
+ fail-closed shell tail that was measured denying benign commands on a fresh clone.
152
+ - Two gaps remain that require repository settings rather than code, and are recorded
153
+ rather than claimed fixed: there is no `v*` **tag ruleset** restricting who may create
154
+ the ref that triggers publishing, and `NPM_TOKEN` is a long-lived repository secret with
155
+ no GitHub environment in front of it.
package/docs/faq.md CHANGED
@@ -1,4 +1,4 @@
1
- # MindForge FAQ (v11.9.3)
1
+ # MindForge FAQ (v11.9.4)
2
2
 
3
3
  ## Is MindForge tied to Claude only?
4
4
  No. MindForge supports Claude Code and Antigravity. Install with `--claude`,
@@ -55,7 +55,7 @@ The `deep-research` workflow was removed before the v11.8.0 release (the superpo
55
55
  ## Version & Stability
56
56
 
57
57
  **Q: What version is current?**
58
- v11.9.3 — verify with `node bin/mindforge-cli.js --version`
58
+ v11.9.4 — verify with `node bin/mindforge-cli.js --version`
59
59
 
60
60
  **Q: Is v11.9.0 production-stable?**
61
61
  Yes. The IQ200 deep-audit (258 discrete checks across 14 dimensions) shows 258/258 passing. 0 CVEs, 0 test failures, 0 ESLint errors, 0 TypeScript errors.
@@ -80,7 +80,7 @@ Spawn dispatch is not yet implemented in v11.9.0. Use `/mindforge:auto` or `/min
80
80
  Tier-3 trust uses in-process key simulation in v11.9.0 — this is intentional and safe. `SECURITY_TIER_3_SIMULATED = true` is the documented v11.x behavior. Hardware TPM/HSM is planned for v12.x.
81
81
 
82
82
  **Q: What is the test coverage?**
83
- 133 test files: 131 pass, 0 failures, 2 env-dependent skips (`browser.test.js` needs a Chromium
83
+ 134 test files: 132 pass, 0 failures, 2 env-dependent skips (`browser.test.js` needs a Chromium
84
84
  daemon, `sre-integration.test.js` needs git worktree support and a clean tree).
85
85
 
86
86
  The enforced floor is **30% lines**, gated in CI by the `mindforge-quality` job
@@ -1,4 +1,4 @@
1
- # MindForge — Getting Started (v11.9.3)
1
+ # MindForge — Getting Started (v11.9.4)
2
2
 
3
3
  This guide gets you from zero to a working MindForge project in under five minutes.
4
4
 
@@ -54,7 +54,13 @@ Run the MindForge MCP server (`mindforge-mcp-server`) over stdio — it exposes
54
54
  claude mcp add mindforge -- npx -y mindforge-mcp-server
55
55
  ```
56
56
 
57
- This server is also published to the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.sairam0424/mindforge` (currently `11.9.0`, marked latest).
57
+ This server is also published to the [MCP Registry](https://registry.modelcontextprotocol.io) as
58
+ `io.github.sairam0424/mindforge`. **The registry entry lags this package** — it is republished
59
+ manually, so check what it actually serves before relying on it:
60
+ `curl -s 'https://registry.modelcontextprotocol.io/v0/servers?search=io.github.sairam0424/mindforge'`.
61
+ To pin a known version instead, install `mindforge-mcp-server` from npm directly. (The previous
62
+ wording named a specific version and marked it latest; both were wrong in both directions, which
63
+ is why this now describes how to check rather than asserting a number.)
58
64
 
59
65
  ### 4. Homebrew
60
66
 
@@ -108,7 +114,7 @@ Or use slash commands: `/mindforge:wf-code-audit`
108
114
  ## Your First 5 Minutes with MindForge
109
115
 
110
116
  1. **Verify install:** `node bin/mindforge-cli.js health`
111
- 2. **Check version:** `node bin/mindforge-cli.js --version` (should print `11.9.3`)
117
+ 2. **Check version:** `node bin/mindforge-cli.js --version` (should print `11.9.4`)
112
118
  3. **List workflows:** `node bin/mindforge-cli.js workflow list`
113
119
  4. **Run first slash command:** Open Claude Code → `/mindforge:status`
114
120
  5. **Onboard your codebase:** Open Claude Code → `/mindforge:wf-onboard-codebase`
@@ -14,11 +14,11 @@ import {
14
14
  } from 'mindforge-sdk';
15
15
  ```
16
16
 
17
- Current SDK version: `11.9.3`
17
+ Current SDK version: `11.9.4`
18
18
 
19
19
  ---
20
20
 
21
- ## SDK Exports (v11.9.3)
21
+ ## SDK Exports (v11.9.4)
22
22
 
23
23
  ```javascript
24
24
  const {
@@ -28,7 +28,7 @@ const {
28
28
  commands, // Command registry
29
29
  batch, // Batch execution
30
30
  MindForgeMemory, // Memory interface
31
- VERSION // '11.9.3'
31
+ VERSION // '11.9.4'
32
32
  } = require('mindforge-sdk');
33
33
  // or: import { MindForgeClient, VERSION } from 'mindforge-sdk';
34
34
  ```
@@ -1,4 +1,4 @@
1
- # MindForge Troubleshooting (v11.9.3)
1
+ # MindForge Troubleshooting (v11.9.4)
2
2
 
3
3
  This page lists common issues and fast fixes. If you get stuck, start with
4
4
  `/mindforge:health`.
@@ -208,3 +208,58 @@ All tests must be run from the MindForge project root: `cd /path/to/MindForge &&
208
208
  **Symptom:** `--version` flag reports "Unknown command" on installs older than v11.9.0.
209
209
 
210
210
  **Fix:** Upgrade: `npx mindforge-cc@latest --claude --local`
211
+
212
+ ---
213
+
214
+ ## Hooks are installed but nothing is blocked
215
+
216
+ **Symptom:** The hook scripts are present under `.claude/hooks/`, but a command that should be denied
217
+ — say `git commit --no-verify` — runs normally.
218
+
219
+ Hooks have two separate failure modes: **not registered** (no config names them) and **registered but
220
+ not live** (the config exists, the harness has not applied it). Check them in that order.
221
+
222
+ **1. Was registration attempted, and what did it decide?** The installer prints one line for every
223
+ outcome, and writes a receipt:
224
+
225
+ ```
226
+ cat .mindforge/hook-registration.json
227
+ ```
228
+
229
+ `registered: false` there carries the reason. Registration is deliberately narrow: Claude Code only,
230
+ `--local` only, non-Windows. A self-install inside a MindForge checkout also declines, because that
231
+ repo maintains its own tracked config.
232
+
233
+ **2. Restart the harness.** Claude Code snapshots hooks at session start, so a registration performed
234
+ during an open session is not live in it. This is the single most common cause.
235
+
236
+ **3. Confirm the project is trusted.** User-tier and project-tier settings are applied independently:
237
+ `~/.claude/settings.json` is the user tier and applies to every session, while
238
+ `<project>/.claude/settings.json` is the project tier and needs the project itself to be trusted.
239
+ Measured on one machine: the user-tier hooks fired on every tool call while the project-tier hooks in
240
+ the same session did not, and that project's entry in `~/.claude.json` had
241
+ `hasTrustDialogAccepted: false`. Accept the trust prompt for the directory, then use `/hooks` to
242
+ confirm the entries are listed.
243
+
244
+ **4. Drive the hook directly** to separate "the hook is broken" from "the hook is not wired". This
245
+ takes the harness out of the loop entirely — a deny-class hook must exit **2**:
246
+
247
+ ```bash
248
+ echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","cwd":"'"$PWD"'","tool_input":{"command":"git commit --no-verify -m x"}}' \
249
+ | node .claude/hooks/run-with-flags.js mindforge-block-no-verify .claude/hooks/mindforge-block-no-verify.js minimal,standard,strict
250
+ echo "exit=$?"
251
+ ```
252
+
253
+ Exit 2 with a `BLOCKED:` line on stderr means the hook works and the problem is registration or
254
+ trust. Exit 0 with the payload echoed back means the dispatcher could not load the script — check
255
+ that the second path exists under `.claude/hooks/`.
256
+
257
+ **5. If you launch the harness somewhere else, install there too.** Project settings are read from the
258
+ directory the harness starts in; they are **not** inherited from a parent directory. An ancestor
259
+ project having its own `.claude/settings.json` does not make its hooks apply here, and does not stop
260
+ these from applying — the installer warns when it sees one, and still registers.
261
+
262
+ **Known residual:** if `CLAUDE_PROJECT_DIR` is unset, or `node` is not on the hook PATH, the
263
+ registered commands exit 1 and the gate is simply absent — the same position as not installing. This
264
+ is a deliberate trade: a fail-closed shell tail was measured denying benign commands on a fresh
265
+ clone.
@@ -1,8 +1,8 @@
1
- # MindForge User Guide (v11.9.3)
1
+ # MindForge User Guide (v11.9.4)
2
2
 
3
3
  This guide gets you from install to productive, with the minimum needed to run MindForge in a real project.
4
4
 
5
- > **v11.9.3 Stats:** 35 workflows · 221 slash commands · 232 engine skills · 216 personas · 0 CVEs · 258/258 IQ200 checks passing
5
+ > **v11.9.4 Stats:** 35 workflows · 221 slash commands · 232 engine skills · 216 personas · 0 CVEs · 258/258 IQ200 checks passing
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -44,7 +44,7 @@ mindforge health # Verify project integrity
44
44
  mindforge security-scan # Run security checks
45
45
  mindforge headless # Run agent in non-interactive mode
46
46
  mindforge --verbose ... # Enable verbose output for debugging
47
- mindforge --version # Print installed version (e.g. 11.9.3) and exit 0
47
+ mindforge --version # Print installed version (e.g. 11.9.4) and exit 0
48
48
  ```
49
49
 
50
50
  Use `--verbose` (or `-v`) on any command for detailed diagnostic output. Use `--version` (or `-V`) to print the installed version and exit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindforge-cc",
3
- "version": "11.9.3",
3
+ "version": "11.9.4",
4
4
  "description": "MindForge — Sovereign Agentic Intelligence Framework. Sovereign Stability: Production-Hardened Agentic Intelligence (v11)",
5
5
  "bin": {
6
6
  "mindforge-cc": "bin/install.js",
@@ -29,6 +29,7 @@
29
29
  ".mindforge/intelligence/",
30
30
  ".mindforge/memory/",
31
31
  "!.mindforge/memory/pattern-library.jsonl",
32
+ "!.mindforge/memory/sync-manifest.json",
32
33
  ".mindforge/metrics/",
33
34
  "!.mindforge/metrics/token-usage.jsonl",
34
35
  ".mindforge/models/",
@@ -1,6 +0,0 @@
1
- {
2
- "pattern-library.jsonl": {
3
- "lastSync": "2026-08-21T12:02:25.078Z",
4
- "localCount": 1
5
- }
6
- }