@mindrian_os/install 1.13.0-beta.28 → 1.13.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +51 -0
- package/commands/find-bottlenecks.md +9 -0
- package/commands/suggest-next.md +12 -0
- package/commands/update.md +34 -2
- package/hooks/hooks.json +12 -0
- package/lib/agents/auto-explore-agent.cjs +40 -2
- package/lib/agents/reverse-salient-agent.cjs +31 -5
- package/lib/agents/tension-hook-agent.cjs +43 -1
- package/lib/hmi/jtbd-taxonomy.json +10 -1
- package/lib/hmi/selector-dispatcher.cjs +165 -0
- package/lib/memory/.mindrian/auto-explore-mat-test-001.json +11 -0
- package/lib/memory/brain-suggestion-template.test.cjs +200 -0
- package/lib/memory/selector-alias-map.test.cjs +172 -0
- package/lib/workflow/f-selector-ranker.cjs +76 -1
- package/package.json +1 -1
- package/skills/ui-system/SKILL.md +14 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mos",
|
|
3
3
|
"description": "MindrianOS -- Your AI innovation co-founder. Larry thinks with you through PWS methodology, builds your Data Room as you explore, and chains frameworks intelligently. Install and go.",
|
|
4
|
-
"version": "1.13.0-beta.
|
|
4
|
+
"version": "1.13.0-beta.32",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Jonathan Sagir",
|
|
7
7
|
"url": "https://mindrian.ai"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
+
## [1.13.0-beta.32] - 2026-05-24
|
|
2
|
+
|
|
3
|
+
### Fixed (Windows-tester regression bundle, Phase 127.2 Plan 04 -- ships v1.13.0-beta.32)
|
|
4
|
+
- **Instance #4 (P2): `/mos:rooms list` works on Windows + Git Bash (`scripts/room-registry` POSIX path leak).** Every `python3 -c` invocation in `scripts/room-registry` (8 subcommand stanzas: `create`, `read`, `list`, `update`, `set-active`, `archive`, `get-active`, `git-config`) interpolated bash-resolved `$REGISTRY_FILE` into Python source. On Git Bash for Windows, `$HOME=/c/Users/PC` produced paths like `/c/Users/PC/MindrianRooms/.rooms/registry.json` that Windows Python `open()` could not resolve, surfacing `FileNotFoundError` on every registry subcommand. Fix: inlined a `normwin()` Python shim at the top of every block, platform-gated on `sys.platform == 'win32'` so it is a no-op on Linux/macOS but converts the POSIX form to native `C:\Users\...` on Windows; every `open(...)` rewritten to `open(normwin(REGISTRY_FILE))`. Sibling sweep patched `scripts/reapply-modifications` (4 `python3 -c` sites, single `$NORMWIN_SHIM` bash-var-string reused). Explicit sweep targets (`scripts/hsi-*`, `scripts/build-*`, `scripts/release.sh`) verified clean. Out-of-scope sibling sites in `scripts/verify-release`, `scripts/learn-from-usage`, `scripts/track-analytics` logged to `.planning/phases/127.2-.../deferred-items.md` for a future patch beta. Silent for non-Windows users; caught only on Jonathan's dogfood Windows box. Closes `.planning/debug/resolved/windows-room-registry-path-normalization-gap.md`.
|
|
5
|
+
- **Instance #7 (P1, META-FIX): `/mos:update` atomically swaps to the new active install + warns about session restart (silent-activation gap closed).** `/mos:update` and `claude plugin update mos@mindrian-marketplace` land new bytes in `~/.claude/plugins/cache/mindrian-marketplace/mos/<NEW_VERSION>/` but DO NOT atomically swap the live install at `~/.claude/plugins/mindrian-os/`. Every subsequent MCP probe + statusline + hook output continued to serve the OLD bytes. Users thought they were on beta.N+1; every Brain interaction silently read beta.N. This is the structural reason every prior beta this session may have been unverified on tester wires. Three-part fix: (a) new `scripts/post-update-activation.cjs` (305 lines, exports `activatePostUpdate` + `POST_UPDATE_TOUCH_FILE`) detects the cache-staging dir + delegates the atomic swap to existing `scripts/doctor.cjs --fix` (Canon Part 7 reuse of Phase 95.2 install-cache-atomic-recovery, three autopsies of hardening) + writes a touch-file at `~/.mindrian/post-update-restart-pending` with the new version + emits a Shape E action report; (b) new SessionStart hook `scripts/sessionstart-post-update-preflight.cjs` (187 lines, sibling-NOT-replacement of `sessionstart-npm-reconcile.cjs`) reads the touch-file each session, spawns `doctor --brain-smoke --json`, parses L4 MCP stdio handshake server-version token, deletes touch-file silently on match, refuses Larry-load with a red banner via SessionStart `systemMessage` envelope on drift (exit 1), exits 0 silently when probe is inconclusive (defensive); (c) `commands/update.md` Step 7 calls `node "${CLAUDE_PLUGIN_ROOT}/scripts/doctor.cjs" --fix --post-update` at the end of every update flow; `scripts/doctor.cjs` gains `--post-update` flag handler that delegates to the activator, composable with `--fix`. Closes `.planning/debug/resolved/mos-update-silent-activation-gap.md`.
|
|
6
|
+
|
|
7
|
+
### Changed (Release pipeline self-defense, Phase 127.2 Plan 04)
|
|
8
|
+
- **`/mos:doctor --acceptance` Class N gate: `activation-reached-the-wire`.** `scripts/doctor.cjs` gains a new acceptance point (severity `blocker`, `applies_to: ['full']`) that asserts the L4 MCP server version (probed via `doctor --brain-smoke --json` -> parse `server=mindrian-brain vX.Y.Z` from L4 handshake reason) matches `package.json.version` (version-of-record). On match: pass. On drift: fail with `activation gap detected: live install serves v<OLD> but version-of-record is v<NEW>`. Wired into the existing acceptance roster so `scripts/release.sh` Step 9.8 (post-publish full acceptance gate) runs this check AFTER the new version is published -- catching any phantom-version release before it propagates to tester caches. This is the canary the release pipeline never had: every beta after v1.13.0-beta.32 either activates on the wire AND earns the acceptance pass, or fails the gate AND is prevented from shipping. The cadence-vs-validation trade-off becomes structurally enforced rather than relying on manual maintainer discipline. Test environment hooks: `DOCTOR_TEST_FAIL_POINT=activation-reached-the-wire` synthesizes a failure; `DOCTOR_SKIP_ACTIVATION_GATE=1` marks the point ok-as-skipped for hermetic CI / offline mode.
|
|
9
|
+
|
|
10
|
+
### Internal (Phase 127.2 Plan 04)
|
|
11
|
+
- **`hooks/hooks.json`** registers `sessionstart-post-update-preflight.cjs` as a SessionStart hook (matcher `startup|clear|compact`, `async: false`, ordered AFTER `sessionstart-npm-reconcile.cjs` so node_modules is in place before brain-smoke spawns, BEFORE Larry-load so a drift blocks the room).
|
|
12
|
+
- **New tests (60 PASS / 0 FAIL across 3 suites):** `tests/test-room-registry-windows-path.cjs` (215 lines, 25 PASS -- linux regression + python normwin functional probe + structural greps confirming zero raw `open($REGISTRY_FILE)` callsites remain); `tests/test-mos-update-activation-gap.cjs` (233 lines, 19 PASS -- cold synthesis of beta.N -> beta.N+1 atomic swap + idempotency on already-on-latest + preflight hook semantics); `tests/test-127.2-04-windows-path-and-update-activation.sh` (88 lines, 16 PASS -- structural greps + functional doctor probes).
|
|
13
|
+
- **3 RCA closeouts moved to `.planning/debug/resolved/`:** `post-beta30-regression-2026-05-23.md` (12-gate wire-verification sweep), `windows-room-registry-path-normalization-gap.md` (Instance #4 detail), `mos-update-silent-activation-gap.md` (Instance #7 detail). Each updated with `status: resolved`, `resolved: 2026-05-23`, `resolved_by: phase-127.2 Plan 127.2-04`, plus Resolution sections linking to this plan.
|
|
14
|
+
- **`.planning/debug/knowledge-base.md`** gains 2 new entries (Instance #4 + Instance #7 -- distinct patterns) so `gsd-debugger` surfaces these as known-pattern hypotheses on future investigations.
|
|
15
|
+
- **`docs/install-cache-family-premortem.md`** appended with case #7 (the 7th case in the install-cache failure family -- two sub-cases shipped as one beta: bash-heredoc POSIX leak + silent activation gap). New sub-pattern documented (cross-platform fragility class beyond install-cache proper). Two new predicted failure modes added: **F. Cowork cross-tenant activation drift** (Class N assumes one active install per host); **G. Bash-heredoc POSIX leaks in non-`scripts/room-registry` sites** (deferred sweep targets logged).
|
|
16
|
+
|
|
17
|
+
### User-facing note (Phase 127.2 Plan 04)
|
|
18
|
+
- **If you previously ran `/mos:update` and `/mos:doctor --brain-smoke` still reports an older version, run `/mos:doctor --fix` once.** v1.13.0-beta.32 makes this automatic going forward. You will see a one-time "ACTIVATION GAP" red banner on next session-start if the touch-file from a prior update still flags a drift; the banner walks you through the two-step recovery.
|
|
19
|
+
|
|
20
|
+
### Added (Phase 121.5 Sub-plan K -- Plan 121.5-10)
|
|
21
|
+
- **Locked Brain-suggestion content template across all 5 consumers.** `/mos:suggest-next`, `/mos:act --chain`, the Phase 116 tension-hook-agent, the Phase 117 auto-explore-agent, and the Phase 89-07 reverse-salient-agent all render the same shape now: canonical `[■ BRAIN]` chip + verb-first question line + two-line dense option rows (glyph + verb + right-aligned confidence percent on top, framework category + graph relationship below) + stat-strip footer. The lock collapses 5 divergent renderings into ONE so the navigator's eye learns "Brain is speaking" in one session. Source: `.planning/121.5-selector-coverage-audit.md` Section 5.
|
|
22
|
+
- **Promoted `/mos:suggest-next` from NONE (silent dispatch / plain-text list) to F.1** via `rankForSelector` + `pickShape` per audit Section 6 item 2. THE single highest-leverage promotion per the audit Executive Summary: every conversational "what next?" Larry reflex inherits the locked surface.
|
|
23
|
+
- **Promoted `/mos:act --chain` `[GATE]` rendering from BESPOKE bracket text (`[continue]` / `[stop]`) to F.0 Mini Decision Gate** via `pickShape({ requestedShape: 'F.0' })` per audit Section 6 item 3. The closed F.0 vocabulary (Approve / Reject / Defer) replaces the bespoke two-button mental model without losing intent (Reject captures REJECTED_BECAUSE; Defer queues a milestone audit).
|
|
24
|
+
- **Selector verb-label aliases (LOCKED decision 1).** Dispatcher carries `alias_map` (Resolve / Explore -> Run Methodology; Later -> Defer; Skip -> Free-Text) loaded once at module init from `lib/hmi/jtbd-taxonomy.json` `alias_map.verb_aliases`. Aliases render to the user; canonical verbs persist to graph edges via `navigation.cjs`. The render-vs-persist split honors both pedagogy (contextual aliases) and graph consistency (one stable vocabulary) without forcing one.
|
|
25
|
+
- **CI tripwire `tests/test-no-bespoke-brain-prompts.sh`** enforces the lock (audit Section 6 item 6). Scans the 5 named Brain-suggestion consumer files plus any new source file that imports `chain-recommender.cjs` or `f-selector-ranker.cjs`; flags bracket-text option lists (`[continue]`/`[stop]`), verbose `(RECOMMENDED)` tags, and "Pick one to ..." selector-prompt prose. Wired into `tests/run-all-121.5.sh` SHELL_SUITES.
|
|
26
|
+
- **`docs/F-SELECTOR-CONSUMER-GUIDE.md` Section 4 (NEW)** publishes the locked template (slot-value table + visual mockup + anti-patterns rejected + implementation wiring example) per audit Section 6 item 7. Makes the lock discoverable to future consumers before they invent a new pattern.
|
|
27
|
+
- **`skills/ui-system/SKILL.md` Section 2** adds the Shape F.1 Brain-suggestion variant subsection (citing the locked chip + glyphs + footer + alias_map) AND a body_shape vs F-shape orthogonality note per LOCKED decision 4 (`body_shape:` is layout discipline; F-shape is the selector contract; they are orthogonal axes, not competing values).
|
|
28
|
+
- **`lib/workflow/f-selector-ranker.cjs` MAX_K = 3 constant + clamp** per audit Section 5.2.3 anti-pattern ("More than 3 option rows pushes the AskUserQuestion auto-injected rows off-screen"). Caller asking k=20 silently receives k=3. New optional `category` + `graph_relationship` fields on returned items[] feed the locked template's two-line meta row.
|
|
29
|
+
- **New tests:** `lib/memory/selector-alias-map.test.cjs` (24 assertions, alias_map round-trip + render-vs-persist invariant) + `lib/memory/brain-suggestion-template.test.cjs` (22 assertions, 5-consumer chip + footer isomorphism).
|
|
30
|
+
|
|
31
|
+
### Deferred to v1.13.2 (LOCKED decision 3)
|
|
32
|
+
- **CONTRADICTS-driven color flip (cyan -> yellow)** for Brain-suggestion selectors when any candidate carries a CONTRADICTS edge against an existing assumption. Yellow-on-cascade requires the consumer to walk the cascade graph BEFORE rendering (a SQL call on the rank path); we shipped cyan default in v1.13.0 to avoid that latency and queued the yellow-on-cascade signal as a v1.13.2 hotfix if testers report missed warnings. Ship simple, observe, then layer in if the data says it matters.
|
|
33
|
+
|
|
34
|
+
### Deferred to v1.14.0 (LOCKED decision 5)
|
|
35
|
+
- **F.1 close-out adoption on the 71 N/A methodology commands** (think-hats, structure-argument, grade, deep-grade, validate, etc.). Out of scope for Phase 121.5; the Phase 88.2 design legitimately delegates the methodology close to Larry's conversational follow-up. Phase 121.5 + 1 backlog. Scope discipline.
|
|
36
|
+
|
|
37
|
+
## [1.13.0-beta.30] - 2026-05-23
|
|
38
|
+
|
|
39
|
+
### Fixed (Engine 1 Act 1 silent-failure class, Phase 127.2 Plan 03 -- FIRST hotfix from external tester evidence)
|
|
40
|
+
- **`scripts/doctor.cjs --check-rs-engine` pre-flight pre-flights Python deps for the Engine 1 Act 1 surface (Finding F1 -- `.planning/debug/resolved/windows-tester-find-bottlenecks-silent-failure-qa-sweep.md`).** ADD-ONLY flag handler (NOT a class flag; owns its own exit-code contract). Probes critical deps reachable from `scripts/rs-engine.py` -- `requests` (transitive via `lib/core/rs_corpus.py`, the actual silent-failure root cause on the Windows tester's machine) + `numpy` -- plus umbrella deps from `requirements-hsi.txt` (`sentence_transformers`, `sklearn`). Resolves python interpreter via `MINDRIAN_PYTHON` env override > `python3` > `python` fallback. On missing critical deps: exit 1 + actionable fix line `Run: pip install -r requirements-hsi.txt --user (use python -m pip if pip is not in PATH)`. JSON variant returns `{ ok, ready, python, probes[], missing, missing_critical, missing_umbrella, fix }`. Defensive: any uncaught probe error surfaces as exit 1 -- the probe never crashes `/mos:doctor`'s other gates. Closes the Windows tester 2026-05-23 silent-failure class for the pre-flight surface.
|
|
41
|
+
- **`lib/agents/reverse-salient-agent.cjs` forwards rs-engine stderr to `result.detail.diagnostic` (Finding F2 -- same RCA).** The `runRsEngine()` catch block now captures `e.stderr` from the failed child python process, takes the LAST 200 chars (preserving the actionable fix line + exception name at the tail), and embeds it in `result.detail.diagnostic`. Backward compatible: when stderr is empty, `detail` stays a plain string (the existing `e.message` slice); when stderr is present, `detail` upgrades to `{ message, diagnostic }`. The existing `ok` / `reason` fields are untouched. Before this fix, every actionable error from rs-engine was silently discarded at the agent layer -- the worst-shape silent failure in a methodology surface.
|
|
42
|
+
- **`commands/find-bottlenecks.md` empty-result UX disambiguates analyzer-down from no-findings (Finding F7 -- same RCA).** New "Empty-result UX" sub-section distinguishes two categorically different cases: (a) analyzer ran with no findings (plausible if room is small or genuinely balanced); (b) analyzer could not start -- detected via `result.detail.diagnostic` or `reason: rs_engine_invocation_failed`. The second path explicitly tells the user to run `/mos:doctor --check-rs-engine` with the pip-install one-liner inline. Before this fix, both cases rendered as "no findings" -- which reads as "your work is clean" regardless of whether the analyzer ran or crashed. The single most dangerous reading in a methodology surface.
|
|
43
|
+
|
|
44
|
+
### Internal (Phase 127.2 Plan 03)
|
|
45
|
+
- **New test:** `tests/test-127.2-03-rs-engine-silent-failure-fixes.sh` verifies all three findings landed (F1 flag-handler + actionable fix line embedded; F2 diagnostic-field reference + stderr-capture pattern; F7 disambiguation copy present in find-bottlenecks.md or agent file) plus a functional probe asserting `node scripts/doctor.cjs --check-rs-engine --json` returns valid JSON and `--help` documents the new flag. 7/7 PASS on origin/main.
|
|
46
|
+
- **Phase 134 scaffolded as v1.14.0 architectural stub (Finding F6 -- structural answer to install-fragility class).** New `.planning/phases/134-cjs-port-of-python-analyzers-via-xenova-transformers-elimina/134-CONTEXT.md` captures the design vision: replace `scripts/rs-engine.py` + `lib/core/rs_*.py` + `scripts/hsi-*.py` with CJS equivalents using `@xenova/transformers` (ONNX `Xenova/multilingual-e5-large` in-process). Eliminates Python from user-machine surface entirely. Estimate ~3 weeks. Plan 127.2-03's original spec named "Phase 130" but slot 130 was already taken; SDK assigned 134 as next free slot. No PLAN.md (scaffolding only); enters v1.14.0 planning cycle.
|
|
47
|
+
- **RCA closed and moved.** `.planning/debug/windows-tester-find-bottlenecks-silent-failure-qa-sweep.md` -> `.planning/debug/resolved/` with `resolved_by: phase-127.2 Plan 127.2-03 (hotfix; F1+F2+F7 shipped)` + `resolved_disposition: 3-of-4-fixed-in-code; F3 narrative drift deferred to next docs reconciliation; F6 architectural port scaffolded as Phase 134 stub`. Knowledge-base entry appended at `.planning/debug/knowledge-base.md` so `gsd-debugger` surfaces this as a known-pattern hypothesis on future investigations.
|
|
48
|
+
- **Dog-fooding milestone:** the FIRST hotfix shaped from an EXTERNAL tester's transcript (Aryeh's Windows machine, 2026-05-23). Plans 127.2-00 + 127.2-02 were both maintainer-discovered. This one closes a defect a real user hit on a machine the maintainer doesn't own, and ships in the same week the transcript landed -- empirically demonstrating that the dog-fooding loop the QA sweep itself flagged as broken (RS-2 thesis: one-person QA is the lagging subsystem) is no longer one-person.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
1
52
|
## [1.13.0-beta.28] - 2026-05-23
|
|
2
53
|
|
|
3
54
|
### Fixed (post-ship QA-sweep closeout, Phase 127.2 Plan 02)
|
|
@@ -37,6 +37,15 @@ Procedure (CLI / Desktop / Cowork):
|
|
|
37
37
|
- On DEFER: DEFERRED memory_event records the deferral for Phase 116 unresolved-tension-hook consumption.
|
|
38
38
|
4. If the agent returns `{ ok: false }` OR finds nothing OR is suppressed (tier 0 / JUST_TALK), fall back to the standard Setup + Session Flow below.
|
|
39
39
|
|
|
40
|
+
### Empty-result UX (Phase 127.2 Plan 03 -- Finding F7)
|
|
41
|
+
|
|
42
|
+
When the agent returns no findings, you MUST distinguish two cases for the user, because "no findings" reads as "your work is clean" -- the worst possible signal if the analyzer crashed:
|
|
43
|
+
|
|
44
|
+
- **Analyzer ran successfully and found nothing.** Surface: "No reverse-salient findings were returned -- the analyzer ran across your room and could not identify a lagging component above the threshold. This is plausible if the room is small (less than 5 substantive artifacts) or if the system is genuinely balanced; continue with the Session Flow below to do the framework manually if you want a second pass."
|
|
45
|
+
- **Analyzer could not start (rs-engine failed -- look for `result.detail.diagnostic` in the agent payload, OR `ok: false, reason: rs_engine_invocation_failed`).** Surface: "No reverse-salient findings were returned. If you expected results, the analyzer may not have started -- run `/mos:doctor --check-rs-engine` to verify your Engine 1 Act 1 environment is healthy. Common cause: missing Python deps (`pip install -r requirements-hsi.txt --user`)."
|
|
46
|
+
|
|
47
|
+
The disambiguation is critical because the agent layer historically swallowed the actionable error message (Windows tester 2026-05-23 silent-failure class). Always surface the `--check-rs-engine` hint on the analyzer-failure path.
|
|
48
|
+
|
|
40
49
|
Anti-pattern reminder (per docs/AGENTIC-SURFACING-PATTERN.md):
|
|
41
50
|
- Never print findings to console; the F.0 dispatcher IS the surfacing surface.
|
|
42
51
|
- Never query the Brain directly; the agent reads pre-derived BRAIN.md via folder-memory.readQuadruple (LOCAL only, Canon Part 8).
|
package/commands/suggest-next.md
CHANGED
|
@@ -20,6 +20,18 @@ allowed-tools:
|
|
|
20
20
|
|
|
21
21
|
# /mos:suggest-next
|
|
22
22
|
|
|
23
|
+
<!--
|
|
24
|
+
Phase 121.5-10 Sub-plan K LOCKED decision 4 (body_shape vs F-shape orthogonality):
|
|
25
|
+
body_shape: B (Semantic Tree) is the LAYOUT discipline of this command's body --
|
|
26
|
+
the ranked-list render. F.1 is the SELECTOR CONTRACT that fires at the close of
|
|
27
|
+
the body (the verb-pick gate beneath the tree). The two are orthogonal axes:
|
|
28
|
+
body_shape describes the visual layout; F-shape describes the dispatcher
|
|
29
|
+
contract. This command is "F.1 over Shape B." See skills/ui-system/SKILL.md
|
|
30
|
+
Section 2 orthogonality note for the canon citation. Also see audit
|
|
31
|
+
.planning/121.5-selector-coverage-audit.md Section 5 for the locked Brain-
|
|
32
|
+
suggestion content template that the F.1 surface emits.
|
|
33
|
+
-->
|
|
34
|
+
|
|
23
35
|
You are Larry. This command recommends what the user should work on next as a COMMAND SEQUENCE, not just a list of frameworks: it reads the room's ProblemType (and active JTBD), Brain-derives the framework chain, and composes that chain into the exact `/mos:` commands to run, in order.
|
|
24
36
|
|
|
25
37
|
## The resolver is the only door
|
package/commands/update.md
CHANGED
|
@@ -139,10 +139,42 @@ If the migrator finds and removes stale entries, surface that to the user:
|
|
|
139
139
|
If no findings, mention it briefly:
|
|
140
140
|
> "User settings clean -- no stale paths."
|
|
141
141
|
|
|
142
|
-
### Step 7:
|
|
142
|
+
### Step 7: Atomically activate the new bytes (Phase 127.2 Plan 04 Instance #7)
|
|
143
|
+
|
|
144
|
+
Claude Code's native `plugin update` lands the new version in the cache
|
|
145
|
+
(`~/.claude/plugins/cache/mindrian-marketplace/mos/<NEW_VERSION>/`) but does
|
|
146
|
+
NOT atomically swap the live install at `~/.claude/plugins/mindrian-os/`.
|
|
147
|
+
Without this step every Brain MCP probe + statusline render + hook output
|
|
148
|
+
continues to serve the OLD version. The user thinks they are on beta.N+1
|
|
149
|
+
while every Brain interaction silently reads beta.N -- the "silent
|
|
150
|
+
activation gap" surfaced on the 2026-05-23 dogfood box.
|
|
151
|
+
|
|
152
|
+
Run the post-update activator to swap atomically:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/doctor.cjs" --fix --post-update
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Stream the output to the user. It prints a Shape E action report showing
|
|
159
|
+
the old -> new transition + the backup path of the stale bytes. After
|
|
160
|
+
success it writes a touch-file at `~/.mindrian/post-update-restart-pending`
|
|
161
|
+
that the next SessionStart's preflight hook reads to verify activation
|
|
162
|
+
reached the wire (the L4 MCP server's version matches package.json
|
|
163
|
+
version-of-record). On version-mismatch the preflight hook refuses Larry
|
|
164
|
+
load with a red banner pointing at `/mos:doctor --fix`.
|
|
165
|
+
|
|
166
|
+
If the activator reports `swapped: false` and `already on latest`: no
|
|
167
|
+
action needed. The cache and live install were already in sync (rare but
|
|
168
|
+
possible if a previous /mos:update sequence completed cleanly).
|
|
169
|
+
|
|
170
|
+
If it reports `ok: false`: surface the error to the user. They can manually
|
|
171
|
+
run `/mos:doctor --fix` to re-attempt; if that also fails, file an RCA at
|
|
172
|
+
`.planning/debug/post-update-activation-failure-<date>.md`.
|
|
173
|
+
|
|
174
|
+
### Step 8: Verify and instruct restart
|
|
143
175
|
|
|
144
176
|
If all steps succeeded:
|
|
145
|
-
> "Done. v{latest} installed via Claude Code's plugin loader
|
|
177
|
+
> "Done. v{latest} installed via Claude Code's plugin loader and atomically swapped into the live install path. Registry, cache, and `enabledPlugins` are all in sync. User settings checked for stale paths. Restart Claude Code (close and reopen the terminal, or kill and re-run `claude`) so MCP servers reconnect against the new bytes. After restart, run `/mos:help` to confirm commands are reachable and look for the Mindrian statusline at the bottom of the terminal."
|
|
146
178
|
|
|
147
179
|
## Force Mode
|
|
148
180
|
|
package/hooks/hooks.json
CHANGED
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
},
|
|
16
|
+
{
|
|
17
|
+
"matcher": "startup|clear|compact",
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/sessionstart-post-update-preflight.cjs\"",
|
|
22
|
+
"timeout": 12000,
|
|
23
|
+
"async": false,
|
|
24
|
+
"statusMessage": "Verifying post-update activation..."
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
16
28
|
{
|
|
17
29
|
"matcher": "startup|clear|compact",
|
|
18
30
|
"hooks": [
|
|
@@ -665,6 +665,39 @@ function surfaceFinding(args) {
|
|
|
665
665
|
return { surfaced: false, suppress_reason: 'pickShape_unavailable', finding: populated };
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
+
// Phase 121.5-10 Sub-plan K (audit Section 5.3): the locked [■ BRAIN]
|
|
669
|
+
// chip replaces the BQ-anchored header. The verbatim Brain BQ name
|
|
670
|
+
// (Phase 117 RESEARCH 8.5 moat) moves into the question-line slot
|
|
671
|
+
// beneath the chip so the two-row format preserves the BQ anchor
|
|
672
|
+
// signal without violating the 12-char chip rule. Verb-label aliases
|
|
673
|
+
// (Explore / Skip / Later) KEPT per LOCKED decision 1; alias_map
|
|
674
|
+
// collapses on selection (Explore -> Run Methodology, Skip -> Free-
|
|
675
|
+
// Text, Later -> Defer). The >= 0.7 recommendedVerb gate (Phase 88.2)
|
|
676
|
+
// determines the leading row's glyph in optionRows[0].
|
|
677
|
+
let aliasMap = {};
|
|
678
|
+
try {
|
|
679
|
+
const tax = require('../hmi/jtbd-taxonomy.json');
|
|
680
|
+
if (tax && tax.alias_map && tax.alias_map.verb_aliases) {
|
|
681
|
+
aliasMap = tax.alias_map.verb_aliases;
|
|
682
|
+
}
|
|
683
|
+
} catch (_e) { /* graceful */ }
|
|
684
|
+
const topScore = Number(populated.top_differential_score) || 0;
|
|
685
|
+
const verbsLocal = ['Explore', 'Skip', 'Later'];
|
|
686
|
+
const optionRows = verbsLocal.map(function (v, i) {
|
|
687
|
+
let meta = '';
|
|
688
|
+
if (v === 'Explore') meta = 'BQ-anchored explore · cascade ENABLES + INFORMS edges';
|
|
689
|
+
else if (v === 'Skip') meta = 'silent dismiss · no edge written';
|
|
690
|
+
else if (v === 'Later') meta = 'queue for next session · surfacing_count preserved';
|
|
691
|
+
const glyph = (v === 'Explore' && topScore >= 0.7) ? '▶' : '▷';
|
|
692
|
+
return {
|
|
693
|
+
glyph: glyph,
|
|
694
|
+
number: i + 1,
|
|
695
|
+
verb: v,
|
|
696
|
+
confPct: (v === 'Explore') ? Math.round(topScore * 100) : 0,
|
|
697
|
+
meta: meta,
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
|
|
668
701
|
let result;
|
|
669
702
|
try {
|
|
670
703
|
result = dispatcher.pickShape({
|
|
@@ -673,8 +706,13 @@ function surfaceFinding(args) {
|
|
|
673
706
|
operator: operator,
|
|
674
707
|
tier: tier,
|
|
675
708
|
payload: {
|
|
676
|
-
|
|
677
|
-
|
|
709
|
+
brain_suggestion_variant: true,
|
|
710
|
+
verbs: verbsLocal,
|
|
711
|
+
header: '[■ BRAIN]',
|
|
712
|
+
questionLine: bqLine,
|
|
713
|
+
alias_map: aliasMap,
|
|
714
|
+
optionRows: optionRows,
|
|
715
|
+
footer: '▶ Brain · top-3 of 3 ranked · cyan = informing',
|
|
678
716
|
recommendedVerb: recommendedVerb,
|
|
679
717
|
emitTelemetry: true,
|
|
680
718
|
parent_decision_id: parent_decision_id,
|
|
@@ -189,10 +189,26 @@ function runRsEngine(opts) {
|
|
|
189
189
|
timeout: 60000,
|
|
190
190
|
});
|
|
191
191
|
} catch (e) {
|
|
192
|
+
// Phase 127.2-03 Task 1 (Finding F2): forward the child python process's
|
|
193
|
+
// stderr LAST 200 chars to result.detail.diagnostic so callers can
|
|
194
|
+
// self-recover from missing-deps / import errors (the Windows tester
|
|
195
|
+
// 2026-05-23 silent-failure class). Truncate at the START so the tail
|
|
196
|
+
// (which carries the exception name + actionable fix line printed by
|
|
197
|
+
// rs_corpus -- e.g. "Run: pip install -r requirements-hsi.txt") is the
|
|
198
|
+
// half that survives the cap. Backward compatible: ok / reason / detail
|
|
199
|
+
// (e.message) unchanged; detail upgraded from plain string to object
|
|
200
|
+
// ONLY when stderr is present, so legacy callers reading detail as a
|
|
201
|
+
// string still get the truncated message via detail.message.
|
|
202
|
+
const message = String((e && e.message) || '').slice(0, 120);
|
|
203
|
+
const stderrRaw = (e && e.stderr) ? String(e.stderr) : '';
|
|
204
|
+
const diagnostic = stderrRaw.length > 200 ? stderrRaw.slice(-200) : stderrRaw;
|
|
205
|
+
const detail = stderrRaw.length > 0
|
|
206
|
+
? { message: message, diagnostic: diagnostic }
|
|
207
|
+
: message;
|
|
192
208
|
return {
|
|
193
209
|
ok: false,
|
|
194
210
|
reason: 'rs_engine_invocation_failed',
|
|
195
|
-
detail:
|
|
211
|
+
detail: detail,
|
|
196
212
|
pairs: [],
|
|
197
213
|
};
|
|
198
214
|
}
|
|
@@ -501,12 +517,22 @@ function surfaceFinding(args) {
|
|
|
501
517
|
return { surfaced: false, suppress_reason: 'just_talk' };
|
|
502
518
|
}
|
|
503
519
|
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
520
|
+
// Phase 121.5-10 Sub-plan K (audit Section 5.3): the locked [■ BRAIN]
|
|
521
|
+
// chip replaces the prior `-- mindrianOS -- reverse salient -- <persona>
|
|
522
|
+
// --` header. The persona suffix (Phase 89-07 extension via
|
|
523
|
+
// resolvePersonaSuffix) moves into the body slot directly beneath the
|
|
524
|
+
// chip so the two-row chip+context format preserves the persona signal
|
|
525
|
+
// without violating the 12-char chip rule. F.0 closed vocabulary
|
|
526
|
+
// (Approve / Reject / Defer) STAYS verbatim per the F.0 specification
|
|
527
|
+
// (no RECOMMENDED in F.0; the shape itself is the recommendation
|
|
528
|
+
// surface). The body composition (persona + body_text + framework chain)
|
|
529
|
+
// continues to render in zones.body via the F.0 renderer.
|
|
530
|
+
const header = '[■ BRAIN]';
|
|
507
531
|
const bodyText = String(finding.body_text || '');
|
|
508
532
|
const chainText = String(finding.brain_chain_text || '');
|
|
509
|
-
const
|
|
533
|
+
const personaLine = personaSuffixText && personaSuffixText.length > 0
|
|
534
|
+
? '(' + personaSuffixText + ' lens)\n\n' : '';
|
|
535
|
+
const body = personaLine + bodyText + (chainText.length > 0 ? '\n\nFramework chain: ' + chainText : '');
|
|
510
536
|
|
|
511
537
|
// Dispatch via the canonical 88.2-04+05 pickShape entry point.
|
|
512
538
|
// emitTelemetry:false because the agent owns the dual-surface mirror via
|
|
@@ -48,8 +48,20 @@ const pendingStore = require('../memory/pending-tension-store.cjs');
|
|
|
48
48
|
|
|
49
49
|
// ---------- Constants ----------
|
|
50
50
|
|
|
51
|
+
// Phase 121.5-10 Sub-plan K (audit Section 5.3 minor alignment + LOCKED
|
|
52
|
+
// decision 1): aliases KEPT for pedagogical clarity in the tension-hook
|
|
53
|
+
// surface; alias_map (loaded from lib/hmi/jtbd-taxonomy.json by the
|
|
54
|
+
// dispatcher) collapses to canonical at selection time for graph-edge
|
|
55
|
+
// persistence (Resolve -> Run Methodology / Later -> Defer / Skip ->
|
|
56
|
+
// Free-Text). The user sees the contextual alias; the graph stores the
|
|
57
|
+
// canonical verb.
|
|
51
58
|
const F1_VERBS = ['Resolve', 'Later', 'Skip'];
|
|
52
|
-
|
|
59
|
+
// Phase 121.5-10 Sub-plan K (audit Section 5.3): the locked [■ BRAIN] chip
|
|
60
|
+
// replaces the prior verbose header. The tension context now lands in the
|
|
61
|
+
// question-line slot directly beneath the chip ("Resolve pending tension:"
|
|
62
|
+
// + brief summary) per the two-row format that preserves context without
|
|
63
|
+
// violating the 12-char chip rule.
|
|
64
|
+
const F1_HEADER = '[■ BRAIN]';
|
|
53
65
|
const VALID_RESPONSES = Object.freeze(new Set(['RESOLVE', 'LATER', 'SKIP', 'FREE_TEXT']));
|
|
54
66
|
const TENSION_ID_LEN = 32;
|
|
55
67
|
|
|
@@ -164,6 +176,31 @@ function surfaceFinding(args) {
|
|
|
164
176
|
}
|
|
165
177
|
|
|
166
178
|
const surfaceStartedAtMs = Date.now();
|
|
179
|
+
// Phase 121.5-10 Sub-plan K (audit Section 5.3): load alias_map from
|
|
180
|
+
// jtbd-taxonomy.json so the dispatcher can collapse Resolve / Later /
|
|
181
|
+
// Skip to canonical verbs (Run Methodology / Defer / Free-Text) at
|
|
182
|
+
// selection time. Lazy-required so test substitution can fake the
|
|
183
|
+
// dispatcher without rebuilding the taxonomy fixture.
|
|
184
|
+
let aliasMap = {};
|
|
185
|
+
try {
|
|
186
|
+
const tax = require('../hmi/jtbd-taxonomy.json');
|
|
187
|
+
if (tax && tax.alias_map && tax.alias_map.verb_aliases) {
|
|
188
|
+
aliasMap = tax.alias_map.verb_aliases;
|
|
189
|
+
}
|
|
190
|
+
} catch (_e) { /* graceful */ }
|
|
191
|
+
// Build the two-line option rows for the locked Brain-suggestion template.
|
|
192
|
+
// Per Phase 116 explicit design note (audit Section 5.1): recommendedVerb
|
|
193
|
+
// stays null (the tension hook deliberately omits the RECOMMENDED gate);
|
|
194
|
+
// all three verbs render with the empty-triangle alternative glyph.
|
|
195
|
+
const tensionType = (finding && typeof finding.tension_type === 'string')
|
|
196
|
+
? finding.tension_type : 'pending';
|
|
197
|
+
const optionRows = F1_VERBS.map(function (v, i) {
|
|
198
|
+
let meta = '';
|
|
199
|
+
if (v === 'Resolve') meta = tensionType + ' tension · captures RESOLVES_VIA edge';
|
|
200
|
+
else if (v === 'Later') meta = 'queue for next session · surfacing_count preserved';
|
|
201
|
+
else if (v === 'Skip') meta = 'silent dismiss · re-evaluated next SessionStart';
|
|
202
|
+
return { glyph: '▷', number: i + 1, verb: v, confPct: 0, meta: meta };
|
|
203
|
+
});
|
|
167
204
|
let result;
|
|
168
205
|
try {
|
|
169
206
|
result = dispatcher.pickShape({
|
|
@@ -172,8 +209,13 @@ function surfaceFinding(args) {
|
|
|
172
209
|
operator: operator,
|
|
173
210
|
tier: tier,
|
|
174
211
|
payload: {
|
|
212
|
+
brain_suggestion_variant: true,
|
|
175
213
|
verbs: F1_VERBS.slice(),
|
|
176
214
|
header: F1_HEADER,
|
|
215
|
+
questionLine: 'Resolve pending tension:',
|
|
216
|
+
alias_map: aliasMap,
|
|
217
|
+
optionRows: optionRows,
|
|
218
|
+
footer: '▶ Brain · top-3 of 3 ranked · cyan = informing',
|
|
177
219
|
recommendedVerb: null, // D-02 neutral; no recommendation
|
|
178
220
|
emitTelemetry: true, // 116-04 selector_presentation event fires via this flag
|
|
179
221
|
},
|
|
@@ -389,5 +389,14 @@
|
|
|
389
389
|
"persona_affinity": [],
|
|
390
390
|
"completion_pattern": null
|
|
391
391
|
}
|
|
392
|
-
]
|
|
392
|
+
],
|
|
393
|
+
"alias_map": {
|
|
394
|
+
"verb_aliases": {
|
|
395
|
+
"Resolve": "Run Methodology",
|
|
396
|
+
"Explore": "Run Methodology",
|
|
397
|
+
"Later": "Defer",
|
|
398
|
+
"Skip": "Free-Text"
|
|
399
|
+
},
|
|
400
|
+
"provenance": "Phase 121.5-10 Sub-plan K (audit Section 5.3 verb-label alias decision; LOCKED decision 1). Aliases render to the user; canonical verbs persist to graph edges via navigation.cjs."
|
|
401
|
+
}
|
|
393
402
|
}
|
|
@@ -81,6 +81,82 @@ function _sha256(s) {
|
|
|
81
81
|
const FREE_TEXT = 'Free-Text';
|
|
82
82
|
const MODE_B_ZONE1_PREFIX = '⚠ Brain unreachable; running on local graph only.';
|
|
83
83
|
|
|
84
|
+
// Phase 121.5-10 Sub-plan K (LOCKED decisions 1 + Bundle A):
|
|
85
|
+
// Brain-suggestion content template constants. The locked chip / glyphs /
|
|
86
|
+
// footer values are non-negotiable per audit Section 5.2 -- consumers passing
|
|
87
|
+
// brain_suggestion_variant:true receive these literal values composed into
|
|
88
|
+
// rendered.zones.body and rendered.zones.footer. Alias map loaded once at
|
|
89
|
+
// module init from lib/hmi/jtbd-taxonomy.json alias_map.verb_aliases.
|
|
90
|
+
const BRAIN_CHIP = '[■ BRAIN]';
|
|
91
|
+
const BRAIN_GLYPH_RECOMMENDED = '▶';
|
|
92
|
+
const BRAIN_GLYPH_ALTERNATIVE = '▷';
|
|
93
|
+
|
|
94
|
+
// Load alias_map once. Safe-require: if jtbd-taxonomy.json is missing or
|
|
95
|
+
// malformed (degraded install), fall back to empty map -- consumers without
|
|
96
|
+
// alias_map get pass-through behavior (verb renders verbatim, no collapse).
|
|
97
|
+
let _aliasMapCache = null;
|
|
98
|
+
function _loadAliasMap() {
|
|
99
|
+
if (_aliasMapCache !== null) return _aliasMapCache;
|
|
100
|
+
try {
|
|
101
|
+
const tax = require('./jtbd-taxonomy.json');
|
|
102
|
+
if (tax && tax.alias_map && tax.alias_map.verb_aliases
|
|
103
|
+
&& typeof tax.alias_map.verb_aliases === 'object') {
|
|
104
|
+
_aliasMapCache = tax.alias_map.verb_aliases;
|
|
105
|
+
return _aliasMapCache;
|
|
106
|
+
}
|
|
107
|
+
} catch (_e) { /* graceful */ }
|
|
108
|
+
_aliasMapCache = {};
|
|
109
|
+
return _aliasMapCache;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Phase 121.5-10 Sub-plan K (LOCKED decision 1): collapse a user-facing verb
|
|
114
|
+
* alias to its canonical form for graph-edge persistence. Aliases live in
|
|
115
|
+
* lib/hmi/jtbd-taxonomy.json alias_map.verb_aliases; unknown verbs pass
|
|
116
|
+
* through unchanged (no-op for already-canonical verbs).
|
|
117
|
+
*
|
|
118
|
+
* Optional explicit aliasMap arg overrides the module-level cache (test seam
|
|
119
|
+
* + per-call override for surfaces with surface-specific aliases).
|
|
120
|
+
*/
|
|
121
|
+
function aliasToCanonical(verb, aliasMap) {
|
|
122
|
+
if (typeof verb !== 'string' || verb.length === 0) return verb;
|
|
123
|
+
const map = (aliasMap && typeof aliasMap === 'object') ? aliasMap : _loadAliasMap();
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(map, verb)) {
|
|
125
|
+
const canonical = map[verb];
|
|
126
|
+
if (typeof canonical === 'string' && canonical.length > 0) return canonical;
|
|
127
|
+
}
|
|
128
|
+
return verb;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Phase 121.5-10 Sub-plan K: compose the locked Brain-suggestion option-row
|
|
133
|
+
* block from a normalized optionRows array. Each row is two lines:
|
|
134
|
+
* Row 1: `<glyph> <N>. <verb>` left-padded, `<conf>%` right-aligned to 80c
|
|
135
|
+
* Row 2: 5-space indent, `<meta>`
|
|
136
|
+
* Per audit Section 5.2.1. Returns the composed string (one block).
|
|
137
|
+
*/
|
|
138
|
+
function composeBrainOptionRows(optionRows) {
|
|
139
|
+
if (!Array.isArray(optionRows) || optionRows.length === 0) return '';
|
|
140
|
+
const COL_WIDTH = 80;
|
|
141
|
+
const blocks = [];
|
|
142
|
+
for (let i = 0; i < optionRows.length; i += 1) {
|
|
143
|
+
const r = optionRows[i] || {};
|
|
144
|
+
const glyph = (r.glyph === BRAIN_GLYPH_RECOMMENDED || r.glyph === BRAIN_GLYPH_ALTERNATIVE)
|
|
145
|
+
? r.glyph : BRAIN_GLYPH_ALTERNATIVE;
|
|
146
|
+
const num = Number.isInteger(r.number) ? r.number : (i + 1);
|
|
147
|
+
const verb = (typeof r.verb === 'string' && r.verb.length > 0) ? r.verb : '';
|
|
148
|
+
const confPct = (Number.isInteger(r.confPct)) ? r.confPct : 0;
|
|
149
|
+
const meta = (typeof r.meta === 'string') ? r.meta : '';
|
|
150
|
+
const left = glyph + ' ' + String(num) + '. ' + verb;
|
|
151
|
+
const right = String(confPct) + '%';
|
|
152
|
+
const padCount = Math.max(1, COL_WIDTH - left.length - right.length);
|
|
153
|
+
const row1 = left + ' '.repeat(padCount) + right;
|
|
154
|
+
const row2 = ' ' + meta;
|
|
155
|
+
blocks.push(row1 + '\n' + row2);
|
|
156
|
+
}
|
|
157
|
+
return blocks.join('\n\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
84
160
|
// Phase 88.2-04: F.* sub-shape registry and JUST_TALK refuse vocabulary.
|
|
85
161
|
// Phase 88.2-05: 'F.0' Mini Decision Gate prepended (closed-vocab; freeTextOffered:false carve-out).
|
|
86
162
|
// Phase 88.2-06: 'F.6' Plan Review Round appended (closed-vocab; routes to the
|
|
@@ -301,6 +377,74 @@ function appendAskUserQuestionTrailer(rendered, subShape) {
|
|
|
301
377
|
return rendered;
|
|
302
378
|
}
|
|
303
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Phase 121.5-10 Sub-plan K: overlay the locked Brain-suggestion content
|
|
382
|
+
* template onto a rendered F.* envelope per audit Section 5.2. Mutates the
|
|
383
|
+
* rendered object in place. Locked slot values per LOCKED Bundle A decisions:
|
|
384
|
+
*
|
|
385
|
+
* - Header chip: [■ BRAIN] (payload.header overrides)
|
|
386
|
+
* - Question line: "Choose next move:" (payload.questionLine overrides;
|
|
387
|
+
* rendered as the first line of zones.body BEFORE the
|
|
388
|
+
* two-line option rows)
|
|
389
|
+
* - Option rows: Two-line dense per Section 5.2.1; composed from
|
|
390
|
+
* payload.optionRows via composeBrainOptionRows().
|
|
391
|
+
* - Footer: Stat-strip line `▶ Brain · top-K of N ranked · cyan
|
|
392
|
+
* = informing` (payload.footer overrides). Prepended
|
|
393
|
+
* to existing footer with a blank-line separator.
|
|
394
|
+
*
|
|
395
|
+
* alias_map: when payload.alias_map is set, contract.alias_map surfaces it
|
|
396
|
+
* for downstream consumers (the selection-time canonical-verb collapse runs
|
|
397
|
+
* in the consumer via aliasToCanonical(verb_chosen, contract.alias_map) per
|
|
398
|
+
* LOCKED decision 1).
|
|
399
|
+
*/
|
|
400
|
+
function applyBrainSuggestionVariant(rendered, payloadObj) {
|
|
401
|
+
if (!rendered || typeof rendered !== 'object') return rendered;
|
|
402
|
+
if (!rendered.zones || typeof rendered.zones !== 'object') return rendered;
|
|
403
|
+
|
|
404
|
+
// Header: enforce the locked chip (allow caller override).
|
|
405
|
+
const header = (typeof payloadObj.header === 'string' && payloadObj.header.length > 0)
|
|
406
|
+
? payloadObj.header : BRAIN_CHIP;
|
|
407
|
+
rendered.zones.header = header;
|
|
408
|
+
|
|
409
|
+
// Body: compose two-line dense option rows + optional question line on top.
|
|
410
|
+
// If optionRows missing, keep the renderer's body (graceful: a Mode B / no-
|
|
411
|
+
// packet caller may legitimately have no rows to render).
|
|
412
|
+
const questionLine = (typeof payloadObj.questionLine === 'string' && payloadObj.questionLine.length > 0)
|
|
413
|
+
? payloadObj.questionLine : 'Choose next move:';
|
|
414
|
+
if (Array.isArray(payloadObj.optionRows) && payloadObj.optionRows.length > 0) {
|
|
415
|
+
const rowsBlock = composeBrainOptionRows(payloadObj.optionRows);
|
|
416
|
+
rendered.zones.body = questionLine + '\n\n' + rowsBlock;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Footer: prepend the stat-strip caption ahead of any existing footer (the
|
|
420
|
+
// AskUserQuestion trailer is appended AFTER this overlay; see pickShape).
|
|
421
|
+
const footer = (typeof payloadObj.footer === 'string' && payloadObj.footer.length > 0)
|
|
422
|
+
? payloadObj.footer : null;
|
|
423
|
+
if (footer !== null) {
|
|
424
|
+
const existing = rendered.zones.footer;
|
|
425
|
+
if (existing === null || existing === undefined || existing === '') {
|
|
426
|
+
rendered.zones.footer = footer;
|
|
427
|
+
} else {
|
|
428
|
+
rendered.zones.footer = footer + '\n\n' + String(existing);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// alias_map: surface on contract so downstream selection handlers can
|
|
433
|
+
// call aliasToCanonical(verb_chosen, contract.alias_map) per LOCKED
|
|
434
|
+
// decision 1 (aliases render to user, canonical persists to graph).
|
|
435
|
+
if (payloadObj.alias_map && typeof payloadObj.alias_map === 'object') {
|
|
436
|
+
if (!rendered.contract || typeof rendered.contract !== 'object') {
|
|
437
|
+
rendered.contract = {};
|
|
438
|
+
}
|
|
439
|
+
rendered.contract.alias_map = payloadObj.alias_map;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Surface the locked chip as a scalar for introspection without zone parsing.
|
|
443
|
+
rendered.brain_suggestion_chip = BRAIN_CHIP;
|
|
444
|
+
|
|
445
|
+
return rendered;
|
|
446
|
+
}
|
|
447
|
+
|
|
304
448
|
function dispatchShapeF(args) {
|
|
305
449
|
const { roomDir, tier, mode, payload } = args;
|
|
306
450
|
const payloadObj = (payload && typeof payload === 'object') ? payload : {};
|
|
@@ -561,6 +705,18 @@ function pickShape(args) {
|
|
|
561
705
|
applyModeBPrefix(result.rendered);
|
|
562
706
|
}
|
|
563
707
|
|
|
708
|
+
// Phase 121.5-10 Sub-plan K: Brain-suggestion variant overlay. Applied
|
|
709
|
+
// BEFORE the AskUserQuestion trailer so the trailer lands AFTER the
|
|
710
|
+
// stat-strip footer per audit Section 5.2. Only fires when payload
|
|
711
|
+
// carries brain_suggestion_variant === true; otherwise no-op (every
|
|
712
|
+
// existing F.* consumer is preserved byte-for-byte).
|
|
713
|
+
if (result && result.shape !== 'error' && !result.passthrough) {
|
|
714
|
+
const payloadObj = (opts.payload && typeof opts.payload === 'object') ? opts.payload : {};
|
|
715
|
+
if (payloadObj.brain_suggestion_variant === true && result.rendered) {
|
|
716
|
+
applyBrainSuggestionVariant(result.rendered, payloadObj);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
564
720
|
// Phase 88.2-04: AskUserQuestion structural-marker trailer + telemetry.
|
|
565
721
|
// Both apply ONLY to successful Shape F.* presentations (umbrella resolves
|
|
566
722
|
// to F.1/F.6; sub-shapes resolve to themselves). Error paths and G/H/A-E
|
|
@@ -619,6 +775,10 @@ function recordSelectorResponse(roomDir, record) {
|
|
|
619
775
|
module.exports = {
|
|
620
776
|
pickShape: pickShape,
|
|
621
777
|
recordSelectorResponse: recordSelectorResponse,
|
|
778
|
+
// Phase 121.5-10 Sub-plan K: alias-to-canonical helper for graph-edge
|
|
779
|
+
// persistence per LOCKED decision 1 (aliases render to user; canonical
|
|
780
|
+
// verbs persist via navigation.cjs).
|
|
781
|
+
aliasToCanonical: aliasToCanonical,
|
|
622
782
|
_internal: {
|
|
623
783
|
resolveTier: resolveTier,
|
|
624
784
|
modeFromTier: modeFromTier,
|
|
@@ -627,9 +787,14 @@ module.exports = {
|
|
|
627
787
|
appendAskUserQuestionTrailer: appendAskUserQuestionTrailer,
|
|
628
788
|
emitPresentationTelemetry: emitPresentationTelemetry,
|
|
629
789
|
justTalkRefuse: justTalkRefuse,
|
|
790
|
+
applyBrainSuggestionVariant: applyBrainSuggestionVariant,
|
|
791
|
+
composeBrainOptionRows: composeBrainOptionRows,
|
|
630
792
|
MODE_B_ZONE1_PREFIX: MODE_B_ZONE1_PREFIX,
|
|
631
793
|
F_SUBSHAPES: F_SUBSHAPES.slice(),
|
|
632
794
|
JUST_TALK: JUST_TALK,
|
|
633
795
|
COMPACTION_VIOLATION_CODE: COMPACTION_VIOLATION_CODE,
|
|
796
|
+
BRAIN_CHIP: BRAIN_CHIP,
|
|
797
|
+
BRAIN_GLYPH_RECOMMENDED: BRAIN_GLYPH_RECOMMENDED,
|
|
798
|
+
BRAIN_GLYPH_ALTERNATIVE: BRAIN_GLYPH_ALTERNATIVE,
|
|
634
799
|
},
|
|
635
800
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
3
|
+
"material_id": "mat-test-001",
|
|
4
|
+
"source_pipeline": "domain",
|
|
5
|
+
"top_differential_score": 0,
|
|
6
|
+
"domain": "tech innovation",
|
|
7
|
+
"bq_subject": "autonomous robotics",
|
|
8
|
+
"top_differential": "<unspecified>: 0.000",
|
|
9
|
+
"semantic_surprise": "A whitespace gap in the canonical decomposition",
|
|
10
|
+
"category_errors_identified": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* Phase 121.5-10 Sub-plan K -- locked Brain-suggestion content template
|
|
6
|
+
* adoption tests. Asserts all 5 consumers render the same chip + question
|
|
7
|
+
* + footer shape per audit Section 5.2.
|
|
8
|
+
*
|
|
9
|
+
* Behavior contract (per plan Task 2 behavior block):
|
|
10
|
+
* T1: scripts/suggest-next-command.cjs invokes pickShape with the locked
|
|
11
|
+
* payload. Captured-stdout assertion: [BRAIN] chip + question line +
|
|
12
|
+
* footer substring all appear once.
|
|
13
|
+
* T2: scripts/act-command.cjs --chain (synthetic non-autonomous step)
|
|
14
|
+
* renders F.0 Mini Gate with [BRAIN] chip; NO [continue]/[stop]
|
|
15
|
+
* bracket text.
|
|
16
|
+
* T3: tension-hook-agent surfaceFinding returns rendered with header
|
|
17
|
+
* === [BRAIN]; questionLine === "Resolve pending tension:".
|
|
18
|
+
* T4: auto-explore-agent surfaceFinding returns header === [BRAIN]; BQ
|
|
19
|
+
* anchor appears in question-line slot.
|
|
20
|
+
* T5: reverse-salient-agent surfaceFinding returns header === [BRAIN];
|
|
21
|
+
* persona suffix appears in body slot beneath the chip.
|
|
22
|
+
* T6: Shape isomorphism -- all 5 consumers share the same chip header.
|
|
23
|
+
*
|
|
24
|
+
* No em-dash check applies.
|
|
25
|
+
*
|
|
26
|
+
* Hermetic: reads only the live repo files. No network. No db. No spawn
|
|
27
|
+
* (the consumer CLIs are invoked via direct require + function call, not
|
|
28
|
+
* child_process, to keep the test fast and deterministic).
|
|
29
|
+
*/
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { spawnSync } = require('child_process');
|
|
35
|
+
|
|
36
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
37
|
+
|
|
38
|
+
let pass = 0;
|
|
39
|
+
let fail = 0;
|
|
40
|
+
const failures = [];
|
|
41
|
+
|
|
42
|
+
function assert(cond, name, detail) {
|
|
43
|
+
if (cond) {
|
|
44
|
+
pass++;
|
|
45
|
+
console.log('PASS ' + name);
|
|
46
|
+
} else {
|
|
47
|
+
fail++;
|
|
48
|
+
failures.push(name + (detail ? ' -- ' + detail : ''));
|
|
49
|
+
console.log('FAIL ' + name + (detail ? ' -- ' + detail : ''));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CHIP = '[■ BRAIN]'; // [■ BRAIN]
|
|
54
|
+
|
|
55
|
+
// --- T1: /mos:suggest-next renders the locked chip + footer ---
|
|
56
|
+
{
|
|
57
|
+
const result = spawnSync('node',
|
|
58
|
+
[path.join(REPO_ROOT, 'scripts', 'suggest-next-command.cjs')],
|
|
59
|
+
{ encoding: 'utf8', cwd: REPO_ROOT });
|
|
60
|
+
const stdout = String(result.stdout || '');
|
|
61
|
+
assert(stdout.indexOf(CHIP) >= 0, 'T1a: suggest-next stdout contains the [BRAIN] chip', 'chip not found');
|
|
62
|
+
assert(stdout.indexOf('Choose next move:') >= 0,
|
|
63
|
+
'T1b: suggest-next stdout contains "Choose next move:" question line');
|
|
64
|
+
assert(stdout.indexOf('▶ Brain · top-') >= 0,
|
|
65
|
+
'T1c: suggest-next stdout contains the stat-strip footer prefix');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- T2: /mos:act --chain renders F.0 with chip; NO bracket text ---
|
|
69
|
+
{
|
|
70
|
+
const actCmd = require(path.join(REPO_ROOT, 'scripts', 'act-command.cjs'));
|
|
71
|
+
// Synthesize a non-autonomous chain step to force the F.0 gate path.
|
|
72
|
+
const workflow = [
|
|
73
|
+
{ step: 1, command: '/mos:beautiful-question', framework: 'BQF' },
|
|
74
|
+
{ step: 2, command: '/mos:think-hats', framework: 'Six Thinking Hats' },
|
|
75
|
+
];
|
|
76
|
+
const autonomyReport = { runnable: false, blockers: [{ step: 2, reason: 'not autonomous_safe' }] };
|
|
77
|
+
const plan = actCmd.planChainRun(workflow, autonomyReport);
|
|
78
|
+
const rendered = actCmd.renderChainReport('test-seed', ['BQF', 'STH'], workflow, autonomyReport, plan);
|
|
79
|
+
|
|
80
|
+
assert(rendered.indexOf(CHIP) >= 0, 'T2a: act --chain gate contains the [BRAIN] chip');
|
|
81
|
+
assert(rendered.indexOf('[continue]') < 0,
|
|
82
|
+
'T2b: act --chain gate has NO [continue] bracket text');
|
|
83
|
+
assert(rendered.indexOf('[stop]') < 0,
|
|
84
|
+
'T2c: act --chain gate has NO [stop] bracket text');
|
|
85
|
+
// F.0 closed vocab markers
|
|
86
|
+
assert(/Approve/.test(rendered), 'T2d: F.0 Approve verb present');
|
|
87
|
+
assert(/Reject/.test(rendered), 'T2e: F.0 Reject verb present');
|
|
88
|
+
assert(/Defer/.test(rendered), 'T2f: F.0 Defer verb present');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- T3: tension-hook-agent surfaceFinding ---
|
|
92
|
+
{
|
|
93
|
+
const agent = require(path.join(REPO_ROOT, 'lib', 'agents', 'tension-hook-agent.cjs'));
|
|
94
|
+
assert(agent.F1_HEADER === CHIP, 'T3a: F1_HEADER constant is the [BRAIN] chip', agent.F1_HEADER);
|
|
95
|
+
assert(Array.isArray(agent.F1_VERBS) && agent.F1_VERBS.length === 3
|
|
96
|
+
&& agent.F1_VERBS[0] === 'Resolve' && agent.F1_VERBS[1] === 'Later' && agent.F1_VERBS[2] === 'Skip',
|
|
97
|
+
'T3b: F1_VERBS retained as [Resolve, Later, Skip] (LOCKED decision 1 aliases)');
|
|
98
|
+
|
|
99
|
+
const finding = agent.composeFinding({
|
|
100
|
+
tension_id: 'a'.repeat(32),
|
|
101
|
+
source_node_id: 'src-node',
|
|
102
|
+
target_node_id: 'tgt-node',
|
|
103
|
+
source_section: 'problem',
|
|
104
|
+
target_section: 'solution',
|
|
105
|
+
tension_type: 'CONTRADICTS',
|
|
106
|
+
});
|
|
107
|
+
const r = agent.surfaceFinding({
|
|
108
|
+
finding: finding,
|
|
109
|
+
roomDir: path.join(REPO_ROOT, 'lib', 'memory'),
|
|
110
|
+
operator: null,
|
|
111
|
+
tier: 2,
|
|
112
|
+
});
|
|
113
|
+
assert(r && r.surfaced === true, 'T3c: surfaced === true');
|
|
114
|
+
assert(r.rendered && r.rendered.zones && r.rendered.zones.header === CHIP,
|
|
115
|
+
'T3d: rendered.zones.header === [BRAIN]', r.rendered && r.rendered.zones && r.rendered.zones.header);
|
|
116
|
+
assert(r.rendered && r.rendered.zones && typeof r.rendered.zones.body === 'string'
|
|
117
|
+
&& r.rendered.zones.body.indexOf('Resolve pending tension:') === 0,
|
|
118
|
+
'T3e: body starts with "Resolve pending tension:"');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- T4: auto-explore-agent surfaceFinding ---
|
|
122
|
+
{
|
|
123
|
+
const agent = require(path.join(REPO_ROOT, 'lib', 'agents', 'auto-explore-agent.cjs'));
|
|
124
|
+
// Build a minimal finding object. The agent's composeAutoExploreFinding is
|
|
125
|
+
// its full constructor; we synthesize a finding with the minimum fields
|
|
126
|
+
// surfaceFinding needs.
|
|
127
|
+
const finding = {
|
|
128
|
+
id: 'b'.repeat(32),
|
|
129
|
+
material_id: 'mat-test-001',
|
|
130
|
+
source_pipeline: 'domain',
|
|
131
|
+
top_differential_score: 0.85,
|
|
132
|
+
domain: 'tech innovation',
|
|
133
|
+
bq_subject: 'autonomous robotics',
|
|
134
|
+
};
|
|
135
|
+
const r = agent.surfaceFinding({
|
|
136
|
+
finding: finding,
|
|
137
|
+
roomDir: path.join(REPO_ROOT, 'lib', 'memory'),
|
|
138
|
+
operator: 'AUTONOMOUS',
|
|
139
|
+
tier: 2,
|
|
140
|
+
});
|
|
141
|
+
assert(r && r.surfaced === true, 'T4a: surfaced === true');
|
|
142
|
+
assert(r.rendered && r.rendered.zones && r.rendered.zones.header === CHIP,
|
|
143
|
+
'T4b: rendered.zones.header === [BRAIN]', r.rendered && r.rendered.zones && r.rendered.zones.header);
|
|
144
|
+
// The BQ-anchored Larry voice line lands in the question-line slot beneath
|
|
145
|
+
// the chip; the body should NOT be empty.
|
|
146
|
+
assert(r.rendered && r.rendered.zones && typeof r.rendered.zones.body === 'string'
|
|
147
|
+
&& r.rendered.zones.body.length > 0,
|
|
148
|
+
'T4c: body slot is non-empty (carries the BQ-anchored line)');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- T5: reverse-salient-agent surfaceFinding ---
|
|
152
|
+
// reverse-salient-agent returns { surfaced, dispatchResult: { shape, rendered } }
|
|
153
|
+
// rather than the surfaced.rendered envelope used by tension-hook + auto-explore.
|
|
154
|
+
// This is the Phase 89-07 native shape; the test asserts against
|
|
155
|
+
// dispatchResult.rendered.zones to match the contract.
|
|
156
|
+
{
|
|
157
|
+
const agent = require(path.join(REPO_ROOT, 'lib', 'agents', 'reverse-salient-agent.cjs'));
|
|
158
|
+
const finding = {
|
|
159
|
+
id: 'c'.repeat(32),
|
|
160
|
+
body_text: 'rs-engine finding body text',
|
|
161
|
+
brain_chain_text: 'BQF -> STH',
|
|
162
|
+
};
|
|
163
|
+
const roleBlend = { primary_role: 'founder' };
|
|
164
|
+
const r = agent.surfaceFinding({
|
|
165
|
+
finding: finding,
|
|
166
|
+
roomDir: path.join(REPO_ROOT, 'lib', 'memory'),
|
|
167
|
+
operator: null,
|
|
168
|
+
tier: 2,
|
|
169
|
+
roleBlend: roleBlend,
|
|
170
|
+
});
|
|
171
|
+
assert(r && r.surfaced === true, 'T5a: surfaced === true');
|
|
172
|
+
const rendered = r && r.dispatchResult && r.dispatchResult.rendered;
|
|
173
|
+
assert(rendered && rendered.zones && rendered.zones.header === CHIP,
|
|
174
|
+
'T5b: dispatchResult.rendered.zones.header === [BRAIN]',
|
|
175
|
+
rendered && rendered.zones && rendered.zones.header);
|
|
176
|
+
// Persona suffix moves into the body slot beneath the chip
|
|
177
|
+
assert(rendered && rendered.zones && typeof rendered.zones.body === 'string'
|
|
178
|
+
&& rendered.zones.body.indexOf('lens') >= 0,
|
|
179
|
+
'T5c: body slot carries the persona "lens" suffix');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- T6: Shape isomorphism across all 5 consumers ---
|
|
183
|
+
{
|
|
184
|
+
// All 5 consumers must render the same [BRAIN] chip header. The chip is
|
|
185
|
+
// exact: 9 chars including brackets, leading filled square + 5-char brand.
|
|
186
|
+
assert(CHIP.length === 9, 'T6a: CHIP is exactly 9 chars (audit Section 5.2.1)');
|
|
187
|
+
// T1 + T2 already covered the CLI consumers; T3 + T4 + T5 covered the 3
|
|
188
|
+
// agentic surfaces. Each test independently asserted the chip; the test
|
|
189
|
+
// file's GREEN exit implies isomorphism.
|
|
190
|
+
assert(true, 'T6b: 5 of 5 consumers asserted to carry the [BRAIN] chip (see T1..T5)');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log('');
|
|
194
|
+
console.log('brain-suggestion-template.test.cjs: ' + pass + ' passed, ' + fail + ' failed');
|
|
195
|
+
if (fail > 0) {
|
|
196
|
+
console.log('FAILURES:');
|
|
197
|
+
for (const f of failures) console.log(' - ' + f);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
process.exit(0);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* Phase 121.5-10 Sub-plan K -- selector alias_map unit tests.
|
|
6
|
+
*
|
|
7
|
+
* Behavior contract (per plan Task 1 behavior block):
|
|
8
|
+
* T1: jtbd-taxonomy.json parses with NEW top-level key alias_map.verb_aliases
|
|
9
|
+
* containing the 4 LOCKED entries (Resolve, Explore, Later, Skip) per
|
|
10
|
+
* LOCKED decision 1.
|
|
11
|
+
* T2: aliasToCanonical round-trip: 'Resolve' -> 'Run Methodology';
|
|
12
|
+
* 'Run Methodology' (already canonical) -> 'Run Methodology' (no-op).
|
|
13
|
+
* T3: pickShape with payload.alias_map: { 'Resolve': 'Run Methodology' }
|
|
14
|
+
* honors LOCKED decision 1 -- contract.alias_map is surfaced to
|
|
15
|
+
* consumers so they can collapse verb_chosen to verb_canonical at
|
|
16
|
+
* selection time.
|
|
17
|
+
* T4: Render-vs-persist invariant: when payload.verbs carries an alias
|
|
18
|
+
* like 'Resolve', the alias label appears in rendered.zones.body;
|
|
19
|
+
* the canonical 'Run Methodology' is what aliasToCanonical returns
|
|
20
|
+
* for graph-edge persistence.
|
|
21
|
+
* T5: Unknown alias passes through unchanged (no-op for non-aliased verbs).
|
|
22
|
+
*
|
|
23
|
+
* No em-dash check applies (this is a test file; no narrative prose).
|
|
24
|
+
*
|
|
25
|
+
* Hermetic: reads only the live repo files via require/fs.readFileSync. No
|
|
26
|
+
* network. No db. No spawn.
|
|
27
|
+
*/
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
34
|
+
const TAXONOMY_PATH = path.join(REPO_ROOT, 'lib', 'hmi', 'jtbd-taxonomy.json');
|
|
35
|
+
const DISPATCHER = require(path.join(REPO_ROOT, 'lib', 'hmi', 'selector-dispatcher.cjs'));
|
|
36
|
+
|
|
37
|
+
let pass = 0;
|
|
38
|
+
let fail = 0;
|
|
39
|
+
const failures = [];
|
|
40
|
+
|
|
41
|
+
function assert(cond, name, detail) {
|
|
42
|
+
if (cond) {
|
|
43
|
+
pass++;
|
|
44
|
+
console.log('PASS ' + name);
|
|
45
|
+
} else {
|
|
46
|
+
fail++;
|
|
47
|
+
failures.push(name + (detail ? ' -- ' + detail : ''));
|
|
48
|
+
console.log('FAIL ' + name + (detail ? ' -- ' + detail : ''));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// --- T1: jtbd-taxonomy.json carries alias_map.verb_aliases with 4 LOCKED entries ---
|
|
53
|
+
{
|
|
54
|
+
const raw = fs.readFileSync(TAXONOMY_PATH, 'utf8');
|
|
55
|
+
let parsed;
|
|
56
|
+
try { parsed = JSON.parse(raw); } catch (e) { parsed = null; }
|
|
57
|
+
assert(parsed !== null, 'T1a: jtbd-taxonomy.json parses as JSON');
|
|
58
|
+
assert(parsed && parsed.alias_map && typeof parsed.alias_map === 'object',
|
|
59
|
+
'T1b: top-level alias_map block present');
|
|
60
|
+
const va = parsed && parsed.alias_map && parsed.alias_map.verb_aliases;
|
|
61
|
+
assert(va && typeof va === 'object', 'T1c: alias_map.verb_aliases present');
|
|
62
|
+
assert(va && va.Resolve === 'Run Methodology',
|
|
63
|
+
'T1d: Resolve -> Run Methodology', va && va.Resolve);
|
|
64
|
+
assert(va && va.Explore === 'Run Methodology',
|
|
65
|
+
'T1e: Explore -> Run Methodology', va && va.Explore);
|
|
66
|
+
assert(va && va.Later === 'Defer',
|
|
67
|
+
'T1f: Later -> Defer', va && va.Later);
|
|
68
|
+
assert(va && va.Skip === 'Free-Text',
|
|
69
|
+
'T1g: Skip -> Free-Text', va && va.Skip);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- T2: aliasToCanonical round-trip ---
|
|
73
|
+
{
|
|
74
|
+
assert(DISPATCHER.aliasToCanonical('Resolve') === 'Run Methodology',
|
|
75
|
+
'T2a: aliasToCanonical(Resolve) === Run Methodology');
|
|
76
|
+
assert(DISPATCHER.aliasToCanonical('Explore') === 'Run Methodology',
|
|
77
|
+
'T2b: aliasToCanonical(Explore) === Run Methodology');
|
|
78
|
+
assert(DISPATCHER.aliasToCanonical('Later') === 'Defer',
|
|
79
|
+
'T2c: aliasToCanonical(Later) === Defer');
|
|
80
|
+
assert(DISPATCHER.aliasToCanonical('Skip') === 'Free-Text',
|
|
81
|
+
'T2d: aliasToCanonical(Skip) === Free-Text');
|
|
82
|
+
// Already-canonical pass-through (no-op)
|
|
83
|
+
assert(DISPATCHER.aliasToCanonical('Run Methodology') === 'Run Methodology',
|
|
84
|
+
'T2e: aliasToCanonical(Run Methodology) is no-op');
|
|
85
|
+
assert(DISPATCHER.aliasToCanonical('Defer') === 'Defer',
|
|
86
|
+
'T2f: aliasToCanonical(Defer) is no-op');
|
|
87
|
+
assert(DISPATCHER.aliasToCanonical('Free-Text') === 'Free-Text',
|
|
88
|
+
'T2g: aliasToCanonical(Free-Text) is no-op');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// --- T3: pickShape carries alias_map onto rendered.contract.alias_map ---
|
|
92
|
+
{
|
|
93
|
+
const result = DISPATCHER.pickShape({
|
|
94
|
+
requestedShape: 'F.1',
|
|
95
|
+
tier: 2,
|
|
96
|
+
payload: {
|
|
97
|
+
brain_suggestion_variant: true,
|
|
98
|
+
header: '[■ BRAIN]',
|
|
99
|
+
questionLine: 'Choose next move:',
|
|
100
|
+
verbs: ['Resolve', 'Later', 'Skip'],
|
|
101
|
+
alias_map: { 'Resolve': 'Run Methodology', 'Later': 'Defer', 'Skip': 'Free-Text' },
|
|
102
|
+
optionRows: [
|
|
103
|
+
{ glyph: '▷', number: 1, verb: 'Resolve', confPct: 70, meta: 'tension hook' },
|
|
104
|
+
{ glyph: '▷', number: 2, verb: 'Later', confPct: 50, meta: 'queue for milestone' },
|
|
105
|
+
{ glyph: '▷', number: 3, verb: 'Skip', confPct: 30, meta: 'silent dismiss' },
|
|
106
|
+
],
|
|
107
|
+
footer: '▶ Brain · top-3 of 3 ranked · cyan = informing',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
assert(result && result.shape === 'F.1', 'T3a: pickShape returned shape F.1');
|
|
111
|
+
assert(result && result.rendered && result.rendered.contract
|
|
112
|
+
&& result.rendered.contract.alias_map
|
|
113
|
+
&& result.rendered.contract.alias_map.Resolve === 'Run Methodology',
|
|
114
|
+
'T3b: contract.alias_map carries the Resolve -> Run Methodology mapping');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- T4: Render-vs-persist invariant ---
|
|
118
|
+
{
|
|
119
|
+
const result = DISPATCHER.pickShape({
|
|
120
|
+
requestedShape: 'F.1',
|
|
121
|
+
tier: 2,
|
|
122
|
+
payload: {
|
|
123
|
+
brain_suggestion_variant: true,
|
|
124
|
+
header: '[■ BRAIN]',
|
|
125
|
+
verbs: ['Resolve', 'Later', 'Skip'],
|
|
126
|
+
alias_map: { 'Resolve': 'Run Methodology', 'Later': 'Defer', 'Skip': 'Free-Text' },
|
|
127
|
+
optionRows: [
|
|
128
|
+
{ glyph: '▷', number: 1, verb: 'Resolve', confPct: 70, meta: 'tension hook' },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const body = (result && result.rendered && result.rendered.zones && result.rendered.zones.body) || '';
|
|
133
|
+
// Alias label appears in rendered body (render to user).
|
|
134
|
+
assert(body.indexOf('Resolve') >= 0, 'T4a: alias label "Resolve" appears in rendered body');
|
|
135
|
+
// aliasToCanonical produces canonical for graph-edge persistence.
|
|
136
|
+
assert(DISPATCHER.aliasToCanonical('Resolve', result.rendered.contract.alias_map)
|
|
137
|
+
=== 'Run Methodology',
|
|
138
|
+
'T4b: aliasToCanonical("Resolve", contract.alias_map) === Run Methodology');
|
|
139
|
+
// The canonical "Run Methodology" string is what would persist to the graph edge,
|
|
140
|
+
// NOT the alias "Resolve" -- this is the render-vs-persist split.
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --- T5: Unknown alias passes through unchanged ---
|
|
144
|
+
{
|
|
145
|
+
assert(DISPATCHER.aliasToCanonical('NotAnAlias') === 'NotAnAlias',
|
|
146
|
+
'T5a: unknown verb is a no-op');
|
|
147
|
+
assert(DISPATCHER.aliasToCanonical('') === '',
|
|
148
|
+
'T5b: empty string is a no-op (input returned as-is)');
|
|
149
|
+
assert(DISPATCHER.aliasToCanonical(null) === null,
|
|
150
|
+
'T5c: null is a no-op (input returned as-is)');
|
|
151
|
+
assert(DISPATCHER.aliasToCanonical(undefined) === undefined,
|
|
152
|
+
'T5d: undefined is a no-op (input returned as-is)');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// --- T6: explicit aliasMap arg overrides the module-level cache ---
|
|
156
|
+
{
|
|
157
|
+
const customMap = { 'CustomAlias': 'Run Methodology' };
|
|
158
|
+
assert(DISPATCHER.aliasToCanonical('CustomAlias', customMap) === 'Run Methodology',
|
|
159
|
+
'T6a: explicit aliasMap arg honored over module default');
|
|
160
|
+
// Module-level cache untouched -- Resolve still resolves via the default map.
|
|
161
|
+
assert(DISPATCHER.aliasToCanonical('Resolve') === 'Run Methodology',
|
|
162
|
+
'T6b: module-level alias_map cache preserved after explicit-arg call');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log('selector-alias-map.test.cjs: ' + pass + ' passed, ' + fail + ' failed');
|
|
167
|
+
if (fail > 0) {
|
|
168
|
+
console.log('FAILURES:');
|
|
169
|
+
for (const f of failures) console.log(' - ' + f);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
process.exit(0);
|
|
@@ -68,6 +68,14 @@ const TAXONOMY_PATH = path.join(__dirname, '..', 'hmi', 'jtbd-taxonomy.json');
|
|
|
68
68
|
// cross-module coupling on the hot ranking path.
|
|
69
69
|
const DEFAULT_SEED = 'Beautiful Question Framework';
|
|
70
70
|
|
|
71
|
+
// Phase 121.5-10 Sub-plan K (audit Section 5.2.3 anti-pattern "More than 3
|
|
72
|
+
// lines per option"): the locked Brain-suggestion template renders TWO lines
|
|
73
|
+
// per option row (glyph + verb + score line, then meta line). With the
|
|
74
|
+
// AskUserQuestion auto-injected "Type something" / "Chat about this" rows
|
|
75
|
+
// plus the footer stat-strip, the visual budget caps at 3 user-facing
|
|
76
|
+
// options. MAX_K = 3 clamps caller k to fit the locked mockup row budget.
|
|
77
|
+
const MAX_K = 3;
|
|
78
|
+
|
|
71
79
|
// Per-process caches. The registry + taxonomy are generated artifacts that do
|
|
72
80
|
// not change during a run; reading each once is the command-resolver.cjs
|
|
73
81
|
// precedent. Tests can override the registry via _test._setRegistry.
|
|
@@ -284,6 +292,50 @@ function _scoreCommand({ cmd, packetOptional, roomState, investment_level }) {
|
|
|
284
292
|
return numerator / denominator;
|
|
285
293
|
}
|
|
286
294
|
|
|
295
|
+
// Phase 121.5-10 Sub-plan K: derive optional `category` field for the locked
|
|
296
|
+
// Brain-suggestion template meta row. Sourced from packetOptional's
|
|
297
|
+
// local_graph_summary.category_hint when present, falling back to the
|
|
298
|
+
// command's registry-declared `kind` field, falling back to '' (empty string
|
|
299
|
+
// is acceptable per the locked template -- the meta row renders blank).
|
|
300
|
+
function _categoryFromPacket(packetOptional, cmd) {
|
|
301
|
+
if (packetOptional && packetOptional.local_graph_summary
|
|
302
|
+
&& typeof packetOptional.local_graph_summary.category_hint === 'string'
|
|
303
|
+
&& packetOptional.local_graph_summary.category_hint.length > 0) {
|
|
304
|
+
return packetOptional.local_graph_summary.category_hint;
|
|
305
|
+
}
|
|
306
|
+
if (cmd && typeof cmd.kind === 'string' && cmd.kind.length > 0) return cmd.kind;
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Phase 121.5-10 Sub-plan K: derive optional `graph_relationship` field for
|
|
311
|
+
// the locked Brain-suggestion template meta row. Sourced from
|
|
312
|
+
// packetOptional's framework_chain_hint -- the highest-confidence edge that
|
|
313
|
+
// touches the framework gets its relationship rendered (e.g. "feeds Porter
|
|
314
|
+
// Five Forces"). Falls back to '' when no edge data exists.
|
|
315
|
+
function _graphRelationshipFromPacket(packetOptional, framework) {
|
|
316
|
+
if (!packetOptional || !packetOptional.local_graph_summary) return '';
|
|
317
|
+
const hint = packetOptional.local_graph_summary.framework_chain_hint;
|
|
318
|
+
if (!hint || !Array.isArray(hint.edges)) return '';
|
|
319
|
+
let bestEdge = null;
|
|
320
|
+
let bestConf = -1;
|
|
321
|
+
for (const e of hint.edges) {
|
|
322
|
+
if (!e) continue;
|
|
323
|
+
const touches = (e.from === framework) || (e.to === framework);
|
|
324
|
+
if (touches && typeof e.confidence === 'number' && e.confidence > bestConf) {
|
|
325
|
+
bestConf = e.confidence;
|
|
326
|
+
bestEdge = e;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (!bestEdge) return '';
|
|
330
|
+
const rel = (typeof bestEdge.relationship === 'string' && bestEdge.relationship.length > 0)
|
|
331
|
+
? bestEdge.relationship : 'feeds';
|
|
332
|
+
const target = (bestEdge.from === framework)
|
|
333
|
+
? (typeof bestEdge.to === 'string' ? bestEdge.to : '')
|
|
334
|
+
: (typeof bestEdge.from === 'string' ? bestEdge.from : '');
|
|
335
|
+
if (target.length === 0) return rel;
|
|
336
|
+
return rel + ' ' + target;
|
|
337
|
+
}
|
|
338
|
+
|
|
287
339
|
// Per-command source attribution per Test 5 (CONTEXT.md acceptance):
|
|
288
340
|
// packet has framework_chain_hint with edges -> 'packet'
|
|
289
341
|
// command has frameworks[] (chain-recommender-derivable)-> 'chain'
|
|
@@ -316,7 +368,13 @@ function rankForSelector(args) {
|
|
|
316
368
|
const roomState = (o.roomState && typeof o.roomState === 'object') ? o.roomState : {};
|
|
317
369
|
const packetOptional = (o.packetOptional && typeof o.packetOptional === 'object')
|
|
318
370
|
? o.packetOptional : null;
|
|
319
|
-
|
|
371
|
+
// Phase 121.5-10 Sub-plan K: clamp k at MAX_K (3) per audit Section 5.2.3
|
|
372
|
+
// anti-pattern -- more than 3 option rows pushes the auto-injected
|
|
373
|
+
// AskUserQuestion "Type something" / "Chat about this" rows off-screen
|
|
374
|
+
// and breaks the locked template visual budget. Caller asking k=20
|
|
375
|
+
// receives k=MAX_K silently. Existing default k=3 unchanged.
|
|
376
|
+
const requestedK = (typeof o.k === 'number' && o.k > 0) ? Math.floor(o.k) : 3;
|
|
377
|
+
const k = Math.min(requestedK, MAX_K);
|
|
320
378
|
const applyDecayWeight = (typeof o._applyDecayWeight === 'function')
|
|
321
379
|
? o._applyDecayWeight : null;
|
|
322
380
|
|
|
@@ -366,6 +424,14 @@ function rankForSelector(args) {
|
|
|
366
424
|
const why = selectWhyContent(jtbd_summary, teaching, investment_level);
|
|
367
425
|
const source = _sourceFor(packetOptional, cmd);
|
|
368
426
|
|
|
427
|
+
// Phase 121.5-10 Sub-plan K: optional category + graph_relationship
|
|
428
|
+
// fields for the locked Brain-suggestion template meta row (audit
|
|
429
|
+
// Section 5.2.1 Row 2). Non-breaking -- consumers that ignore them get
|
|
430
|
+
// the same shape they had before; consumers wiring the locked template
|
|
431
|
+
// (suggest-next, act --chain) read them to compose optionRows[].meta.
|
|
432
|
+
const category = _categoryFromPacket(packetOptional, cmd);
|
|
433
|
+
const graph_relationship = _graphRelationshipFromPacket(packetOptional, framework);
|
|
434
|
+
|
|
369
435
|
scored.push({
|
|
370
436
|
command: cmd.command,
|
|
371
437
|
jtbd_label,
|
|
@@ -376,6 +442,8 @@ function rankForSelector(args) {
|
|
|
376
442
|
why,
|
|
377
443
|
source,
|
|
378
444
|
investment_level,
|
|
445
|
+
category,
|
|
446
|
+
graph_relationship,
|
|
379
447
|
});
|
|
380
448
|
}
|
|
381
449
|
|
|
@@ -403,6 +471,10 @@ module.exports = {
|
|
|
403
471
|
renderInvestmentBadge,
|
|
404
472
|
renderSliceBadge,
|
|
405
473
|
renderNoneFitAffordance,
|
|
474
|
+
// Phase 121.5-10 Sub-plan K: MAX_K constant exported so consumers wiring
|
|
475
|
+
// the locked Brain-suggestion template know the row-budget cap (audit
|
|
476
|
+
// Section 5.2.3 anti-pattern enforcement).
|
|
477
|
+
MAX_K,
|
|
406
478
|
// Test seam (private; consumed only by lib/memory/f-selector-ranker.test.cjs).
|
|
407
479
|
_test: {
|
|
408
480
|
_resetCaches,
|
|
@@ -415,6 +487,9 @@ module.exports = {
|
|
|
415
487
|
_applyDecay,
|
|
416
488
|
_recencyDecay,
|
|
417
489
|
_problemTypeBind,
|
|
490
|
+
_categoryFromPacket,
|
|
491
|
+
_graphRelationshipFromPacket,
|
|
418
492
|
DEFAULT_SEED,
|
|
493
|
+
MAX_K,
|
|
419
494
|
},
|
|
420
495
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindrian_os/install",
|
|
3
|
-
"version": "1.13.0-beta.
|
|
3
|
+
"version": "1.13.0-beta.32",
|
|
4
4
|
"description": "Install MindrianOS into Claude Code with one command -- `npx @mindrian_os/install`. 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",
|
|
@@ -39,6 +39,8 @@ Every output has exactly 4 zones in fixed order. No reordering. No invention.
|
|
|
39
39
|
|
|
40
40
|
> This section documents the current Shape F sub-shape catalog as shipped through Phase 88.2. Today's seven sub-shapes (F.0 through F.6) are the shipped vocabulary as of Phase 121.5; additive expansion is reserved for future lens-aware variants (e.g. v1.14.0 dual-graph work). Treat the catalog as the current canon, not a closed terminal set.
|
|
41
41
|
|
|
42
|
+
> **Orthogonality note (Plan 121.5-10 LOCKED decision 4):** `body_shape:` frontmatter and Shape F sub-shape are ORTHOGONAL axes. `body_shape` describes the LAYOUT discipline of the command body (Shape A Mondrian Board, Shape B Semantic Tree, Shape C Room Card, Shape D Document View, Shape E Action Report). Shape F.x describes the SELECTOR CONTRACT that fires at the close of the body to capture the navigator's next-move decision. A command can carry `body_shape: B` (Semantic Tree layout) AND surface an F.1 selector at its close; these are not competing values -- they describe different surfaces of the same render. Example: `/mos:suggest-next` carries `body_shape: B` (renders the ranked list as a tree) plus an F.1 selector (the verb-pick gate beneath the tree). The `/mos:hmi-status` doctor check enforces body_shape coverage; the F-selector ranker enforces F-shape contracts. The two are not double-counted.
|
|
43
|
+
|
|
42
44
|
### Shape A: Mondrian Board
|
|
43
45
|
**Used by:** `/mos:status`, `/mos:diagnose`, `/mos:radar`, `/mos:admin`
|
|
44
46
|
Progress bars per section. 10-char: `filled` fill, `dot` empty. Section names left-aligned padded. Entry count + MINTO health (`checkmark`/`dot`/`--`). Summary line at bottom.
|
|
@@ -118,6 +120,18 @@ Keyboard: up-arrow / down-arrow (or J / K) to navigate, Enter to select, `?` to
|
|
|
118
120
|
|
|
119
121
|
State-update hook: append to STATE.md Decisions section with timestamp + chosen verb + context snapshot. A typed edge is added to the local graph: (navigator) -[CHOSE {verb}]-> (current-artifact).
|
|
120
122
|
|
|
123
|
+
##### Brain-suggestion variant (Plan 121.5-10 LOCKED)
|
|
124
|
+
|
|
125
|
+
When the F.1 selector consumes Brain-ranked next moves (the 5 surfaces: `/mos:suggest-next`, `/mos:act --chain` pre-gate, the Phase 116 tension-hook-agent, the Phase 117 auto-explore-agent, and the Phase 89-07 reverse-salient-agent), the renderer applies a LOCKED visual variant per the Phase 121.5 selector audit Section 5.2. The lock is non-negotiable; any future Brain-suggestion consumer MUST follow this shape (the `tests/test-no-bespoke-brain-prompts.sh` CI tripwire enforces it).
|
|
126
|
+
|
|
127
|
+
- **Header chip:** `[■ BRAIN]` (literal -- 9 chars including brackets). Distinct from `[GATE]` / `[CONTEXT]` / `[NEXT MOVE]` chips by the leading filled-square glyph.
|
|
128
|
+
- **Question line:** `Choose next move:` (default; per-surface variants land here per Section 5.3 adoption diffs -- tension surfaces use `Resolve pending tension:`; BQ surfaces use the verbatim Brain BQ name; reverse-salient surfaces carry the persona suffix in the body slot rather than question line because F.0 has no question slot).
|
|
129
|
+
- **Option rows:** TWO lines per row. Row 1 = `<glyph> <N>. <Run Verb>` left-padded + `<conf>%` right-aligned to the 80-col boundary. Row 2 = 5-space indent, `<framework category> · <graph relationship>`. Glyphs: `▶` (right-triangle-filled) for >= 0.7 confidence top pick; `▷` (right-triangle-empty) for sub-0.7 alternatives. The one-glyph hierarchy replaces the verbose `(RECOMMENDED)` tag without consuming horizontal space.
|
|
130
|
+
- **Footer:** Stat-strip line `▶ Brain · top-<K> of <N> ranked · <color> = informing`. Three signals in one line: provenance, scale, and color-legend reminding the navigator that the cyan rail means Brain is informing, not commanding.
|
|
131
|
+
- **Zone 1 left-rail color:** `cyan` default. Yellow-on-cascade for CONTRADICTS edges is DEFERRED to v1.13.2 hotfix per LOCKED decision 3 (audit Section 7 Open Question 3).
|
|
132
|
+
- **Verb-label aliases:** registered aliases ALLOWED per LOCKED decision 1 (audit Section 7 Open Question 1). The dispatcher carries `alias_map` (loaded from `lib/hmi/jtbd-taxonomy.json` `alias_map.verb_aliases`); aliases render to the user; canonical verbs persist to graph edges via `navigation.cjs`. Default 4 aliases: Resolve / Explore -> Run Methodology; Later -> Defer; Skip -> Free-Text.
|
|
133
|
+
- **Full template + slot-value table + anti-patterns rejected:** `docs/F-SELECTOR-CONSUMER-GUIDE.md` Section 4 (NEW; published as part of Plan 121.5-10).
|
|
134
|
+
|
|
121
135
|
#### Shape F.2 - Path Control
|
|
122
136
|
|
|
123
137
|
Purpose: When the navigator is choosing structure, not content. Plan / Replan variants. Ties to Claude Code Plan Mode.
|