mindforge-cc 11.9.3 → 11.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mindforge/config.json +1 -1
- package/.mindforge/engine/autonomous/headless-adapter.md +8 -1
- package/CHANGELOG.md +294 -5
- package/MINDFORGE.md +2 -2
- package/README.md +57 -28
- package/RELEASENOTES.md +187 -0
- package/SECURITY.md +1 -1
- package/bin/dashboard/metrics-aggregator.js +6 -1
- package/bin/governance/approve.js +12 -1
- package/bin/governance/verify-approvals.js +13 -1
- package/bin/installer/hook-registration.js +54 -11
- package/bin/installer-core.js +31 -3
- package/bin/utils/readiness-gate.js +19 -3
- package/changelogs/v11.9.3.md +14 -5
- package/changelogs/v11.9.4.md +178 -0
- package/changelogs/v11.9.5.md +104 -0
- package/docs/faq.md +3 -3
- package/docs/getting-started.md +9 -3
- package/docs/sdk-reference.md +17 -11
- package/docs/troubleshooting.md +56 -1
- package/docs/user-guide.md +3 -3
- package/package.json +3 -1
- package/.mindforge/memory/sync-manifest.json +0 -6
package/RELEASENOTES.md
CHANGED
|
@@ -1,5 +1,192 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## v11.9.5 — 2026-08-22 — The release path can no longer strand itself, and the SDK ships
|
|
4
|
+
|
|
5
|
+
### Why this release exists
|
|
6
|
+
|
|
7
|
+
11.9.4 published two packages and then failed on the third, and that failure took the release
|
|
8
|
+
page and the `stable` dist-tag with it:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
success Publish to npm <- mindforge-cc@11.9.4 (irreversible)
|
|
12
|
+
success Publish standalone MCP server to npm <- mindforge-mcp-server@11.9.4 (irreversible)
|
|
13
|
+
failure Publish the SDK to npm <- 422: "repository.url" is ""
|
|
14
|
+
skipped Create GitHub Release
|
|
15
|
+
skipped Point the stable dist-tag at this release
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Two causes, both fixed here.
|
|
19
|
+
|
|
20
|
+
**The metadata was only ever validated by the registry.** `sdk/package.json` had no
|
|
21
|
+
`repository` field, and npm compares that field against the attestation **server-side at PUT**.
|
|
22
|
+
`npm publish --dry-run` does not check it and nothing here read `repository` at all — so all six
|
|
23
|
+
preflight gates passed on a manifest guaranteed to be rejected. There is now an offline gate in
|
|
24
|
+
*preflight*, ahead of every publish, that discovers the provenance-publishing packages from the
|
|
25
|
+
workflow itself. Run against a worktree at tag `v11.9.4` — the tree the registry rejected — it
|
|
26
|
+
exits 1 and names the file. It would have stopped that release.
|
|
27
|
+
|
|
28
|
+
**The step order let an optional package strand the release.** The SDK publish sat before the
|
|
29
|
+
release page and the dist-tag move, so its failure skipped both. The same mode had already fired
|
|
30
|
+
on v11.5.1 and v11.8.3. Reordered so the steps that *finish* a release run ahead of any additive
|
|
31
|
+
package publish — which means this release moves `stable` forward even if a package fails again.
|
|
32
|
+
|
|
33
|
+
### The user-visible part
|
|
34
|
+
|
|
35
|
+
**`mindforge-sdk` publishes for the first time since 11.8.0, and for the first time with
|
|
36
|
+
provenance.** Everything fixed in the SDK across 11.8.1–11.9.4 had reached nobody, including
|
|
37
|
+
this: `WebSocketEventStream` scheduled a reconnect and nothing handled the returned promise, so
|
|
38
|
+
a failed reconnect was an unhandled rejection — fatal under Node's default mode, **terminating
|
|
39
|
+
the caller's process**. Every consumer on 11.8.0 still has that.
|
|
40
|
+
|
|
41
|
+
### Also fixed
|
|
42
|
+
|
|
43
|
+
- `bin/utils/readiness-gate.js` scored `RELEASENOTES.md` on existence while its changelog sibling
|
|
44
|
+
checked the version was *in* the file. That asymmetry is why both prose surfaces shipped stale
|
|
45
|
+
in 11.9.3 and again in 11.9.4. Both are now checked, anchored rather than by substring.
|
|
46
|
+
**Policy change:** every release from here needs an entry in this file.
|
|
47
|
+
- `docs/sdk-reference.md` claimed `npx mindforge-cc@stable` installs the SDK "as part of the
|
|
48
|
+
framework". Measured false — the published package declares exactly `express` and `sql.js` and
|
|
49
|
+
ships no `sdk/`. Removed.
|
|
50
|
+
|
|
51
|
+
### Note on 11.9.4
|
|
52
|
+
|
|
53
|
+
It is complete, but was finished by hand: its GitHub Release was created at the existing tag from
|
|
54
|
+
the registry's own tarball, verified against `dist.integrity` and both attestation bundles'
|
|
55
|
+
subject digests. The `v11.9.4` tag was deliberately **not** moved, because two published
|
|
56
|
+
packages' provenance attests to the commit it names.
|
|
57
|
+
|
|
58
|
+
## v11.9.4 — 2026-08-22 — Delivery: the gates register, the tarball matches its tag
|
|
59
|
+
|
|
60
|
+
### The headline
|
|
61
|
+
|
|
62
|
+
**11.9.3 shipped the hook-registration code and then declined to run it.** The installer skipped
|
|
63
|
+
whenever any ancestor directory contained a `.claude`. On a machine that has ever run Claude Code
|
|
64
|
+
that means `~/.claude`, so essentially every install copied the enforcement in and wired none of it.
|
|
65
|
+
|
|
66
|
+
Measured against the published tarballs in a confined sandbox, with `~/.claude` as the only ancestor:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
11.9.3: 11 hook scripts installed, 0 registered, no settings.json written
|
|
70
|
+
11.9.4: 8 registered, preflight executed 7 of 8, 3 deny-class verified blocking
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The reason the installer printed was wrong three separate ways:
|
|
74
|
+
|
|
75
|
+
1. `~/.claude/settings.json` is the **user tier**, applied in addition to the project tier. Its
|
|
76
|
+
existence says nothing about whether a project file is read — so the condition that suppressed
|
|
77
|
+
the gates was satisfied by an ordinary laptop.
|
|
78
|
+
2. For a real project ancestor the claim is false too: that file is not read either. Proved with a
|
|
79
|
+
marker hook two levels up that never fired across a dozen tool calls. Skipping did not deliver
|
|
80
|
+
the gates elsewhere; it delivered them nowhere.
|
|
81
|
+
3. The git-boundary guard was dead code, which is why the walk reached `$HOME` at all.
|
|
82
|
+
|
|
83
|
+
It now warns and registers anyway. A registration that turns out inert costs nothing; a skip is
|
|
84
|
+
guaranteed inert.
|
|
85
|
+
|
|
86
|
+
### ⚠️ Behaviour change under a patch bump
|
|
87
|
+
|
|
88
|
+
The installer now writes `.claude/settings.json` on projects where it previously declined — for most
|
|
89
|
+
users, **0 registered hooks becomes 8**, three of which can block a tool call. It merges append-only
|
|
90
|
+
into any existing file, backs the previous one up under `.mindforge/backups/`, and records exactly
|
|
91
|
+
what it did in `.mindforge/hook-registration.json`.
|
|
92
|
+
|
|
93
|
+
A registered hook is only *live* if the harness has been **restarted** (hooks are snapshotted at
|
|
94
|
+
session start), the project is **trusted** in the harness, and `CLAUDE_PROJECT_DIR` is set with
|
|
95
|
+
`node` on the hook PATH. See *Hooks are installed but nothing is blocked* in
|
|
96
|
+
`docs/troubleshooting.md`.
|
|
97
|
+
|
|
98
|
+
### Fixes
|
|
99
|
+
|
|
100
|
+
- The installer's only failure-path pointer led nowhere: it said "see `docs/troubleshooting.md`",
|
|
101
|
+
where the word *hook* appeared **0** times. That section now exists.
|
|
102
|
+
- The published tarball was not reproducible from its own tag. `.mindforge/memory/sync-manifest.json`
|
|
103
|
+
— gitignored, written at runtime — was **1 of 1979** shipped files not tracked at `v11.9.3`, so
|
|
104
|
+
provenance attested to a tree containing a file the repository does not contain.
|
|
105
|
+
- The README understated the product: it still said **no channel registers hooks** and that the
|
|
106
|
+
plugin dispatcher crashes on every fire. Measured: 14 of 14 plugin path tokens resolve and two
|
|
107
|
+
deny-class hooks return exit 2. A document that under-claims a security capability is the same
|
|
108
|
+
defect as one that over-claims it.
|
|
109
|
+
- Defects an 8-agent audit found in the *published* 11.9.3: an empty release page, a shipped CI
|
|
110
|
+
snippet telling users to `npx` a package we do not own, and a version-source gate that missed a
|
|
111
|
+
live defect twice.
|
|
112
|
+
- The Homebrew formula carries the real 11.9.3 digest, verified against an independent measurement.
|
|
113
|
+
|
|
114
|
+
### Known issue in this release
|
|
115
|
+
|
|
116
|
+
**`mindforge-sdk` did not publish and remains at 11.8.0.** A publish step was added for it and the
|
|
117
|
+
registry rejected it with 422: `sdk/package.json` carried no `repository` field, which npm's
|
|
118
|
+
provenance verification validates **server-side at publish time** — `npm publish --dry-run` does not
|
|
119
|
+
check it, and no gate here did either.
|
|
120
|
+
|
|
121
|
+
Worse, the step was placed *before* the release-page and dist-tag steps, so its failure skipped
|
|
122
|
+
both. `mindforge-cc@11.9.4` and `mindforge-mcp-server@11.9.4` published correctly with provenance;
|
|
123
|
+
the release page and the `stable` tag were completed by hand afterwards. Both causes are fixed for
|
|
124
|
+
the next release: the missing field, an offline preflight gate that refuses to reach a publish
|
|
125
|
+
without it, and a reordering so the steps that finish a release run ahead of any optional package.
|
|
126
|
+
|
|
127
|
+
## v11.9.3 — 2026-08-21 — Honesty: gates that can fail, commands that run, a release path that is checked
|
|
128
|
+
|
|
129
|
+
### What's New
|
|
130
|
+
Nothing. Twenty-one fixes sharing a single defect: **an instrument reported success while doing
|
|
131
|
+
nothing.** Gates that could not fail, tests a comment satisfied, docs naming scripts that do not
|
|
132
|
+
exist, commands printing success while performing no action, and a publish path no check ever
|
|
133
|
+
touched.
|
|
134
|
+
|
|
135
|
+
### ⚠️ Breaking (under a patch bump)
|
|
136
|
+
Six behaviour changes, each a bug fix whose correct behaviour differs from what shipped. Read these
|
|
137
|
+
if you script against the CLI or the installer.
|
|
138
|
+
|
|
139
|
+
- **Routed CLI commands now act on YOUR project, not MindForge's checkout.** The router passed
|
|
140
|
+
`cwd: ROOT`, so `mindforge classify` diffed MindForge's repository instead of yours and
|
|
141
|
+
`mindforge health` inspected MindForge's `node_modules`. Of 27 routed commands, 7 changed
|
|
142
|
+
behaviour — all in the correct direction.
|
|
143
|
+
- **`npx mindforge-cc install`, and any other positional argument, now exits 1.** The installer
|
|
144
|
+
takes flags only and used to ignore stray words silently, so that command appeared to work while
|
|
145
|
+
configuring nothing. Use `npx mindforge-cc --claude --local`.
|
|
146
|
+
- **`mindforge verify` now SKIPS unavailable stages instead of failing them.** A project with no
|
|
147
|
+
ESLint config or no test script was reported as FAILING those stages rather than as not having
|
|
148
|
+
them. CI that relied on a non-zero exit there will now pass. A run where every stage skipped
|
|
149
|
+
prints a "NOTHING WAS VERIFIED" banner rather than a clean bill of health.
|
|
150
|
+
- **`scripts/sync-version.js` now exits non-zero when the plugin build artifacts are stale.** A bump
|
|
151
|
+
used to report success and exit 0 while leaving `npm test` red.
|
|
152
|
+
- **A self-install no longer writes over your tracked files.** Running the installer inside a
|
|
153
|
+
MindForge checkout printed that it was skipping and then overwrote 149 tracked files, including
|
|
154
|
+
`CLAUDE.md`, `.claude/**`, `.agent/**` and `.mindforge/**`.
|
|
155
|
+
- **Releases must be tagged on a commit reachable from `main`.**
|
|
156
|
+
|
|
157
|
+
### Fixes
|
|
158
|
+
- **11 of 27 routed CLI verbs died on `MODULE_NOT_FOUND` in a real install.** The router shipped;
|
|
159
|
+
six of the scripts it dispatches to did not.
|
|
160
|
+
- **Every `--global` install reported failure on a correct run.** Verification demanded six `bin/**`
|
|
161
|
+
paths regardless of scope, so a global install — which writes 389 files to `$HOME/.claude` and,
|
|
162
|
+
deliberately, zero to `bin/` — ended `6 of 12 required file(s) missing` and exit 1, with a
|
|
163
|
+
`--force` retry that could not help.
|
|
164
|
+
- **`--fetch-sha` hashed npm's 404 error body into the Homebrew formula.** For an unpublished
|
|
165
|
+
version the registry answers `{"error":"Not found"}` and `curl -sL` exits 0, so the digest written
|
|
166
|
+
was the SHA-256 of that error text — the same constant for every unpublished version — and
|
|
167
|
+
`--check` then passed. It now fails closed on both status and gzip magic bytes.
|
|
168
|
+
- **No version channel covered a document a user receives.** `SECURITY.md` — the security policy at
|
|
169
|
+
the root of the published package — said "Current version: 11.9.0", and five docs titled
|
|
170
|
+
themselves v11.9.0: three releases stale while every npm manifest was correct. A channel that does
|
|
171
|
+
not exist cannot drift, so `--check` was green the whole time.
|
|
172
|
+
- **The tag push that publishes was exempt from every gate.** Publishing is triggered by exactly one
|
|
173
|
+
event — a `v*` tag push — and the repository's only ruleset targets branches, so its six required
|
|
174
|
+
checks applied to nothing on the path that ships. GitHub cannot attach required status checks to a
|
|
175
|
+
tag, so this had to be fixed in the workflow itself.
|
|
176
|
+
- **The `stable` dist-tag sat three releases behind `latest`** (11.8.3 against 11.9.2), so
|
|
177
|
+
`npm i mindforge-cc@stable` delivered a build with none of the 11.9.x fixes. The release workflow
|
|
178
|
+
now moves it as its final step — forward-only, prereleases skipped.
|
|
179
|
+
- **`mindforge dashboard --status` and `--stop` were documented and implemented nowhere.** Both
|
|
180
|
+
printed nothing useful. `--stop` also identified its target by the SHAPE of a command line, which
|
|
181
|
+
matched any `node <anything>/dashboard/server.js` — verified against an unrelated app's dashboard.
|
|
182
|
+
- **`temporal cleanup` printed "Cleaning up…" and "Cleanup complete." with no cleanup between them.**
|
|
183
|
+
- **The protocol files instructed the agent to run scripts that do not exist** (`soul-engine.js`,
|
|
184
|
+
`shard-controller.js`), and 15 phantom `/mindforge:` slash commands appeared in shipped docs. A
|
|
185
|
+
reader following the user guide typed `/mindforge:personas --list` and got nothing.
|
|
186
|
+
- **Every abandoned exit left a full copy of the database on disk** — 1.8 GB of orphaned `.tmp` files.
|
|
187
|
+
|
|
188
|
+
Full detail, including the measured evidence for each: [changelogs/v11.9.3.md](./changelogs/v11.9.3.md).
|
|
189
|
+
|
|
3
190
|
## v11.9.2 — 2026-08-16 — Correctness: config gate, audit chain, retrieval, cost ledger
|
|
4
191
|
|
|
5
192
|
### What's New
|
package/SECURITY.md
CHANGED
|
@@ -210,7 +210,12 @@ function getApprovals() {
|
|
|
210
210
|
const { verifyRecord } = require('../governance/approval-record');
|
|
211
211
|
let currentVersion = null;
|
|
212
212
|
try {
|
|
213
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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,
|
|
376
|
-
if (dir ===
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
};
|
package/bin/installer-core.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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(
|
|
1332
|
+
Theme.printHeader(bannerVersion);
|
|
1305
1333
|
Theme.printBrandManifest();
|
|
1306
1334
|
// Check for updates only
|
|
1307
1335
|
if (isCheck) {
|
|
@@ -100,12 +100,28 @@ function releaseReadinessChecks(root) {
|
|
|
100
100
|
pass: Boolean(pkgVersion) && pkgVersion === configVersion,
|
|
101
101
|
fix: `Align versions: package.json=${pkgVersion || '?'} vs config.json=${configVersion || '?'}`,
|
|
102
102
|
},
|
|
103
|
+
// CHECKS CONTENT, NOT MERE EXISTENCE, and that change has a cost worth naming: from now on every
|
|
104
|
+
// release needs a RELEASENOTES entry. Six published 11.x versions do not have one (11.0.0, 11.2.0,
|
|
105
|
+
// 11.2.1, 11.4.0, 11.7.1, 11.9.1) — the file has been curated rather than exhaustive. Those gaps are
|
|
106
|
+
// unaffected, because this only ever looks for the version being released.
|
|
107
|
+
//
|
|
108
|
+
// WHY IT CHANGED. This gate passed on `fileExists` while its changelog sibling three lines down
|
|
109
|
+
// checked that the version was IN the file. That asymmetry is why both prose surfaces shipped stale
|
|
110
|
+
// twice running: the immutable 11.9.3 tarball's README said "Latest release v11.9.2", and the
|
|
111
|
+
// immutable 11.9.4 tarball shipped with no 11.9.4 entry here at all — while README.md offers this
|
|
112
|
+
// file as the human-readable route to the BREAKING notes. 348a3a2c fixed the identical defect one
|
|
113
|
+
// release earlier by hand and added no gate, and it recurred immediately.
|
|
114
|
+
//
|
|
115
|
+
// Anchored, not `includes`. A substring test for "11.9.4" is also satisfied by "## v11.9.40" and by
|
|
116
|
+
// "## v11.9.4-notyet" — measured, when the equivalent test assertion was falsified.
|
|
103
117
|
{
|
|
104
118
|
id: 'releasenotes',
|
|
105
|
-
label: 'RELEASENOTES.md present',
|
|
119
|
+
label: 'RELEASENOTES.md present and has an entry for the current version',
|
|
106
120
|
points: 2,
|
|
107
|
-
pass: fileExists(root, 'RELEASENOTES.md')
|
|
108
|
-
|
|
121
|
+
pass: fileExists(root, 'RELEASENOTES.md') && Boolean(pkgVersion)
|
|
122
|
+
&& new RegExp(`^## v${pkgVersion.replace(/\./g, '\\.')}(?![\\d.\\w-])`, 'm')
|
|
123
|
+
.test(safeRead(root, 'RELEASENOTES.md')),
|
|
124
|
+
fix: `Add a "## v${pkgVersion || '?'}" section to RELEASENOTES.md. README.md links here as the human-readable route to the BREAKING notes, so releasing without one points readers at a file whose newest entry describes a different version.`,
|
|
109
125
|
},
|
|
110
126
|
{
|
|
111
127
|
id: 'changelog',
|
package/changelogs/v11.9.3.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
145
|
-
|
|
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
|