@mindrian_os/cli 1.15.3-beta.8 → 1.16.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +841 -0
  2. package/README.md +5 -3
  3. package/package.json +4 -2
package/CHANGELOG.md CHANGED
@@ -1,3 +1,844 @@
1
+ ## [1.16.0-beta.1] - 2026-07-31
2
+
3
+ ### Added
4
+ -
5
+
6
+ ## [1.15.3] - 2026-07-31
7
+
8
+ ### Added
9
+ - **The room now learns which of its two ranking signals has actually been right for you, not
10
+ just from a fixed prior forever (RCA hedge-fold-has-no-production-trigger).** When Larry picks
11
+ which of several fired reaches to surface, two signals vote: how well an idea seems to fit
12
+ right now, and how far up the fixed list it sits. A Phase 222 layer was supposed to learn,
13
+ from your own past accept/reject choices, how much to trust each of those two signals for
14
+ you specifically, and quietly re-weight them over time. It never once ran, on any install,
15
+ because nothing in the shipped code ever handed it your room's database to learn from, so it
16
+ sat at a permanent, correct-looking cold start. Nothing you saw was wrong: the ranking still
17
+ worked, it was just always computed from the same starting assumption instead of from your
18
+ own outcomes. There is now one deliberate command, `node scripts/hedge-refit-pipeline.cjs
19
+ <room>`, that runs that learning step on purpose, rather than it riding along as a side effect
20
+ of serving a turn. Canon Part 9: it reaches your room's database only through the one
21
+ existing local chokepoint, same as everything else. Canon Part 8: zero network, zero Brain,
22
+ nothing but your own room's past decisions.
23
+
24
+ ### Changed
25
+ - **The stated minimum Node version is now 22.16.0, up from 22.5.0, because that is the version
26
+ where the room.db write-safety setting actually starts working (Phase 236, GRAPHDB-03).** Since
27
+ Phase 218-02 the room's database has been opened with a five second "wait your turn" setting, so
28
+ that when two things try to write at the same moment the second one waits instead of failing
29
+ instantly. That setting is called `timeout`, and it is passed to Node's built-in `node:sqlite`
30
+ module. Two different Node versions matter here and they are easy to confuse. `node:sqlite`
31
+ stopped needing a special startup flag at v22.13.0, but the `timeout` setting itself was not
32
+ added until **v22.16.0**. In between, on 22.13 through 22.15, the module loads fine and the
33
+ code looks correct, but `node:sqlite` accepts settings it does not recognise without
34
+ complaining, so `timeout` is quietly thrown away and contended writes still fail at zero
35
+ milliseconds exactly as before. Nothing warns you. We confirmed this on a live runtime by
36
+ reading `PRAGMA busy_timeout` back after opening: `0` on a version without the option, `5000`
37
+ with it. The old floor of `>=22.5.0` was wrong twice over, because on 22.5 through 22.12
38
+ `require('node:sqlite')` throws outright without the flag. Source for the v22.16.0 figure:
39
+ Context7 against the Node.js v22.x API docs, specifically the `timeout` option's
40
+ version-history entry, not the module's separate unflagging entry.
41
+ **User-visible consequence:** npm will now refuse an install on Node 22.5.x through 22.15.x
42
+ that it previously accepted. That is deliberate. The code genuinely does not run safely on
43
+ those versions. Upgrade Node to 22.16.0 or newer (`nvm install 22`, `fnm install 22`, or your
44
+ package manager) before updating. CI already runs the Node 22 major line, which resolves above
45
+ the new floor, so CI keeps exercising a runtime users actually have.
46
+
47
+ ## [1.15.3-beta.50] - 2026-07-28
48
+
49
+ ### Added
50
+ - **A doctor check that catches a Data Room whose graph never learned how the ideas relate,
51
+ and an automatic repair for the rooms already in that state (Phase 233, RCA items 4c/4d).**
52
+ Two things live inside `room.db`: `BELONGS_TO` edges, which say which artifact sits in which
53
+ section (a filing cabinet), and cascade edges (INFORMS, CONTRADICTS, CONVERGES, INVALIDATES,
54
+ ENABLES, REFINES, ROOT_CAUSES), which say how the ideas actually relate (the part that makes
55
+ the room think). Before Phase 224-02 shipped on 2026-07-23, a failed derivation quietly
56
+ deleted its own retry signal, so a room could end up with the first and never the second and
57
+ nothing anywhere would say so. Phase 224-02 stopped that happening again, but it was
58
+ forward-only: roughly 16 live rooms were already in that state and stayed there. This release
59
+ closes both halves. New `graph-derive-health` doctor class (`/mos:doctor
60
+ --graph-derive-health`, or add `--cascade-rooms` to sweep every room) reports FAIL on exactly
61
+ that shape and WARN on a derive queue stuck past three days or a recorded failure log. New
62
+ `--heal-room` flag re-enqueues every affected room; it is the literal flag the
63
+ v1.13.0-beta.16 rename table has pointed at since before it existed, made real here for the
64
+ first time. And a one-time `graph-derive-heal-retrofit` module repairs already-damaged rooms
65
+ by itself the first time doctor runs after the update, with no flag to discover and nothing
66
+ to opt into, because a user should not have to know their graph was damaged in order to get
67
+ it fixed. Both share ONE detection function, so the check and the repair can never drift
68
+ apart. The heal restores the retry signal; the real edges land the next time an in-session
69
+ derive runs. Tri-Polar: CLI gets the full report, Desktop and Cowork get a one-sentence
70
+ re-derive nudge on the existing SessionStart install-drift slot rather than a raw table.
71
+ Canon Part 8: every new path is a read-only local `room.db` read plus a local JSON queue
72
+ write, zero network and zero Brain. Ground-truth tested (20 scenarios) against real `room.db`
73
+ files and real queue files read back off disk, never a mocked return value.
74
+
75
+ ### Fixed
76
+ - **Your room's discovery engine was ranking its own backup files as its top insights, and
77
+ then throwing every result away (Phase 233, RCA Section 9 Defects #4/#5).** The HSI pass is
78
+ the part that reads your artifacts and says "these two distant pieces are secretly related".
79
+ It walks your room looking for content, and every other walker in the codebase was taught in
80
+ Phase 200 to read one shared list of folders to ignore. This one walker was never migrated,
81
+ so it kept a private copy that had gone stale: it never learned to skip `.snapshots`
82
+ (historical state dumps of your own room) or `sub-rooms` (nested rooms that carry their own
83
+ graph). The result on a real room: 207 files scored, and all twenty of its "top discoveries"
84
+ were near-identical backup copies of each other. Then the edge writer correctly refused every
85
+ one of them, because backup files are not artifacts in your graph, and wrote zero edges. A
86
+ full expensive pass, a confident report, and nothing to show for it. Now `compute-hsi.py`
87
+ reads the same shared list as everything else (so this class of drift cannot recur), and a
88
+ new `--scope-to-nodes` mode scores only artifacts that actually exist in your graph, which is
89
+ both cheaper and the only set that can produce an edge.
90
+ - **Healing a damaged graph now runs its four stages in the one order that works, and the last
91
+ stage stopped deleting the work of the third (Phase 233).** Repairing a room is not one
92
+ action, it is four, and each one eats what the previous one produced: index every artifact as
93
+ a node, score similarity across those nodes, write the semantic edges, then run the
94
+ generative tier. Run them out of order and every stage reports success while producing
95
+ nothing. New `node scripts/graph-heal-pipeline.cjs <room>` runs all four in the mandated
96
+ order, reusing each existing implementation unchanged. Running it live on the room from the
97
+ original investigation exposed one more instance of the same bug class this whole phase is
98
+ about: the fourth stage opened by clearing and rebuilding the graph, which deleted the twenty
99
+ semantic edges the third stage had written seconds earlier. The pipeline printed "wrote 20
100
+ connection edges" into a room that ended up holding none. The rebuild is now suppressed when
101
+ the caller already indexed the room, and that room finished the run with 61 artifact nodes,
102
+ 20 semantic edges and 47 typed relationship edges, up from zero. Every other caller of the
103
+ backfill is byte-unchanged. A missing Python or embedding library degrades exactly the two
104
+ stages that need them and never the other two. Canon Part 8: local only, zero network, zero
105
+ Brain.
106
+ - **The graph derivation engine could quietly dial a dead account instead of scoring your
107
+ work locally (Phase 233, RCA items 4b/4e).** `runDerivation` is the composer that turns a
108
+ pair of your artifacts into a typed relationship edge. It takes the scoring function as an
109
+ argument, and until now, if a caller forgot to pass one, it silently fell back to a hosted
110
+ Anthropic API call. That fallback account has been out of credit for months: a live probe
111
+ returned `400 credit balance is too low`. So "forgot to pass the scorer" did not look like a
112
+ mistake at the point it was made; it looked like working code that failed later, over the
113
+ network, for a reason that had nothing to do with the actual bug. That fallback is now gated.
114
+ Omitting the scorer throws `deriveFn_required_no_hosted_default` immediately, before the room
115
+ database is even opened, and the error names the local scorer to use instead. Nothing shipped
116
+ is affected: every real caller already passes one. An operator who genuinely wants the hosted
117
+ path can still have it by setting `MINDRIAN_ALLOW_HOSTED_DERIVE=1` with a funded
118
+ `ANTHROPIC_API_KEY`, and gets byte-identical behavior to before. Gated, not deleted. Also
119
+ fixed: the background drain's header comment still claimed it "CLEARS the drained entry"
120
+ after every pass, which stopped being true when Phase 224-02 made failed entries survive and
121
+ retry. It now states what actually happens, plus the real division of labor: the background
122
+ pass enqueues, preserves on failure, and scores locally, while in-session `/mos:graph
123
+ --derive` is the wider net that also reaches rooms the background pass structurally cannot
124
+ see. Both default to the same local score-based scorer, and that is no longer a claim in a
125
+ comment: a new regression test swaps the shared scorer for a recorder, runs both paths over
126
+ two identical rooms, and compares the edges that land on disk.
127
+ - **`orchestration rooms-open` reported a confirmation-shaped success while never switching
128
+ the active room.** Live-reproduced 2026-07-22: the call returned a full "Room State" payload
129
+ footed with the correct target room, yet `room-registry get-active` still returned the
130
+ previous room and the next Write was blocked with "Active room is <previous>". Root cause:
131
+ `rooms-open` was a DECLARED-BUT-UNIMPLEMENTED command. It passed Zod validation via its
132
+ membership in `ORCHESTRATION_COMMANDS`, matched no handler, and fell through to a generic
133
+ reference-echo fallback shared by 18 of the 22 orchestration commands. That fallback built
134
+ its response from `commands/rooms.md` read off disk, STATE.md from the boot-frozen `roomDir`
135
+ closure (which is why the payload showed an unrelated room's content), and a verbatim echo of
136
+ the caller's own `room` argument -- then appended "Room operation complete". No byte of the
137
+ response derived from an operation, because none was attempted: before this fix, zero product
138
+ `.cjs` code anywhere called `room-registry set-active`, so the MCP surface advertised
139
+ multi-room management while having no room-switch capability at all (worst on Desktop and
140
+ Cowork, which have no shell fallback). Fixed with a new `lib/core/room-open.cjs` chokepoint
141
+ that wraps the one authoritative writer (Canon Part 7) and gates `ok:true` behind a post-write
142
+ `get-active` read-back, so a success-shaped payload is now structurally impossible unless the
143
+ switch actually landed; it also writes the per-session binding so `write-scope-check.cjs`
144
+ authorizes the session without depending on the raceable global field, and it preserves the
145
+ `commands/rooms.md` Step 2 human gate for reopening an archived room. Structurally, the same
146
+ fallback no longer lets any state-mutating orchestration command claim completion it cannot
147
+ back up: `rooms-new`, `rooms-close`, and `rooms-archive` now carry an explicit NOT EXECUTED
148
+ banner. 10 hermetic regression tests assert registry ground truth, not response shape.
149
+
150
+ ## [1.15.3-beta.48] - 2026-07-26
151
+
152
+ ### Added
153
+ - **Room-graph density read (Phase 232.1).** `/mos:doctor` and `room_state status`/`get-state`
154
+ now self-report node/edge counts per `room.db`, read exclusively through a new read-only
155
+ `navigation.cjs` door (`openRoomDbReadOnlyForCaller`) -- never the mutating door, never a
156
+ direct `room.db` open. Closes SEED-074's own "suggested first move"
157
+ (`.planning/seeds/SEED-074-local-graph-read-layer-lacks-salience-and-query-time-joins.md`):
158
+ the seed's actual PageRank/Louvain/query-time-join target stays gated exactly as written;
159
+ this only makes its own trigger condition (measured room.db density) self-reported instead
160
+ of something a human has to remember to check by hand. No output string anywhere claims a
161
+ room graph is dense, at-risk, or healthy (hard guard, grep-verified). Goal-backward
162
+ verification PASS, 6/6 must-haves (`232.1-VERIFICATION.md`).
163
+ - Local reified-claim `ContradictionEvent` primitive (quick task 260725-9ca), with a hermetic
164
+ acceptance test.
165
+
166
+ ### Fixed
167
+ - **`room_bind` could report `ok:true` while a sibling MCP read tool silently resolved a
168
+ stale, unrelated room.** Root cause: 20 of 21 MCP tool call sites were missing the
169
+ `CLAUDE_CODE_SESSION_ID` stdio fallback that `room_bind` itself already had, so a
170
+ session-id mismatch between the bind call and a later read call could bind one room but
171
+ read another without ever surfacing an error. Fixed via one shared
172
+ `resolveEffectiveSessionId` helper, wired into all 21 sites; live-reproduced, then closed,
173
+ tests green. Full reproduction and root cause also filed in the `rethinking-mindrianos`
174
+ room per this repo's dev-research compositing rule.
175
+ - **Phase 232.1's own room-graph density census could silently drop a room that used the
176
+ `abs_path` registry field instead of `path`,** undermining the one thing the census exists
177
+ to be accurate about. `resolveRoomPath` now checks `abs_path` first, `path` as fallback,
178
+ matching the precedence every other production call site in this codebase already honors;
179
+ pinned by a mutation-verified regression test (reverted, confirmed red, restored, confirmed
180
+ green). The identical gap in the earlier, already-shipped `cascade-rooms-module.cjs` this
181
+ was copied from is left alone -- out of this phase's scope, tracked separately as
182
+ low-severity follow-up debt.
183
+
184
+ ## [1.15.3-beta.46] - 2026-07-23
185
+
186
+ ### Added
187
+ -
188
+
189
+ ### Fixed
190
+ - **The F.8 "bind session to room" Decision Gate (and any PRIMARY-path registry gate)
191
+ force-fired on turns whose ONLY content was an automated background-task-completion
192
+ notification, with zero real user text, blocking continuation with a Stop hook error even
193
+ though there was nothing for the navigator to decide that turn.** Root cause:
194
+ `precedingUserText` resolves to `''` for two very different reasons that
195
+ `scripts/check-card-fire.cjs` could not previously distinguish -- a genuinely terse HUMAN
196
+ turn ("ok", "go on") and a SYNTHETIC preceding transcript record (a `tool_result` envelope
197
+ from a background tool call, or an automated task-notification block) with no
198
+ human-authored text at all. `lib/core/gate-relevance.cjs`'s conservative low-signal branch
199
+ forced (assumed relevant) on both, which is only correct for the first -- there is no human
200
+ turn for a Decision Gate to be relevant or irrelevant to on the second. Fixed by adding a
201
+ new `preceding_user_text_source` signal (`'typed' | 'tool_result' | 'none'`), classified by
202
+ a new `classifyPrecedingUserContentSource` helper in `readTranscriptTurn` and threaded
203
+ through `deriveTurnSignals`; `classifyCardFire`'s PRIMARY-path relevance branch now bypasses
204
+ forcing immediately when the source is confirmed `'tool_result'`
205
+ (`reason: 'preceding-turn-synthetic-no-user-engagement'`), before ever reaching the
206
+ conservative low-signal branch. A genuinely terse human turn against the identical gate
207
+ still force-fires, unweakened (the WR-06/CR-06 floor). Live-reproduced 3 consecutive times
208
+ in one session; the mechanism itself was first diagnosed 2026-07-06 (as a fact, not yet
209
+ treated as a defect) and confirmed recurring 2026-07-11 and 2026-07-22. Full RCA:
210
+ `.planning/debug/resolved/room-bind-gate-fires-on-notification-only-turns.md`.
211
+ - **The semantic-edge derivation queue (`graph-derive-queue.json`) silently cleared itself
212
+ on every failed derivation, in every room, forever -- so no room has ever gotten a real
213
+ INFORMS/CONTRADICTS/CONVERGES cascade edge.** Root cause: `scripts/gsd-graph-derive-drain.cjs`'s
214
+ `drainDerive()` caught a `runDerivation` throw, pushed the failed room to `drained` anyway,
215
+ and rewrote the queue empty with no log -- destroying the retry signal on every SessionStart
216
+ run regardless of outcome. Two of the three originally-stacked causes (a dead standalone
217
+ Anthropic API key; the headless path computing a null single-pair derive with no real
218
+ artifact pairs) had already been resolved upstream by Phase 224-02's switch to local
219
+ embeddings; the silent-clear was the one still live. Fixed via a new `reconcileQueue()`: a
220
+ failed room is now kept and retried (capped at `MAX_DERIVE_ATTEMPTS=5`, then dropped
221
+ `permanent:true` and logged, never silently), and every failure appends to a new
222
+ `<room>/.mindrian/graph-derive-failures.json`. Root-caused by cross-referencing a TDS
223
+ "context rot" research thread against the live debug queue, not filed fresh. Full RCA:
224
+ `.planning/debug/graph-derive-silent-clear-dead-api-derivation.md`. Formalizing the
225
+ derivation transport, healing the ~16 already-damaged rooms, and a doctor health check
226
+ remain open (register items 4b-4e).
227
+ - **`room_search` applied its 50-result cap in raw filesystem-walk order, before any
228
+ relevance ranking -- so a genuinely relevant match in a late-traversed folder could lose to
229
+ 50 incidental early-folder hits, and a same-entity query could return dozens of
230
+ near-duplicate lines from one file.** Root cause: `lib/mcp/tools/room.cjs`'s `searchRoom`
231
+ was a plain `String.includes` grep that pushed matches in directory-entry order and
232
+ returned the instant either `SEARCH_MAX_RESULTS` (50) or `SEARCH_MAX_FILES` (500) was hit --
233
+ capped-then-never-ranked, not ranked-then-capped. Fixed via a rank-then-cap rewrite
234
+ (`collectMatches`/`rankMatches`): match density x0.7 + recency x0.3, with a 5-per-file slice
235
+ cap so one file can't monopolize the result budget. `graph_query` was audited in the same
236
+ pass and found already ranked correctly (a composite relevance score already lives in
237
+ `navigation/neighborhood.cjs`); `whitespace_scan` is unranked but uncapped, deferred pending
238
+ the graph-derive edge-density fix above (little signal to rank while most rooms carry
239
+ near-zero semantic edges). Full RCA: `.planning/debug/graph-query-results-unranked.md`.
240
+ - **The M:OS Canonical Design System v1.1 "bake into all HTML artifacts" mandate was only
241
+ ~30% actually landed, despite its own commit (`a9e1ee88`) and Phase 232-01 claiming it was
242
+ done.** The CSS bundle, loader (`mosStyleTag()`), and `lib/wiki/wiki-layout.cjs`'s
243
+ retokenization were real; `scripts/generate-deck.cjs`, `generate-hub.cjs`,
244
+ `generate-lobby.cjs`, and `generate-snapshot.cjs` had zero reference to `mosStyleTag()`,
245
+ `dashboard/index.html` had zero M:OS tokens, and the mandate's own doc
246
+ (`skills/ui-system/rules/design-system.md`) and `SKILL.md` section 0 did not exist. Wired
247
+ all 4 generators to `mosStyleTag()` (cream default, `data-theme="light"`), injected canonical
248
+ tokens plus a role-based CSS variable alias layer into `dashboard/index.html` and
249
+ `dashboard/export-template.html` (legacy `--mondrian-*`/`--ds-*` names aliased onto canonical
250
+ values in place, not renamed), and authored the missing mandate docs. Code review caught 2
251
+ real regressions before this shipped: (1) 3 of the 4 generators' own pre-existing `<style>`
252
+ blocks redeclared the same token names with old hex values LATER in the document, so the
253
+ mandate rendered in the markup but had zero visual effect by CSS cascade -- fixed by removing
254
+ the colliding redeclarations; (2) `dashboard/index.html`'s dark-to-light polarity flip broke
255
+ hardcoded Cytoscape graph-label colors and hover overlays tuned for the old dark theme --
256
+ fixed by repointing them at the resolved ink values. Both independently re-verified via
257
+ Playwright (`getComputedStyle`) against live-regenerated output, not just diffs.
258
+
259
+ ## [1.15.3-beta.44] - 2026-07-23
260
+
261
+ ### Added
262
+ -
263
+
264
+ ### Fixed
265
+ - **Every Stop hook turn showed a raw Claude Code hook JSON validation error
266
+ ("Hook JSON output validation failed: - : Invalid input") instead of the intended calm
267
+ systemMessage, whenever `scripts/check-card-fire.cjs` force-blocked a turn to demand an
268
+ AskUserQuestion card fire.** Root cause: Claude Code's Stop-hook output schema does not
269
+ define a `hookSpecificOutput` variant for the Stop event at all (the union covers only
270
+ PreToolUse, UserPromptSubmit, and PostToolUse); including the key on a Stop envelope
271
+ rejects the WHOLE envelope (`additionalProperties: false`), not just that key, silently
272
+ replacing the carefully-set `decision`/`reason`/`systemMessage` with a raw schema-error
273
+ dump. This is the 4th live occurrence of the same defect class: fixed once in
274
+ `scripts/on-stop` (v1.10.9 -> v1.10.10, 2026-04-15), then reintroduced in
275
+ `scripts/feynman-minto-guardian.cjs` (under a since-corrected comment that had the rule
276
+ backwards), reintroduced again as a regression inside `scripts/on-stop` itself (the
277
+ Phase 198-09 MCP-first thin-adapter branch), and hit live by a real user via
278
+ `scripts/check-card-fire.cjs` today. Fixed at all 3 sites: `hookSpecificOutput` removed
279
+ outright (and removed from `check-card-fire.cjs`'s own envelope-key allowlist so it can't
280
+ silently slip back in); the calm, human-facing `decision`/`reason`/`systemMessage` fields
281
+ each branch already set are unaffected. New structural regression gate:
282
+ `scripts/check-hook-schema-compatibility.cjs` (previously unwired and, worse, encoding the
283
+ opposite/wrong rule) is corrected and wired into `scripts/verify-release` (section 16) --
284
+ it enumerates every script Claude Code registers as a Stop hook straight off
285
+ `hooks/hooks.json`, follows one level of subprocess invocation, and fails the release if
286
+ any of them would emit a Stop-shaped `hookSpecificOutput` again. Full RCA:
287
+ `.planning/debug/resolved/stop-hook-invalid-hookspecificoutput-schema.md`.
288
+
289
+ ## [1.15.3-beta.42] - 2026-07-23
290
+
291
+ ### Fixed
292
+ - **Windows-only: Python source interpolating shell variables directly (`normwin('$VAR')`) raised a
293
+ `SyntaxError` at Python compile time whenever the interpolated value contained a native Windows
294
+ path with a backslash, before `normwin()` ever ran.** This is the same family of bug as beta.40's
295
+ `os.rename`/`os.replace` fix, but one layer earlier: 36 interpolation sites across
296
+ `scripts/room-registry`, `scripts/resolve-room`, `scripts/update-icm-index`, and
297
+ `scripts/on-cwd-changed` built Python heredoc/`-c` source by quoting a shell variable straight
298
+ into the source string, instead of passing it through `sys.argv` (the safe pattern
299
+ `room-registry`'s own `_write_current_room()` already used). Fixed by converting all 36 sites to
300
+ `sys.argv`-based parameter passing. New regression suite
301
+ (`tests/test-room-registry-windows-python-interp.cjs`, 29/29) includes a load-bearing control
302
+ proving the old interpolation shape fails to compile on a backslash value and the new
303
+ `sys.argv` shape does not. Root-caused and fixed same day as beta.40; independently verified live
304
+ on the reporter's Windows install.
305
+ - **Windows-only: the regression tests' own Python-probe spawn mechanism corrupted probe source
306
+ containing a backslash-next-to-a-quote, at the Windows `CreateProcess` argv-marshalling
307
+ boundary.** Three test files (`test-room-registry-windows-atomic-replace.cjs`,
308
+ `test-room-registry-windows-python-interp.cjs`, `test-room-registry-windows-path.cjs`) each
309
+ routed a Python probe body through `bash -c` as a positional argv element, to survive the two
310
+ bugs above -- safe on Linux/macOS, but Node spawning `bash.exe` on Windows re-quotes argv before
311
+ bash's own `$1` expansion runs, silently mangling the probe. Fixed by writing the probe source to
312
+ a temp `.py` file and executing the file path instead of the source text -- a path has no
313
+ embedded quote/backslash-adjacent-quote sequences, eliminating the defect class rather than
314
+ special-casing which characters are unsafe. Verified green on this dev box (21/21, 29/29, 25/25,
315
+ byte-identical to pre-fix baselines); Windows re-verification of this exact patch is pending.
316
+
317
+ ## [1.15.3-beta.40] - 2026-07-23
318
+
319
+ ### Fixed
320
+ - **Windows-only: the room registry silently wedged after the first write, every write
321
+ after that returning non-zero and never sticking.** Root cause: Python's `os.rename()`
322
+ is not POSIX `rename(2)` on Windows -- it raises `FileExistsError [WinError 183]` when
323
+ the destination already exists, instead of overwriting. Every atomic-write tmp-swap in
324
+ `scripts/room-registry`, `scripts/resolve-room`, `scripts/update-icm-index`, and
325
+ `scripts/on-cwd-changed` used `os.rename(tmp, dst)`, so the first write to a destination
326
+ (cold room creation, first `/mos:rooms list`) always succeeded and looked healthy, while
327
+ every subsequent write (`set-active`, `update`, `archive`, git-config sync) silently
328
+ wedged on Windows, leaving an orphaned `.tmp` and a frozen registry. This repo's own test
329
+ suite runs only under WSL/Linux, where `os.rename` already overwrites happily, so the gap
330
+ was invisible to CI for the test suite's entire lifetime. Found live by a Windows install
331
+ testing v1.15.3-beta.38; fix and both the semantics claim and the end-to-end unwedge were
332
+ independently re-verified live on that same Windows install. Fixed: `os.rename` ->
333
+ `os.replace` at all 9 call sites (byte-identical behavior on Linux/macOS, overwrite-safe
334
+ on Windows). New regression test (`tests/test-room-registry-windows-atomic-replace.cjs`)
335
+ and a new release-time gate (`scripts/verify-release` section 15) now fail the release if
336
+ a bare `os.rename(` reappears anywhere in `scripts/`.
337
+
338
+ ## [1.15.3-beta.38] - 2026-07-23
339
+
340
+ ### Changed
341
+ - **Brain default endpoint flipped from the legacy `mindrian-brain.onrender.com` (Neo4j Aura
342
+ + Pinecone) to the new Memgraph-backed `pws-brain-mcp.onrender.com`** (`lib/core/brain-client.cjs`).
343
+ Step 4 of the approved phased Memgraph migration (step 1, an auth-header double-Bearer-prefix
344
+ fix, shipped dark in beta.36's line). Live-verified against the real production endpoint with
345
+ a real key before flipping: `brain_search`/`brain_schema`/`brain_stats`/`brain_ask`/
346
+ `brain_ask_anything` all return real data (28k+ nodes) through the exact same response shapes
347
+ this client already parses. `brain_query`/`brain_write` (raw Cypher) remain admin-tier gated on
348
+ the new server exactly as they already were on the old one -- regular users never had raw-Cypher
349
+ access on either server, so this introduces zero regression; every caller already degrades
350
+ gracefully to a Tier-0 fallback. Every existing Brain API key works unchanged (same Supabase
351
+ `brain_api_keys` Bearer contract) -- no action required from any user. `MINDRIAN_BRAIN_URL`
352
+ still overrides the default for staging/self-hosted use, unchanged.
353
+
354
+ ## [1.15.3-beta.36] - 2026-07-22
355
+
356
+ ### Fixed
357
+ - **`room_bind` could never write a per-session room binding on stdio, so all CLI sessions
358
+ fell back to one shared, unlocked `registry.json` active-room field.** Root-caused via a
359
+ full RCA (`.planning/debug/resolved/registry-active-room-concurrent-session-collision.md`):
360
+ `writeSessionBinding` -- the only function that sets a session's own room binding -- has
361
+ exactly two call sites, both gated behind `room_bind`'s `effectiveSessionId` check, which
362
+ requires the MCP SDK's `extra.sessionId` (never populated on stdio) or an explicit
363
+ `sessionId` argument (nothing supplied one automatically). So no CLI session could ever
364
+ populate its own binding, and every session's write-target resolution fell through to a
365
+ single global field that multiple concurrent `claude` CLI processes on one machine then
366
+ raced to overwrite -- confirmed live with 4 concurrent sessions on this machine, one
367
+ session's active room silently clobbering another's. Fixed: `room_bind` now falls back to
368
+ `process.env.CLAUDE_CODE_SESSION_ID` as a third-priority session identifier on stdio
369
+ (precedence: explicit param > SDK `extra.sessionId` > `CLAUDE_CODE_SESSION_ID` >
370
+ `no_session_id`), so a CLI session can finally write a real per-session binding. The
371
+ separate F.8 binding-ambiguity-card logic in the same handler is untouched. New test:
372
+ `tests/test-room-bind-stdio-session-fallback.cjs` (4 assertions); all 21 pre-existing tests
373
+ touching `room_bind`/`tool-router.cjs`/`session-binding.cjs` still pass.
374
+ - **`write-scope-check.cjs`'s own session-identity fallback checked the wrong environment
375
+ variable name.** It read `process.env.CLAUDE_SESSION_ID`, which this runtime never sets;
376
+ the real variable is `CLAUDE_CODE_SESSION_ID`. Fixed with a backward-compatible fallback
377
+ chain (`CLAUDE_CODE_SESSION_ID` first, legacy `CLAUDE_SESSION_ID` second) so existing test
378
+ fixtures that set the old name are unaffected. A smaller contributor to the active-room
379
+ confusion above than first estimated (this hook already preferred the hook payload's own
380
+ `session_id` ahead of the env check), but a real, worthwhile correctness fix on its own.
381
+
382
+ ## [1.15.3-beta.34] - 2026-07-21
383
+
384
+ ### Fixed
385
+ - **Stop hook (`check-card-fire.cjs`) force-fired a stale Decision-Gate card on unrelated
386
+ terse turns.** Fourth live occurrence of the over-enforcement class (dominant reason
387
+ `reached-registry-gate-no-card`, 30 of 41 records in a 24h diagnostic window). Two stacked
388
+ root causes: (1) `lib/core/card-fire-sidechannel.cjs`'s reach-mint record had no
389
+ session/turn scoping and a 10-minute TTL, so one real gate mint anywhere leaked into every
390
+ later turn, every session, for 10 minutes; (2) `lib/core/gate-relevance.cjs`'s
391
+ `gateTopicallyRelevant` defaulted to force-fire whenever the preceding user text carried
392
+ fewer than 2 subject tokens -- true for nearly every terse slash command, so short turns
393
+ were the LEAST protected against a stale gate. Fixed structurally: a turn-scoped freshness
394
+ window (`TURN_FRESH_MS`) replaces the unscoped union, and the relevance floor now checks
395
+ gate staleness (`opts.gateStale`) instead of defaulting to force on low signal. A model
396
+ that has already judged a reach-card gate stale and moved on in prose is no longer
397
+ overridden by the hook. Verified end-to-end against the live incident shape; full
398
+ card-fire/gate-relevance/connector-registry suites green.
399
+
400
+ ## [1.15.3-beta.32] - 2026-07-20
401
+
402
+ ### Added
403
+ - **BlockNote Wiki Convergence (Phase 232): `/mos:wiki` gets a real editing surface.** The wiki
404
+ now opens to a Room Home dashboard (governing thought, Larry's Briefing, gaps, per-section
405
+ progress) instead of the graph, and every article is directly editable in a BlockNote surface
406
+ themed to the M:OS Canonical Design System -- edit, click Save, the change writes straight to
407
+ the room's `.md` file (no confirmation dialog, no conflict check, by design). `[[wikilinks]]`
408
+ render as clickable pills inside the editor; Backlinks and See Also stay wired to the existing
409
+ SQLite graph edges. New: per-article PDF and Word export, and a real `/mos:wiki --export`
410
+ static-share bundle (previously documented, never implemented). The client bundle (React +
411
+ BlockNote) is walled off in its own `lib/wiki/editor-src/` build, so the plugin's own
412
+ dependencies stay CJS-only -- zero React/Next.js/BlockNote added to the install footprint.
413
+ A live browser walkthrough caught and fixed two integration bugs before ship (a save/load URL
414
+ encoding mismatch, and a JSON-vs-plaintext response contract mismatch that would have written
415
+ raw JSON into article files instead of markdown).
416
+
417
+ ## [1.15.3-beta.30] - 2026-07-20
418
+
419
+ ### Added
420
+ -
421
+
422
+ ## [1.15.3-beta.28] - 2026-07-18
423
+
424
+ ### Added
425
+
426
+ - **Eureka: killed two distinct causes of unusable portfolio-scan output, plus a warm-cache
427
+ MCP path.** Live-verified on two independently-chosen real rooms
428
+ (`aion-eureka-synergy`, `iia-deeptech-centers`), not just fixture-green.
429
+
430
+ - **Seam 2 (statement-metadata gap, RESOLVED).** Every entity-entity Opportunity Statement
431
+ was rendering the literal placeholder text "unknown x unknown approach to a unknown x
432
+ unknown cross-domain bridge" instead of a real mechanism. Root cause: Phase 218 wired
433
+ entity nodes (company/technology/market) into the 215 opportunity-statement pipeline but
434
+ patched only the `title` slot for that node class, leaving `section`/`primary_problem`/
435
+ `problems`/`shared_problems` falling through to content-node defaults entity nodes
436
+ structurally can't satisfy. Fixed in `lib/core/eureka/room-native-substrate.cjs`: the
437
+ entity-node branch now inherits `section` from its already-shipped `DESCRIBES` edge to its
438
+ source memory_artifact (55/56 entities have one; pure composition over an existing edge,
439
+ sibling of the prior title fix), with an `entityType` fallback for the remainder, plus a
440
+ relation-edge-typed bridge label (`competes-with`/`uses-component`/`supplies-to`) in
441
+ `scripts/eureka-portfolio-report.cjs` instead of the generic phrase. 22/25 -> 0/25
442
+ "unknown x unknown" statements on both proving rooms.
443
+ - **Seam 3 (candidate-generation gap, RESOLVED).** Real content was getting ranked against
444
+ its own containing section (`problem-definition` x `problem-definition`) because `Section`
445
+ container nodes -- the room's own top-level folder nodes -- were admitted as pairing
446
+ candidates. The critic already had a correctly-firing rejection tag for this
447
+ (`domain_swap_invariant`) but nothing upstream excluded these pairs before they consumed
448
+ ranked-list slots. Fixed with an additive either-endpoint `Section` exclusion at the same
449
+ generation-layer insertion point the 260715-0nj scaffold-pair fix established (Reuse
450
+ Before Build), with an honest `container_pairs_excluded` counter surfaced in provenance.
451
+ 1,575 degenerate pairs (9.8% of the candidate set) excluded on `iia-deeptech-centers`;
452
+ proven a true no-op on `aion-eureka-synergy` (0 Section nodes there) by output diff, not
453
+ by assertion.
454
+ - **New: `eureka-run`/`eureka-status`/`eureka-report` on the `intelligence` MCP router
455
+ tool.** Calls the same governed dispatcher (`scripts/eureka-command.cjs` `main(argv)`)
456
+ in-process instead of spawning a fresh child process per call, so
457
+ `embedding-spine.cjs`'s existing module-level encoder cache stays warm across scans on
458
+ the Phase 198 resident daemon (spiked: same-process call 2 is 0ms vs. call 1's 179ms,
459
+ unmodified cache mechanism). Transport-gated per call: in-process on the http resident
460
+ daemon; detached child on stdio, since `process.stdout` is the JSON-RPC framing channel
461
+ there and the scan writes progress to stdout. Registered on the `intelligence` tool's
462
+ enum only, outside the 65-command CLI/MCP parity array (mirrors the `eureka_critic`
463
+ precedent). `/mos:eureka`'s CLI behavior and output contract are unchanged (verified
464
+ byte-identical).
465
+
466
+ Flagged, deliberately out of scope for this pass: Seam 1 (entity-extraction noise --
467
+ generic-noun and near-duplicate entities still reach the ranker on `aion-eureka-synergy`,
468
+ tracked separately) and a newly-observed WhitespaceZone-dominated pairing pattern on
469
+ `iia-deeptech-centers` (AHP composite doesn't differentiate a room where ~87% of nodes are
470
+ whitespace hypotheses) -- both real, both future work, neither papered over.
471
+
472
+ - **Phase 230: MindrianOS Skill Fleet Optimization -- the harness for testing whether
473
+ MindrianOS's own 124 skills trigger correctly and stay quiet when they shouldn't.**
474
+ Two workstreams. WS1 (trigger-accuracy, all 124 skills): per-family eval-query generation
475
+ exploiting sibling near-misses, a roster-wide judge funnel (one call scores a query against
476
+ all 124 skill descriptions at once, catching competitive collisions isolated per-skill
477
+ grading structurally cannot see), flagged skills escalate to a real live trigger-test loop
478
+ with train/validation-gated description revision. WS2 (code-quality, the ~59 script/
479
+ workflow-backed skills -- the design estimated ~10-20, the real inventory came in ~3x
480
+ higher, disclosed rather than silently re-scoped): adversarially-verified review
481
+ (Refute-or-Promote) with a deterministic evidence-quote anchor so a fabricated finding
482
+ cannot reach the report. Live-smoke-tested end to end on a 13-skill human-approved
483
+ calibration set (`scripts/skillopt-*.cjs`, `lib/core/skillopt-schemas.cjs`,
484
+ `tests/run-all-230.sh`, 9 deterministic legs): the real Skill-fire detector proved correct
485
+ in both directions on fresh live captures (it turned out MindrianOS's own `mos:` skills
486
+ fire via an MCP tool call, not Claude Code's native Skill tool -- caught before anything
487
+ was built on the wrong assumption), and WS2 independently re-discovered the real
488
+ `check-card-fire.cjs` over-enforcement defect (see Fixed, below) with zero false positive
489
+ on a known-clean control. The smoke calibration gate itself came in under tolerance (30%
490
+ agreement vs. an 85% bar) -- accepted as informative, not blocking, since most of the gap
491
+ is real full-roster collisions a human's isolated pre-labels couldn't see plus one disclosed
492
+ query-labeling bug; the reconciliation (fix the labeling bug, re-run smoke) is tracked as
493
+ SEED-061, not silently dropped. **The full 124-skill fleet run and any multi-agent
494
+ Workflow-tool orchestration are explicitly deferred behind a future opt-in -- this release
495
+ ships the harness, not a fleet run.** Nothing was ever written to a real `SKILL.md` or
496
+ script; every proposed change surfaces in a human-approved report only.
497
+
498
+ ### Fixed
499
+
500
+ - **`check-card-fire.cjs` no longer force-fires the Decision-Gate card on plain prose with no
501
+ actual gate.** Two independent over-fire mechanisms, logged three times across 12 days
502
+ (2026-07-05, 2026-07-11, 2026-07-17) before being root-caused against a live 17-record
503
+ intercept-log replay: (1) the backstop's bare numbered-prose detector had a 7/7
504
+ false-positive rate in the logged evidence and zero true catches -- retired outright,
505
+ genuine ASCII-box degrades stay caught by the separate bracket-arm detector, unchanged;
506
+ (2) the primary registry-gated path fired on `ran_entries` alone, which a side-channel
507
+ session-key/TTL union bled into every turn for roughly 10 minutes regardless of relevance
508
+ -- now requires a confirmed, non-empty gate-subject plus relevance against that real
509
+ subject. Verified against all 7 real logged firings (0/7 re-fire) plus the full
510
+ card-fire-specific suite (11/11 + 27 assertions). Trade-off, disclosed not hidden: a lone
511
+ genuine numbered-prose fork no longer force-fires at the hook level and now depends on the
512
+ model's own Phase-210/SEED-021 judgment -- the same trust boundary the existing
513
+ under-firing watch (`feedback_false_success_silent_skip_gates_academy_testers.md`) already
514
+ tracks from the opposite direction.
515
+ - **Per-session room binding no longer re-prompts every turn after a real bind.** The MCP
516
+ `room_bind` tool wrote the session's binding state keyed by the actual Claude session UUID;
517
+ the CLI `UserPromptSubmit` hook read it keyed by `process.env.CLAUDE_SESSION_ID`, which is
518
+ unset in that hook's execution context, so it silently fell back to a
519
+ `sha256(roomDir+day)` hash key that never matched -- confirmed with an exact hash-vs-
520
+ on-disk-filename proof, not inferred. The hook now reads the real session id from its own
521
+ stdin payload first. This also un-breaks Phase 225's zero-score gate (SEED-039), which
522
+ shared the same key-mismatch root and was never separately regressed -- just never covered.
523
+ - **The reach/navigation dial no longer offers a topically-unrelated room or claim with no
524
+ relevance check.** `cross_room` was a permanent member of the reach candidate bank, always
525
+ offered in a cold room's top-3 regardless of what the live conversation was actually about,
526
+ filtered only by advisory instruction text the model had to apply itself. A structural
527
+ relevance gate (`lib/hmi/reach-relevance-gate.cjs`) now suppresses off-topic candidates by
528
+ token overlap against the live turn before they're ever offered; `cross_room` also no
529
+ longer "borrows from itself" (filling its own room-name slot with the current room).
530
+
531
+ ## [1.15.3-beta.26] - 2026-07-16
532
+
533
+ ### Added
534
+ - **Phase 227: Ignite / mode-select timing across turns 1-4 (SEED-060).** The session-start
535
+ mode-selection Decision Gate (Just Talk / Explore+Capture / Build a Room) gets a structural,
536
+ advisory-only backstop: a new `doctor.cjs` check class (`lib/core/mode-select-sidechannel.cjs`
537
+ + `lib/core/doctor/mode-select-checkpoint-module.cjs`) detects a silent skip (the gate neither
538
+ firing a card nor stating a default) and warns, never blocks, never re-fires the gate itself.
539
+ A systemic sweep of methodology skills for the same loose-description auto-fire bypass that
540
+ let `trending-to-absurd` reach for itself on a casual remark before its 2026-06-24 fix found
541
+ and closed 3 trivial instances inline (`MOSDeckEngine`, `client-discovery-interview`,
542
+ `mullins-scaffold`), with the rest reported and explicitly deferred. A scripted regression
543
+ test (`tests/test-227-frontdoor-restraint.cjs`) now proves the front-door restraint fix holds
544
+ without needing a live human tester re-run. `skills/larry-personality/SKILL.md` names ignite
545
+ for the first time and documents the gate's timing with real Hooked-Model (Fogg B=MAP / TARI)
546
+ reasoning: the gate is a Prompt, not an Investment, fired only when the navigator's opener
547
+ does not already signal a lane. `conversation-mode`'s Mode 3 (Build a Room) now routes through
548
+ ignite's Directive/`--express` path instead of calling `/mos:new-project` directly, correctly
549
+ reserving Gate B1's four-door persona pick for sessions that genuinely have not yet
550
+ established a role or venture. Same-day code review (independently re-verified, not
551
+ self-certified) caught and fixed two real defects before this landed: Mode 3's routing text
552
+ originally claimed established context unconditionally even on a cold direct pick with zero
553
+ prior exchange, and the mode-select "card-fired" recorder was wired against text no live code
554
+ path actually renders through `pickShape()`. Both closed at the design level in this same
555
+ pass (independently re-verified: 8/8 + 4/4 tests pass, both diffs re-read after landing),
556
+ not patched around the symptom.
557
+
558
+ - **Phase 229: HUJI Pitch Feedback Module (IN PROGRESS, 8/9 plans -- not yet shipped).**
559
+ MindrianOS's first paying job: turn each student's diarized 5-minute pitch transcript into
560
+ one Minto-structured formative feedback artifact, batch-orchestrated across 200+ submissions
561
+ at a $4-5/unit cost ceiling, local-only scoring (Brain read-only, generic handles per Canon
562
+ Part 8). Built so far: the evidence/feedback zod schemas plus generated JSON Schema
563
+ (`229-01`); a labeled test-inventory harness and `run-all-229.sh` aggregator (`229-02`);
564
+ deterministic code checks covering quote verification, recall, drift, schema validation,
565
+ similarity, cost, and Part-8 hygiene (`229-03`); the `PWS_grading` recipe with a
566
+ score-and-continue rubric (`229-04`); a Stage A intake adapter porting the Claims-Aware
567
+ Fusion Mode A pipeline (`229-05`); an LLM judge spawner with a calibration protocol that
568
+ fails closed below a 0.7 anchor-hygiene bar (`229-06`); and a single-submission runner plus
569
+ batch orchestrator with pool/ledger/resume/retry and G1-G6 per-unit guardrails (`229-07`,
570
+ `229-08`). Not yet shipped: `229-09`, the mandatory demo run and human verdict checkpoint
571
+ this whole pipeline is gated on before any real submission gets scored -- per this project's
572
+ own standing rule, judge accuracy and calibration are a human-verify bar, never
573
+ self-certified.
574
+
575
+ ## [1.15.3-beta.24] - 2026-07-16
576
+
577
+ ### Added
578
+ - No discrete feature completed exactly at this tag. This pre-release snapshot captured
579
+ in-progress work on Phase 227 (the mode-select firing checkpoint, sweep, and Mode 3 routing
580
+ work) and Phase 229 (the HUJI Pitch Feedback Module's Stage A intake adapter) mid-flight,
581
+ neither phase-complete at cut time. Phase 227's full, finished feature set is recorded once
582
+ under `[Unreleased]` above rather than split and duplicated across the interim tags it
583
+ happened to span.
584
+
585
+ ## [1.15.3-beta.22] - 2026-07-16
586
+
587
+ ### Added
588
+ - No discrete feature completed exactly at this tag either, for the same reason as beta.24:
589
+ a pre-release snapshot mid-flight through Phase 227 (the skill-description sweep landed
590
+ here) and Phase 229 (PWS_grading recipe + score-and-continue rubric work).
591
+
592
+ ## [1.15.3-beta.20] - 2026-07-16
593
+
594
+ ### Added
595
+ - **Backfilled here (this changelog under-documented this tag at cut time): five phases that
596
+ actually shipped in this release window.**
597
+ - **Phase 222 (reach-ranking-unification).** The three surfaces that suggest a next move
598
+ (`/mos:suggest-next`, the reach-candidates list, and the auto-fire engine) now always
599
+ agree on the top pick instead of occasionally diverging, and the ranking improves over
600
+ a room's own accept/reject history rather than staying static.
601
+ - **Phase 223 (jtbd-driven-intelligence-pipeline, governed bono).** `/mos:bono` is now an
602
+ 8-phase governed research debate with Six-Thinking-Hats-style scrutiny (the Black hat
603
+ must disconfirm first, the White hat must cite-or-retract) and three explicit navigator
604
+ approval pauses (topic, hypothesis, ruling) instead of one collapsed confirmation. New
605
+ `/mos:intel-pipeline` command runs a staged research pipeline oriented on the room's
606
+ active JTBD (calibrate -> decompose -> fan out -> compute -> synthesize -> close), pausing
607
+ for approval twice and disclosing a thin fan-out pass rather than silently proceeding.
608
+ - **Phase 224 (graph-derivation-harness, SEED-034).** Every markdown write to a room now
609
+ enqueues and background-derives typed graph edges, closing the previously twice-reconfirmed
610
+ 0-typed-edge gap on the write path -- no manual "derive" step required.
611
+ - **Phase 225 (per-session-room-binding, SEED-039).** Session-to-room binding now correctly
612
+ supports multi-room binds. Fixed during code review: a binding answer was silently
613
+ collapsing a multi-room selection down to a single room regardless of what was picked.
614
+ - **Phase 226 (eureka-reasoning-mode-fallback, SEED-058).** When the local embedding encoder
615
+ is unavailable, `/mos:eureka` now degrades to a labeled, lower-confidence REASONING MODE
616
+ result (a real short ranked list with an honest caveat naming the degrade cause) instead of
617
+ a hard `pairs_scored: 0` dead end. `banked` is structurally `false` on every reasoning-mode
618
+ row; a later healthy re-run over the same room surfaces the reasoning-to-embedded delta
619
+ instead of silently replacing the earlier result. Same-day code review found and fixed
620
+ three real data-loss edge cases in the upgrade-delta path before this shipped (a repeated
621
+ degrade could silently overwrite a completed reasoning report with no trace; a stale
622
+ session file could let a later healthy run get clobbered; reseeding could silently orphan
623
+ an in-progress judging session).
624
+ - **Eureka entity extraction gets a two-tier WHAT-vs-WHY classifier.** A free, fully local
625
+ embedding pass (`lib/core/eureka/embedding-classifier.cjs`) now resolves the confident
626
+ majority of candidates at zero API spend, reusing the same encoder Eureka's own ranking
627
+ already depends on. The existing LLM classifier is demoted to an escalation-only path,
628
+ called per artifact only for the genuinely ambiguous residual the embedding tier cannot
629
+ confidently place. Measured on a real room: 61.1% of candidates resolve locally and
630
+ correctly, 14.3% fewer artifact-level LLM calls. Honest degrade throughout: no LLM key
631
+ means a disclosed low-confidence embedding best-guess, never a silent default; `classifier_source`
632
+ now reports `embedding` / `model` / `mixed` / `fallback` so every result states which tier
633
+ produced it. Tunable via `MINDRIAN_WHATWHY_MARGIN` (default 0.10, calibrated against a
634
+ measured holdout set). (Quick task 260714-k44.)
635
+
636
+ ### Fixed
637
+ - **A low-confidence WHY term is no longer structurally indistinguishable from a confident one.**
638
+ When Eureka's two-tier classifier places a candidate as a framework (WHY) term but has no
639
+ working LLM to confirm the low-margin embedding best-guess, the term lands in the artifact's
640
+ `framework_terms` prop. The only trace that the guess was low-confidence lived in the aggregate
641
+ `status.json` counter (`tier2_low_confidence`); once written onto the node, the guess looked
642
+ exactly like a confidently-resolved term. Each term that lands via the no-LLM degrade path is
643
+ now disclosed per-term in an additive `framework_terms_low_confidence` sibling prop (always a
644
+ subset of `framework_terms`); a confident later run removes the marker; and existing readers of
645
+ `framework_terms`, which stays a plain comma-joined scalar, are unaffected. The 219 metadata
646
+ test now pins the disclosure so it cannot silently disappear. Caught live by the run-all-221
647
+ regression chain. (Quick task 260715-cu8.)
648
+ - **Eureka's ranked top-25 no longer refills with scaffold pairs when real entities are thin.**
649
+ Every room stores one `memory_artifact` node per file as document scaffolding. When a room's
650
+ real-entity cohort is thin, those scaffolding nodes were pairing with each other and flooding
651
+ the ranked top-25 with `memory_artifact`-vs-`memory_artifact` pairs that carry no cross-domain
652
+ signal (measured at 72.0 percent of the top-25 on a live room once entity extraction correctly
653
+ thinned the entity population). Scaffold-vs-scaffold pairs are now excluded from the ranked-pair
654
+ candidate set by construction, at the point the candidate list is built, so the structural share
655
+ drops to 0.0 percent on the same live substrate regardless of how sparse the real entities are.
656
+ The exclusions are counted honestly in the report provenance (`scaffold_pairs_excluded`, in both
657
+ the JSON and the markdown table), never silently dropped. Pairs with only ONE scaffolding side
658
+ are unaffected, so a real entity paired with the artifact it came from still ranks. (Quick task
659
+ 260715-0nj.)
660
+ - **Eureka's entity-extraction pre-step no longer fails silently.** `/mos:eureka run`'s
661
+ auto-extraction step (shipped in beta.18) could fail (a thrown error, or the more likely
662
+ internally-caught non-zero return) with zero visible trace: exit 0, status `done`, nothing
663
+ in the report to say extraction never actually populated the graph. This reproduced the
664
+ exact false-success shape found in a live intern QA session. Failures on both paths now
665
+ surface as an additive `extraction_error` field in the eureka status.json plus one stderr
666
+ line; ranking, fallback behavior, and exit codes are unchanged (the degrade-never-throw
667
+ contract stays intact, only the silence is gone). Proven via a RED-then-GREEN reproduction
668
+ test wired into the permanent suite. (Quick task 260714-jjm.)
669
+
670
+ ## [1.15.3-beta.18] - 2026-07-13
671
+
672
+ ### Added
673
+ -
674
+
675
+ ## [1.15.3-beta.16] - 2026-07-13
676
+
677
+ ### Added
678
+ - **Opportunity follow-through: surfaced opportunities stop dying as files and one-liners.**
679
+ Every opportunity now flows through the Harvest Formula lifecycle (candidate -> qualified ->
680
+ explored -> promoted | parked | retired) as a real graph node with append-only stage history -
681
+ who advanced it, why, and on what evidence, at every step.
682
+ - **Eureka statements now bank as proposed opportunity nodes.** The portfolio scan's ranked
683
+ statements get a REAL awaited Grounding Guard verdict (a bounded async resolution pass over
684
+ the Phase 212 critic - previously the sync emitter could never await it, so nothing ever
685
+ banked on a live run). Statements the critic passes bank as `opportunity` nodes with
686
+ DERIVED_FROM evidence edges; statements it rejects stay honestly unbanked with the verdict
687
+ named. Tunable via `MINDRIAN_OPPORTUNITY_BANK_PREDICATE` (critic | critic+tail | all).
688
+ - **Harvest sensor (SENS-14): graph events become scored opportunity candidates.** A producer
689
+ on the insight-sensor rail harvests candidates from five lanes (eureka proposals, bridges,
690
+ contradictions, whitespace, meeting filings), classifies each through the Gibson Four-Lens
691
+ (leveraging_resources / challenging_orthodoxies / understanding_needs / harnessing_trends),
692
+ and scores them with HarvestIndex_v1. The bridge lane rides the real extraction edge
693
+ vocabulary (COMPETES_WITH / USES_COMPONENT / SUPPLIES_TO), so it finds genuine cross-entity
694
+ signal on real rooms, not just fixture edges.
695
+ - **Qualification Decision Gate (`/mos:qualify-opportunity`).** Harvested candidates come to
696
+ YOU at a real card showing why each one qualified (Q1..Q8 rubric verdicts + machine-readiness
697
+ components; an unknown is typed `unknown`, never a fabricated zero). Five verbs:
698
+ Qualify+file, Park, Retire, Explore, Skip. A Skip writes a typed REJECTED_BECAUSE edge -
699
+ rejection is data the ranker learns from. Nothing qualifies without your explicit verb.
700
+ - **[Explore]: one explicit action turns a qualified opportunity into deep research**
701
+ (`/mos:explore-opportunity`). Runs the explored-stage chain - deep research, diffusion and
702
+ timing, analogies, web validation - and files a Minto-shaped opportunity artifact (governing
703
+ thought + SCQA + cited sources) into `opportunity-bank/` plus a research corpus artifact into
704
+ `research/`, both through the navigation.cjs gates with typed evidence edges. When the
705
+ engine cannot run, the surface OFFERS an LLM manual fallback at a card - honestly labeled
706
+ `engine_mode: llm_manual_baseline`, never silent, never the default.
707
+ - **Frontmatter metadata extraction slice.** Artifact frontmatter (methodology, status,
708
+ created) now lands as graph properties during extraction, so engines reason over what the
709
+ files already declare.
710
+ - **Web ingestion agent: any URL becomes room knowledge in one governed move.** Paste a link
711
+ in conversation, or run `/mos:research <url>`, and after you approve at the card the page
712
+ is fetched (Tavily Extract, server-side clean markdown), filed as a cited research artifact
713
+ in `research/` (source URL, capture date, content hash, review status: proposed), and its
714
+ entities land in the room graph so every engine can use them. Nothing is ever fetched or
715
+ filed without your explicit verb ([Ingest] [Ingest+Explore] [Skip]).
716
+ - **Pasted-URL sensor (SENS-15).** A bare URL in your turn offers an ingest card
717
+ contextually - it never auto-files, and it stays quiet for URLs inside code blocks,
718
+ quotes, or ones the room already ingested.
719
+ - **Content-hash idempotency + SUPERSEDES versioning.** Re-ingesting an unchanged page is an
720
+ honest no-op; a changed page files a NEW version linked to the prior one - history is
721
+ append-only, nothing is overwritten.
722
+ - **Watched sources: crawl-and-learn on cadence.** Register sources in
723
+ `.mindrian/watched-sources.json` and the scout cadence re-ingests changed pages under a
724
+ per-run cap (default 2), with cadence provenance stamped on every artifact. Findings
725
+ surface as candidates at existing gates - never auto-qualified.
726
+ - **Provider honesty everywhere (research_mode envelope).** Every ingest and research run
727
+ names which provider produced the bytes (tavily-extract / webfetch / manual), which mode
728
+ it ran in (normal / web_degraded_local_fallback / local_only / insufficient_evidence),
729
+ and never reports success with empty results. A failed fetch is a typed refusal, not a
730
+ silent empty.
731
+ - **Part 8 + inbound safety on the new surface.** Outbound carries the URL only through the
732
+ audited egress chokepoint; inbound web content is data end to end (prompt-injection
733
+ inert, size-bounded, path-safe filing, no symlink escape), adversarially test-pinned.
734
+ - **Every research/recovery stage now produces a typed envelope instead of guessing from an
735
+ empty result.** `lib/core/recovery/stage-envelope.cjs` gives all 13 pipeline stages
736
+ (retrieval, discovery, filing, and more) one shared shape: status (ok / empty_valid /
737
+ degraded / failed / blocked), a named failure_class from a frozen 13-class vocabulary,
738
+ retryable, provenance, and timestamps. A zero-result stage and a broken stage used to look
739
+ identical (both "empty"); now they carry different, typed reasons, and a validator enforces
740
+ the pairing rules (a failed/blocked stage MUST name its class; an ok stage MUST NOT).
741
+ - **When a research engine breaks, Mindrian now recovers through a real 6-tier ladder instead
742
+ of just failing.** `dispatchRecovery` reads the typed envelopes and tries, in order: (0)
743
+ nothing wrong, (1) one bounded idempotent retry for a transient failure, (2) a local
744
+ governed substitute (your room's own corpus, or its cache, honestly labeled - never "live"
745
+ when it isn't), (3) an OFFERED high-effort LLM recovery pass at a Decision Gate (never
746
+ silent, never the default), (4) naming the smallest missing thing a human needs to fix (a
747
+ credential, an engine), (5) honest termination when nothing worked - a partial result
748
+ naming exactly which engines are still down, never a complete-looking bundle papering over
749
+ a gap.
750
+ - **Running out of Claude spend mid-recovery is now its own honestly-named failure, not a
751
+ retry loop.** `spend_limit_exceeded` is a structural, first-class failure_class: it forces
752
+ `retryable:false` at the moment the envelope is built (not just checked later), and
753
+ short-circuits straight past every retry/substitute/LLM-recovery tier to a plain human
754
+ message: "raise your limit at claude.ai/settings/usage, or wait for the monthly reset."
755
+ This closes a real gap this exact session hit: four parallel agents stalling out on an
756
+ account spend cap, with no honest way for the system to say so.
757
+ - **The high-effort LLM recovery pass runs through a 7-step, resumable, audited case file,
758
+ never a black box.** A gate-offered recovery run (diagnose -> plan -> execute -> validate
759
+ -> reconcile -> resume -> surface) journals every step to a real case file under
760
+ `.mindrian/recovery/<run_id>/` so a crash mid-run resumes exactly where it left off, never
761
+ re-doing completed steps. Five hard fences, each proven by an adversarial test: the
762
+ Brain-egress boundary can't be weakened from inside a recovery run, an unknown component
763
+ can never be silently upgraded to "supported," every write still goes through the one real
764
+ writer (no raw DB access from a recovery hook), a filing is only ever called "recovered" if
765
+ a readback actually confirms it landed, and hostile text embedded in a source (a fake
766
+ instruction, a fake tool call) is always treated as inert data, never executed.
767
+ - **Recovery outcomes are now honestly composed, never inferred.** `composeRecoveryResult`
768
+ derives one of five outcomes (recovered / partial_recovery / degraded_recovery /
769
+ manual_intervention_required / insufficient_evidence) strictly from what actually happened:
770
+ "recovered" requires every stage envelope to validate AND any attempted filing to be
771
+ readback-confirmed - one unconfirmed filing forces `partial_recovery`, never a false
772
+ "recovered." The result rides as an additive `research_mode` + disclosure field on every
773
+ touched surface (research, opportunity exploration, URL ingestion) without changing any
774
+ existing field's meaning.
775
+ - **A gap in one accessible corpus is never reported as "this doesn't exist."** The
776
+ vantage-error lesson from this exact release wave (an external research pass wrongly
777
+ concluded a shipped phase was "missing" because it only checked one gitignored, unpushed
778
+ corpus) is now a structural, permanent rule: the only gap scope the recovery composer can
779
+ ever emit is `corpus` (a provisional, vantage-scoped gap), never `project` (a claim of
780
+ project-level nonexistence) - enforced by a source scan that fails the build if that ever
781
+ changes, plus a permanent regression fixture that encodes this exact mistake so it can
782
+ never silently return.
783
+ - **14-class recovery matrix, offline and permanent.** Every named failure class (network
784
+ timeout, missing credential, contract violation, policy block, cadence-vs-on-demand,
785
+ multi-engine outage, spend limit, vantage-scoped gap, and more) is asserted end to end
786
+ through the real dispatch and controller seams, with zero network calls - two of the
787
+ fourteen (the vantage rule and spend_limit_exceeded) are locked as PERMANENT fixtures
788
+ precisely because this session discovered both the hard way.
789
+
790
+ ### Fixed
791
+ - **Windows FTS5 crash: eureka degrades bi-modal instead of dying.** On machines whose Node
792
+ SQLite lacks the FTS5 module, the tri-modal index used to crash the whole scan with
793
+ `no such module: fts5`. A capability probe now selects the backend up front: with FTS5 the
794
+ lexical leg runs as before; without it the scan runs honestly on the two remaining legs
795
+ (vector + graph) and stamps `fts_backend: absent (bi-modal degrade)` in provenance. Never a
796
+ crash, never a silent lie. Live-validated on the exact Windows machine that exposed the bug
797
+ (corepower-isolation, 219-VERIFICATION.md Section 4).
798
+
799
+ ## [1.15.3-beta.14] - 2026-07-12
800
+
801
+ ### Added
802
+ -
803
+
804
+ ### Fixed
805
+ - **The card-fire backstop no longer force-fires on benign numbered lists** (`scripts/check-card-fire.cjs`). The `ASCII_BOX_GLYPH_RE` alternative-4 shape (a bare `1. / 2.` numbered-prose list, added Phase 209-07) matched ANY 2+-item list on shape alone, so an ordinary Action Footer or a step-by-step explanation that shared any incidental vocabulary with the user's turn was misread as an unfired Decision Gate and hard-blocked. New `GATE_FRAMING_RE` co-requirement (CR-05): a bare numbered-prose list counts as a backstop hit ONLY when a choice-framing cue (`?`, or one of `which / would you like / pick / choose / select / type 1`) sits inside the matched span or the ~150 chars before it. Alternatives 1-3 (bracket notation, the `type 1, 2, or 3` literal, the multiline bracket box) stay unconditional, and `ASCII_BOX_GLYPH_RE` itself is byte-identical so the retry-key signature and the Phase 209 regex-matrix tests are untouched. The Phase 209 floor survives: a genuine hand-rolled fork carrying a framing cue still intercepts.
806
+ - **The Stop-hook `reason` slug no longer reaches the user as a fake "Stop hook error"** (`scripts/check-card-fire.cjs`, `buildEnforcementEnvelope`). The 2026-07-05 fix added a `systemMessage` on the premise that Claude Code surfaces `reason` as "Stop hook error: <reason>" ONLY when no `systemMessage` is present; live observation proved that premise FALSE (it renders `reason` regardless). The only lever is the `reason` CONTENT, so it is now a calm, human-safe phrase on BOTH the intercept and degrade branches (CR-06), never the internal slug. The slug is preserved for telemetry, relocated to the new local diagnostic log (below), not deleted. Confirmed `turnContextHash` never reads `reason`, so the bounded-escape retry key is unaffected (asserted by a new non-effect test).
807
+ - **New local-only intercept diagnostic log** (`~/.mindrian/card-fire-intercepts.log`, CR-07). Append-only JSONL written whenever the backstop intercepts or degrades, capturing `{ timestamp, session_id, reason (the ORIGINAL slug), gate_signature, ran_entries, matched_glyph_span, output_text (truncated ~4000 chars) }`. TTL-pruned on every write by the same `RETRY_TTL_MS` the retry side-file uses, so it cannot grow unbounded. Canon Part 8: LOCAL disk only (`~/.mindrian`), never the Brain, never a network wire. This turns the still-open "unexplained backstop trigger" mystery (`live-session-running-stale-plugin-cache-fixes-inert`) into a one-log-read diagnosis on its next occurrence.
808
+ - **`check-card-fire.cjs`'s `gate-is-simple-binary` exemption swallowed genuine two-option forced-choice forks, not just yes/no closers.** The exemption (added 2026-07-05 to stop over-firing on trivial binaries like "Want those?") used a bare `gateLabels.length === 2` cardinality check, which cannot distinguish that from a genuine two-way strategic fork ("run research vs build the plan"). An intern QA session missed 3 such forks in one session because each carried exactly 2 option labels. Now requires the labels to be YES/NO-SHAPED (new `lib/core/gate-relevance.cjs::isYesNoShapedGate`, extracted from `gateAlreadyAnswered`'s existing yes/no answer-matching), not merely 2-in-number, so a real yes/no closer stays exempt while a genuine 2-option fork force-fires like a 3+-way one. Post-merge integration fix: `GATE_FRAMING_RE` widened with a cardinality + choice-noun cue ("two options", "3 paths") so a fork phrased without a literal `?`/`which`/`pick` still force-fires, closing a gap CR-05 would otherwise have silently reopened. `.planning/debug/intern-w1-card-discipline-decay.md`.
809
+ - **MCP `room_state` reads re-resolve the active room per call, same as `room_content` writes** (`lib/mcp/tool-router.cjs`). The `status` / `analyze` / `compute-state` / `get-state` / `suggest-next` branches read the boot-time closure `roomDir` directly, so a mid-session room switch (or a room created after the MCP daemon booted) was invisible to them and `status` falsely reported "No room initialized" against a room with real content. Now reuses the same `resolveWriteTargetDir` resolver the beta.12 `room_content` write fix already proved correct. intern-w1-room-state-false-empty.
810
+ - **`intelligence:research` now actually fetches instead of echoing its own command spec** (`lib/mcp/tool-router.cjs`). The `research` sub-command fell through to the generic `buildContext()` doc+state-echo helper (the same fallback reasoning-only ops like `grade`/`whitespace` correctly use), so it deterministically returned `commands/research.md`'s own frontmatter and spec text for any input, with zero web fetch. Now special-cases `research` to invoke `research-context-extractor.cjs` -> `source-lens-driver.cjs` (Stage 1-4) and return real findings with source/url/evidence-tier; filing (Stage 6-7) stays a human decision per Canon Part 9 role 5, never auto-wired inside a single MCP call. `commands/mva-brief.md` (the only other `reach_id: deep_research` command) checked and confirmed unaffected -- it runs its own Bash script, not this tool. intern-w1-research-reach-broken.
811
+ - **STATE.md is now actually recomputed after a room-section write, not just reported as recomputed** (`lib/core/intelligence-cascade.cjs`, `lib/core/state-ops.cjs`). `scripts/compute-state` only prints the STATE.md body to stdout by design; it never writes the file itself, so every caller owns persistence. The automatic PostToolUse cascade (Step 8) and the MCP `room_state compute-state` command both discarded that stdout while reporting/implying success, so a filed artifact never updated the room's own intelligence layer in the same turn it was filed. Cascade Step 8 now captures and persists the stdout directly; `state-ops.cjs::computeState()` now persists at the single Node chokepoint, mirroring the pattern already correct in `scripts/on-stop` / `on-task-complete` / `on-agent-complete`. intern-w1-state-not-recomputed.
812
+ - **The session-start mode-selection Decision Gate (`skills/conversation-mode/SKILL.md`) can silently skip with zero detectable signal** (RCA `intern-w1-mode-gate-skip`). Root cause: two converging structural gaps. (1) `scripts/build-render-coverage.cjs::buildMdKeyspace()` walked only `commands/*.md`, never `skills/*/SKILL.md`, so a skill-declared `hitl_shape` Decision Gate could never register in `data/render-coverage-registry.json` -- PRIMARY detection was structurally blind to every skill-declared gate. New `buildSkillKeyspace()` (a third, additive registry keyspace, mirroring the existing commands walk) closes this; `skills/conversation-mode/SKILL.md` now registers as `declared_shape: F.1, wired: true`. (2) `scripts/check-shape-declaration.cjs` had no predicate catching a surface that self-declares BOTH a genuine `hitl_shape` fork AND `connector.excluded:true` (the no-fork exemption) at once -- a direct contradiction of this repo's own CLAUDE.md Part 11 text: "a render-only or pure-capability skill is exempt via its existing connector.excluded:true + reason, never via a fork it does not have." A new predicate now WARNs (advisory, non-blocking per the existing Phase 210 policy) on this exact contradiction. Extending PRIMARY detection to skills also surfaced 5 pre-existing, previously-invisible unwired skill declarations (`MOSDeckEngine`, `client-discovery-interview`, `intelligence-orchestrator`, `mullins-scaffold`, `mva-pipeline`) and 54 additional pre-existing hasShape-and-excluded contradictions beyond conversation-mode -- both are real, tracked findings surfaced for the first time by this fix, out of scope to resolve here, and named in `tests/test-209-declared-implies-wired.cjs`. `scripts/check-card-fire.cjs` (the Stop-hook backstop, the third converging gap in the original RCA) is untouched by this fix.
813
+ - **11 SKILL.md files documented `bash scripts/<name>` as if `scripts/` were skill-local** (`skills/rooms`, `publish`, `new-project`, `setup`, `room`, `file-meeting`, `wiki`, `vault`, `ingest-methodology`, `ignite`, `export`, plus `commands/new-project.md`). The scripts only ever existed at the plugin root, so any invocation with cwd != plugin root failed exit 127. Prefixed all 72 call sites with `${CLAUDE_PLUGIN_ROOT}` (quoted), the proven convention already used in ~38 other SKILL.md files, `hooks.json`, and `.mcp.json`. Also removed the co-located `PLUGIN_ROOT="$(dirname "$(dirname "$(readlink -f "$0")")")"` pattern (`skills/rooms/SKILL.md` Step 2.5, `skills/new-project/SKILL.md`, `commands/new-project.md`, referenced by `skills/ignite/SKILL.md`) -- confirmed broken under the Bash tool's actual invocation mechanism (`$0` resolves to the shell binary, computing `/usr` as the plugin root on every call) -- replaced with `${CLAUDE_PLUGIN_ROOT}` throughout. See `.planning/debug/intern-w1-rooms-skill-script-path.md`.
814
+ - **`/mos:rooms new` could silently fail to create a room while narrating success.** `scripts/resolve-room`'s legacy-fallback branch returned the pre-existing `room/` path with exit 0 (success) whether or not a new room was actually registered, indistinguishable from a real registry hit -- the direct mechanism behind a false "Room's live" claim when no `cv-project/` directory or registry entry ever existed. Added a `--strict` mode: a bare legacy fallback (no `--adopt`) now prints a `FALLBACK:` stdout marker and exits 2, never 0 -- fully backward compatible for every existing caller that omits the flag. Also tightened `skills/rooms/SKILL.md` Step 2's legacy-room adoption prompt to the same "FIRE THE CARD -- mandatory" doctrine `/mos:ignite`'s B1/B2 gates carry, added an explicit warning against narrating room creation before `birthRoom()` returns `{ok:true}`, and fixed the routing note that mislabeled Step 2 as "(name/slug capture)" (Step 1 captures the name/slug; Step 2 is the adoption check). See `.planning/debug/resolved/intern-w1-rooms-new-silent-fail.md`.
815
+ - **`/mos:doctor` Class H (`install-incomplete-module.cjs`) no longer false-positives "install incomplete" on a healthy one-command marketplace install.** Class H never received the `resolveActivePluginRoot()` topology-awareness fix Class A/Class I got in Phase 123 -- it only recognized a legacy `.install-receipt.json` or a user-level `statusLine` block as "healthy," neither of which a marketplace-cache install ever produces. Its `--fix` path then wrote a `~/.claude/settings.json` `statusLine` override pointing at the hardcoded legacy install path, which does not exist on a marketplace-cache-only machine -- silently breaking the statusline for the rest of the session (user-level settings override plugin-level; the broken exec never surfaced an error in chat). Class H now checks topology first and reports healthy without touching the legacy signals. `statusline-visibility-module.cjs` (Class G) also now tests the EFFECTIVE resolved statusline command (the user-level override if present, else the plugin's own), not always the plugin's own file, so the self-heal's re-verification can actually catch a broken override instead of reporting "ok" regardless. The SessionStart self-heal's outer timeout (`scripts/check-onboard-statusline.cjs`) is raised from 4000ms to 10000ms to clear its own nested worst-case spawn budget (8000ms) instead of getting killed mid-repair. Fix re-implemented against the post-Phase-217 `lib/core/doctor/*-module.cjs` files (the original worktree's fix predated that migration). RCA: `.planning/debug/resolved/intern-w1-statusline-room-mismatch.md`.
816
+
817
+ ## [1.15.3-beta.12] - 2026-07-06
818
+
819
+ ### Added
820
+ - **Phase 211 Eureka generator now runs at production scale.** The tri-modal room.db retrieval engine (FTS5 lexical + sqlite-vec vector + RRF fusion, `mdbr-leaf-ir` local embedder) completes end-to-end against a real 2117-node room after two blocker fixes (below). This is the GENERATOR half of the "two-in-a-box"; the critic (Phase 212) is planned, not yet shipped.
821
+ - **Generic `csv-to-idea-graph` export capability** (`scripts/csv-to-idea-graph.cjs`). Turns any relationship CSV (a pairs edge-list plus optional node-enrichment CSV, column-mapped via CLI args) into a De Stijl navigable idea-graph through the shipped dashboard template -- Section-clustered by a chosen grouping column, layer-toggled, every node/edge citation-tagged. Extends the `generate-standalone` export family; zero tenant hardcoding. Hermetic `tests/test-csv-to-idea-graph.cjs` 21/21.
822
+
823
+ ### Fixed
824
+ - **MCP `room_content` writes re-resolve the active room per call** (`lib/mcp/tool-router.cjs`). The MCP server froze its write target at boot-time cwd, so a mid-session `room-registry set-active` never reached it and writes (file-opportunity / create-funding / update-funding-stage) misrouted to the spawn-time room. Now each write branch calls `resolve-active-room.cjs` (the canonical resolver -- this was a fifth active-room guesser never migrated onto it, the exact stale-closure class Phase 212 D5 warns against). Also aligned `opportunitySchema` to `fileOpportunity` (title optional with a title-or-program refine, coerced numerics). Commit `7a84d38b`.
825
+ - **Strict-mode no longer fires false room-switch / session-binding gates** (`lib/core/room-classifier-strict-mode.cjs`, `scripts/intent-classifier.cjs`). A bare numeric menu reply (`NUMERIC_PATTERN` made the verb optional) and product-branded paste blocks (brand tokens credited as room-name matches) both triggered spurious "switch rooms" / "bind session" interruptions on nearly every turn. Verb now required; brand/boilerplate stop-set excluded from name-entity credit. Commit `e23060cd`.
826
+ - **`birthRoom` binds the newborn room into the session write scope** (`lib/core/navigation/room-birth.cjs`). It flipped only the registry active pointer; Phase 194 (PSB) made the per-session bound SET the primary write authority, so a just-created room was BLOCKED for writes. Now unions the new slug into the session binding as primary via the shipped `session-binding.cjs`; `ignite` threads the real `CLAUDE_SESSION_ID`. Commit `3ad78e70`.
827
+ - **Frontmatter schema validator reconciled to the actual writers** (`lib/core/frontmatter-schemas.cjs`). The Phase 88.1-07 schema codified an aspirational vocabulary no scaffold/doc/compute-state writer ever emitted, so the plugin's own output failed its own advisory schema (a Canon Part 6 dog-food self-violation) and polluted the offense log. Relaxed ROOM.md/STATE.md/artifact-default required sets to what writers emit, added a USER.md schema, and split violation messages into missing-vs-unexpected. New reconcile test scaffolds a room and asserts zero blocking violations. Commit `2602c65b`.
828
+ - **Embedding OOM on large-N rooms** (`lib/core/eureka/embedding-spine.cjs`): `embedTexts` embedded the whole corpus in one forward pass (~26.7GB ONNX allocation on 2117 nodes). Now batched (`MINDRIAN_EMBED_BATCH`, default 32). Commit `c222ff7d`.
829
+ - **vec0 offline-load failure** (`lib/core/eureka/vector-store.cjs`): the backend was inferred from stale table existence, so a table from a prior run threw `no such module: vec0`. Now a per-process capability probe selects the backend; confirmed sqlite-vec loads on Node 22 via a `better-sqlite3` allowExtension handle (the >=23.5 floor is `node:sqlite`-only). Commit `73698c73`.
830
+ - **Claim-text persistence + read-side fallbacks** (D15): `writeClaimNode` persists claim `text`; tri-modal index read-side fallbacks for claim/WhitespaceZone/Artifact. Commits `3d1b27a4`, `af24b697`.
831
+ - **`.gitignore` room.db patterns** backing the "never commit room.db" comment (Part 8 hygiene). Commit `a4cd48dc`.
832
+
833
+ ### Housekeeping
834
+ - **JHTV tenant data + JHU-specific tooling relocated out of the product** into the `jhtv-oliver-kuntz` room, with a `.gitignore` leak guard (Canon three-layer: tenant data/tooling lives in the Room, never the Plugin). The reusable graph capability was generalized (see Added). Commit `57bad7ed`.
835
+ - **Planning (not shipped code):** Phases 212 (Eureka Grounding Guard critic, 5 plans), 212.5 (graph substrate), 213-215 (15 checked plans total) and SEED-053 (methodology-chain MCP tool) registered for the next arc. 213/214 execution is gated on the curing-track verdict + 212-05 calibration.
836
+
837
+ ## [1.15.3-beta.10] - 2026-07-05
838
+
839
+ ### Added
840
+ - **Ratification-tracked next-actions now surface in the statusline's `Next:` slot.** Quick task 260705-ui4, motivated by the rethinking-mindrianos standing-consultant room's own unconverted research entries (`ratification_status: proposed` frontmatter) going invisible once the routing engine had nothing else to offer. Reuses the existing `next-move-cache` mechanism end to end (Canon Part 7: no second cache) -- new `lib/statusline/ratification-next.cjs` resolves the active room FRESH per call (avoiding the frozen-roomDir staleness class found at `bin/mindrian-mcp-server.cjs:65`), shallow-scans `research/*/` for `ratification_status: proposed`, and returns an enum/count-only cue (`ratify strong (2 open)`, never entry titles or target prose -- Canon Part 8). `persistFromDecision(decision, opts)` gained an opt-in `opts.fallbackProvider` on the case-3 clear leg only; no-opts behavior stays byte-identical, so the existing clear-semantics tests are unmodified. The statusline's context-percentage color contract (50/65/80 thresholds) and risk chip are untouched -- the `Next:` segment itself carries zero color/ANSI treatment, so the new cue needed none. New tests/test-statusline-ratification-next.cjs 12/12; context-aware 19/19 unmodified; live-signals 10/10.
841
+
1
842
  ## [1.15.3-beta.8] - 2026-07-05
2
843
 
3
844
  ### Added
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  Powered by PWS (Problems Worth Solving), an innovation methodology built and tested through 20 years of teaching by Prof. Lawrence Aronhime.
11
11
  Engineered by Jonathan Sagir.
12
12
 
13
- [![Version](https://img.shields.io/badge/version-1.15.1--beta.1-1E3A6E)](CHANGELOG.md)
13
+ [![Version](https://img.shields.io/badge/version-1.15.3--beta.48-1E3A6E)](CHANGELOG.md)
14
14
  [![License](https://img.shields.io/badge/license-BSL_1.1-C8A43C)](LICENSE)
15
15
  [![Works on](https://img.shields.io/badge/CLI_+_Desktop_+_Cowork-2D6B4A)](#three-surfaces)
16
16
 
@@ -50,7 +50,7 @@ You talk; the room writes itself. Every conversation, every meeting, every decis
50
50
 
51
51
  ### The room surfaces what you cannot see
52
52
 
53
- Every time you add something new, the system compares it against everything already there. Larry tells you what just changed, what contradicts what, what connects to what, and what is now missing. You decide: APPROVE, REJECT (with a reason), or DEFER. The reason becomes part of the room. The next scan is smarter.
53
+ Every time you add something new, the system compares it against everything already there. Larry tells you what just changed, what contradicts what, what connects to what, and what is now missing. You decide: APPROVE, REJECT (with a reason), or DEFER. The reason becomes part of the room. The next scan is smarter. When a scan surfaces an opportunity, it does not stop at a headline: you qualify it at a card, and one explicit Explore turns it into cited deep research filed in your opportunity bank. The web works the same way: paste a URL and, once you approve, the page is filed as a cited source in your room and compared against everything already there. When something breaks mid-research, it tells you exactly what happened and what it tried next, never a silent empty result.
54
54
 
55
55
  ---
56
56
 
@@ -121,12 +121,14 @@ The commands below are internals. You never have to memorize them or type them.
121
121
  /mos:bono # a six-hats research-and-debate team on your question
122
122
  /mos:map-unknowns # hunt the claims you are most confident about, and wrong
123
123
  /mos:file-meeting # paste a transcript, Larry files it
124
+ /mos:research <url> # paste a link, approve the card, the page becomes cited room knowledge
124
125
  /mos:opportunities # what grants match this room right now
126
+ /mos:qualify-opportunity # judge a surfaced opportunity at a card; Explore turns it into research
125
127
  /mos:graph "what is the weakest assumption in my financial model?"
126
128
  /mos:grade # honest assessment against real ventures
127
129
  ```
128
130
 
129
- That is a slice of 107 commands across 14 skills and 9 agents. If you do not know which one to run, that is the normal case. Just talk: Larry reaches for the right one.
131
+ That is a slice of 111 commands across 124 skills and 9 agents. If you do not know which one to run, that is the normal case. Just talk: Larry reaches for the right one.
130
132
 
131
133
  ---
132
134
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindrian_os/cli",
3
- "version": "1.15.3-beta.8",
3
+ "version": "1.16.0-beta.1",
4
4
  "description": "Install MindrianOS into Claude Code with one command -- `npx @mindrian_os/cli`. Ships the MindrianOS plugin (Larry + PWS methodology + Data Room) plus a setup/diagnostics CLI (install/doctor/update).",
5
5
  "scripts": {
6
6
  "mcp": "node bin/mindrian-mcp-server.cjs",
@@ -19,6 +19,7 @@
19
19
  "CHANGELOG.md"
20
20
  ],
21
21
  "dependencies": {
22
+ "@huggingface/transformers": "^4.2.0",
22
23
  "@ig3/markdown-it-wikilinks": "^1.0.2",
23
24
  "@modelcontextprotocol/ext-apps": "^1.5.0",
24
25
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -30,10 +31,11 @@
30
31
  "gray-matter": "^4.0.3",
31
32
  "markdown-it": "^14.1.0",
32
33
  "semver": "^7.7.4",
34
+ "sqlite-vec": "^0.1.9",
33
35
  "zod": "^3.25.76"
34
36
  },
35
37
  "engines": {
36
- "node": ">=22.5.0"
38
+ "node": ">=22.16.0"
37
39
  },
38
40
  "license": "BSL-1.1"
39
41
  }