@mindrian_os/cli 2.0.0-beta.3 → 2.0.0-beta.31

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 (1829) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.mcp.json +14 -0
  3. package/CHANGELOG.md +467 -0
  4. package/agents/analogy-query-fetcher.md +119 -0
  5. package/agents/brain-query.md +91 -0
  6. package/agents/competitor-watch-fetcher.md +94 -0
  7. package/agents/framework-runner.md +258 -0
  8. package/agents/grading.md +216 -0
  9. package/agents/grant-reviewer.md +191 -0
  10. package/agents/investor.md +141 -0
  11. package/agents/larry-extended.md +215 -0
  12. package/agents/meeting-perspective-extractor.md +94 -0
  13. package/agents/opportunity-scanner.md +105 -0
  14. package/agents/persona-analyst.md +201 -0
  15. package/agents/research.md +103 -0
  16. package/agents/reverse-salient-agent.md +42 -0
  17. package/agents/vault-section-reviewer.md +107 -0
  18. package/assets/banner-showcase.html +537 -0
  19. package/assets/fonts/BebasNeue-Regular.ttf +0 -0
  20. package/assets/fonts/Inter-Bold.ttf +0 -0
  21. package/assets/fonts/Inter-Regular.ttf +0 -0
  22. package/assets/logo.svg +10 -0
  23. package/bin/local-chain-recommender.cjs +256 -0
  24. package/bin/mindrian-brain-mcp-client.cjs +319 -0
  25. package/bin/mindrian-mcp-server.cjs +445 -0
  26. package/bin/mindrian-mcp-shim.cjs +100 -0
  27. package/bin/mindrian-tools.cjs +784 -0
  28. package/commands/act.md +529 -0
  29. package/commands/admin.md +439 -0
  30. package/commands/agentshield.md +49 -0
  31. package/commands/analyze-needs.md +78 -0
  32. package/commands/analyze-systems.md +73 -0
  33. package/commands/analyze-timing.md +98 -0
  34. package/commands/auto-explore.md +85 -0
  35. package/commands/beautiful-question.md +76 -0
  36. package/commands/bono.md +152 -0
  37. package/commands/brain-derive.md +137 -0
  38. package/commands/build-knowledge.md +121 -0
  39. package/commands/build-thesis.md +94 -0
  40. package/commands/causal.md +269 -0
  41. package/commands/challenge-assumptions.md +78 -0
  42. package/commands/compare-ventures.md +117 -0
  43. package/commands/correct-reference-now.md +88 -0
  44. package/commands/dashboard.md +144 -0
  45. package/commands/deck.md +117 -0
  46. package/commands/deep-grade.md +214 -0
  47. package/commands/diagnose.md +131 -0
  48. package/commands/diagnostics.md +195 -0
  49. package/commands/dial-memory-refresh.md +113 -0
  50. package/commands/diffusion.md +129 -0
  51. package/commands/discover.md +221 -0
  52. package/commands/doctor.md +271 -0
  53. package/commands/dogfood-flush.md +130 -0
  54. package/commands/dominant-designs.md +76 -0
  55. package/commands/eureka.md +315 -0
  56. package/commands/explain-decision.md +135 -0
  57. package/commands/explore-domains.md +78 -0
  58. package/commands/explore-futures.md +67 -0
  59. package/commands/explore-opportunity.md +172 -0
  60. package/commands/explore-trends.md +78 -0
  61. package/commands/export.md +128 -0
  62. package/commands/feynman-timeline-refresh.md +102 -0
  63. package/commands/file-meeting.md +1195 -0
  64. package/commands/find-analogies.md +369 -0
  65. package/commands/find-bottlenecks.md +108 -0
  66. package/commands/find-connections.md +111 -0
  67. package/commands/funding.md +114 -0
  68. package/commands/futures.md +128 -0
  69. package/commands/grade-grant.md +297 -0
  70. package/commands/grade.md +257 -0
  71. package/commands/graph.md +218 -0
  72. package/commands/hat-briefing.md +163 -0
  73. package/commands/heal.md +72 -0
  74. package/commands/help.md +176 -0
  75. package/commands/hmi-status.md +75 -0
  76. package/commands/ignite.md +229 -0
  77. package/commands/ingest-methodology.md +128 -0
  78. package/commands/intel-pipeline.md +133 -0
  79. package/commands/jtbd.md +262 -0
  80. package/commands/leadership.md +113 -0
  81. package/commands/lean-canvas.md +74 -0
  82. package/commands/macro-trends.md +88 -0
  83. package/commands/map-unknowns.md +76 -0
  84. package/commands/memory-cortex-reach.md +79 -0
  85. package/commands/memory.md +213 -0
  86. package/commands/models.md +200 -0
  87. package/commands/mos-reason.md +489 -0
  88. package/commands/mos.md +162 -0
  89. package/commands/mullins.md +155 -0
  90. package/commands/mva-brief.md +86 -0
  91. package/commands/mva-option.md +119 -0
  92. package/commands/mva-report.md +76 -0
  93. package/commands/new-project.md +633 -0
  94. package/commands/new-surface.md +116 -0
  95. package/commands/onboard.md +489 -0
  96. package/commands/operator.md +168 -0
  97. package/commands/opportunities.md +177 -0
  98. package/commands/organize.md +74 -0
  99. package/commands/persona.md +276 -0
  100. package/commands/pipeline.md +167 -0
  101. package/commands/present.md +125 -0
  102. package/commands/publish.md +227 -0
  103. package/commands/pws-brain.md +154 -0
  104. package/commands/qualify-opportunity.md +113 -0
  105. package/commands/query.md +70 -0
  106. package/commands/radar.md +110 -0
  107. package/commands/reanalyze.md +124 -0
  108. package/commands/research.md +467 -0
  109. package/commands/room.md +385 -0
  110. package/commands/rooms.md +682 -0
  111. package/commands/root-cause.md +77 -0
  112. package/commands/rs-experts.md +153 -0
  113. package/commands/rs-explain.md +134 -0
  114. package/commands/rs-fetch.md +119 -0
  115. package/commands/rs-thesis.md +131 -0
  116. package/commands/scenario-plan.md +80 -0
  117. package/commands/scheduled-tasks.md +308 -0
  118. package/commands/score-innovation.md +78 -0
  119. package/commands/scout.md +379 -0
  120. package/commands/setup.md +658 -0
  121. package/commands/show.md +71 -0
  122. package/commands/skill.md +108 -0
  123. package/commands/snapshot.md +179 -0
  124. package/commands/speakers.md +118 -0
  125. package/commands/splash.md +55 -0
  126. package/commands/stance.md +91 -0
  127. package/commands/status.md +109 -0
  128. package/commands/structure-argument.md +82 -0
  129. package/commands/suggest-next.md +141 -0
  130. package/commands/systems-thinking.md +224 -0
  131. package/commands/think-hats.md +86 -0
  132. package/commands/trending-to-absurd.md +151 -0
  133. package/commands/update.md +256 -0
  134. package/commands/user-needs.md +77 -0
  135. package/commands/validate.md +77 -0
  136. package/commands/value-proposition.md +116 -0
  137. package/commands/vault.md +330 -0
  138. package/commands/visualize.md +71 -0
  139. package/commands/whitespace.md +541 -0
  140. package/commands/wiki.md +96 -0
  141. package/data/ROOM.md +47 -0
  142. package/data/brain-census.generated.json +2617 -0
  143. package/data/brain-orchestration-projection.json +4973 -0
  144. package/data/brain-packet-schema.json +545 -0
  145. package/data/brain-surface-contract.json +32 -0
  146. package/data/capability-ledger.json +391 -0
  147. package/data/capability-map-registry.json +23 -0
  148. package/data/command-registry.json +2433 -0
  149. package/data/connector-coverage-ledger.json +1618 -0
  150. package/data/connector-out-of-spine-allowlist.json +71 -0
  151. package/data/connector-registry.json +3637 -0
  152. package/data/corpus-stats-source.json +11 -0
  153. package/data/corpus-stats.generated.json +10 -0
  154. package/data/cross-domain-analogues.json +15 -0
  155. package/data/deck-aliases.json +6 -0
  156. package/data/deck-styles.json +85 -0
  157. package/data/deployment-surfaces.json +69 -0
  158. package/data/dispatch-framework-map.json +18 -0
  159. package/data/doctor-modules.json +206 -0
  160. package/data/dual-graph-health-baseline.json +12 -0
  161. package/data/eureka-critic-tags.json +32 -0
  162. package/data/first-reward-surfaces.json +66 -0
  163. package/data/first-touch-surfaces.json +60 -0
  164. package/data/flagship-floor-set.json +37 -0
  165. package/data/framework-names.json +121 -0
  166. package/data/grant-rubric-fixtures/early-stage-pre-seed.json +18 -0
  167. package/data/grant-rubric-fixtures/early-stage-round-a.json +18 -0
  168. package/data/grant-rubric-fixtures/early-stage-seed.json +18 -0
  169. package/data/grant-rubric-fixtures/hybrid-seed-incentive.json +18 -0
  170. package/data/grant-rubric-fixtures/rd-fund.json +18 -0
  171. package/data/grant-rubric-fixtures/regional-innovation-incubators.json +18 -0
  172. package/data/grant-rubric-fixtures/technology-incubators.json +18 -0
  173. package/data/grant-rubric-fixtures/tnufa.json +167 -0
  174. package/data/grant-rubric-schema.json +33 -0
  175. package/data/harness-fixtures/converged-room/.context/ROOM.md +13 -0
  176. package/data/harness-fixtures/converged-room/.intelligence/ROOM.md +13 -0
  177. package/data/harness-fixtures/converged-room/.snapshots/ROOM.md +13 -0
  178. package/data/harness-fixtures/converged-room/MINTO.md +19 -0
  179. package/data/harness-fixtures/converged-room/STATE.md +73 -0
  180. package/data/harness-fixtures/converged-room/USER.md +23 -0
  181. package/data/harness-fixtures/converged-room/assets/ROOM.md +13 -0
  182. package/data/harness-fixtures/converged-room/business-model/CONTEXT.md +32 -0
  183. package/data/harness-fixtures/converged-room/business-model/ROOM.md +36 -0
  184. package/data/harness-fixtures/converged-room/competitive-analysis/CONTEXT.md +33 -0
  185. package/data/harness-fixtures/converged-room/competitive-analysis/ROOM.md +35 -0
  186. package/data/harness-fixtures/converged-room/financial-model/CONTEXT.md +30 -0
  187. package/data/harness-fixtures/converged-room/financial-model/ROOM.md +35 -0
  188. package/data/harness-fixtures/converged-room/funding/CONTEXT.md +41 -0
  189. package/data/harness-fixtures/converged-room/funding/ROOM.md +35 -0
  190. package/data/harness-fixtures/converged-room/legal-ip/CONTEXT.md +30 -0
  191. package/data/harness-fixtures/converged-room/legal-ip/ROOM.md +35 -0
  192. package/data/harness-fixtures/converged-room/market-analysis/CONTEXT.md +32 -0
  193. package/data/harness-fixtures/converged-room/market-analysis/ROOM.md +35 -0
  194. package/data/harness-fixtures/converged-room/opportunity-bank/CONTEXT.md +39 -0
  195. package/data/harness-fixtures/converged-room/opportunity-bank/ROOM.md +36 -0
  196. package/data/harness-fixtures/converged-room/problem-definition/CONTEXT.md +32 -0
  197. package/data/harness-fixtures/converged-room/problem-definition/ROOM.md +36 -0
  198. package/data/harness-fixtures/converged-room/references/ROOM.md +13 -0
  199. package/data/harness-fixtures/converged-room/references/SECTION-SCHEMA.md +213 -0
  200. package/data/harness-fixtures/converged-room/references/SUB-SCHEMAS.md +91 -0
  201. package/data/harness-fixtures/converged-room/solution-design/CONTEXT.md +36 -0
  202. package/data/harness-fixtures/converged-room/solution-design/ROOM.md +36 -0
  203. package/data/harness-fixtures/converged-room/strategy/CONTEXT.md +35 -0
  204. package/data/harness-fixtures/converged-room/strategy/ROOM.md +35 -0
  205. package/data/harness-fixtures/converged-room/team/ROOM.md +13 -0
  206. package/data/harness-fixtures/converged-room/team-execution/CONTEXT.md +36 -0
  207. package/data/harness-fixtures/converged-room/team-execution/ROOM.md +35 -0
  208. package/data/harness-manifest.json +74 -0
  209. package/data/harness-policies/CONTEXT.md +55 -0
  210. package/data/harness-policies/_schema.json +42 -0
  211. package/data/harness-policies/contract-parity-larry.json +63 -0
  212. package/data/harness-policies/gate-card-fire.json +13 -0
  213. package/data/harness-policies/gate-graph-derive-health.json +13 -0
  214. package/data/harness-policies/gate-shape-declaration.json +13 -0
  215. package/data/harness-policies/gate-tool-honesty.json +13 -0
  216. package/data/harness-policies/gate-worktree-hygiene.json +13 -0
  217. package/data/harness-policies/memory-write-policy.json +28 -0
  218. package/data/harness-policies/registry-drift.json +13 -0
  219. package/data/harness-policies/release-payload-ceiling.json +13 -0
  220. package/data/harness-policies/voice-backend-noun-free.json +13 -0
  221. package/data/harness-policies/voice-fork-as-card.json +13 -0
  222. package/data/harness-policies/voice-glyph-present.json +13 -0
  223. package/data/harness-policies/voice-hyphens-only.json +13 -0
  224. package/data/help-groups.json +236 -0
  225. package/data/hitl-shape-backfill.json +777 -0
  226. package/data/hitl-shape-declaration-fixtures/invalid-bad-shape.json +5 -0
  227. package/data/hitl-shape-declaration-fixtures/invalid-missing-why.json +4 -0
  228. package/data/hitl-shape-declaration-fixtures/invalid-ranker-contradiction.json +6 -0
  229. package/data/hitl-shape-declaration-fixtures/invalid-skill-gap.json +3 -0
  230. package/data/hitl-shape-declaration-fixtures/valid-multi-stage.json +7 -0
  231. package/data/hitl-shape-declaration-fixtures/valid-single-shape.json +5 -0
  232. package/data/hitl-shape-declaration-fixtures/valid-skill-exempt.json +5 -0
  233. package/data/hitl-shape-declaration-schema.json +34 -0
  234. package/data/hitl-stages-fixtures/bono.json +10 -0
  235. package/data/hitl-stages-fixtures/pipeline-1.json +9 -0
  236. package/data/hitl-stages-fixtures/pipeline-2.json +9 -0
  237. package/data/hitl-stages-fixtures/pipeline-3.json +10 -0
  238. package/data/hitl-stages-fixtures/research.json +10 -0
  239. package/data/hitl-stages-fixtures/rs-convergence.json +10 -0
  240. package/data/hitl-stages-fixtures/rs-family.json +11 -0
  241. package/data/hitl-stages-fixtures/rs-reverse-salient.json +10 -0
  242. package/data/hitl-stages-fixtures/trending-to-absurd.json +11 -0
  243. package/data/hitl-stages-schema.json +26 -0
  244. package/data/mcp-tool-connectors.json +464 -0
  245. package/data/mva-agent-prompts.json +31 -0
  246. package/data/mva-deck-template.html +15 -0
  247. package/data/mva-heuristic-keywords.json +35 -0
  248. package/data/orchestration-command-ledger.json +576 -0
  249. package/data/orchestration-unwired-allowlist.json +6 -0
  250. package/data/portfolio-ahp-matrix.json +16 -0
  251. package/data/publish-needs.json +76 -0
  252. package/data/render-coverage-registry.json +1152 -0
  253. package/data/research-sources.json +58 -0
  254. package/data/roadmap-type-chains.json +28 -0
  255. package/data/room-blueprints.json +156 -0
  256. package/data/subagent-dispatch-grants.json +150 -0
  257. package/hooks/hooks.json +493 -0
  258. package/hooks/run-hook.cmd +64 -0
  259. package/lib/__init__.py +0 -0
  260. package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
  261. package/lib/agents/auto-explore-agent.cjs +1163 -0
  262. package/lib/agents/mva/brain-classic-traps.cjs +55 -0
  263. package/lib/agents/mva/brain-cross-domain.cjs +79 -0
  264. package/lib/agents/mva/brain-similar-ventures.cjs +93 -0
  265. package/lib/agents/mva/dashboard-graph-neighborhood.cjs +72 -0
  266. package/lib/agents/mva/index.cjs +42 -0
  267. package/lib/agents/mva/six-hats-red-black.cjs +137 -0
  268. package/lib/agents/mva/tavily-funding-scan.cjs +147 -0
  269. package/lib/agents/mva/test-all-six-agents.cjs +467 -0
  270. package/lib/agents/reverse-salient-agent.cjs +757 -0
  271. package/lib/agents/tension-hook-agent.cjs +586 -0
  272. package/lib/brain/ROOM.md +44 -0
  273. package/lib/brain/chain-recommender.cjs +784 -0
  274. package/lib/brain/curation-batch.cjs +519 -0
  275. package/lib/brain/framework-chain-slice.cjs +252 -0
  276. package/lib/brain/hypergraph-event-schema.cjs +184 -0
  277. package/lib/chat/chat-context.js +185 -0
  278. package/lib/chat/chat-panel.js +721 -0
  279. package/lib/chat/fabric-chat.cjs +288 -0
  280. package/lib/chat/generative-tools.js +219 -0
  281. package/lib/conversation/ROOM.md +39 -0
  282. package/lib/conversation/classifier-rules.json +38 -0
  283. package/lib/conversation/classifier.cjs +264 -0
  284. package/lib/conversation/operator-epistemic-cap.test.cjs +156 -0
  285. package/lib/conversation/operator.cjs +492 -0
  286. package/lib/conversation/operator.test.cjs +160 -0
  287. package/lib/copy/115-spec-strings.cjs +55 -0
  288. package/lib/core/__init__.py +0 -0
  289. package/lib/core/__nav-stub.cjs +14 -0
  290. package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
  291. package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
  292. package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
  293. package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
  294. package/lib/core/__pycache__/rs_corpus_exclude.cpython-312.pyc +0 -0
  295. package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
  296. package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
  297. package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
  298. package/lib/core/abstraction-gate.cjs +133 -0
  299. package/lib/core/act-jtbd-blurb.cjs +231 -0
  300. package/lib/core/artifact-id.cjs +148 -0
  301. package/lib/core/asset-ops.cjs +151 -0
  302. package/lib/core/auto-commit-throttle.cjs +129 -0
  303. package/lib/core/bearer-token.cjs +199 -0
  304. package/lib/core/behavioral/observation-schema.cjs +86 -0
  305. package/lib/core/bono/cell-fanout.cjs +305 -0
  306. package/lib/core/bono/debate-composition.cjs +495 -0
  307. package/lib/core/bono/hat-governance.cjs +367 -0
  308. package/lib/core/bono/persona-research.cjs +288 -0
  309. package/lib/core/bono/reviewer-governance.cjs +331 -0
  310. package/lib/core/brain-client.cjs +2365 -0
  311. package/lib/core/brain-derivation-prompts.cjs +331 -0
  312. package/lib/core/brain-derivation-queue.cjs +535 -0
  313. package/lib/core/brain-derivation.cjs +740 -0
  314. package/lib/core/brain-md-schema.cjs +528 -0
  315. package/lib/core/brain-md-staleness.cjs +357 -0
  316. package/lib/core/brain-response-sanitize.cjs +396 -0
  317. package/lib/core/breakthrough/canary.cjs +134 -0
  318. package/lib/core/breakthrough/canary.test.cjs +136 -0
  319. package/lib/core/breakthrough/detectors.cjs +359 -0
  320. package/lib/core/breakthrough/detectors.test.cjs +333 -0
  321. package/lib/core/breakthrough/ethics-fence.cjs +129 -0
  322. package/lib/core/breakthrough/ethics-fence.test.cjs +178 -0
  323. package/lib/core/breakthrough/resurfacing.cjs +150 -0
  324. package/lib/core/breakthrough/resurfacing.test.cjs +233 -0
  325. package/lib/core/breakthrough/review-queue.cjs +168 -0
  326. package/lib/core/breakthrough/review-queue.test.cjs +160 -0
  327. package/lib/core/breakthrough/scanner-d17-d18.test.cjs +229 -0
  328. package/lib/core/breakthrough/scanner.cjs +435 -0
  329. package/lib/core/breakthrough/scanner.test.cjs +267 -0
  330. package/lib/core/breakthrough/schema.cjs +180 -0
  331. package/lib/core/breakthrough/schema.test.cjs +256 -0
  332. package/lib/core/breakthrough/scoring.cjs +293 -0
  333. package/lib/core/breakthrough/scoring.test.cjs +423 -0
  334. package/lib/core/breakthrough/verb-dispatch.cjs +230 -0
  335. package/lib/core/breakthrough/verb-dispatch.test.cjs +185 -0
  336. package/lib/core/breakthrough/voice-scaffold.cjs +249 -0
  337. package/lib/core/breakthrough/voice-scaffold.test.cjs +251 -0
  338. package/lib/core/bridge-writer.cjs +477 -0
  339. package/lib/core/cache-prune.cjs +314 -0
  340. package/lib/core/card-fire-gate.cjs +107 -0
  341. package/lib/core/card-fire-sidechannel.cjs +518 -0
  342. package/lib/core/chain-executor.cjs +939 -0
  343. package/lib/core/chain-retry.cjs +214 -0
  344. package/lib/core/chain-step-dispatcher.cjs +397 -0
  345. package/lib/core/chat-context-builder.cjs +253 -0
  346. package/lib/core/check-plugin-enabled.cjs +145 -0
  347. package/lib/core/close-loop-writer.cjs +492 -0
  348. package/lib/core/command-registration-check.cjs +246 -0
  349. package/lib/core/correlation-label-index.cjs +230 -0
  350. package/lib/core/correlation.cjs +112 -0
  351. package/lib/core/coverage-monitor.cjs +183 -0
  352. package/lib/core/coverage-rollup.cjs +232 -0
  353. package/lib/core/cross-room-aggregator.cjs +1127 -0
  354. package/lib/core/cross-room-store.cjs +292 -0
  355. package/lib/core/cross-room-triggers.cjs +222 -0
  356. package/lib/core/daily-briefing.cjs +438 -0
  357. package/lib/core/decision-axes.cjs +178 -0
  358. package/lib/core/decision-capture.cjs +618 -0
  359. package/lib/core/deck-design-rules.cjs +211 -0
  360. package/lib/core/deep-links.cjs +82 -0
  361. package/lib/core/directive-envelope.cjs +217 -0
  362. package/lib/core/directive-envelope.test.cjs +225 -0
  363. package/lib/core/dispatch-optimizer.cjs +354 -0
  364. package/lib/core/doc-text-extractor.cjs +133 -0
  365. package/lib/core/doctor/capability-ledger-module.cjs +170 -0
  366. package/lib/core/doctor/card-fire-health-module.cjs +159 -0
  367. package/lib/core/doctor/cascade-rooms-active-module.cjs +127 -0
  368. package/lib/core/doctor/cascade-rooms-module.cjs +184 -0
  369. package/lib/core/doctor/class-m-brain-smoke.cjs +479 -0
  370. package/lib/core/doctor/class-m-brain-smoke.test.cjs +448 -0
  371. package/lib/core/doctor/class-s-eureka-smoke.cjs +380 -0
  372. package/lib/core/doctor/deployment-surfaces-module.cjs +408 -0
  373. package/lib/core/doctor/deprecated-usage-module.cjs +155 -0
  374. package/lib/core/doctor/eureka-fts-health-module.cjs +236 -0
  375. package/lib/core/doctor/graph-derive-heal-retrofit-module.cjs +148 -0
  376. package/lib/core/doctor/graph-derive-health-module.cjs +486 -0
  377. package/lib/core/doctor/install-incomplete-module.cjs +320 -0
  378. package/lib/core/doctor/install-state-module.cjs +503 -0
  379. package/lib/core/doctor/mcp-surface-module.cjs +369 -0
  380. package/lib/core/doctor/mode-select-checkpoint-module.cjs +113 -0
  381. package/lib/core/doctor/plugin-enabled-state-module.cjs +86 -0
  382. package/lib/core/doctor/room-graph-density-module.cjs +181 -0
  383. package/lib/core/doctor/room-md-module.cjs +198 -0
  384. package/lib/core/doctor/shared.cjs +555 -0
  385. package/lib/core/doctor/stale-first-touch-copy-module.cjs +83 -0
  386. package/lib/core/doctor/statusline-visibility-module.cjs +295 -0
  387. package/lib/core/doctor/ui-compliance-module.cjs +223 -0
  388. package/lib/core/doctor/umbilical-module.cjs +486 -0
  389. package/lib/core/doctor/verify-surface-module.cjs +82 -0
  390. package/lib/core/doctor/voice-style-log-module.cjs +146 -0
  391. package/lib/core/domain-insight-sweep.cjs +224 -0
  392. package/lib/core/drift-baseline.cjs +362 -0
  393. package/lib/core/drift-runtime-reachability.cjs +283 -0
  394. package/lib/core/dual-path-detector.cjs +84 -0
  395. package/lib/core/dual-path-detector.test.cjs +334 -0
  396. package/lib/core/enrichment-queue.cjs +636 -0
  397. package/lib/core/eureka/ahp-weights.cjs +232 -0
  398. package/lib/core/eureka/analogy-fitness.cjs +326 -0
  399. package/lib/core/eureka/compression-meter.cjs +165 -0
  400. package/lib/core/eureka/embedding-classifier.cjs +226 -0
  401. package/lib/core/eureka/embedding-spine.cjs +563 -0
  402. package/lib/core/eureka/entity-classifier.cjs +232 -0
  403. package/lib/core/eureka/entity-extractor.cjs +356 -0
  404. package/lib/core/eureka/eureka-enable.cjs +228 -0
  405. package/lib/core/eureka/eureka-offer.cjs +225 -0
  406. package/lib/core/eureka/eureka-reach-runner.cjs +430 -0
  407. package/lib/core/eureka/explore-chain.cjs +834 -0
  408. package/lib/core/eureka/explored-artifact.cjs +456 -0
  409. package/lib/core/eureka/fts-index-lifecycle.cjs +265 -0
  410. package/lib/core/eureka/grade-grant-examine.cjs +544 -0
  411. package/lib/core/eureka/grade-grant.cjs +596 -0
  412. package/lib/core/eureka/hybrid-retrieve.cjs +256 -0
  413. package/lib/core/eureka/lateral-engine-adapter.cjs +118 -0
  414. package/lib/core/eureka/lexical-overlap.cjs +98 -0
  415. package/lib/core/eureka/online-pattern-query.cjs +142 -0
  416. package/lib/core/eureka/opportunity-harvest.cjs +752 -0
  417. package/lib/core/eureka/opportunity-statement.cjs +519 -0
  418. package/lib/core/eureka/portfolio-dimensions.cjs +255 -0
  419. package/lib/core/eureka/qualify-opportunity.cjs +607 -0
  420. package/lib/core/eureka/reasoning-mode.cjs +481 -0
  421. package/lib/core/eureka/report-html.cjs +368 -0
  422. package/lib/core/eureka/research-filing.cjs +547 -0
  423. package/lib/core/eureka/room-native-substrate.cjs +343 -0
  424. package/lib/core/eureka/tail-quadrant.cjs +177 -0
  425. package/lib/core/eureka/tri-modal-index.cjs +564 -0
  426. package/lib/core/eureka/vector-store.cjs +414 -0
  427. package/lib/core/eureka-critic.cjs +733 -0
  428. package/lib/core/eureka-deps-resolver.cjs +179 -0
  429. package/lib/core/expert-genesis.cjs +437 -0
  430. package/lib/core/expert-library.cjs +303 -0
  431. package/lib/core/exports-log.cjs +79 -0
  432. package/lib/core/feynman/ROOM.md +25 -0
  433. package/lib/core/feynman/dial-memory-renderer.cjs +279 -0
  434. package/lib/core/feynman/dial-memory-runner.cjs +173 -0
  435. package/lib/core/feynman/feynman-seed-writer.cjs +187 -0
  436. package/lib/core/feynman/timeline-renderer.cjs +197 -0
  437. package/lib/core/feynman/timeline-runner.cjs +281 -0
  438. package/lib/core/feynman-minto-invariants.cjs +609 -0
  439. package/lib/core/findings-wirer.cjs +360 -0
  440. package/lib/core/first-touch-version-stamper.cjs +113 -0
  441. package/lib/core/folder-memory-async.cjs +424 -0
  442. package/lib/core/folder-memory-shared.cjs +890 -0
  443. package/lib/core/folder-memory.cjs +546 -0
  444. package/lib/core/framework-chain-composer.cjs +583 -0
  445. package/lib/core/frontmatter-schemas.cjs +468 -0
  446. package/lib/core/fusion-router.cjs +540 -0
  447. package/lib/core/futures/causal-cue.cjs +91 -0
  448. package/lib/core/futures/orchestrator.cjs +1391 -0
  449. package/lib/core/futures/subsystem-render.cjs +164 -0
  450. package/lib/core/gate-relevance.cjs +278 -0
  451. package/lib/core/git-ops.cjs +141 -0
  452. package/lib/core/graph-backfill.cjs +562 -0
  453. package/lib/core/graph-candidate-producer.cjs +235 -0
  454. package/lib/core/graph-derivation.cjs +546 -0
  455. package/lib/core/graph-derive-classifier.cjs +352 -0
  456. package/lib/core/graph-ops.cjs +274 -0
  457. package/lib/core/graph-refine-loop.cjs +194 -0
  458. package/lib/core/graph-self-heal.cjs +289 -0
  459. package/lib/core/greeting-intent-detector.cjs +201 -0
  460. package/lib/core/grill-engine.cjs +459 -0
  461. package/lib/core/hat-persistence.cjs +274 -0
  462. package/lib/core/hsi-engine.cjs +439 -0
  463. package/lib/core/hsi-lsa.cjs +246 -0
  464. package/lib/core/hsi-spectral.cjs +554 -0
  465. package/lib/core/hsi-to-graph.test.cjs +168 -0
  466. package/lib/core/icm-forest.cjs +703 -0
  467. package/lib/core/ignite-branch-gate.cjs +66 -0
  468. package/lib/core/index.cjs +60 -0
  469. package/lib/core/insight-sensors.cjs +1000 -0
  470. package/lib/core/install-state.cjs +242 -0
  471. package/lib/core/integration-registry.cjs +232 -0
  472. package/lib/core/intel-pipeline.cjs +545 -0
  473. package/lib/core/intelligence-cascade.cjs +852 -0
  474. package/lib/core/issue-tree.cjs +269 -0
  475. package/lib/core/larry-thinness-acknowledgment.cjs +64 -0
  476. package/lib/core/larry-thinness-acknowledgment.test.cjs +97 -0
  477. package/lib/core/lazygraph-ops.cjs +1453 -0
  478. package/lib/core/lens-engine.cjs +434 -0
  479. package/lib/core/leverage-scan.cjs +218 -0
  480. package/lib/core/llm-name-suggester.cjs +194 -0
  481. package/lib/core/llm-name-suggester.test.cjs +132 -0
  482. package/lib/core/lru-cache.cjs +139 -0
  483. package/lib/core/mcp-dep-heal.cjs +475 -0
  484. package/lib/core/mcp-dep-heal.test.cjs +253 -0
  485. package/lib/core/mcp-profiles.cjs +185 -0
  486. package/lib/core/meeting-ops.cjs +54 -0
  487. package/lib/core/memory/governance-candidate-raiser.cjs +205 -0
  488. package/lib/core/memory/governance-candidate.cjs +95 -0
  489. package/lib/core/memory/reconcile-memory-runner.cjs +653 -0
  490. package/lib/core/memory-ops.cjs +641 -0
  491. package/lib/core/meter/gate-density-reader.cjs +112 -0
  492. package/lib/core/meter/transfer-reader.cjs +276 -0
  493. package/lib/core/meter/two-gauge.cjs +245 -0
  494. package/lib/core/methodology-ingest.cjs +346 -0
  495. package/lib/core/metrics/larryreach-loop-health.cjs +142 -0
  496. package/lib/core/migration-snapshot.cjs +287 -0
  497. package/lib/core/migration-snapshot.test.cjs +174 -0
  498. package/lib/core/migrations/ROOM.md +33 -0
  499. package/lib/core/migrations/phase-109-nodes-provenance.cjs +400 -0
  500. package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
  501. package/lib/core/migrations/phase-160-nodes-bitemporal.cjs +168 -0
  502. package/lib/core/migrations/phase-160-nodes-bitemporal.test.cjs +140 -0
  503. package/lib/core/migrations/phase-162-section-nodes.cjs +156 -0
  504. package/lib/core/migrations/phase-222-ranker-weights.cjs +62 -0
  505. package/lib/core/migrations/phase-224-edge-review-status.cjs +87 -0
  506. package/lib/core/mindrian-brain-shim.test.cjs +221 -0
  507. package/lib/core/mode-select-sidechannel.cjs +259 -0
  508. package/lib/core/model-profiles.cjs +246 -0
  509. package/lib/core/mullins-scaffold.cjs +258 -0
  510. package/lib/core/mullins-scaffold.test.cjs +128 -0
  511. package/lib/core/mva-agent-contract.cjs +170 -0
  512. package/lib/core/mva-agent-contract.test.cjs +169 -0
  513. package/lib/core/mva-budget.cjs +75 -0
  514. package/lib/core/mva-budget.test.cjs +68 -0
  515. package/lib/core/mva-classifier.cjs +392 -0
  516. package/lib/core/mva-classifier.test.cjs +248 -0
  517. package/lib/core/mva-deck-builder.cjs +452 -0
  518. package/lib/core/mva-deck-builder.test.cjs +287 -0
  519. package/lib/core/mva-detect.smoke.test.cjs +209 -0
  520. package/lib/core/mva-dispatcher.cjs +110 -0
  521. package/lib/core/mva-dispatcher.test.cjs +216 -0
  522. package/lib/core/mva-option-router.cjs +414 -0
  523. package/lib/core/mva-option-router.test.cjs +483 -0
  524. package/lib/core/mva-orchestrator.cjs +365 -0
  525. package/lib/core/mva-orchestrator.test.cjs +916 -0
  526. package/lib/core/mva-progressive-renderer.cjs +194 -0
  527. package/lib/core/mva-progressive-renderer.test.cjs +157 -0
  528. package/lib/core/mva-rule-linter.cjs +458 -0
  529. package/lib/core/mva-rule-linter.test.cjs +484 -0
  530. package/lib/core/mva-state.cjs +159 -0
  531. package/lib/core/mva-telemetry.cjs +58 -0
  532. package/lib/core/mva-telemetry.test.cjs +196 -0
  533. package/lib/core/mva-vercel-deploy.cjs +168 -0
  534. package/lib/core/mva-vercel-deploy.test.cjs +239 -0
  535. package/lib/core/nav-dial.cjs +440 -0
  536. package/lib/core/navigation/ROOM.md +15 -0
  537. package/lib/core/navigation/abstraction-claim.cjs +185 -0
  538. package/lib/core/navigation/calibration-gate.cjs +284 -0
  539. package/lib/core/navigation/calibration-log.cjs +141 -0
  540. package/lib/core/navigation/confirm-node.cjs +73 -0
  541. package/lib/core/navigation/dashboard-helpers.cjs +145 -0
  542. package/lib/core/navigation/edges.cjs +1150 -0
  543. package/lib/core/navigation/evidence-claim.cjs +186 -0
  544. package/lib/core/navigation/explanation.cjs +48 -0
  545. package/lib/core/navigation/file-evidence-readback.cjs +242 -0
  546. package/lib/core/navigation/focus.cjs +155 -0
  547. package/lib/core/navigation/get-domains-for-trends.cjs +277 -0
  548. package/lib/core/navigation/governance.cjs +101 -0
  549. package/lib/core/navigation/grant-rubric.cjs +218 -0
  550. package/lib/core/navigation/graph-export.cjs +325 -0
  551. package/lib/core/navigation/ingestion.cjs +109 -0
  552. package/lib/core/navigation/insights-stale.test.cjs +127 -0
  553. package/lib/core/navigation/insights.cjs +574 -0
  554. package/lib/core/navigation/lens-nodes.cjs +315 -0
  555. package/lib/core/navigation/memory-artifacts.cjs +437 -0
  556. package/lib/core/navigation/memory-cortex-packet.cjs +274 -0
  557. package/lib/core/navigation/memory-events.cjs +811 -0
  558. package/lib/core/navigation/neighborhood.cjs +79 -0
  559. package/lib/core/navigation/packet.cjs +420 -0
  560. package/lib/core/navigation/planning-artifacts.cjs +219 -0
  561. package/lib/core/navigation/projections.cjs +295 -0
  562. package/lib/core/navigation/ranker-weights.cjs +133 -0
  563. package/lib/core/navigation/reasoning-write.cjs +211 -0
  564. package/lib/core/navigation/reconcile-guard.cjs +98 -0
  565. package/lib/core/navigation/reified-claim.cjs +287 -0
  566. package/lib/core/navigation/research-preflight.cjs +156 -0
  567. package/lib/core/navigation/room-birth.cjs +1213 -0
  568. package/lib/core/navigation/room-context.cjs +380 -0
  569. package/lib/core/navigation/room-home.cjs +142 -0
  570. package/lib/core/navigation/spine-events.cjs +548 -0
  571. package/lib/core/navigation/synthetic-expert.cjs +177 -0
  572. package/lib/core/navigation/transitions.cjs +253 -0
  573. package/lib/core/navigation/typed-claim.cjs +208 -0
  574. package/lib/core/navigation/typed-domain.cjs +249 -0
  575. package/lib/core/navigation/typed-entity.cjs +236 -0
  576. package/lib/core/navigation/typed-frame.cjs +257 -0
  577. package/lib/core/navigation/typed-open-question.cjs +130 -0
  578. package/lib/core/navigation/typed-opportunity.cjs +418 -0
  579. package/lib/core/navigation-engine-offer.cjs +776 -0
  580. package/lib/core/navigation-engine-shared.cjs +256 -0
  581. package/lib/core/navigation-engine.cjs +1696 -0
  582. package/lib/core/navigation.cjs +686 -0
  583. package/lib/core/nl-graph-queries.cjs +164 -0
  584. package/lib/core/node-insert-epistemic.test.cjs +212 -0
  585. package/lib/core/node-insert-overrides.test.cjs +273 -0
  586. package/lib/core/node-insert.cjs +274 -0
  587. package/lib/core/npm-cli-resolve.cjs +151 -0
  588. package/lib/core/npm-cli-resolve.test.cjs +153 -0
  589. package/lib/core/npm-install-lock.cjs +326 -0
  590. package/lib/core/npm-install-lock.test.cjs +379 -0
  591. package/lib/core/numeric/sklearn-stopwords-v1.json +325 -0
  592. package/lib/core/numeric/svd.cjs +377 -0
  593. package/lib/core/numeric/tfidf.cjs +241 -0
  594. package/lib/core/offer-presenter.cjs +406 -0
  595. package/lib/core/opportunity-extractor.cjs +183 -0
  596. package/lib/core/opportunity-ops.cjs +1423 -0
  597. package/lib/core/orchestration-candidate-lift.cjs +284 -0
  598. package/lib/core/part8-egress-guard.cjs +536 -0
  599. package/lib/core/part8-egress-guard.test.cjs +194 -0
  600. package/lib/core/part8-egress-ontology.cjs +174 -0
  601. package/lib/core/part8-egress-ontology.test.cjs +133 -0
  602. package/lib/core/persona-ops.cjs +692 -0
  603. package/lib/core/persona-override.cjs +327 -0
  604. package/lib/core/persona-taxonomy.cjs +328 -0
  605. package/lib/core/pinecone-inference.cjs +176 -0
  606. package/lib/core/pitch-feedback-schemas.cjs +303 -0
  607. package/lib/core/planning/artifact-brain-packet.cjs +212 -0
  608. package/lib/core/planning/reconcile-runner.cjs +347 -0
  609. package/lib/core/platform-gates.cjs +120 -0
  610. package/lib/core/platform.cjs +257 -0
  611. package/lib/core/proactive-intelligence.cjs +528 -0
  612. package/lib/core/problem-type-router.cjs +315 -0
  613. package/lib/core/publish-needs-default-lane.cjs +110 -0
  614. package/lib/core/ralph-loop-gate.cjs +48 -0
  615. package/lib/core/reader/ab-harness.cjs +177 -0
  616. package/lib/core/reader/decide-projection-reader.cjs +358 -0
  617. package/lib/core/reasoning-ops.cjs +639 -0
  618. package/lib/core/recipe-maps.cjs +442 -0
  619. package/lib/core/recovery/case-file.cjs +358 -0
  620. package/lib/core/recovery/controller.cjs +1008 -0
  621. package/lib/core/recovery/dispatcher.cjs +533 -0
  622. package/lib/core/recovery/result-semantics.cjs +488 -0
  623. package/lib/core/recovery/stage-envelope.cjs +338 -0
  624. package/lib/core/refusal-messaging.cjs +569 -0
  625. package/lib/core/refusal-messaging.test.cjs +220 -0
  626. package/lib/core/repo-version.cjs +134 -0
  627. package/lib/core/research-cache.cjs +165 -0
  628. package/lib/core/research-cache.test.cjs +137 -0
  629. package/lib/core/research-context-extractor.cjs +283 -0
  630. package/lib/core/research-corpus.cjs +814 -0
  631. package/lib/core/research-corpus.test.cjs +280 -0
  632. package/lib/core/research-filing-selector.cjs +189 -0
  633. package/lib/core/resolve-active-room.cjs +602 -0
  634. package/lib/core/resolve-brain-key.cjs +273 -0
  635. package/lib/core/resolve-umbilical-target.cjs +288 -0
  636. package/lib/core/resolve-vercel-key.cjs +107 -0
  637. package/lib/core/resolve-vercel-key.test.cjs +137 -0
  638. package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
  639. package/lib/core/room-auto-create.cjs +318 -0
  640. package/lib/core/room-auto-create.test.cjs +198 -0
  641. package/lib/core/room-chooser.cjs +244 -0
  642. package/lib/core/room-classifier-strict-mode.cjs +235 -0
  643. package/lib/core/room-db.cjs +348 -0
  644. package/lib/core/room-discard-cascade.cjs +238 -0
  645. package/lib/core/room-discard-cascade.test.cjs +135 -0
  646. package/lib/core/room-name-validator.cjs +132 -0
  647. package/lib/core/room-name-validator.test.cjs +156 -0
  648. package/lib/core/room-naming-selector.cjs +357 -0
  649. package/lib/core/room-naming-selector.test.cjs +277 -0
  650. package/lib/core/room-open.cjs +259 -0
  651. package/lib/core/room-ops-async.cjs +92 -0
  652. package/lib/core/room-ops-shared.cjs +64 -0
  653. package/lib/core/room-ops-sync.cjs +70 -0
  654. package/lib/core/room-ops.cjs +32 -0
  655. package/lib/core/room-receipt-emit.cjs +63 -0
  656. package/lib/core/room-root.cjs +93 -0
  657. package/lib/core/room-skeleton-scaffold.cjs +669 -0
  658. package/lib/core/room-skeleton-scaffold.test.cjs +296 -0
  659. package/lib/core/room-type-detector.cjs +386 -0
  660. package/lib/core/rs-backend-dispatch.cjs +64 -0
  661. package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
  662. package/lib/core/rs-brain-substrate.cjs +570 -0
  663. package/lib/core/rs-breakthrough-scorer.cjs +255 -0
  664. package/lib/core/rs-canon-violations.cjs +82 -0
  665. package/lib/core/rs-chain-feeder.cjs +381 -0
  666. package/lib/core/rs-commercial-assessor.cjs +280 -0
  667. package/lib/core/rs-corpus-quality-gate.cjs +62 -0
  668. package/lib/core/rs-differential-scorer.cjs +629 -0
  669. package/lib/core/rs-domain-analyzer.cjs +385 -0
  670. package/lib/core/rs-egress-prompts.cjs +113 -0
  671. package/lib/core/rs-egress-telemetry.cjs +225 -0
  672. package/lib/core/rs-egress-violations.cjs +53 -0
  673. package/lib/core/rs-engine.cjs +597 -0
  674. package/lib/core/rs-expert-brain-projection.cjs +280 -0
  675. package/lib/core/rs-expert-mapper.cjs +467 -0
  676. package/lib/core/rs-fetcher-academic.cjs +697 -0
  677. package/lib/core/rs-fetcher-experts.cjs +314 -0
  678. package/lib/core/rs-fetcher-industry.cjs +731 -0
  679. package/lib/core/rs-fetcher-patents.cjs +564 -0
  680. package/lib/core/rs-innovation-classifier.cjs +194 -0
  681. package/lib/core/rs-math.cjs +458 -0
  682. package/lib/core/rs-mind-map.cjs +656 -0
  683. package/lib/core/rs-neo4j-writer.cjs +388 -0
  684. package/lib/core/rs-nl-to-query.cjs +435 -0
  685. package/lib/core/rs-pinecone-bridge.cjs +344 -0
  686. package/lib/core/rs-preprocessor.cjs +350 -0
  687. package/lib/core/rs-query-matrix.cjs +316 -0
  688. package/lib/core/rs-query-to-text.cjs +438 -0
  689. package/lib/core/rs-sqlite-mirror.cjs +495 -0
  690. package/lib/core/rs-thesis-generator.cjs +188 -0
  691. package/lib/core/rs_cache.py +635 -0
  692. package/lib/core/rs_corpus.py +562 -0
  693. package/lib/core/rs_corpus_exclude.py +62 -0
  694. package/lib/core/rs_hybrid.py +589 -0
  695. package/lib/core/rs_math.py +287 -0
  696. package/lib/core/rs_rooms.py +196 -0
  697. package/lib/core/salient-governance.cjs +315 -0
  698. package/lib/core/scheduled-scanner.cjs +658 -0
  699. package/lib/core/scratchpad-ops.cjs +316 -0
  700. package/lib/core/seam-liveness.cjs +191 -0
  701. package/lib/core/seam-liveness.test.cjs +177 -0
  702. package/lib/core/section-8-trace-schema.cjs +138 -0
  703. package/lib/core/section-registry.cjs +167 -0
  704. package/lib/core/security/agentshield-adapters.test.cjs +104 -0
  705. package/lib/core/security/agentshield-run.cjs +343 -0
  706. package/lib/core/security/agentshield-scanner.cjs +314 -0
  707. package/lib/core/security/agentshield-scanner.test.cjs +259 -0
  708. package/lib/core/sensors/hat-scoping-table.cjs +103 -0
  709. package/lib/core/sensors/sensor-circularity.cjs +278 -0
  710. package/lib/core/sensors/sensor-content-relevance.cjs +407 -0
  711. package/lib/core/sensors/sensor-diffusion-adoption.cjs +230 -0
  712. package/lib/core/sensors/sensor-eureka.cjs +208 -0
  713. package/lib/core/sensors/sensor-expert-skill.cjs +293 -0
  714. package/lib/core/sensors/sensor-external-fact.cjs +184 -0
  715. package/lib/core/sensors/sensor-gate-approach.cjs +104 -0
  716. package/lib/core/sensors/sensor-jtbd-reweight.cjs +141 -0
  717. package/lib/core/sensors/sensor-lagging-component.cjs +115 -0
  718. package/lib/core/sensors/sensor-memory-cortex.cjs +111 -0
  719. package/lib/core/sensors/sensor-methodology-decision.cjs +131 -0
  720. package/lib/core/sensors/sensor-opportunity-harvest.cjs +195 -0
  721. package/lib/core/sensors/sensor-perspective-lock.cjs +174 -0
  722. package/lib/core/sensors/sensor-priority.cjs +183 -0
  723. package/lib/core/sensors/sensor-recency.cjs +151 -0
  724. package/lib/core/sensors/sensor-roadmap-type.cjs +421 -0
  725. package/lib/core/sensors/sensor-room-pick.cjs +238 -0
  726. package/lib/core/sensors/sensor-show-share.cjs +215 -0
  727. package/lib/core/sensors/sensor-temporal-blindness.cjs +123 -0
  728. package/lib/core/sensors/sensor-types.cjs +294 -0
  729. package/lib/core/sensors/sensor-url-ingest.cjs +234 -0
  730. package/lib/core/session-binding.cjs +208 -0
  731. package/lib/core/session-presence.cjs +404 -0
  732. package/lib/core/session-register.cjs +138 -0
  733. package/lib/core/session-state.cjs +144 -0
  734. package/lib/core/shallow-doc-parser.cjs +353 -0
  735. package/lib/core/shallow-doc-parser.test.cjs +226 -0
  736. package/lib/core/skill-activation-router.cjs +284 -0
  737. package/lib/core/skillopt-schemas.cjs +509 -0
  738. package/lib/core/stale-copy-scanner.cjs +190 -0
  739. package/lib/core/stance-state.cjs +153 -0
  740. package/lib/core/state-aware-router.cjs +78 -0
  741. package/lib/core/state-ops.cjs +80 -0
  742. package/lib/core/state-version.cjs +350 -0
  743. package/lib/core/statusline-cache.cjs +266 -0
  744. package/lib/core/statusline-liveness-gate.cjs +105 -0
  745. package/lib/core/surface-fence.cjs +127 -0
  746. package/lib/core/synthesizers/comparison-matrix.cjs +82 -0
  747. package/lib/core/synthesizers/convergence-map.cjs +107 -0
  748. package/lib/core/synthesizers/domain-hierarchy.cjs +138 -0
  749. package/lib/core/synthesizers/source-comparison.cjs +56 -0
  750. package/lib/core/synthesizers/tension-map.cjs +119 -0
  751. package/lib/core/synthetic-expert-behavior-gate.cjs +99 -0
  752. package/lib/core/synthetic-expert-construction-gate.cjs +170 -0
  753. package/lib/core/telemetry/schema.cjs +168 -0
  754. package/lib/core/telemetry/schema.test.cjs +124 -0
  755. package/lib/core/telemetry/validator.cjs +200 -0
  756. package/lib/core/telemetry/validator.test.cjs +188 -0
  757. package/lib/core/telemetry/writer.cjs +141 -0
  758. package/lib/core/telemetry/writer.test.cjs +331 -0
  759. package/lib/core/temporal/date-sync-gate.cjs +256 -0
  760. package/lib/core/temporal/date-sync-gate.test.cjs +191 -0
  761. package/lib/core/temporal/dual-stamp.cjs +172 -0
  762. package/lib/core/temporal/dual-stamp.test.cjs +172 -0
  763. package/lib/core/temporal/point-in-time.cjs +76 -0
  764. package/lib/core/temporal/point-in-time.test.cjs +169 -0
  765. package/lib/core/temporal/recency-decay.cjs +119 -0
  766. package/lib/core/temporal/recency-decay.test.cjs +140 -0
  767. package/lib/core/temporal/reference-now.cjs +281 -0
  768. package/lib/core/temporal/reference-now.test.cjs +225 -0
  769. package/lib/core/temporal/resolve-relative-time.cjs +116 -0
  770. package/lib/core/temporal/resolve-relative-time.test.cjs +138 -0
  771. package/lib/core/temporal/supersession.cjs +250 -0
  772. package/lib/core/temporal/supersession.test.cjs +103 -0
  773. package/lib/core/terminal-capability.cjs +88 -0
  774. package/lib/core/token-estimator.cjs +348 -0
  775. package/lib/core/tone-filter.cjs +147 -0
  776. package/lib/core/trending-to-absurd/orchestrator.cjs +341 -0
  777. package/lib/core/trending-to-absurd/stage7-roadmap.cjs +317 -0
  778. package/lib/core/trending-to-absurd/variance.cjs +169 -0
  779. package/lib/core/unknowns/bandit.cjs +332 -0
  780. package/lib/core/unknowns/corpus-adapter.cjs +327 -0
  781. package/lib/core/unknowns/dsp.cjs +356 -0
  782. package/lib/core/unknowns/edge-writer.cjs +211 -0
  783. package/lib/core/unknowns/iface.cjs +212 -0
  784. package/lib/core/unknowns/orchestrator.cjs +375 -0
  785. package/lib/core/unknowns/pattern-miner.cjs +214 -0
  786. package/lib/core/unknowns/rumsfeld-matrix.cjs +155 -0
  787. package/lib/core/unknowns/verdict.cjs +254 -0
  788. package/lib/core/update-path.cjs +57 -0
  789. package/lib/core/url-ingest.cjs +568 -0
  790. package/lib/core/user-archetype.cjs +284 -0
  791. package/lib/core/user-md-ops.cjs +578 -0
  792. package/lib/core/venture-shape-nudge.cjs +178 -0
  793. package/lib/core/venture-shape-nudge.test.cjs +266 -0
  794. package/lib/core/verb-reach-affinity.cjs +299 -0
  795. package/lib/core/visual-ops.cjs +692 -0
  796. package/lib/core/voice-transition-detector.cjs +125 -0
  797. package/lib/core/watched-sources.cjs +237 -0
  798. package/lib/core/write-lock.cjs +149 -0
  799. package/lib/graph/canvas-graph.js +467 -0
  800. package/lib/graph/constellation-config.cjs +299 -0
  801. package/lib/graph/graph-detail-panel.js +165 -0
  802. package/lib/hmi/ROOM.md +47 -0
  803. package/lib/hmi/across-session-memory.cjs +669 -0
  804. package/lib/hmi/agentshield-drift-gate.cjs +142 -0
  805. package/lib/hmi/brain-review-packet.cjs +221 -0
  806. package/lib/hmi/cortex-reach-adapter.cjs +271 -0
  807. package/lib/hmi/cross-room-memory.cjs +618 -0
  808. package/lib/hmi/decoy-tier.cjs +395 -0
  809. package/lib/hmi/depth-state.cjs +164 -0
  810. package/lib/hmi/dial-label-composer.cjs +410 -0
  811. package/lib/hmi/dial-presenter.cjs +508 -0
  812. package/lib/hmi/dial-reach-orchestrator.cjs +377 -0
  813. package/lib/hmi/dial-selector.cjs +239 -0
  814. package/lib/hmi/f1-pick-capture-cli.cjs +131 -0
  815. package/lib/hmi/f3-depth-capture-cli.cjs +77 -0
  816. package/lib/hmi/f4-scope-capture-cli.cjs +86 -0
  817. package/lib/hmi/f8-action-capture-cli.cjs +145 -0
  818. package/lib/hmi/f8-capture.test.cjs +77 -0
  819. package/lib/hmi/f9-ordered-capture-cli.cjs +138 -0
  820. package/lib/hmi/harvest-scope-state.cjs +202 -0
  821. package/lib/hmi/hats-persona-cache.cjs +142 -0
  822. package/lib/hmi/jtbd-classifier.cjs +219 -0
  823. package/lib/hmi/jtbd-state.cjs +263 -0
  824. package/lib/hmi/jtbd-taxonomy.json +402 -0
  825. package/lib/hmi/mindrian-ascii-logo.cjs +96 -0
  826. package/lib/hmi/part8-egress-gate.cjs +149 -0
  827. package/lib/hmi/reach-component-map.json +47 -0
  828. package/lib/hmi/reach-relevance-gate.cjs +147 -0
  829. package/lib/hmi/selector-dispatcher.cjs +1201 -0
  830. package/lib/hmi/selector-dispatcher.test.cjs +141 -0
  831. package/lib/hmi/selector-telemetry.cjs +269 -0
  832. package/lib/hmi/shape-f0-renderer.cjs +145 -0
  833. package/lib/hmi/shape-f1-fallback.cjs +80 -0
  834. package/lib/hmi/shape-f1-renderer.cjs +219 -0
  835. package/lib/hmi/shape-f2-renderer.cjs +132 -0
  836. package/lib/hmi/shape-f3-parity.test.cjs +64 -0
  837. package/lib/hmi/shape-f3-renderer.cjs +66 -0
  838. package/lib/hmi/shape-f4-parity.test.cjs +72 -0
  839. package/lib/hmi/shape-f4-renderer.cjs +72 -0
  840. package/lib/hmi/shape-f5-renderer.cjs +155 -0
  841. package/lib/hmi/shape-f6-plan-review-renderer.cjs +324 -0
  842. package/lib/hmi/shape-f6-renderer.cjs +144 -0
  843. package/lib/hmi/shape-f7-breakthrough-renderer.cjs +234 -0
  844. package/lib/hmi/shape-f7-breakthrough-renderer.test.cjs +233 -0
  845. package/lib/hmi/shape-f8-renderer.cjs +150 -0
  846. package/lib/hmi/shape-f8-renderer.test.cjs +70 -0
  847. package/lib/hmi/shape-f9-renderer.cjs +187 -0
  848. package/lib/hmi/shape-f9-renderer.test.cjs +80 -0
  849. package/lib/hmi/shape-g-renderer.cjs +219 -0
  850. package/lib/hmi/shape-h-renderer.cjs +222 -0
  851. package/lib/hmi/tier-check.cjs +63 -0
  852. package/lib/hmi/turn-text.cjs +268 -0
  853. package/lib/hmi/voice-color-mark.cjs +318 -0
  854. package/lib/hmi/voice-style-log.cjs +276 -0
  855. package/lib/import/PRECONDITIONS.md +41 -0
  856. package/lib/import/branding.cjs +210 -0
  857. package/lib/import/branding.test.cjs +235 -0
  858. package/lib/import/classifications-sync.cjs +104 -0
  859. package/lib/import/classifications-sync.test.cjs +129 -0
  860. package/lib/import/enricher.cjs +296 -0
  861. package/lib/import/enricher.test.cjs +273 -0
  862. package/lib/import/integration.test.cjs +376 -0
  863. package/lib/import/manifest.cjs +129 -0
  864. package/lib/import/manifest.schema.json +185 -0
  865. package/lib/import/manifest.test.cjs +123 -0
  866. package/lib/import/meeting-detector.cjs +92 -0
  867. package/lib/import/meeting-detector.test.cjs +100 -0
  868. package/lib/import/person-detector.cjs +229 -0
  869. package/lib/import/person-detector.test.cjs +149 -0
  870. package/lib/import/report.cjs +186 -0
  871. package/lib/import/report.test.cjs +186 -0
  872. package/lib/import/room-md-scaffolder.cjs +49 -0
  873. package/lib/import/router.cjs +224 -0
  874. package/lib/import/router.test.cjs +356 -0
  875. package/lib/import/run-all-tests.cjs +36 -0
  876. package/lib/import/smoke-test.cjs +213 -0
  877. package/lib/import/smoke-test.test.cjs +148 -0
  878. package/lib/import/undo.test.cjs +199 -0
  879. package/lib/import/vault-scanner.cjs +105 -0
  880. package/lib/import/vault-scanner.test.cjs +67 -0
  881. package/lib/lens-engine/correlation-resolver.cjs +174 -0
  882. package/lib/lens-engine/source-lens-driver.cjs +736 -0
  883. package/lib/mcp/adapter-client.cjs +103 -0
  884. package/lib/mcp/app-html/dashboard.html +316 -0
  885. package/lib/mcp/app-html/graph.html +428 -0
  886. package/lib/mcp/app-html/mindrian-platform.html +1841 -0
  887. package/lib/mcp/app-html/wiki.html +383 -0
  888. package/lib/mcp/app-views.cjs +322 -0
  889. package/lib/mcp/brain-composition-census.cjs +184 -0
  890. package/lib/mcp/brain-router.cjs +530 -0
  891. package/lib/mcp/capability-registry.cjs +62 -0
  892. package/lib/mcp/contract-version.cjs +59 -0
  893. package/lib/mcp/daemon-lifecycle.cjs +324 -0
  894. package/lib/mcp/gate-dedup.cjs +120 -0
  895. package/lib/mcp/gate-ledger.cjs +145 -0
  896. package/lib/mcp/gate-render.cjs +483 -0
  897. package/lib/mcp/hook-adapter-audit.cjs +199 -0
  898. package/lib/mcp/larry-context.cjs +42 -0
  899. package/lib/mcp/mcp-first-flag.cjs +147 -0
  900. package/lib/mcp/no-instructions.test.cjs +270 -0
  901. package/lib/mcp/pipeline-state.cjs +424 -0
  902. package/lib/mcp/prompts.cjs +421 -0
  903. package/lib/mcp/register-core-tools.cjs +78 -0
  904. package/lib/mcp/resources.cjs +359 -0
  905. package/lib/mcp/runtime-instructions.cjs +38 -0
  906. package/lib/mcp/session-catchup.cjs +349 -0
  907. package/lib/mcp/session-registry.cjs +113 -0
  908. package/lib/mcp/session-room.cjs +124 -0
  909. package/lib/mcp/sse-event-bus.cjs +98 -0
  910. package/lib/mcp/stop-gate-handler.cjs +589 -0
  911. package/lib/mcp/surface-detect.cjs +186 -0
  912. package/lib/mcp/tool-router.cjs +2113 -0
  913. package/lib/mcp/tools/chain.cjs +674 -0
  914. package/lib/mcp/tools/claim.cjs +180 -0
  915. package/lib/mcp/tools/context.cjs +92 -0
  916. package/lib/mcp/tools/dual-path.cjs +79 -0
  917. package/lib/mcp/tools/gate.cjs +392 -0
  918. package/lib/mcp/tools/graph-reason.cjs +130 -0
  919. package/lib/mcp/tools/graph.cjs +321 -0
  920. package/lib/mcp/tools/identity.cjs +174 -0
  921. package/lib/mcp/tools/room.cjs +449 -0
  922. package/lib/mcp/tools/sensors.cjs +450 -0
  923. package/lib/mcp/tools/status.cjs +183 -0
  924. package/lib/mcp/tools/stop-gate.cjs +103 -0
  925. package/lib/mcp/tools/views.cjs +382 -0
  926. package/lib/mcp/tree-watcher.cjs +199 -0
  927. package/lib/memory/.mindrian/auto-explore-mat-test-001.json +11 -0
  928. package/lib/memory/aaak-compress.cjs +403 -0
  929. package/lib/memory/aaak-compress.test.cjs +288 -0
  930. package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
  931. package/lib/memory/bearer-token.test.cjs +315 -0
  932. package/lib/memory/body-shape-coverage.test.cjs +268 -0
  933. package/lib/memory/brain-cache-lru.test.cjs +259 -0
  934. package/lib/memory/brain-client-query-shape.test.cjs +160 -0
  935. package/lib/memory/brain-cypher-chain-slice.test.cjs +368 -0
  936. package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
  937. package/lib/memory/brain-derivation-queue.test.cjs +553 -0
  938. package/lib/memory/brain-derivation.test.cjs +639 -0
  939. package/lib/memory/brain-derive-command.test.cjs +534 -0
  940. package/lib/memory/brain-derive-curation.test.cjs +243 -0
  941. package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
  942. package/lib/memory/brain-md-schema.test.cjs +467 -0
  943. package/lib/memory/brain-md-staleness.test.cjs +525 -0
  944. package/lib/memory/brain-server-resolution.test.cjs +314 -0
  945. package/lib/memory/brain-suggestion-template.test.cjs +200 -0
  946. package/lib/memory/chain-recommender-canonical.test.cjs +208 -0
  947. package/lib/memory/chain-recommender.test.cjs +233 -0
  948. package/lib/memory/chat-context.test.cjs +128 -0
  949. package/lib/memory/command-registry.test.cjs +220 -0
  950. package/lib/memory/connector-registry.test.cjs +94 -0
  951. package/lib/memory/correlation-label-index.test.cjs +119 -0
  952. package/lib/memory/correlation.test.cjs +253 -0
  953. package/lib/memory/cross-room-aggregator.test.cjs +909 -0
  954. package/lib/memory/curation-132-03-dedup-held-rename.test.cjs +251 -0
  955. package/lib/memory/curation-132-05-pseudonymize.test.cjs +205 -0
  956. package/lib/memory/curation-batch.test.cjs +213 -0
  957. package/lib/memory/dashboard-server.test.cjs +256 -0
  958. package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
  959. package/lib/memory/decision-capture.test.cjs +632 -0
  960. package/lib/memory/decision-capture.worker.cjs +70 -0
  961. package/lib/memory/doctor-deprecation-surface.test.cjs +188 -0
  962. package/lib/memory/dual-graph-health.test.cjs +485 -0
  963. package/lib/memory/explain-decision-command.test.cjs +521 -0
  964. package/lib/memory/explain-decision-footer.test.cjs +316 -0
  965. package/lib/memory/explored-materials-store.cjs +392 -0
  966. package/lib/memory/f-selector-ranker.test.cjs +593 -0
  967. package/lib/memory/feynman-minto-guardian.test.cjs +863 -0
  968. package/lib/memory/feynman-minto-invariants.test.cjs +554 -0
  969. package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
  970. package/lib/memory/feynman-prompts.cjs +151 -0
  971. package/lib/memory/feynman-prompts.test.cjs +96 -0
  972. package/lib/memory/first-touch-version.test.cjs +198 -0
  973. package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
  974. package/lib/memory/folder-memory.test.cjs +503 -0
  975. package/lib/memory/framework-chain-composer.test.cjs +549 -0
  976. package/lib/memory/frontmatter-schema-validator.test.cjs +344 -0
  977. package/lib/memory/guardian-onstop-reaches-user.test.cjs +396 -0
  978. package/lib/memory/heal-command.test.cjs +604 -0
  979. package/lib/memory/help-coverage.test.cjs +108 -0
  980. package/lib/memory/help-renderer.test.cjs +145 -0
  981. package/lib/memory/huji-run-one-async-parity.test.cjs +242 -0
  982. package/lib/memory/hypergraph-event-schema.test.cjs +116 -0
  983. package/lib/memory/index-artifact-transaction.test.cjs +333 -0
  984. package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
  985. package/lib/memory/local-chain-recommender.test.cjs +172 -0
  986. package/lib/memory/mcp-input-validation.test.cjs +240 -0
  987. package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
  988. package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
  989. package/lib/memory/minto-debounce-consumer-census.test.cjs +567 -0
  990. package/lib/memory/minto-debouncer.test.cjs +407 -0
  991. package/lib/memory/minto-debouncer.worker.cjs +46 -0
  992. package/lib/memory/minto-migration-v88.test.cjs +265 -0
  993. package/lib/memory/minto-schema-v88.test.cjs +390 -0
  994. package/lib/memory/mos-status-renderer.test.cjs +631 -0
  995. package/lib/memory/narrative-schema.cjs +376 -0
  996. package/lib/memory/narrative-schema.test.cjs +209 -0
  997. package/lib/memory/nav-dial.test.cjs +414 -0
  998. package/lib/memory/navigation-engine-core.test.cjs +712 -0
  999. package/lib/memory/navigation-engine-offer.test.cjs +404 -0
  1000. package/lib/memory/navigation-hook-resolver.test.cjs +177 -0
  1001. package/lib/memory/navigation-invariants.test.cjs +483 -0
  1002. package/lib/memory/navigation-projections.test.cjs +241 -0
  1003. package/lib/memory/navigation-write-edge.test.cjs +206 -0
  1004. package/lib/memory/offer-closer.test.cjs +258 -0
  1005. package/lib/memory/offer-presenter.test.cjs +554 -0
  1006. package/lib/memory/on-stop-snapshot.test.cjs +404 -0
  1007. package/lib/memory/orchestration-projection.test.cjs +706 -0
  1008. package/lib/memory/packet-chain-hint.test.cjs +407 -0
  1009. package/lib/memory/packet-schema-validation.test.cjs +317 -0
  1010. package/lib/memory/palette-consistency.test.cjs +127 -0
  1011. package/lib/memory/pending-tension-store.cjs +453 -0
  1012. package/lib/memory/per-command-jtbd-derivation.test.cjs +130 -0
  1013. package/lib/memory/per-command-teaching.test.cjs +110 -0
  1014. package/lib/memory/post-compact-reinjection.test.cjs +854 -0
  1015. package/lib/memory/post-write-triple.test.cjs +317 -0
  1016. package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
  1017. package/lib/memory/precommit-real-commit.test.cjs +301 -0
  1018. package/lib/memory/problem-type-router.test.cjs +656 -0
  1019. package/lib/memory/query-efficiency-telemetry.test.cjs +612 -0
  1020. package/lib/memory/recompile-room-references.test.cjs +392 -0
  1021. package/lib/memory/recompile-room-references.worker.cjs +42 -0
  1022. package/lib/memory/record-decision-dual-write.test.cjs +454 -0
  1023. package/lib/memory/render-v2-disposition.test.cjs +199 -0
  1024. package/lib/memory/room-classifier-strict-mode.test.cjs +430 -0
  1025. package/lib/memory/room-minto-hook.test.cjs +398 -0
  1026. package/lib/memory/rs-discovery-engine.test.cjs +323 -0
  1027. package/lib/memory/run-feynman-tests.cjs +2101 -0
  1028. package/lib/memory/security-trifecta.test.cjs +329 -0
  1029. package/lib/memory/selector-alias-map.test.cjs +172 -0
  1030. package/lib/memory/selector-decisions.test.cjs +417 -0
  1031. package/lib/memory/selector-miss.test.cjs +290 -0
  1032. package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
  1033. package/lib/memory/session-start-triple-injection.test.cjs +514 -0
  1034. package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
  1035. package/lib/memory/sessionstart-coordinator.test.cjs +446 -0
  1036. package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
  1037. package/lib/memory/skill-activation-router.test.cjs +465 -0
  1038. package/lib/memory/skill-vs-code-drift.test.cjs +257 -0
  1039. package/lib/memory/soft-alias.test.cjs +144 -0
  1040. package/lib/memory/spine-events-correlation.test.cjs +198 -0
  1041. package/lib/memory/stale-copy-scanner.test.cjs +294 -0
  1042. package/lib/memory/stamp-artifact-write.test.cjs +304 -0
  1043. package/lib/memory/state-aware-router.test.cjs +90 -0
  1044. package/lib/memory/statusline-active-room-write.test.cjs +245 -0
  1045. package/lib/memory/statusline-active-room.test.cjs +315 -0
  1046. package/lib/memory/statusline-minto-segment.test.cjs +292 -0
  1047. package/lib/memory/statusline-two-row.test.cjs +338 -0
  1048. package/lib/memory/suggest-next-workflow.test.cjs +176 -0
  1049. package/lib/memory/sync-async-entry-points.test.cjs +204 -0
  1050. package/lib/memory/terminal-capability.test.cjs +155 -0
  1051. package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
  1052. package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
  1053. package/lib/memory/test-rs-brain-substrate.cjs +636 -0
  1054. package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
  1055. package/lib/memory/test-rs-canon-violations.cjs +218 -0
  1056. package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
  1057. package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
  1058. package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
  1059. package/lib/memory/test-rs-differential-scorer.cjs +480 -0
  1060. package/lib/memory/test-rs-discovery-engine.cjs +648 -0
  1061. package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
  1062. package/lib/memory/test-rs-egress-primitives.cjs +420 -0
  1063. package/lib/memory/test-rs-expert-mapper.cjs +547 -0
  1064. package/lib/memory/test-rs-explain-command.cjs +443 -0
  1065. package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
  1066. package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
  1067. package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
  1068. package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
  1069. package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
  1070. package/lib/memory/test-rs-mind-map.cjs +646 -0
  1071. package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
  1072. package/lib/memory/test-rs-nl-to-query.cjs +449 -0
  1073. package/lib/memory/test-rs-pinecone-bridge.cjs +282 -0
  1074. package/lib/memory/test-rs-preprocessor.cjs +433 -0
  1075. package/lib/memory/test-rs-query-matrix.cjs +391 -0
  1076. package/lib/memory/test-rs-query-to-text.cjs +551 -0
  1077. package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
  1078. package/lib/memory/test-rs-thesis-generator.cjs +360 -0
  1079. package/lib/memory/triple-context-formatter.cjs +473 -0
  1080. package/lib/memory/triple-context-formatter.test.cjs +442 -0
  1081. package/lib/memory/user-md-persona.test.cjs +565 -0
  1082. package/lib/memory/userpromptsubmit-integration.test.cjs +787 -0
  1083. package/lib/memory/validators/README.md +157 -0
  1084. package/lib/memory/validators/brain-md-invariants.cjs +475 -0
  1085. package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
  1086. package/lib/memory/validators/external-academic-invariants.cjs +249 -0
  1087. package/lib/memory/validators/external-industry-invariants.cjs +271 -0
  1088. package/lib/memory/validators/external-patents-invariants.cjs +266 -0
  1089. package/lib/memory/validators/minto-invariants.cjs +62 -0
  1090. package/lib/memory/validators/navigation-invariants.cjs +340 -0
  1091. package/lib/memory/validators/queue-health.cjs +95 -0
  1092. package/lib/memory/validators/snapshot-integrity.cjs +129 -0
  1093. package/lib/memory/validators/stale-lifecycle.cjs +116 -0
  1094. package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
  1095. package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
  1096. package/lib/memory/workflow-layer-e2e.test.cjs +262 -0
  1097. package/lib/memory/write-lock-atomic.test.cjs +137 -0
  1098. package/lib/memory/write-lock-atomic.worker.cjs +55 -0
  1099. package/lib/parity/check-parity.cjs +83 -0
  1100. package/lib/presentation/presentation-server.cjs +101 -0
  1101. package/lib/presentation/presentation-watcher.cjs +123 -0
  1102. package/lib/quickview/hub-server.cjs +719 -0
  1103. package/lib/quickview/server.cjs +533 -0
  1104. package/lib/render/JTBD-PALETTES.md +145 -0
  1105. package/lib/render/ROOM.md +111 -0
  1106. package/lib/render/render-v2.cjs +532 -0
  1107. package/lib/render/render-v2.test.cjs +267 -0
  1108. package/lib/render/render.cjs +65 -0
  1109. package/lib/sessionstart/budget-compressor.cjs +130 -0
  1110. package/lib/sessionstart/contributor-interface.cjs +134 -0
  1111. package/lib/sessionstart/contributor-isolator.cjs +128 -0
  1112. package/lib/sessionstart/precedence-ladder.cjs +47 -0
  1113. package/lib/state/ROOM.md +46 -0
  1114. package/lib/state/state-md-parser.cjs +215 -0
  1115. package/lib/statusline/ROOM.md +38 -0
  1116. package/lib/statusline/banner-suppression.cjs +50 -0
  1117. package/lib/statusline/cockpit-renderer.cjs +443 -0
  1118. package/lib/statusline/cockpit-signals.cjs +312 -0
  1119. package/lib/statusline/cockpit-telemetry.cjs +152 -0
  1120. package/lib/statusline/ctx-window.cjs +135 -0
  1121. package/lib/statusline/governing-thought-truncator.cjs +45 -0
  1122. package/lib/statusline/next-move-cache.cjs +235 -0
  1123. package/lib/statusline/ratification-next.cjs +164 -0
  1124. package/lib/statusline/room-health-cache.cjs +200 -0
  1125. package/lib/statusline/surface-detect.cjs +85 -0
  1126. package/lib/statusline/two-row-renderer.cjs +207 -0
  1127. package/lib/statusline/version-resolver.cjs +81 -0
  1128. package/lib/ui/design-system.cjs +19 -0
  1129. package/lib/update-bootstrap.sh.template +145 -0
  1130. package/lib/vault/frontmatter-schema.cjs +297 -0
  1131. package/lib/vault/room-scanner.cjs +365 -0
  1132. package/lib/vault/wikilink-builder.cjs +231 -0
  1133. package/lib/vault/wikilink-builder.test.cjs +182 -0
  1134. package/lib/wiki/briefing.cjs +193 -0
  1135. package/lib/wiki/editor-dist/wiki-editor.css +1 -0
  1136. package/lib/wiki/editor-dist/wiki-editor.js +1327 -0
  1137. package/lib/wiki/graph-links.cjs +281 -0
  1138. package/lib/wiki/page-renderer.cjs +229 -0
  1139. package/lib/wiki/room-home.cjs +353 -0
  1140. package/lib/wiki/wiki-chat.cjs +81 -0
  1141. package/lib/wiki/wiki-export.cjs +364 -0
  1142. package/lib/wiki/wiki-layout.cjs +1143 -0
  1143. package/lib/wiki/wiki-search.cjs +142 -0
  1144. package/lib/wiki/wiki-server.cjs +748 -0
  1145. package/lib/wiki/wiki-watcher.cjs +105 -0
  1146. package/lib/workflow/ROOM.md +47 -0
  1147. package/lib/workflow/chain-source.cjs +179 -0
  1148. package/lib/workflow/command-resolver.cjs +155 -0
  1149. package/lib/workflow/command-resolver.test.cjs +235 -0
  1150. package/lib/workflow/cross-room-umbilical-closer.cjs +364 -0
  1151. package/lib/workflow/dial-close-reach.cjs +405 -0
  1152. package/lib/workflow/f-selector-ranker.cjs +1148 -0
  1153. package/lib/workflow/f1-pick-consumer.cjs +232 -0
  1154. package/lib/workflow/f3-depth-consumer.cjs +98 -0
  1155. package/lib/workflow/f4-scope-consumer.cjs +112 -0
  1156. package/lib/workflow/f8-consumer.test.cjs +82 -0
  1157. package/lib/workflow/f8-fanout-consumer.cjs +208 -0
  1158. package/lib/workflow/f9-consumer.test.cjs +71 -0
  1159. package/lib/workflow/f9-ordered-consumer.cjs +199 -0
  1160. package/lib/workflow/local-chain-recommender.cjs +330 -0
  1161. package/lib/workflow/memory-cascade-f9-adapter.cjs +273 -0
  1162. package/lib/workflow/memory-governance-closer.cjs +384 -0
  1163. package/lib/workflow/offer-closer.cjs +407 -0
  1164. package/lib/workflow/reach-hedge-ranker.cjs +809 -0
  1165. package/lib/workflow/reach-reject-reader.cjs +372 -0
  1166. package/lib/workflow/reconcile-f9-adapter.cjs +299 -0
  1167. package/lib/workflow/selector-decisions.cjs +478 -0
  1168. package/lib/workflow/session-binding-consumer.cjs +249 -0
  1169. package/npm-shrinkwrap.json +1544 -0
  1170. package/output-styles/destijl.md +67 -0
  1171. package/package.json +22 -4
  1172. package/pipelines/PWS_grading/01-deep-grade.md +48 -0
  1173. package/pipelines/PWS_grading/02-mullins.md +50 -0
  1174. package/pipelines/PWS_grading/03-build-thesis.md +60 -0
  1175. package/pipelines/PWS_grading/04-structure-argument.md +77 -0
  1176. package/pipelines/PWS_grading/CHAIN.md +79 -0
  1177. package/pipelines/analogy/01-decompose.md +80 -0
  1178. package/pipelines/analogy/02-abstract.md +87 -0
  1179. package/pipelines/analogy/03-search.md +135 -0
  1180. package/pipelines/analogy/04-transfer.md +101 -0
  1181. package/pipelines/analogy/05-validate.md +106 -0
  1182. package/pipelines/analogy/CHAIN.md +73 -0
  1183. package/pipelines/discovery/01-explore-domains.md +44 -0
  1184. package/pipelines/discovery/02-think-hats.md +50 -0
  1185. package/pipelines/discovery/03-analyze-needs.md +54 -0
  1186. package/pipelines/discovery/CHAIN.md +48 -0
  1187. package/pipelines/thesis/01-structure-argument.md +45 -0
  1188. package/pipelines/thesis/02-challenge-assumptions.md +48 -0
  1189. package/pipelines/thesis/03-build-thesis.md +54 -0
  1190. package/pipelines/thesis/CHAIN.md +48 -0
  1191. package/references/brain/causal-directives.md +91 -0
  1192. package/references/brain/causal-enrichment.cypher +165 -0
  1193. package/references/brain/command-triggers-schema.md +15 -0
  1194. package/references/brain/graph-architecture.md +317 -0
  1195. package/references/brain/query-patterns.md +504 -0
  1196. package/references/brain/room-hierarchy-schema.md +218 -0
  1197. package/references/brain/schema.md +86 -0
  1198. package/references/capability-radar/capabilities-index.md +269 -0
  1199. package/references/capability-radar/changelog-cache.md +95 -0
  1200. package/references/causal/causal-schema.md +103 -0
  1201. package/references/design/email-template-standard.md +169 -0
  1202. package/references/design/graph-visualization-standard.md +178 -0
  1203. package/references/design/newsletter-email-template.html +307 -0
  1204. package/references/document-generation.md +179 -0
  1205. package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
  1206. package/references/import-config.md +141 -0
  1207. package/references/integrations/detection-patterns.md +101 -0
  1208. package/references/meeting/artifact-template.md +377 -0
  1209. package/references/meeting/cross-meeting-intelligence.md +216 -0
  1210. package/references/meeting/cross-relationship-patterns.md +202 -0
  1211. package/references/meeting/extraction-perspectives.md +225 -0
  1212. package/references/meeting/filing-protocol.md +222 -0
  1213. package/references/meeting/knowledge-typing.md +242 -0
  1214. package/references/meeting/live-join-interface.md +244 -0
  1215. package/references/meeting/section-mapping.md +192 -0
  1216. package/references/meeting/segment-classification.md +258 -0
  1217. package/references/meeting/speaker-profile-template.md +219 -0
  1218. package/references/meeting/summary-template.md +348 -0
  1219. package/references/meeting/transcript-patterns.md +226 -0
  1220. package/references/methodology/analyze-needs.md +135 -0
  1221. package/references/methodology/analyze-systems.md +121 -0
  1222. package/references/methodology/analyze-timing.md +149 -0
  1223. package/references/methodology/beautiful-question.md +109 -0
  1224. package/references/methodology/build-knowledge.md +161 -0
  1225. package/references/methodology/build-thesis-scored.md +60 -0
  1226. package/references/methodology/build-thesis.md +237 -0
  1227. package/references/methodology/causal-loop-diagrams.md +65 -0
  1228. package/references/methodology/challenge-assumptions.md +127 -0
  1229. package/references/methodology/diagnose.md +169 -0
  1230. package/references/methodology/dominant-designs.md +212 -0
  1231. package/references/methodology/explore-domains.md +147 -0
  1232. package/references/methodology/explore-futures.md +163 -0
  1233. package/references/methodology/explore-trends.md +129 -0
  1234. package/references/methodology/find-bottlenecks.md +131 -0
  1235. package/references/methodology/grade.md +211 -0
  1236. package/references/methodology/huji-stage-a-intake.md +246 -0
  1237. package/references/methodology/index.md +34 -0
  1238. package/references/methodology/leadership.md +200 -0
  1239. package/references/methodology/lean-canvas.md +116 -0
  1240. package/references/methodology/leverage-scan-signatures.md +83 -0
  1241. package/references/methodology/macro-trends.md +192 -0
  1242. package/references/methodology/map-unknowns.md +137 -0
  1243. package/references/methodology/mullins-7-domains.md +104 -0
  1244. package/references/methodology/problem-types.md +65 -0
  1245. package/references/methodology/root-cause.md +178 -0
  1246. package/references/methodology/rubric-huji.md +160 -0
  1247. package/references/methodology/sapphire-encoding.md +355 -0
  1248. package/references/methodology/scenario-plan.md +190 -0
  1249. package/references/methodology/score-innovation.md +154 -0
  1250. package/references/methodology/skillopt-judge-rubric.md +48 -0
  1251. package/references/methodology/skillopt-queries-rubric.md +60 -0
  1252. package/references/methodology/skillopt-refute-rubric.md +59 -0
  1253. package/references/methodology/skillopt-review-rubric.md +72 -0
  1254. package/references/methodology/skillopt-revise-rubric.md +48 -0
  1255. package/references/methodology/structure-argument.md +158 -0
  1256. package/references/methodology/systems-thinking.md +231 -0
  1257. package/references/methodology/think-hats.md +147 -0
  1258. package/references/methodology/triz-matrix.json +751 -0
  1259. package/references/methodology/triz-principles.md +501 -0
  1260. package/references/methodology/user-needs.md +199 -0
  1261. package/references/methodology/validate.md +163 -0
  1262. package/references/methodology/value-proposition.md +244 -0
  1263. package/references/opportunities/funding-lifecycle.md +103 -0
  1264. package/references/opportunities/grant-api-patterns.md +99 -0
  1265. package/references/opportunities/opportunity-template.md +84 -0
  1266. package/references/opportunities/tnufa-rubric.md +81 -0
  1267. package/references/personality/assessment-philosophy.md +72 -0
  1268. package/references/personality/lexicon.md +100 -0
  1269. package/references/personality/persona-chains.md +56 -0
  1270. package/references/personality/pws-lexicon-full.md +499 -0
  1271. package/references/personality/voice-dna.md +156 -0
  1272. package/references/personas/hat-perspectives.md +76 -0
  1273. package/references/personas/persona-template.md +63 -0
  1274. package/references/pipeline/act-output-contract.md +88 -0
  1275. package/references/pipeline/chains-index.md +39 -0
  1276. package/references/pws-profile-generation.md +79 -0
  1277. package/references/reasoning/reasoning-schema.md +143 -0
  1278. package/references/reasoning/reasoning-template.md +68 -0
  1279. package/references/reasoning/run-template.md +38 -0
  1280. package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
  1281. package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
  1282. package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
  1283. package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
  1284. package/references/security/agentshield-baseline.json +8 -0
  1285. package/references/security/cve-db.json +220 -0
  1286. package/references/taxonomy/TAXONOMY.md +192 -0
  1287. package/references/templates/MINTO.md +36 -0
  1288. package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
  1289. package/references/vault-kit/README.md +35 -0
  1290. package/references/vault-kit/app.json +12 -0
  1291. package/references/vault-kit/appearance.json +12 -0
  1292. package/references/vault-kit/graph.json +35 -0
  1293. package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
  1294. package/references/vault-kit/templates/new-artifact.md +37 -0
  1295. package/references/vault-kit/templates/new-meeting-note.md +35 -0
  1296. package/references/vault-kit/templates/new-team-profile.md +29 -0
  1297. package/references/vault-kit/templates/new-xref.md +35 -0
  1298. package/references/visual/ROOM.md +55 -0
  1299. package/references/visual/palette.json +54 -0
  1300. package/references/visual/symbol-system.md +151 -0
  1301. package/scripts/189-plurai-gate-check.cjs +88 -0
  1302. package/scripts/198-plurai-gate-check.cjs +118 -0
  1303. package/scripts/198-rollback-rehearsal.cjs +233 -0
  1304. package/scripts/83-scope-injection.test.cjs +648 -0
  1305. package/scripts/PYTHON_GATES.md +78 -0
  1306. package/scripts/__pycache__/compute-hsi.cpython-312.pyc +0 -0
  1307. package/scripts/__pycache__/detect-reverse-salients.cpython-312.pyc +0 -0
  1308. package/scripts/__pycache__/rs-engine.cpython-312.pyc +0 -0
  1309. package/scripts/act-command.cjs +518 -0
  1310. package/scripts/admin-brain-write.cjs +442 -0
  1311. package/scripts/admin-command-gate.cjs +178 -0
  1312. package/scripts/agentshield-scan-cli.cjs +226 -0
  1313. package/scripts/agentshield-sessionstart-scan.cjs +282 -0
  1314. package/scripts/analogy-fitness-report.cjs +266 -0
  1315. package/scripts/analyze-room +1079 -0
  1316. package/scripts/apply-teaching-content.cjs +298 -0
  1317. package/scripts/async-artifact-auto-commit.cjs +485 -0
  1318. package/scripts/audit-active-phase-resolver.cjs +160 -0
  1319. package/scripts/audit-body-shape-coverage.cjs +213 -0
  1320. package/scripts/auto-explore-drain.cjs +274 -0
  1321. package/scripts/auto-explore-fingerprint.cjs +354 -0
  1322. package/scripts/auto-explore-fire.cjs +359 -0
  1323. package/scripts/backfill-correlation-id.cjs +425 -0
  1324. package/scripts/backfill-hitl-shape.cjs +276 -0
  1325. package/scripts/backfill-hitl-shape.test.cjs +307 -0
  1326. package/scripts/backup-modifications +68 -0
  1327. package/scripts/banner +182 -0
  1328. package/scripts/brain-derivation-drain.cjs +291 -0
  1329. package/scripts/brain-derive-command.cjs +846 -0
  1330. package/scripts/brain-normalize-books.cypher +505 -0
  1331. package/scripts/brain-normalize-final.cypher +1355 -0
  1332. package/scripts/brain-normalize-problemtype.cypher +475 -0
  1333. package/scripts/brain-normalize-supplement.cypher +449 -0
  1334. package/scripts/brain-normalize-targeted.cypher +344 -0
  1335. package/scripts/brain-normalize-v194.cypher +61 -0
  1336. package/scripts/brain-normalize.cypher +530 -0
  1337. package/scripts/brain-response-sanitize-hook.cjs +165 -0
  1338. package/scripts/build-brain-census.cjs +957 -0
  1339. package/scripts/build-brain-packet-schema.cjs +225 -0
  1340. package/scripts/build-command-registry.cjs +587 -0
  1341. package/scripts/build-connector-registry.cjs +1331 -0
  1342. package/scripts/build-corpus-stats.cjs +408 -0
  1343. package/scripts/build-dist-bundles.cjs +495 -0
  1344. package/scripts/build-ecosystem-graph.cjs +490 -0
  1345. package/scripts/build-graph +1462 -0
  1346. package/scripts/build-graph-from-sqlite.cjs +97 -0
  1347. package/scripts/build-harness-manifest.cjs +1090 -0
  1348. package/scripts/build-jtbd-nudges +205 -0
  1349. package/scripts/build-new-surface.cjs +805 -0
  1350. package/scripts/build-orchestration-projection.cjs +1221 -0
  1351. package/scripts/build-render-coverage.cjs +637 -0
  1352. package/scripts/build-skill-mirrors.cjs +681 -0
  1353. package/scripts/cache-hitrate-report.cjs +216 -0
  1354. package/scripts/causal-to-graph.cjs +163 -0
  1355. package/scripts/check-abstraction-fixture-neutral.cjs +160 -0
  1356. package/scripts/check-admin-identity.cjs +212 -0
  1357. package/scripts/check-brain-tool-liveness.cjs +502 -0
  1358. package/scripts/check-card-fire.cjs +1809 -0
  1359. package/scripts/check-cirs-declaration.cjs +279 -0
  1360. package/scripts/check-deck-design.cjs +104 -0
  1361. package/scripts/check-dual-graph-health.cjs +645 -0
  1362. package/scripts/check-first-touch-drift.cjs +220 -0
  1363. package/scripts/check-flagship-floor.cjs +391 -0
  1364. package/scripts/check-framework-vocabulary-drift.cjs +563 -0
  1365. package/scripts/check-gate-seam.cjs +178 -0
  1366. package/scripts/check-graph-derive-health.cjs +208 -0
  1367. package/scripts/check-graph-export-typemap.cjs +118 -0
  1368. package/scripts/check-help-coverage.cjs +290 -0
  1369. package/scripts/check-hitl-stages.cjs +219 -0
  1370. package/scripts/check-hook-schema-compatibility.cjs +231 -0
  1371. package/scripts/check-hsi-deps +42 -0
  1372. package/scripts/check-kuzu-reintroduction.cjs +251 -0
  1373. package/scripts/check-onboard +43 -0
  1374. package/scripts/check-onboard-statusline.cjs +363 -0
  1375. package/scripts/check-palette-consistency.cjs +106 -0
  1376. package/scripts/check-pending-ambiguous.cjs +244 -0
  1377. package/scripts/check-pending-breakthrough.cjs +189 -0
  1378. package/scripts/check-pending-breakthrough.test.cjs +166 -0
  1379. package/scripts/check-pending-naming-decision.cjs +97 -0
  1380. package/scripts/check-plugin-path-anchoring.cjs +830 -0
  1381. package/scripts/check-publish-needs.cjs +126 -0
  1382. package/scripts/check-registry-drift.cjs +206 -0
  1383. package/scripts/check-release-payload-ceiling.cjs +269 -0
  1384. package/scripts/check-render-coverage.cjs +558 -0
  1385. package/scripts/check-research-isomorphism.cjs +446 -0
  1386. package/scripts/check-reward-before-investment.cjs +261 -0
  1387. package/scripts/check-room-blueprints.cjs +213 -0
  1388. package/scripts/check-schema-aliases.cjs +532 -0
  1389. package/scripts/check-shape-declaration.cjs +958 -0
  1390. package/scripts/check-shape-declaration.test.cjs +185 -0
  1391. package/scripts/check-skill-spec.cjs +312 -0
  1392. package/scripts/check-skill-vs-code-drift.cjs +178 -0
  1393. package/scripts/check-substrate.cjs +513 -0
  1394. package/scripts/check-tool-honesty.cjs +1601 -0
  1395. package/scripts/check-update +60 -0
  1396. package/scripts/check-version-and-sha.cjs +366 -0
  1397. package/scripts/check-voice-style.cjs +184 -0
  1398. package/scripts/check-worktree-hygiene.cjs +530 -0
  1399. package/scripts/classify-insight +59 -0
  1400. package/scripts/coherence-smoke-test.cjs +489 -0
  1401. package/scripts/compute-bayesian-surprise.py +175 -0
  1402. package/scripts/compute-blindspot-mass.py +209 -0
  1403. package/scripts/compute-disruption-index.py +211 -0
  1404. package/scripts/compute-element-novelty.py +254 -0
  1405. package/scripts/compute-external-whitespace.py +563 -0
  1406. package/scripts/compute-hsi.py +947 -0
  1407. package/scripts/compute-meetings-intelligence +716 -0
  1408. package/scripts/compute-opportunity-state +17 -0
  1409. package/scripts/compute-state +393 -0
  1410. package/scripts/compute-team +1112 -0
  1411. package/scripts/compute-whitespace-embeddings.py +464 -0
  1412. package/scripts/compute-whitespace-gaps.py +562 -0
  1413. package/scripts/compute_topic_forest.py +684 -0
  1414. package/scripts/consolidate-pinecone.py +265 -0
  1415. package/scripts/context-monitor +943 -0
  1416. package/scripts/create-speaker-profile +311 -0
  1417. package/scripts/cross-room-detect.cjs +195 -0
  1418. package/scripts/csv-to-idea-graph.cjs +755 -0
  1419. package/scripts/ctxl-eval.cjs +846 -0
  1420. package/scripts/curation-132-03-dedup-held-rename.cjs +395 -0
  1421. package/scripts/curation-132-05-pseudonymize.cjs +189 -0
  1422. package/scripts/derive-verb-reach-affinity.cjs +507 -0
  1423. package/scripts/detect-reverse-salients.py +242 -0
  1424. package/scripts/diagnostics-command.cjs +345 -0
  1425. package/scripts/dial-memory-refresh-command.cjs +147 -0
  1426. package/scripts/discover-analogy-whitespace.py +433 -0
  1427. package/scripts/discover-hsi-whitespace.py +352 -0
  1428. package/scripts/discover-rs-whitespace.py +327 -0
  1429. package/scripts/discovery-cycle.cjs +625 -0
  1430. package/scripts/disposition-render-v2.cjs +229 -0
  1431. package/scripts/doctor-preflight-format.cjs +101 -0
  1432. package/scripts/doctor.cjs +4327 -0
  1433. package/scripts/dogfood-bridge/append-to-queue.sh +59 -0
  1434. package/scripts/dogfood-bridge/path-filter.cjs +86 -0
  1435. package/scripts/dogfood-derive.cjs +158 -0
  1436. package/scripts/dogfood-emit.cjs +126 -0
  1437. package/scripts/empathy-observation-emit.cjs +108 -0
  1438. package/scripts/enrichment-queue-append.cjs +157 -0
  1439. package/scripts/ensure-brain-baseline.cjs +117 -0
  1440. package/scripts/entity-extract.cjs +1135 -0
  1441. package/scripts/eureka-command.cjs +687 -0
  1442. package/scripts/eureka-critic-run.cjs +662 -0
  1443. package/scripts/eureka-portfolio-report.cjs +2038 -0
  1444. package/scripts/eureka-room-report.cjs +419 -0
  1445. package/scripts/explain-decision-command.cjs +499 -0
  1446. package/scripts/export-pinecone-embeddings.cjs +516 -0
  1447. package/scripts/extract-room-intelligence.cjs +714 -0
  1448. package/scripts/fetch-brain-baseline.cjs +190 -0
  1449. package/scripts/fetch-brain-baseline.py +360 -0
  1450. package/scripts/feynman-minto-guardian.cjs +672 -0
  1451. package/scripts/feynman-timeline-refresh-command.cjs +270 -0
  1452. package/scripts/file-asset +253 -0
  1453. package/scripts/fire-sensor-event.cjs +83 -0
  1454. package/scripts/first-install-router.cjs +968 -0
  1455. package/scripts/frontmatter-schema-validator.cjs +339 -0
  1456. package/scripts/fts-index-drain.cjs +221 -0
  1457. package/scripts/generate-chat-embed.cjs +179 -0
  1458. package/scripts/generate-deck.cjs +322 -0
  1459. package/scripts/generate-export.cjs +474 -0
  1460. package/scripts/generate-hub.cjs +2553 -0
  1461. package/scripts/generate-lobby.cjs +718 -0
  1462. package/scripts/generate-presentation.cjs +988 -0
  1463. package/scripts/generate-presentation.test.cjs +405 -0
  1464. package/scripts/generate-section-intelligence.cjs +330 -0
  1465. package/scripts/generate-snapshot.cjs +1410 -0
  1466. package/scripts/generate-standalone +159 -0
  1467. package/scripts/git-ops +211 -0
  1468. package/scripts/graph-heal-pipeline.cjs +321 -0
  1469. package/scripts/gsd-artifact-graph-hook.cjs +219 -0
  1470. package/scripts/gsd-graph-derive-drain.cjs +571 -0
  1471. package/scripts/gsd-graph-derive-sweep.cjs +195 -0
  1472. package/scripts/heal-command.cjs +1365 -0
  1473. package/scripts/hedge-refit-pipeline.cjs +207 -0
  1474. package/scripts/help-renderer.cjs +303 -0
  1475. package/scripts/hmi-compliance-poll.cjs +451 -0
  1476. package/scripts/hmi-status-command.cjs +510 -0
  1477. package/scripts/hooked-rescore-117.cjs +224 -0
  1478. package/scripts/hooks/pre-commit +533 -0
  1479. package/scripts/hooks/pre-commit-room-minto-guard.cmd +43 -0
  1480. package/scripts/hooks/pre-commit-room-minto-guard.sh +533 -0
  1481. package/scripts/hsi-to-graph.cjs +194 -0
  1482. package/scripts/huji-batch.cjs +821 -0
  1483. package/scripts/huji-eval.cjs +1158 -0
  1484. package/scripts/huji-intake.cjs +563 -0
  1485. package/scripts/huji-pin-smoketest.cjs +454 -0
  1486. package/scripts/huji-run-one-async.cjs +305 -0
  1487. package/scripts/huji-run-one.cjs +671 -0
  1488. package/scripts/install-pre-commit.sh +113 -0
  1489. package/scripts/intent-classifier +106 -0
  1490. package/scripts/intent-classifier.cjs +3620 -0
  1491. package/scripts/interpret-whitespace.cjs +691 -0
  1492. package/scripts/jtbd-command.cjs +810 -0
  1493. package/scripts/jtbd-update.cjs +299 -0
  1494. package/scripts/label-topic-forest.cjs +667 -0
  1495. package/scripts/learn-from-usage +168 -0
  1496. package/scripts/lib/__pycache__/ensure_ml_deps.cpython-312.pyc +0 -0
  1497. package/scripts/lib/ensure_ml_deps.py +97 -0
  1498. package/scripts/load-embeddings-into-neo4j.cjs +569 -0
  1499. package/scripts/memory-artifact-graph-hook.cjs +191 -0
  1500. package/scripts/memory-command.cjs +687 -0
  1501. package/scripts/memory-completion-detector.cjs +217 -0
  1502. package/scripts/memory-lifecycle.cjs +620 -0
  1503. package/scripts/memory-resume-nudge.cjs +233 -0
  1504. package/scripts/methodology-ingest-local.sh +36 -0
  1505. package/scripts/migrate-brain-mcp-from-http-to-stdio.cjs +190 -0
  1506. package/scripts/migrate-brain-mcp-from-http-to-stdio.test.cjs +242 -0
  1507. package/scripts/migrate-hats-to-roomdb.cjs +217 -0
  1508. package/scripts/migrate-lazygraph.cjs +250 -0
  1509. package/scripts/migrate-minto-schema-v88.cjs +296 -0
  1510. package/scripts/migrate-plugin-root-refs.cjs +168 -0
  1511. package/scripts/migrate-room-sections-v275.cjs +556 -0
  1512. package/scripts/migrate-rooms +417 -0
  1513. package/scripts/migrate-skill-frontmatter.cjs +472 -0
  1514. package/scripts/migrate-stale-user-settings.cjs +296 -0
  1515. package/scripts/migrate-telemetry-v1.cjs +384 -0
  1516. package/scripts/migrate-telemetry-v1.test.cjs +356 -0
  1517. package/scripts/minto-debouncer.cjs +428 -0
  1518. package/scripts/mos-status.cjs +622 -0
  1519. package/scripts/mva-detect.cjs +229 -0
  1520. package/scripts/mva-run.cjs +36 -0
  1521. package/scripts/on-agent-complete +163 -0
  1522. package/scripts/on-cwd-changed +149 -0
  1523. package/scripts/on-file-changed +68 -0
  1524. package/scripts/on-stop +617 -0
  1525. package/scripts/on-task-complete +194 -0
  1526. package/scripts/operator-command.cjs +858 -0
  1527. package/scripts/operator-update.cjs +308 -0
  1528. package/scripts/part8-egress-guard-hook.cjs +229 -0
  1529. package/scripts/pipeline-command.cjs +271 -0
  1530. package/scripts/post-compact +383 -0
  1531. package/scripts/post-update-activation.cjs +339 -0
  1532. package/scripts/post-write +283 -0
  1533. package/scripts/power-demo-prompt.md +621 -0
  1534. package/scripts/pre-compact +273 -0
  1535. package/scripts/preflight-auto-explore.cjs +314 -0
  1536. package/scripts/preflight-doctor.cjs +225 -0
  1537. package/scripts/preflight-release-drift.cjs +168 -0
  1538. package/scripts/preflight-tension-surface.cjs +414 -0
  1539. package/scripts/probe-brain-contract.cjs +502 -0
  1540. package/scripts/publish-ops +346 -0
  1541. package/scripts/query-efficiency-telemetry.cjs +364 -0
  1542. package/scripts/query-semantic-scholar.cjs +545 -0
  1543. package/scripts/reapply-modifications +49 -0
  1544. package/scripts/reassign-primary.cjs +125 -0
  1545. package/scripts/recompile-room-references.cjs +601 -0
  1546. package/scripts/release-beta-preflight.sh +69 -0
  1547. package/scripts/render-pdf +588 -0
  1548. package/scripts/render-viz +190 -0
  1549. package/scripts/research-speaker +239 -0
  1550. package/scripts/resolve-room +204 -0
  1551. package/scripts/restore-post-compact-context.cjs +320 -0
  1552. package/scripts/room-auto-create-nudge.cjs +136 -0
  1553. package/scripts/room-naming-selector.cjs +201 -0
  1554. package/scripts/room-registry +767 -0
  1555. package/scripts/rs-discovery-engine.cjs +725 -0
  1556. package/scripts/rs-engine.py +2073 -0
  1557. package/scripts/rs-experts-command.cjs +300 -0
  1558. package/scripts/rs-explain-command.cjs +323 -0
  1559. package/scripts/rs-fetch-command.cjs +170 -0
  1560. package/scripts/rs-thesis-command.cjs +203 -0
  1561. package/scripts/rs-vector-bridge.cjs +410 -0
  1562. package/scripts/run-harness.cjs +890 -0
  1563. package/scripts/scout-cadence-guard.cjs +323 -0
  1564. package/scripts/scout-cadence-runner.cjs +509 -0
  1565. package/scripts/scout-telemetry-aggregator.cjs +268 -0
  1566. package/scripts/sealed-walker.py +96 -0
  1567. package/scripts/seed-brain-commands.cjs +324 -0
  1568. package/scripts/self-update +105 -0
  1569. package/scripts/sentinel-deadline-monitor +241 -0
  1570. package/scripts/sentinel-health-check +198 -0
  1571. package/scripts/sentinel-snapshot +42 -0
  1572. package/scripts/serve-dashboard +103 -0
  1573. package/scripts/serve-dashboard-live +932 -0
  1574. package/scripts/serve-presentation +97 -0
  1575. package/scripts/serve-wiki +90 -0
  1576. package/scripts/session-end-presence.cjs +79 -0
  1577. package/scripts/session-start +1942 -0
  1578. package/scripts/sessionstart-coordinator.cjs +249 -0
  1579. package/scripts/sessionstart-npm-reconcile.cjs +111 -0
  1580. package/scripts/sessionstart-post-update-preflight.cjs +303 -0
  1581. package/scripts/sessionstart-reference-now-seed.cjs +137 -0
  1582. package/scripts/setup-hooks.sh +112 -0
  1583. package/scripts/skillopt-codereview.cjs +830 -0
  1584. package/scripts/skillopt-eval.cjs +519 -0
  1585. package/scripts/skillopt-funnel.cjs +1076 -0
  1586. package/scripts/skillopt-genqueries.cjs +571 -0
  1587. package/scripts/skillopt-inventory.cjs +319 -0
  1588. package/scripts/skillopt-merge.cjs +656 -0
  1589. package/scripts/skillopt-triggerloop.cjs +1126 -0
  1590. package/scripts/soft-alias-runner.cjs +116 -0
  1591. package/scripts/stamp-artifact-write.cjs +276 -0
  1592. package/scripts/stamp-firing-block.cjs +325 -0
  1593. package/scripts/state-write.cjs +97 -0
  1594. package/scripts/statusline-fallback-echo.cjs +297 -0
  1595. package/scripts/statusline-mos +116 -0
  1596. package/scripts/statusline-mos-dispatch +127 -0
  1597. package/scripts/suggest-next-command.cjs +416 -0
  1598. package/scripts/sweep-skill-descriptions.cjs +263 -0
  1599. package/scripts/sync-rooms-brain +438 -0
  1600. package/scripts/sync-rooms-graph +264 -0
  1601. package/scripts/telemetry-command-invocation.cjs +120 -0
  1602. package/scripts/test-doctor-recovery.cjs +242 -0
  1603. package/scripts/test-fresh-install.md +140 -0
  1604. package/scripts/track-analytics +156 -0
  1605. package/scripts/transcribe-audio +148 -0
  1606. package/scripts/transcript-ingest.cjs +225 -0
  1607. package/scripts/update-icm-index +153 -0
  1608. package/scripts/v182-brain-optimize.cypher +1191 -0
  1609. package/scripts/validate-model-profiles +93 -0
  1610. package/scripts/vault-content-reformatter.cjs +581 -0
  1611. package/scripts/vault-export-orchestrator.cjs +400 -0
  1612. package/scripts/vault-footer-injector.cjs +315 -0
  1613. package/scripts/vault-import.cjs +839 -0
  1614. package/scripts/vault-integration-test.cjs +318 -0
  1615. package/scripts/vault-regenerate-all.cjs +216 -0
  1616. package/scripts/vault-regenerate-all.test.cjs +138 -0
  1617. package/scripts/vault-rules-generator.cjs +220 -0
  1618. package/scripts/vault-section-minto-generator.cjs +1642 -0
  1619. package/scripts/vault-section-minto-generator.integration.test.cjs +576 -0
  1620. package/scripts/vault-section-minto-generator.test.cjs +291 -0
  1621. package/scripts/vault-section-state-generator.cjs +385 -0
  1622. package/scripts/vault-welcome-generator.cjs +403 -0
  1623. package/scripts/vault-wikilink-injector.cjs +449 -0
  1624. package/scripts/verify-phase-132.cjs +338 -0
  1625. package/scripts/verify-release +744 -0
  1626. package/scripts/whitespace-command.cjs +831 -0
  1627. package/scripts/whitespace-to-brain.cjs +383 -0
  1628. package/scripts/whitespace-to-graph.cjs +220 -0
  1629. package/scripts/wikilink-batch.cjs +102 -0
  1630. package/scripts/wikilink-file.cjs +139 -0
  1631. package/scripts/write-bridge-artifacts.cjs +140 -0
  1632. package/scripts/write-scope-check +12 -0
  1633. package/scripts/write-scope-check.cjs +461 -0
  1634. package/scripts/write-whitespace-sections.cjs +483 -0
  1635. package/settings.json +7 -0
  1636. package/skills/act/SKILL.md +523 -0
  1637. package/skills/admin/SKILL.md +437 -0
  1638. package/skills/agentshield/SKILL.md +50 -0
  1639. package/skills/analyze-needs/SKILL.md +74 -0
  1640. package/skills/analyze-systems/SKILL.md +69 -0
  1641. package/skills/analyze-timing/SKILL.md +94 -0
  1642. package/skills/auto-explore/SKILL.md +84 -0
  1643. package/skills/beautiful-question/SKILL.md +72 -0
  1644. package/skills/bono/SKILL.md +146 -0
  1645. package/skills/brain-connector/SKILL.md +206 -0
  1646. package/skills/brain-derive/SKILL.md +140 -0
  1647. package/skills/build-knowledge/SKILL.md +118 -0
  1648. package/skills/build-thesis/SKILL.md +90 -0
  1649. package/skills/causal/SKILL.md +265 -0
  1650. package/skills/challenge-assumptions/SKILL.md +74 -0
  1651. package/skills/client-discovery-interview/SKILL.md +94 -0
  1652. package/skills/client-discovery-interview/discovery-scaffold.json +41 -0
  1653. package/skills/client-discovery-interview/question-bank.md +149 -0
  1654. package/skills/compare-ventures/SKILL.md +113 -0
  1655. package/skills/context-engine/SKILL.md +169 -0
  1656. package/skills/conversation-mode/SKILL.md +189 -0
  1657. package/skills/correct-reference-now/SKILL.md +86 -0
  1658. package/skills/dashboard/SKILL.md +142 -0
  1659. package/skills/deck/SKILL.md +113 -0
  1660. package/skills/deep-grade/SKILL.md +210 -0
  1661. package/skills/diagnose/SKILL.md +128 -0
  1662. package/skills/diagnostics/SKILL.md +193 -0
  1663. package/skills/dial-memory-refresh/SKILL.md +116 -0
  1664. package/skills/diffusion/SKILL.md +125 -0
  1665. package/skills/discover/SKILL.md +216 -0
  1666. package/skills/doctor/SKILL.md +269 -0
  1667. package/skills/dogfood-flush/SKILL.md +133 -0
  1668. package/skills/dominant-designs/SKILL.md +72 -0
  1669. package/skills/eureka/SKILL.md +313 -0
  1670. package/skills/explain-decision/SKILL.md +138 -0
  1671. package/skills/explore-domains/SKILL.md +74 -0
  1672. package/skills/explore-futures/SKILL.md +64 -0
  1673. package/skills/explore-opportunity/SKILL.md +168 -0
  1674. package/skills/explore-trends/SKILL.md +74 -0
  1675. package/skills/export/SKILL.md +126 -0
  1676. package/skills/feynman-timeline-refresh/SKILL.md +105 -0
  1677. package/skills/file-meeting/SKILL.md +1190 -0
  1678. package/skills/find-analogies/SKILL.md +360 -0
  1679. package/skills/find-bottlenecks/SKILL.md +104 -0
  1680. package/skills/find-connections/SKILL.md +108 -0
  1681. package/skills/funding/SKILL.md +110 -0
  1682. package/skills/futures/SKILL.md +124 -0
  1683. package/skills/grade/SKILL.md +252 -0
  1684. package/skills/grade-grant/SKILL.md +293 -0
  1685. package/skills/graph/SKILL.md +216 -0
  1686. package/skills/hat-briefing/SKILL.md +160 -0
  1687. package/skills/heal/SKILL.md +69 -0
  1688. package/skills/help/SKILL.md +172 -0
  1689. package/skills/hmi-status/SKILL.md +73 -0
  1690. package/skills/ignite/SKILL.md +225 -0
  1691. package/skills/ingest-methodology/SKILL.md +125 -0
  1692. package/skills/intel-pipeline/SKILL.md +128 -0
  1693. package/skills/intelligence-orchestrator/SKILL.md +262 -0
  1694. package/skills/jtbd/SKILL.md +261 -0
  1695. package/skills/larry-personality/SKILL.md +570 -0
  1696. package/skills/larry-personality/framework-chains.md +92 -0
  1697. package/skills/larry-personality/mode-engine.md +185 -0
  1698. package/skills/leadership/SKILL.md +109 -0
  1699. package/skills/lean-canvas/SKILL.md +70 -0
  1700. package/skills/macro-trends/SKILL.md +84 -0
  1701. package/skills/map-unknowns/SKILL.md +72 -0
  1702. package/skills/memory/SKILL.md +212 -0
  1703. package/skills/memory-cortex-reach/SKILL.md +80 -0
  1704. package/skills/models/SKILL.md +197 -0
  1705. package/skills/mos/SKILL.md +160 -0
  1706. package/skills/mos-deck-engine/SKILL.md +329 -0
  1707. package/skills/mos-reason/SKILL.md +484 -0
  1708. package/skills/mullins/SKILL.md +151 -0
  1709. package/skills/mullins-scaffold/SKILL.md +132 -0
  1710. package/skills/mullins-scaffold/scaffold.json +223 -0
  1711. package/skills/mva-brief/SKILL.md +87 -0
  1712. package/skills/mva-option/SKILL.md +120 -0
  1713. package/skills/mva-pipeline/SKILL.md +158 -0
  1714. package/skills/mva-report/SKILL.md +79 -0
  1715. package/skills/new-project/SKILL.md +629 -0
  1716. package/skills/new-surface/SKILL.md +112 -0
  1717. package/skills/onboard/SKILL.md +485 -0
  1718. package/skills/operator/SKILL.md +167 -0
  1719. package/skills/opportunities/SKILL.md +171 -0
  1720. package/skills/organize/SKILL.md +69 -0
  1721. package/skills/persona/SKILL.md +272 -0
  1722. package/skills/pipeline/SKILL.md +164 -0
  1723. package/skills/present/SKILL.md +123 -0
  1724. package/skills/publish/SKILL.md +224 -0
  1725. package/skills/pws-brain/SKILL.md +152 -0
  1726. package/skills/pws-methodology/SKILL.md +92 -0
  1727. package/skills/qualify-opportunity/SKILL.md +111 -0
  1728. package/skills/query/SKILL.md +68 -0
  1729. package/skills/radar/SKILL.md +106 -0
  1730. package/skills/reanalyze/SKILL.md +122 -0
  1731. package/skills/research/SKILL.md +462 -0
  1732. package/skills/room/SKILL.md +381 -0
  1733. package/skills/room-passive/SKILL.md +174 -0
  1734. package/skills/room-proactive/SKILL.md +344 -0
  1735. package/skills/rooms/SKILL.md +678 -0
  1736. package/skills/root-cause/SKILL.md +73 -0
  1737. package/skills/rs-experts/SKILL.md +151 -0
  1738. package/skills/rs-explain/SKILL.md +130 -0
  1739. package/skills/rs-fetch/SKILL.md +115 -0
  1740. package/skills/rs-thesis/SKILL.md +129 -0
  1741. package/skills/scenario-plan/SKILL.md +76 -0
  1742. package/skills/scheduled-tasks/SKILL.md +301 -0
  1743. package/skills/score-innovation/SKILL.md +74 -0
  1744. package/skills/scout/SKILL.md +370 -0
  1745. package/skills/setup/SKILL.md +654 -0
  1746. package/skills/show/SKILL.md +68 -0
  1747. package/skills/skill/SKILL.md +104 -0
  1748. package/skills/snapshot/SKILL.md +178 -0
  1749. package/skills/speakers/SKILL.md +116 -0
  1750. package/skills/splash/SKILL.md +54 -0
  1751. package/skills/stance/SKILL.md +89 -0
  1752. package/skills/status/SKILL.md +107 -0
  1753. package/skills/structure-argument/SKILL.md +78 -0
  1754. package/skills/suggest-next/SKILL.md +137 -0
  1755. package/skills/systems-thinking/SKILL.md +220 -0
  1756. package/skills/think-hats/SKILL.md +82 -0
  1757. package/skills/trending-to-absurd/SKILL.md +100 -0
  1758. package/skills/ui-system/SKILL.md +474 -0
  1759. package/skills/ui-system/design-system/M-OS-DESIGN-SYSTEM.md +273 -0
  1760. package/skills/ui-system/design-system/SPEC.md +72 -0
  1761. package/skills/ui-system/design-system/image-prompt-style.md +29 -0
  1762. package/skills/ui-system/design-system/mos-design-system.css +240 -0
  1763. package/skills/ui-system/rules/design-system.md +60 -0
  1764. package/skills/ui-system/rules/dual-palette.md +156 -0
  1765. package/skills/ui-system/rules/glyph-disambiguation.md +171 -0
  1766. package/skills/ui-system/rules/shape-f-zero-and-six.md +169 -0
  1767. package/skills/update/SKILL.md +254 -0
  1768. package/skills/user-needs/SKILL.md +73 -0
  1769. package/skills/validate/SKILL.md +73 -0
  1770. package/skills/value-proposition/SKILL.md +141 -0
  1771. package/skills/vault/SKILL.md +326 -0
  1772. package/skills/visualize/SKILL.md +69 -0
  1773. package/skills/whitespace/SKILL.md +539 -0
  1774. package/skills/wiki/SKILL.md +94 -0
  1775. package/templates/_base.html +20 -0
  1776. package/templates/chat-panel.html +205 -0
  1777. package/templates/constellation-chat-hooks.js +89 -0
  1778. package/templates/constellation.html +1272 -0
  1779. package/templates/destijl-base.css +268 -0
  1780. package/templates/discovery/discovery-brief-template.md +97 -0
  1781. package/templates/due-diligence.html +49 -0
  1782. package/templates/executive-summary.html +40 -0
  1783. package/templates/icm/CLAUDE.md +31 -0
  1784. package/templates/icm/GROUP-CLAUDE.md +33 -0
  1785. package/templates/icm/INDEX.md +26 -0
  1786. package/templates/import/stage-contracts/01-ingest.md +34 -0
  1787. package/templates/import/stage-contracts/02-classify.md +48 -0
  1788. package/templates/import/stage-contracts/03-route.md +42 -0
  1789. package/templates/import/stage-contracts/04-enrich.md +40 -0
  1790. package/templates/investment-thesis.html +25 -0
  1791. package/templates/meeting-report.html +361 -0
  1792. package/templates/presentation/dashboard.html +1150 -0
  1793. package/templates/presentation/deck.html +540 -0
  1794. package/templates/presentation/diagrams.html +437 -0
  1795. package/templates/presentation/graph.html +391 -0
  1796. package/templates/presentation/insights.html +548 -0
  1797. package/templates/presentation/wiki.html +533 -0
  1798. package/templates/profile.html +136 -0
  1799. package/templates/room-context/last-session.md +56 -0
  1800. package/templates/room-context/methodology-history.md +28 -0
  1801. package/templates/room-context/rejection-log.md +25 -0
  1802. package/templates/room-context/weekly-digest.md +54 -0
  1803. package/templates/room-skeleton/MINTO.md.tmpl +19 -0
  1804. package/templates/room-skeleton/ROOM.md.identity.tmpl +13 -0
  1805. package/templates/room-skeleton/ROOM.md.section.tmpl +33 -0
  1806. package/templates/room-skeleton/STATE.md.tmpl +33 -0
  1807. package/templates/room-skeleton/USER.md.tmpl +23 -0
  1808. package/templates/room-skeleton/references/.gitkeep +0 -0
  1809. package/templates/room-skeleton/references/SECTION-SCHEMA.md +213 -0
  1810. package/templates/room-skeleton/references/SUB-SCHEMAS.md +91 -0
  1811. package/templates/room-skeleton/section-contracts/.gitkeep +0 -0
  1812. package/templates/room-skeleton/section-contracts/business-model.md +32 -0
  1813. package/templates/room-skeleton/section-contracts/competitive-analysis.md +33 -0
  1814. package/templates/room-skeleton/section-contracts/financial-model.md +30 -0
  1815. package/templates/room-skeleton/section-contracts/funding.md +41 -0
  1816. package/templates/room-skeleton/section-contracts/legal-ip.md +30 -0
  1817. package/templates/room-skeleton/section-contracts/market-analysis.md +32 -0
  1818. package/templates/room-skeleton/section-contracts/opportunity-bank.md +39 -0
  1819. package/templates/room-skeleton/section-contracts/problem-definition.md +32 -0
  1820. package/templates/room-skeleton/section-contracts/solution-design.md +36 -0
  1821. package/templates/room-skeleton/section-contracts/strategy.md +35 -0
  1822. package/templates/room-skeleton/section-contracts/team-execution.md +36 -0
  1823. package/templates/shared.css +427 -0
  1824. package/templates/showcase/blueprint.html +432 -0
  1825. package/templates/showcase/library.html +571 -0
  1826. package/templates/showcase/narrative.html +361 -0
  1827. package/templates/showcase/overview.html +468 -0
  1828. package/templates/showcase/synthesis.html +473 -0
  1829. package/templates/signature-footer.html +34 -0
@@ -0,0 +1,4327 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
4
+ * Phase 93 D2 -- /mos:doctor diagnostic + recovery for install-cache drift.
5
+ *
6
+ * Detects whether the live install at ~/.claude/plugins/mindrian-os/ is
7
+ * out of sync with the marketplace cache at
8
+ * ~/.claude/plugins/cache/mindrian-marketplace/mos/<latest>/, and offers
9
+ * a one-shot recovery via --fix that backs up the stale install and
10
+ * replaces it with the latest cached version.
11
+ *
12
+ * MODES:
13
+ * doctor read-only diagnostic - exits with status code only
14
+ * doctor --fix runs backup-then-replace recovery if drift detected
15
+ * doctor --json machine-readable output (for hooks / regression tests)
16
+ *
17
+ * Exit codes:
18
+ * 0 -> healthy, no drift
19
+ * 1 -> drift detected (read-only mode)
20
+ * 2 -> drift detected and recovered (--fix mode)
21
+ * 3 -> internal error (cannot read directories, no marketplace cache, etc.)
22
+ *
23
+ * Phase 93 scope. See docs/autopsies/2026-04-28-install-cache-drift-incident.md.
24
+ */
25
+
26
+ 'use strict';
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const os = require('os');
31
+ // Phase 126 Plan-02: semver package for numeric-aware prerelease ordering
32
+ // in cmpVersion (line ~194). semver@^7.7.4 is a PRODUCTION dependency in
33
+ // package.json (it was a devDependency until the debug session
34
+ // mcp-servers-cache-missing-node-modules audit found that classification
35
+ // was wrong -- doctor.cjs is a user-facing runtime script, so semver must
36
+ // be present on every install, including the vendored production tree).
37
+ // Canon Part 7 reuse: scripts/release.sh also uses semver for pre-release
38
+ // algebra; this is the same dep reused for scripts/doctor.cjs.
39
+ const semver = require('semver');
40
+
41
+ // -- Paths -----------------------------------------------------------
42
+
43
+ // Phase 217 Plan-01 (D-02): shared constants + helpers + the pure class-A
44
+ // constituent readers live in the leaf module lib/core/doctor/shared.cjs. ONE
45
+ // destructuring require -- doctor.cjs -> shared.cjs is the ONLY direction (no
46
+ // back-require). C (the color table), HOME, PLUGIN_HOME, INSTALL_DIR,
47
+ // MARKETPLACE_CACHE_DIR, PLUGIN_ROOT, parseVersion, cmpVersion,
48
+ // resolveActivePluginRoot (re-exported by shared from
49
+ // lib/core/active-plugin-root.cjs), readRegistry,
50
+ // readInstalledPluginsVersion, checkInstallVersion, checkMarketplaceCache, and
51
+ // checkDevSourceConsistency all resolve here. INSTALL_DIR remains the source of
52
+ // truth for the class A check; new code MUST use resolveActivePluginRoot()
53
+ // instead of the hardcoded INSTALL_DIR constant.
54
+ const {
55
+ C,
56
+ HOME,
57
+ PLUGIN_HOME,
58
+ INSTALL_DIR,
59
+ MARKETPLACE_CACHE_DIR,
60
+ PLUGIN_ROOT,
61
+ parseVersion,
62
+ cmpVersion,
63
+ resolveActivePluginRoot,
64
+ readRegistry,
65
+ readInstalledPluginsVersion,
66
+ checkInstallVersion,
67
+ checkMarketplaceCache,
68
+ checkDevSourceConsistency,
69
+ // Phase 217 Plan 06: the class I/J shared readers moved to shared.cjs with the
70
+ // I/J migration. doctor.cjs still needs these five in the
71
+ // --report-registration-bug assembler (Canon Part 7 reuse); the rest are now
72
+ // internal to the install-state / deployment-surfaces runner modules.
73
+ collectVersionOfRecord,
74
+ computeVersionDivergences,
75
+ readLegacyConfigPin,
76
+ detectMarketplaceCacheInstall,
77
+ readInstalledPluginsInstallPath,
78
+ } = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'shared.cjs'));
79
+
80
+ // Phase 217 Plan 06: class I (install-state) + class J (deployment-surfaces)
81
+ // migrated to registry-driven cadence:always runner modules. The accumulative
82
+ // engine owns their check + fix-then-recheck under the --install-state flag
83
+ // (both share it, exactly as the retired main() block). doctor.cjs imports the
84
+ // runners' RAW check entries ONLY for the --acceptance path (buildAcceptance
85
+ // checklist keys on the historical 'healthy' status); never a duplicate body
86
+ // (Pitfall 4). The runner check(ctx) surfaces map 'healthy' -> 'ok' for the
87
+ // engine's report.checks vocabulary.
88
+ const installStateModule = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'install-state-module.cjs'));
89
+ const deploymentSurfacesModule = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'deployment-surfaces-module.cjs'));
90
+ const checkInstallState = installStateModule.checkInstallState;
91
+ const checkDeploymentSurfaces = deploymentSurfacesModule.checkDeploymentSurfaces;
92
+ // Phase 217 Plan 04: the SINGLE umbilical/room target resolver + the .room-root
93
+ // walk-up + the section walk + generator spawn all moved WITH the class B/C/E
94
+ // checks into their registry runners (lib/core/doctor/cascade-rooms*-module.cjs,
95
+ // room-md-module.cjs). doctor.cjs no longer references any of them directly.
96
+ // Phase 139 Plan-02 (S2 accumulative-engine skeleton): the version-dispatch
97
+ // substrate. The hand-maintained module registry (data/doctor-modules.json,
98
+ // mirrors data/deployment-surfaces.json's "new entry = no code change" pattern)
99
+ // + doctor's OWN persisted applied-through watermark (readDoctorApplied /
100
+ // writeDoctorApplied at ~/.mindrian/doctor-applied.json -- NOT the session-start-
101
+ // overwritten ~/.mindrian-last-version, per the watermark_not_lastversion LOCKED
102
+ // decision). The selector (runAccumulativeEngine) generalizes the proven
103
+ // lib/core/install-state.cjs::migrateIfNeeded semver-dispatch + future-version-
104
+ // DEFER + additive-idempotency shape: it runs every module whose
105
+ // introduced_version is in (applied_through, running], idempotently.
106
+ const DOCTOR_MODULES_PATH = path.join(__dirname, '..', 'data', 'doctor-modules.json');
107
+ const { readDoctorApplied, writeDoctorApplied } = require(path.join(__dirname, '..', 'lib', 'core', 'migration-snapshot.cjs'));
108
+ // Phase 95.6 D-09 / R-01: the install-receipt const (INSTALL_RECEIPT_JSON) moved
109
+ // into lib/core/doctor/install-incomplete-module.cjs with the class H check
110
+ // (Phase 217 Plan 05).
111
+
112
+ // -- Argument parsing ------------------------------------------------
113
+
114
+ function parseArgs(argv) {
115
+ const flags = {
116
+ fix: false, json: false, verbose: false,
117
+ cascadeRooms: false, verifySurface: false, roomMd: false, uiCompliance: false,
118
+ statuslineVisibility: false,
119
+ // Phase 217 Plan-02 (D-05): class card-fire-health -- reports the health of
120
+ // the scripts/check-card-fire.cjs self-diagnostic instrument (intercept log
121
+ // exists/valid-JSONL/fresh, classifier/counter seams intact, session store
122
+ // parseable). cadence:always module, so a --card-fire-health-only run is a
123
+ // class-flag run (exit-0 invariant, no once-heal pass); also set by --all.
124
+ cardFireHealth: false,
125
+ installState: false,
126
+ // Phase 121.5-05 Sub-plan F (SEED-007 absorption): class K (stale-first-
127
+ // touch-copy) checks every first-touch surface declared in
128
+ // data/first-touch-surfaces.json for two regression patterns:
129
+ // (1) stale_version_hardcode -- a vX.Y.Z literal older than the running
130
+ // plugin version (the SEED-007 trigger);
131
+ // (2) em_dash_violation -- any U+2014 EM DASH in a surface flagged with
132
+ // em_dash_check: true (the feedback_no_emdashes hard rule).
133
+ // Per data/first-touch-surfaces.json each surface declares both invariants
134
+ // and an allowlist of doc-example version strings. Renamed from class H
135
+ // in 121.5-05-PLAN.md (class H is install-incomplete; deviation Rule 3).
136
+ staleFirstTouch: false,
137
+ // Phase 121.5-08 Sub-plan J (D-09 final clause): class L
138
+ // (deprecated-commands-you-still-use). Scans recent
139
+ // ~/.claude/projects/.../*.jsonl session transcripts (last 7 days)
140
+ // for /mos:<deprecated> patterns where the deprecated name maps to a
141
+ // canonical /mos:<new> per the v1.13.0-beta.16+ rename table:
142
+ // heal -> doctor --heal-room
143
+ // query -> graph
144
+ // organize -> rooms organize
145
+ // hmi-status -> doctor --ui-compliance
146
+ // visualize -> dashboard --mermaid
147
+ // diagnostics -> fingerprint
148
+ // MINDRIAN_DOCTOR_TRANSCRIPTS_DIR overrides the default scan dir for
149
+ // hermetic tests. Activated by --deprecated-usage AND --all.
150
+ // Pure LOCAL scan; zero network surface (Canon Part 8).
151
+ // Letter L chosen because A..K are already assigned (A install-cache,
152
+ // B+C cascade-rooms, D verify-surface, E room-md, F ui-compliance,
153
+ // G statusline-visibility, H install-incomplete, I install-state,
154
+ // J deployment-surfaces, K stale-first-touch-copy).
155
+ deprecatedUsage: false,
156
+ // Phase 127-02 BRAIN-MCP-127-08: class M (Brain end-to-end smoke).
157
+ // 6-layer probe (plugin root resolver, key resolver, HTTPS schema probe,
158
+ // MCP stdio handshake, e2e brain_schema via the shim, store identity).
159
+ // Diagnostic-only; class-flag invariant applies (exit 0 even on FAIL).
160
+ // Letter M is the next free letter after L (CONTEXT D4 text reads "K"
161
+ // but K is taken).
162
+ brainSmoke: false,
163
+ // quick(260706-13z) D14: class S (Eureka local-embedding-stack smoke).
164
+ // 4-layer probe (deps present, vec backend, model cache, graceful degrade).
165
+ // Diagnostic-only; class-flag invariant applies (exit 0 even on FAIL). Never
166
+ // downloads a model unless MINDRIAN_EUREKA_SMOKE_ALLOW_DOWNLOAD=1. Letter S
167
+ // is the next free letter after R (runtime-reachability, Phase 185).
168
+ eurekaSmoke: false,
169
+ // Phase 123 Plan-04 (HARNESS-123-11 + HARNESS-123-12): release-gate-as-a-
170
+ // command. --acceptance runs the 7-point checklist; --pre-tag filters to
171
+ // the 5 pre-tag-applicable points; --light-npx skips the mktemp HOME
172
+ // override sandbox in the npx-roundtrip point (operator opt-in for slow
173
+ // networks / hermetic CI). --acceptance has its OWN exit-code contract
174
+ // (0 = all points passed; 1 = any point failed) and is NOT a class flag,
175
+ // so the class-flag-always-exit-0 invariant does NOT apply.
176
+ //
177
+ // Phase 126.1 hotfix (2026-05-15): added `preFlight` tier. release.sh
178
+ // Step 6.6 calls --pre-tag AFTER Steps 3-6 intentionally bump
179
+ // plugin.json + package.json + CHANGELOG, so verify-release-clean-tree
180
+ // cannot live in 'pre-tag'. The new `pre-flight` tier is a strict
181
+ // subset of pre-tag MINUS in-flight-incompatible checks; release.sh
182
+ // Step 2.5 calls --pre-flight BEFORE Step 3 mutates anything, restoring
183
+ // the clean-tree gate. Mutually exclusive with --pre-tag; if both
184
+ // pass, --pre-tag wins.
185
+ acceptance: false, preTag: false, preFlight: false, lightNpx: false,
186
+ // Phase 127.2-03 Task 2 (Finding F1 + F7): --check-rs-engine pre-flights
187
+ // Python deps reachable from scripts/rs-engine.py (Engine 1 Act 1 surface
188
+ // for /mos:find-bottlenecks et al.). ADD-ONLY -- not a class flag; has
189
+ // its OWN dispatch + exit-code contract (0 = deps present; 1 = deps
190
+ // missing with actionable fix line). NOT in --all because --all is for
191
+ // class A-M drift detection, not for environment readiness probes.
192
+ // Closes the Windows tester 2026-05-23 silent-failure class.
193
+ checkRsEngine: false,
194
+ // Phase 127.2 Plan 04 Instance #7: --post-update routes through
195
+ // scripts/post-update-activation.cjs (atomic-swap delegate to the
196
+ // existing --fix pipeline + touch-file write so the next session's
197
+ // sessionstart-post-update-preflight.cjs verifies activation reached
198
+ // the wire). ADD-ONLY -- not a class flag; has its OWN dispatch +
199
+ // exit-code contract (0 = activated or already on latest; 1 = swap
200
+ // attempted and failed). Composes with --fix when called as
201
+ // `doctor --fix --post-update` from /mos:update Step 7.
202
+ postUpdate: false,
203
+ // Phase 146 Plan 04 (ACPT-01..05) + Phase 150.5-03 (ACPT-06):
204
+ // --dogfood-acceptance is the LOOP-FIRES gate -- the Phase 146 milestone
205
+ // gate. It runs the 6 ACPT dogfood drivers
206
+ // (tests/test-acpt-0{1,2,3,4,5,6}-*.cjs) as child processes and aggregates
207
+ // their exit codes. It has its OWN exit-code contract (0 = every ACPT
208
+ // hermetic leg passed; non-zero = a leg failed), separate from the existing
209
+ // release --acceptance, and mirroring how --acceptance is "NOT a class flag"
210
+ // with its own contract -- the class-flag-always-exit-0 invariant does NOT
211
+ // apply. Per the ROADMAP Phase 146 mandate: exit 0 = the milestone ships as
212
+ // "Larry Reaches"; non-zero = the milestone is renamed. The ACPT-05 driver's
213
+ // LIVE mode_a arm self-skips when Brain is unreachable (Plan 03), so the gate
214
+ // passes on the hermetic legs WITHOUT a reachable Brain -- the gate must NOT
215
+ // require Brain (Canon Part 8: --dogfood-acceptance adds zero network surface
216
+ // in doctor itself; the spawned drivers are LOCAL/hermetic).
217
+ dogfoodAcceptance: false,
218
+ // Phase 150-08 (MEM-09 / D-09): --claims is the CLAIM HARNESS gate -- the
219
+ // public-site-claim acceptance gate. It shells tests/claim-harness/run-all-
220
+ // claims.sh (the C1..C7 falsifiable drivers + the gates) and returns its OWN
221
+ // self-contained exit code (0 = every claim is true by instrumentation;
222
+ // non-zero = a claim failed), a SIBLING of --acceptance, NOT folded into the
223
+ // class roster (the class-flag-always-exit-0 invariant does NOT apply). Canon
224
+ // Part 8: the harness is LOCAL/hermetic; the Brain LIVE arms self-skip via
225
+ // class-m-brain-smoke, so the gate is CI-green without a reachable Brain. The
226
+ // DOCTOR_CLAIM_FAIL_POINT env hook (mirroring DOCTOR_TEST_FAIL_POINT) makes
227
+ // the gate's own failure path testable.
228
+ claims: false,
229
+ // Phase 150.9 Plan-02: --drift activates Class P (prose-vs-code, report-only)
230
+ // + Class Q (gsd-record drift) + the DRIFT.md writer. OPT-IN ONLY (D-04):
231
+ // NOT added to the --all block, NOT in the default session-start roster.
232
+ drift: false,
233
+ all: false, simulateWrite: null,
234
+ scanCommandsDir: null, scanScriptsDir: null,
235
+ // Phase 194-07 (PSB-14): --bind-check <roomDir> local room-health job.
236
+ bindCheck: false, bindCheckDir: null,
237
+ // Quick task 260705-jeq (D-01, D-02): --report-registration-bug is a NEW
238
+ // read-only sibling mode (like --check-rs-engine) that proves every locally-
239
+ // checkable cause of "valid install yet a command did not register" is CLEAN
240
+ // and assembles a paste-ready Anthropic bug report. Diagnostic-only: it never
241
+ // claims a "healthy"/"fixed" status and is NOT a --fix action. NOT in --all
242
+ // because --all is the class A-M drift roster, not an escalation reporter.
243
+ // Exit 0 whenever the report assembles (even offline); non-zero only if the
244
+ // assembler itself throws. Report goes to stdout only (never a file).
245
+ reportRegistrationBug: false,
246
+ // Phase 233 Plan-01 (RCA 4d): graph-derive-health. Reports whether a room's
247
+ // SEMANTIC edge layer ever landed -- BELONGS_TO edges present with ZERO
248
+ // cascade edges means the derivation never succeeded (the ~16-damaged-room
249
+ // shape). Scopes to the active room by default; --cascade-rooms widens it to
250
+ // every registered room, exactly as classes B and C already share that flag.
251
+ // --heal-room is the operator-facing sugar for `--graph-derive-health --fix`
252
+ // and is the literal flag the v1.13.0-beta.16 rename table
253
+ // ('heal' -> 'doctor --heal-room') has advertised since before it existed;
254
+ // this is where that documented mapping becomes real. Pure LOCAL room.db
255
+ // reads through the read-only navigation door + a LOCAL queue write; zero
256
+ // network surface (Canon Part 8).
257
+ graphDeriveHealth: false,
258
+ };
259
+ for (const arg of argv) {
260
+ if (arg === '--fix') flags.fix = true;
261
+ else if (arg === '--json') flags.json = true;
262
+ else if (arg === '--verbose' || arg === '-v') flags.verbose = true;
263
+ else if (arg === '--cascade-rooms') flags.cascadeRooms = true;
264
+ else if (arg === '--verify-surface') flags.verifySurface = true;
265
+ else if (arg === '--room-md') flags.roomMd = true;
266
+ else if (arg === '--ui-compliance') flags.uiCompliance = true;
267
+ else if (arg === '--statusline-visibility') flags.statuslineVisibility = true;
268
+ else if (arg === '--card-fire-health') flags.cardFireHealth = true;
269
+ else if (arg === '--install-state') flags.installState = true;
270
+ else if (arg === '--stale-first-touch') flags.staleFirstTouch = true;
271
+ else if (arg === '--deprecated-usage') flags.deprecatedUsage = true;
272
+ else if (arg === '--graph-derive-health') flags.graphDeriveHealth = true;
273
+ // --heal-room is sugar, not a second code path: it activates the SAME class
274
+ // and turns on --fix, so the heal runs through the engine's one
275
+ // fix-then-recheck flow (never a bespoke heal dispatch).
276
+ else if (arg === '--heal-room') { flags.graphDeriveHealth = true; flags.fix = true; }
277
+ else if (arg === '--brain-smoke') flags.brainSmoke = true;
278
+ else if (arg === '--eureka-smoke') flags.eurekaSmoke = true;
279
+ else if (arg === '--drift') flags.drift = true;
280
+ else if (arg === '--acceptance') flags.acceptance = true;
281
+ else if (arg === '--pre-tag') flags.preTag = true;
282
+ else if (arg === '--pre-flight') flags.preFlight = true;
283
+ else if (arg === '--light-npx') flags.lightNpx = true;
284
+ else if (arg === '--check-rs-engine') flags.checkRsEngine = true;
285
+ // Quick task 260705-jeq: sibling reporting mode; deliberately NOT added to
286
+ // the --all activation block below (same exclusion rationale as
287
+ // --check-rs-engine: --all is class A-M drift detection, not escalation).
288
+ else if (arg === '--report-registration-bug') flags.reportRegistrationBug = true;
289
+ else if (arg === '--post-update') flags.postUpdate = true;
290
+ else if (arg === '--dogfood-acceptance') flags.dogfoodAcceptance = true;
291
+ else if (arg === '--claims') flags.claims = true;
292
+ else if (arg === '--dry-run') flags.dryRun = true;
293
+ // Phase 194-07 (PSB-14 / D-10): --bind-check <roomDir> is a SIBLING flag (like
294
+ // --acceptance) with its OWN never-block exit-0 contract. It runs a LIGHTWEIGHT
295
+ // LOCAL structural room-health check + registers this session's presence at
296
+ // BIND-TIME (never per-turn, never a Brain call).
297
+ else if (arg === '--bind-check') flags.bindCheck = true;
298
+ else if (arg.startsWith('--bind-check=')) { flags.bindCheck = true; flags.bindCheckDir = arg.slice('--bind-check='.length); }
299
+ else if (arg === '--all') flags.all = true;
300
+ else if (arg.startsWith('--simulate-write=')) flags.simulateWrite = arg.slice('--simulate-write='.length);
301
+ else if (arg.startsWith('--scan-commands=')) flags.scanCommandsDir = arg.slice('--scan-commands='.length);
302
+ else if (arg.startsWith('--scan-scripts=')) flags.scanScriptsDir = arg.slice('--scan-scripts='.length);
303
+ else if (arg === '--help' || arg === '-h') {
304
+ console.log(usageText());
305
+ process.exit(0);
306
+ }
307
+ }
308
+ // --all activates every class flag.
309
+ if (flags.all) {
310
+ flags.cascadeRooms = true;
311
+ flags.verifySurface = true;
312
+ flags.roomMd = true;
313
+ flags.uiCompliance = true;
314
+ flags.statuslineVisibility = true;
315
+ flags.installState = true;
316
+ flags.staleFirstTouch = true;
317
+ flags.deprecatedUsage = true;
318
+ flags.brainSmoke = true;
319
+ flags.cardFireHealth = true;
320
+ flags.graphDeriveHealth = true;
321
+ }
322
+ // --pre-tag implies --acceptance (convenience; running --pre-tag standalone
323
+ // is meaningless).
324
+ if (flags.preTag) flags.acceptance = true;
325
+ // --pre-flight implies --acceptance for the same reason. If both --pre-tag
326
+ // and --pre-flight are passed, --pre-tag wins downstream (the mode resolver
327
+ // checks preTag first); --pre-flight is the strict-subset tier release.sh
328
+ // Step 2.5 uses BEFORE Step 3 mutates the tree.
329
+ if (flags.preFlight) flags.acceptance = true;
330
+ // Phase 194-07: capture the space-separated `--bind-check <roomDir>` value (the
331
+ // `=` form is captured inline above). The value is the arg following the flag,
332
+ // provided it is not itself a flag.
333
+ if (flags.bindCheck && !flags.bindCheckDir) {
334
+ const bcIdx = argv.indexOf('--bind-check');
335
+ if (bcIdx !== -1 && typeof argv[bcIdx + 1] === 'string' && !argv[bcIdx + 1].startsWith('--')) {
336
+ flags.bindCheckDir = argv[bcIdx + 1];
337
+ }
338
+ }
339
+ return flags;
340
+ }
341
+
342
+ function usageText() {
343
+ return `Usage: doctor.cjs [flags]
344
+
345
+ Default (no flag) runs class A install-cache check + class N plugin-enabled-state
346
+ (the silent-disable detector -- runs on a bare doctor run because the plugin's own
347
+ SessionStart hooks cannot fire while the plugin is DISABLED).
348
+
349
+ Class flags (combine freely; --all activates them all):
350
+ --cascade-rooms class B (.room-root sentinel) + class C (active-room guard silence)
351
+ --graph-derive-health graph-derive-health (RCA 4d): did this room's semantic edge layer ever
352
+ land? Reports FAIL when BELONGS_TO edges exist with zero cascade edges,
353
+ WARN on a derive queue entry stuck past 3 days or a recorded failure log.
354
+ Active room by default; add --cascade-rooms to sweep every room.
355
+ --heal-room sugar for --graph-derive-health --fix: re-enqueues every WARN/FAIL room
356
+ through the existing dedup-by-roomDir derive sweep (idempotent).
357
+ --verify-surface class D (live cascade end-to-end against test fixture)
358
+ --room-md class E (ROOM.md/MINTO.md presence under .room-root)
359
+ --ui-compliance class F (UI Ruling System scan)
360
+ --statusline-visibility class G (user-settings drift, plugin install integrity, statusline-mos isolated execution)
361
+ + class H (install-incomplete: missing statusLine block and/or a halted .install-receipt.json)
362
+ --card-fire-health card-fire-health (D-05): health of the check-card-fire.cjs self-diagnostic
363
+ instrument -- intercept log exists/valid-JSONL/fresh (~/.mindrian/card-fire-
364
+ intercepts.log), classifier/counter library seams intact, render-coverage
365
+ registry parses, session store readable. cadence:always, LOCAL-only, no fix.
366
+ --install-state class I (install-state + topology + 6-way version-of-record consistency)
367
+ + class J (deployment-surface manifest reconciliation)
368
+ --stale-first-touch class K (stale-first-touch-copy: stale version literals + em-dash violations
369
+ in greeting surfaces declared by data/first-touch-surfaces.json; SEED-007 absorption)
370
+ --deprecated-usage class L (deprecated commands you still use: scans last-7-days session
371
+ transcripts at ~/.claude/projects/.../*.jsonl for /mos:<deprecated>
372
+ patterns; surfaces a per-command "use /mos:<new> instead" hint.
373
+ Phase 121.5-08 Sub-plan J. LOCAL-only, zero network.)
374
+ --brain-smoke class M (Brain end-to-end smoke: 6-layer probe -- plugin root,
375
+ key resolver, HTTPS schema, MCP stdio handshake, e2e brain_schema,
376
+ store identity (stale-replica detection, quick 260819-c9b).
377
+ Diagnostic-only; reports the exact failing layer. Phase 127-02.)
378
+ --eureka-smoke class S (Eureka local-embedding-stack smoke: 4-layer probe --
379
+ deps present, vec backend, model cache, graceful degrade.
380
+ NON-cascading; never downloads a model unless
381
+ MINDRIAN_EUREKA_SMOKE_ALLOW_DOWNLOAD=1; rolls into --acceptance.
382
+ Diagnostic-only; reports the exact stopping layer. quick 260706-13z.)
383
+ class N plugin-enabled-state (DRIFT-12 / silent-disable detector): reads
384
+ ~/.claude/settings.json enabledPlugins + installed_plugins.json.
385
+ installed && enabled===false -> CRITICAL (re-enable hint); enabled
386
+ ===true or absent -> OK. Runs on a bare doctor run AND under --all (NOT
387
+ behind a flag -- a disabled plugin cannot run its own hooks). LOCAL
388
+ read only, never writes settings.json.
389
+ --drift class P (prose-vs-code drift, REPORT-ONLY -- wraps
390
+ check-skill-vs-code-drift + check-first-touch-drift; never
391
+ edits prose, even under --fix) + class Q (gsd-record drift --
392
+ shells out to gsd-tools validate health --raw, parses W007
393
+ ROADMAP gaps + I001 missing SUMMARYs). With --fix, writes the
394
+ DRIFT.md baseline (root index + per-folder detail) and stubs
395
+ missing SUMMARYs (mechanical heal); prose stays report-only.
396
+ OPT-IN ONLY: independent of --all, NOT in the session-start
397
+ roster. Release/CI invokes it explicitly. LOCAL-only, zero
398
+ network. Phase 150.9 (FIX-13).
399
+ + class R (runtime-reachability drift -- the RUNTIME
400
+ assertion P/Q lack: FAILS NON-ZERO when a capability is
401
+ WIRED in the connector registry but UNREACHABLE by
402
+ decide() at runtime via the Phase-184 projection reader;
403
+ deterministic, LOCAL-only, zero network. Phase 185
404
+ DRIFT-01).
405
+ --all activate all class flags
406
+
407
+ Bind-time lifecycle job (Phase 194-07 -- separate from class flags):
408
+ --bind-check <roomDir> PSB-14 / D-10: a LIGHTWEIGHT LOCAL room-health check run
409
+ at BIND-TIME (never per-turn, never a Brain call). Verifies
410
+ the room dir exists + carries a .room-root sentinel +
411
+ .mindrian/room.db opens with nodes/edges tables, and on a
412
+ green report registers this session in the room's presence
413
+ ledger (lib/core/session-presence.cjs). Rides the stale-reap
414
+ cadence (dead-pid or mtime>5m presence files are reaped; a
415
+ live pid never). NEVER-BLOCK: an unhealthy room degrades to an
416
+ advisory and STILL exits 0 (binding is never hard-blocked).
417
+ Also appends a WATCH-only WARN finding row (Phase 225-02) when
418
+ the bundled SQLite is inside the upstream WAL-reset corruption
419
+ window (< 3.51.3, fixed upstream in commit 298a1c84) AND a live
420
+ co-session is present in this room -- detect-only, an
421
+ environment condition the user cannot repair locally; never
422
+ touches report.healthy or the exit code (still never-block).
423
+
424
+ Release-gate runner (Phase 123 Plan-04 -- separate from class flags):
425
+ --acceptance run the 7-point release-gate checklist (install-state +
426
+ deployment-surfaces + version-of-record-repo + verify-release +
427
+ doctor-all + version-of-record-published + npx-roundtrip).
428
+ Hard abort (exit non-zero) on any sub-check failure.
429
+ NO --allow override -- release infra is the one gate you cannot skip.
430
+ --pre-tag with --acceptance: filter to the 5 pre-tag-applicable points
431
+ (skips version-of-record-published + npx-roundtrip which are
432
+ false until AFTER the tag + npm publish). --pre-tag IMPLIES
433
+ --acceptance. release.sh Step 6.6 runs this BEFORE git tag.
434
+ --pre-flight with --acceptance: filter to pre-flight-applicable points
435
+ (strict subset of --pre-tag minus in-flight-incompatible
436
+ checks like verify-release-clean-tree). Runs BEFORE release.sh
437
+ mutates plugin.json / package.json / CHANGELOG. Mutually
438
+ exclusive with --pre-tag; if both pass, --pre-tag wins.
439
+ --light-npx with --acceptance (full): skip the mktemp HOME-override sandbox
440
+ in the npx-roundtrip point; resolve npx --no-install --help
441
+ instead of doing a live install. Operator opt-in.
442
+
443
+ Environment readiness probes (Phase 127.2 Plan 03 -- separate from class flags):
444
+ --check-rs-engine pre-flight Python deps reachable from scripts/rs-engine.py
445
+ (the Engine 1 Act 1 surface for /mos:find-bottlenecks et al.).
446
+ Probes top-level imports against the runtime's python (python3
447
+ preferred, python fallback). On missing deps emits an actionable
448
+ fix line: "Run: pip install -r requirements-hsi.txt --user".
449
+ Closes the Windows tester 2026-05-23 silent-failure class.
450
+ Exit 0 = deps present; exit 1 = deps missing (own contract,
451
+ NOT the class-flag-always-exit-0 invariant).
452
+ --report-registration-bug (Quick task 260705-jeq) READ-ONLY escalation reporter:
453
+ proves every locally-checkable cause of "valid install yet a
454
+ command did not register" is CLEAN (install-cache drift,
455
+ enabledPlugins silent-disable, legacy config.json pin,
456
+ marketplace clone git-dirt, version-of-record legs, on-disk
457
+ command count, static precondition sweep), then assembles a
458
+ paste-ready Anthropic bug report. Diagnostic-only: never a
459
+ --fix, never claims a status; the root cause is a confirmed
460
+ host-side core bug. stdout only; --json for the machine shape.
461
+ Exit 0 whenever the report assembles (even offline); non-zero
462
+ only if the assembler itself throws. NOT in --all.
463
+ --post-update (Phase 127.2 Plan 04 Instance #7) atomically activate
464
+ freshly-landed cache-staging bytes via scripts/post-update-
465
+ activation.cjs (delegates to --fix pipeline + writes the
466
+ ~/.mindrian/post-update-restart-pending touch-file). Wired
467
+ into /mos:update Step 7 + composable as --fix --post-update.
468
+ Exit 0 = activated or already on latest; exit 1 = swap failed.
469
+
470
+ Loop-fires gate (Phase 146 -- the milestone gate; separate from class flags):
471
+ --dogfood-acceptance run the 6 ACPT dogfood drivers (engine-fires,
472
+ websearch-hat-scoped, first-material-explore,
473
+ filing-cascade-surfaces, brain-derive-tier-rise,
474
+ dial-atomic-emission) as
475
+ child processes and aggregate their exit codes. This is
476
+ the Phase 146 loop-fires gate: it certifies the loop
477
+ FIRES across the dogfood criteria. It has its OWN
478
+ exit-code contract (0 = all 6 ACPT hermetic legs
479
+ passed; non-zero = a leg failed) and is NOT a class
480
+ flag -- the class-flag-always-exit-0 invariant does NOT
481
+ apply. Per the ROADMAP Phase 146 mandate: exit 0 = the
482
+ milestone ships as "Larry Reaches"; non-zero = the
483
+ milestone is renamed. The ACPT-05 LIVE mode_a arm
484
+ self-skips when Brain is unreachable, so the gate is
485
+ green on the hermetic legs WITHOUT a reachable Brain --
486
+ the gate never requires Brain/web/Vercel. Composes with
487
+ tests/run-all-146.sh (the full-surface aggregator).
488
+
489
+ Claim harness gate (Phase 150 -- the public-site-claim gate; separate from class flags):
490
+ --claims run the claim harness (tests/claim-harness/run-all-
491
+ claims.sh): the C1..C7 falsifiable public-site-claim
492
+ drivers + the gates. A SIBLING of --acceptance: it has
493
+ its OWN exit-code contract (0 = every claim is true by
494
+ instrumentation; non-zero = a claim failed) and is NOT a
495
+ class flag -- the class-flag-always-exit-0 invariant does
496
+ NOT apply. Canon Part 8: the harness is LOCAL/hermetic;
497
+ the Brain LIVE arms self-skip via class-m-brain-smoke, so
498
+ the gate is green WITHOUT a reachable Brain. The semantic
499
+ claims (C2-good, C4-relevance) are carved to the Part-10
500
+ human empathy gate, never machine-faked. The
501
+ DOCTOR_CLAIM_FAIL_POINT env hook synthesizes a failure of
502
+ the gate for its own self-test.
503
+
504
+ Behavior flags:
505
+ --fix attempt auto-recovery for each class that supports it
506
+ (class H: re-stamps the canonical statusLine block when missing, idempotently)
507
+ --json machine-readable output (for hooks / regression tests)
508
+ --verbose, -v extra diagnostic detail
509
+ --simulate-write=<p> simulate a PWD write at <p> for class C active-room mismatch
510
+ --scan-commands=<d> override commands/ scan dir for class F (default: ./commands)
511
+ --scan-scripts=<d> override scripts/ scan dir for class F (default: ./scripts)
512
+ --help, -h print this usage
513
+
514
+ Exit codes:
515
+ 0 healthy, no drift across activated classes; --acceptance: all points passed
516
+ 1 drift detected (read-only mode); --acceptance: any point failed (hard abort)
517
+ 2 drift detected and recovered (--fix mode)
518
+ 3 internal error
519
+ `;
520
+ }
521
+
522
+ // -- Comparable semver helpers + diagnostic checks -------------------
523
+ // parseVersion, cmpVersion, checkInstallVersion, checkMarketplaceCache, and
524
+ // checkDevSourceConsistency moved to lib/core/doctor/shared.cjs (Phase 217
525
+ // Plan-01 D-02) and are imported at the top of this file.
526
+
527
+ // -- Recovery --------------------------------------------------------
528
+ // Phase 95.2 D-01..D-04: atomic-swap recovery.
529
+ // Pattern: cp -> verify -> two-step rename. Destructive ops only after
530
+ // non-destructive verification has passed. POSIX rename(2) is atomic on
531
+ // the same filesystem (~/.claude/plugins/ is single-mount in practice).
532
+ // For cross-fs (EXDEV) we fall back to cpSync+rmSync per safeRename().
533
+
534
+ function performRecoveryAtomic(installVersion, latestCacheVersion) {
535
+ const sourceDir = path.join(MARKETPLACE_CACHE_DIR, latestCacheVersion);
536
+ if (!fs.existsSync(sourceDir)) {
537
+ return { status: 'error', detail: `marketplace cache for ${latestCacheVersion} not found at ${sourceDir}`, stage: 'precheck' };
538
+ }
539
+ const installNewDir = INSTALL_DIR + '.new';
540
+ const ts = new Date().toISOString().replace(/[:.]/g, '').replace(/T/, '-').slice(0, 15);
541
+ // Finding F: 'missing' is more truthful than 'unknown' when the install dir doesn't exist.
542
+ const backupTag = installVersion || (fs.existsSync(INSTALL_DIR) ? 'unknown' : 'missing');
543
+ const backupDir = path.join(PLUGIN_HOME, `mindrian-os.stale-${backupTag}-${ts}`);
544
+
545
+ // Phase 1: prepare install.new (cleanup any stale install.new from a prior failed run -- recover-the-recoverer logic)
546
+ try {
547
+ if (fs.existsSync(installNewDir)) fs.rmSync(installNewDir, { recursive: true, force: true });
548
+ } catch (err) {
549
+ return { status: 'error', detail: `failed to clean prior install.new: ${err.message}`, stage: 'cleanup-installnew' };
550
+ }
551
+
552
+ // Finding E: hermetic test injection point.
553
+ if (process.env.MOS_TEST_FORCE_FAIL === 'copy') {
554
+ return { status: 'error', detail: 'MOS_TEST_FORCE_FAIL=copy injection', stage: 'cp', installNewDir };
555
+ }
556
+
557
+ // Phase 2: cp via fs.cpSync (Finding A; replaces the prior shell-out copy -- Windows-functional, no exec)
558
+ try {
559
+ fs.cpSync(sourceDir, installNewDir, { recursive: true, dereference: false, preserveTimestamps: true });
560
+ } catch (err) {
561
+ // install untouched; install.new may be incomplete -- leave for inspection.
562
+ return { status: 'error', detail: `cp failed: ${err.message}`, stage: 'cp', installNewDir };
563
+ }
564
+
565
+ // Phase 3: structural version verify (D-02). Read plugin.json from install.new ONLY; do not load any plugin code.
566
+ if (process.env.MOS_TEST_FORCE_FAIL === 'verify') {
567
+ return { status: 'error', detail: 'MOS_TEST_FORCE_FAIL=verify injection', stage: 'verify', installNewDir };
568
+ }
569
+ try {
570
+ const pj = JSON.parse(fs.readFileSync(path.join(installNewDir, '.claude-plugin/plugin.json'), 'utf8'));
571
+ if (pj.version !== latestCacheVersion) {
572
+ return { status: 'error', detail: `verify failed: install.new version is ${pj.version}, expected ${latestCacheVersion}`, stage: 'verify', installNewDir };
573
+ }
574
+ } catch (err) {
575
+ return { status: 'error', detail: `verify failed: ${err.message}`, stage: 'verify', installNewDir };
576
+ }
577
+
578
+ // Phase 4: two-step atomic-swap.
579
+ // Phase 4a: mv install -> install.stale-X (only if install exists). Finding D safeRename for cross-fs.
580
+ let backedUp = false;
581
+ if (fs.existsSync(INSTALL_DIR)) {
582
+ if (process.env.MOS_TEST_FORCE_FAIL === 'rename-old') {
583
+ return { status: 'error', detail: 'MOS_TEST_FORCE_FAIL=rename-old injection', stage: 'backup-mv', installNewDir };
584
+ }
585
+ try {
586
+ safeRename(INSTALL_DIR, backupDir);
587
+ backedUp = true;
588
+ } catch (err) {
589
+ // install untouched (rename didn't complete); install.new left for inspection.
590
+ return { status: 'error', detail: `backup mv failed: ${err.message}`, stage: 'backup-mv', installNewDir };
591
+ }
592
+ }
593
+
594
+ // Phase 4b: mv install.new -> install. The atomic moment.
595
+ if (process.env.MOS_TEST_FORCE_FAIL === 'commit') {
596
+ // Simulate post-backup commit failure -> trigger D-03 rollback path.
597
+ if (backedUp) {
598
+ try { safeRename(backupDir, INSTALL_DIR); } catch (_) { /* double failure; user must recover manually */ }
599
+ }
600
+ return { status: 'error', detail: 'recovery failed -- live install restored from backup; investigate manually', stage: 'commit-rollback', exitCode: 4 };
601
+ }
602
+ try {
603
+ safeRename(installNewDir, INSTALL_DIR);
604
+ } catch (err) {
605
+ // D-03 rollback: restore from backup so live install is recovered to pre-recovery state.
606
+ if (backedUp) {
607
+ try { safeRename(backupDir, INSTALL_DIR); }
608
+ catch (_) { /* double failure; backup remains on disk for manual recovery */ }
609
+ }
610
+ return { status: 'error', detail: `recovery failed -- live install restored from backup; investigate manually (root: ${err.message})`, stage: 'commit-rollback', exitCode: 4 };
611
+ }
612
+
613
+ // Phase 5: post-swap verification (cheap re-check; should-never-fail given Phase 3 already passed).
614
+ const after = checkInstallVersion();
615
+ if (after.status !== 'ok' || after.version !== latestCacheVersion) {
616
+ return { status: 'error', detail: `post-swap verify mismatch: install version is ${after.version || 'unknown'}, expected ${latestCacheVersion}`, backup: backedUp ? backupDir : null };
617
+ }
618
+ return { status: 'ok', backup: backedUp ? backupDir : null, recoveredVersion: latestCacheVersion };
619
+ }
620
+
621
+ // Finding D: rename(2) is atomic on the same filesystem; cpSync+rmSync fallback for EXDEV.
622
+ function safeRename(src, dst) {
623
+ try {
624
+ fs.renameSync(src, dst);
625
+ } catch (err) {
626
+ if (err && err.code === 'EXDEV') {
627
+ // Cross-filesystem move; copy + delete (NOT atomic, but only path that works across mounts).
628
+ fs.cpSync(src, dst, { recursive: true, dereference: false, preserveTimestamps: true });
629
+ fs.rmSync(src, { recursive: true, force: true });
630
+ return;
631
+ }
632
+ throw err;
633
+ }
634
+ }
635
+
636
+ // readRegistry moved to lib/core/doctor/shared.cjs (Phase 217 Plan-01 D-02) and
637
+ // is imported at the top of this file. The class B/C/E section-walk helpers
638
+ // (SKIP_DIRS, listSubdirs) + the generator spawn (invokeGenerator) + findRoomRoot
639
+ // moved WITH those checks into their registry runners in Phase 217 Plan 04.
640
+
641
+ // -- Class D / G / H bodies removed (Phase 217 Plan 05) -----------------------
642
+ //
643
+ // checkSurfaceVerification (class D), checkStatuslineVisibility +
644
+ // performStatuslineFix (class G), and checkInstallIncomplete + performClassHFix
645
+ // (class H) -- plus their private helpers (STALE_STATUSLINE_PATH_REGEX,
646
+ // stripAnsi, CLASS_H_CANONICAL_STEPS, classHActionString,
647
+ // userSettingsHasStatusLine) and the INSTALL_RECEIPT_JSON const -- moved into
648
+ // lib/core/doctor/verify-surface-module.cjs, statusline-visibility-module.cjs,
649
+ // and install-incomplete-module.cjs. The accumulative engine owns their
650
+ // dispatch + fix-then-recheck flow via the registry (data/doctor-modules.json).
651
+
652
+ // -- Class I + Class J: install-state + topology + version-of-record ---
653
+ // + deployment-surface manifest reconciliation (Phase 123 Plan-03).
654
+ //
655
+ // Class I (HARNESS-123-07): install-state record present + internally
656
+ // consistent; topology classification (Bug 7 fix); 6-way version-of-record
657
+ // equality across installed_plugins.json <-> record.active_version <->
658
+ // record.statusline_renders_version <-> record.last_version_file_value
659
+ // <-> record.path_bin_version <-> ~/.mindrian-last-version. STRING
660
+ // EQUALITY tolerates the 4-component non-semver 1.12.5.1 case.
661
+ //
662
+ // Class J (HARNESS-123-08): walks data/deployment-surfaces.json; per
663
+ // surface, checks expected vs observed per check_kind (marker | exact-
664
+ // value | observed-only); honors topology_scope='dev-clone' (skipped on
665
+ // non-dev-clone) and reconcile='never' (excluded from auto-fix); honors
666
+ // the schema extension `path_within_file` (added in Plan-03 to fix the
667
+ // live-dev-box settings.json whole-file-comparison bug).
668
+ //
669
+ // Aggressive --fix (HARNESS-123-09): missing record (spawn session-start);
670
+ // wrong ~/.mindrian-last-version (rewrite); legacy-clone migration
671
+ // (backup-verify-remove with the dev-clone safety belt -- NEVER touches
672
+ // a dev clone); conservative installed_plugins.json repair (back up
673
+ // first; repoint at newest valid marketplace-cache dir; restart note);
674
+ // flag-only for topology=='not-found' / vanished PATH-bin / wrong
675
+ // statusline_renders_version.
676
+ //
677
+ // Class-flag invariant: when --install-state (or --all) is set, exit is
678
+ // always 0 unless an explicit --fix attempt failed.
679
+ //
680
+ // Canon Part 8: all of class I + class J is LOCAL-only. Zero fetch / http /
681
+ // curl / brain.mindrian / tavily in any of these functions.
682
+ //
683
+ // Phase 217 Plan 06: the class I helpers + checkInstallState + performClassIFix
684
+ // moved to lib/core/doctor/install-state-module.cjs; the class J helpers +
685
+ // checkDeploymentSurfaces + performClassJFix moved to
686
+ // lib/core/doctor/deployment-surfaces-module.cjs; the shared readers
687
+ // (readLegacyConfigPin, detectMarketplaceCacheInstall, collectVersionOfRecord,
688
+ // computeVersionDivergences, ...) moved to lib/core/doctor/shared.cjs. Both are
689
+ // now registry-driven cadence:always runners under the --install-state flag
690
+ // (install-state before deployment-surfaces so class J reads class I's
691
+ // same-invocation result via ctx.checks). doctor.cjs imports only the runners'
692
+ // RAW check entries (checkInstallState / checkDeploymentSurfaces, bound near the
693
+ // shared.cjs require) for the --acceptance path.
694
+
695
+
696
+ // -- Acceptance: release-gate-as-a-command (Phase 123 Plan-04) -------
697
+ //
698
+ // `mindrian-os doctor --acceptance` runs a 7-point checklist that asserts the
699
+ // release is consistent end-to-end. `--pre-tag` filters to the 5 points true
700
+ // BEFORE the tag + npm publish lands. Both are HARD ABORTS -- no --allow
701
+ // override (per CONTEXT D-16: release infra is the one gate you cannot skip).
702
+ //
703
+ // THIS BLOCK IS NOT A CLASS FLAG. It has its own exit-code contract:
704
+ // 0 = all (filtered) points passed
705
+ // 1 = any point failed
706
+ // The class-flag-always-exit-0 graceful-degradation invariant does NOT apply.
707
+ //
708
+ // Canon Part 8 (LOCAL-only invariant): the npx-roundtrip + version-of-record-
709
+ // published points are the ONE network touch in this phase. They run ONLY
710
+ // during a release (when release.sh invokes --acceptance after the push).
711
+ // --pre-tag has ZERO network -- it stays Part-8 clean for in-session use.
712
+ //
713
+ // Test-mode env hooks (honored only when DOCTOR_TEST_MODE=1):
714
+ // DOCTOR_TEST_STUB_POST_PUBLISH=1 post-publish points throw if invoked
715
+ // (asserts the --pre-tag filter works)
716
+ // DOCTOR_TEST_FAIL_POINT=<point_id> synthesize a failure of the named point
717
+ // DOCTOR_VERIFY_RELEASE_PATH=<path> override scripts/verify-release path
718
+
719
+ // evaluateMarketplaceSourcePin(source, ver) -- pure helper, exported for
720
+ // hermetic tests (Phase 341 D-01/D-06/T-341-17). Compares a marketplace
721
+ // plugins[0].source object against the released version. Accepts EITHER
722
+ // shape during the git-to-npm transition:
723
+ // - npm shape (source.version present): source.source must be 'npm',
724
+ // source.package must be '@mindrian_os/cli', source.version must equal
725
+ // ver exactly (no v prefix), and NEITHER a residual 'ref' NOR a
726
+ // residual 'url' key may be present -- a stale ref/url sitting beside a
727
+ // correct version is exactly the half-migrated state D-01 warns about.
728
+ // - legacy git shape (source.ref present, no source.version): source.ref
729
+ // must equal 'v' + ver. Accepted only until the marketplace catalog's
730
+ // first post-341 release flips it to the npm shape (release.sh Step 4).
731
+ // Neither shape recognized (no version, no ref) is a failure.
732
+ function evaluateMarketplaceSourcePin(source, ver) {
733
+ if (!source || typeof source !== 'object') {
734
+ return { ok: false, finding: 'marketplace plugins[0].source is missing or not an object', detail: { source: source } };
735
+ }
736
+ const hasVersion = Object.prototype.hasOwnProperty.call(source, 'version');
737
+ const hasRef = Object.prototype.hasOwnProperty.call(source, 'ref');
738
+ const hasUrl = Object.prototype.hasOwnProperty.call(source, 'url');
739
+
740
+ if (hasVersion) {
741
+ // npm shape (or a half-migrated attempt at it).
742
+ if (hasRef || hasUrl) {
743
+ return {
744
+ ok: false,
745
+ finding: 'marketplace source carries a residual ref/url key alongside version (half-migrated D-01 state): ' + JSON.stringify(source),
746
+ detail: { source: source, residualRef: source.ref, residualUrl: source.url },
747
+ };
748
+ }
749
+ if (source.source !== 'npm' || source.package !== '@mindrian_os/cli') {
750
+ return {
751
+ ok: false,
752
+ finding: 'marketplace source is not the npm shape {source:npm, package:@mindrian_os/cli}: got ' + JSON.stringify(source),
753
+ detail: { source: source },
754
+ };
755
+ }
756
+ if (source.version !== ver) {
757
+ return { ok: false, finding: 'marketplace source.version is ' + source.version + ', expected ' + ver, detail: { version: source.version, expected: ver } };
758
+ }
759
+ return { ok: true, finding: null, detail: { shape: 'npm', version: source.version } };
760
+ }
761
+
762
+ if (hasRef) {
763
+ // Legacy git shape, accepted during the transition (the live
764
+ // marketplace has not flipped yet as of Phase 341 Plan 05).
765
+ const expectedRef = 'v' + ver;
766
+ if (source.ref !== expectedRef) {
767
+ return { ok: false, finding: 'marketplace source.ref is ' + source.ref + ', expected ' + expectedRef, detail: { ref: source.ref, expected: expectedRef } };
768
+ }
769
+ return { ok: true, finding: null, detail: { shape: 'git-legacy', ref: source.ref } };
770
+ }
771
+
772
+ return { ok: false, finding: 'marketplace source has neither a version nor a ref key: ' + JSON.stringify(source), detail: { source: source } };
773
+ }
774
+
775
+ function buildAcceptanceChecklist(ctx) {
776
+ const home = ctx.home;
777
+ const pluginRoot = ctx.pluginRoot;
778
+ const flagLightNpx = ctx.flagLightNpx;
779
+ const inTestMode = process.env.DOCTOR_TEST_MODE === '1';
780
+ return [
781
+ {
782
+ id: 'install-state',
783
+ label: 'install-state record present + snapshot matches a live spot-check',
784
+ severity: 'blocker',
785
+ applies_to: ['pre-tag', 'full'],
786
+ run: async function () {
787
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'install-state') {
788
+ return { ok: false, finding: 'install-state synthesized failure (test mode)', detail: {} };
789
+ }
790
+ const c = checkInstallState({ home: home });
791
+ // status: 'healthy' | 'warn' | 'error'. We allow 'healthy' through; anything
792
+ // else fails the gate. (Findings drive the finding string for the operator.)
793
+ const ok = c.status === 'healthy';
794
+ let finding = null;
795
+ if (!ok) {
796
+ if (Array.isArray(c.findings) && c.findings.length) {
797
+ finding = c.findings[0].finding || c.findings[0].id || ('install-state status: ' + c.status);
798
+ } else {
799
+ finding = 'install-state status: ' + c.status;
800
+ }
801
+ }
802
+ return { ok: ok, finding: finding, detail: { status: c.status, findings: c.findings, topology: c.topology } };
803
+ },
804
+ },
805
+ {
806
+ id: 'deployment-surfaces',
807
+ label: 'every owned deployment surface reconciled',
808
+ severity: 'blocker',
809
+ applies_to: ['pre-tag', 'full'],
810
+ run: async function () {
811
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'deployment-surfaces') {
812
+ return { ok: false, finding: 'deployment-surfaces synthesized failure (test mode)', detail: {} };
813
+ }
814
+ // Reuse install-state for the topology + active root/version handles.
815
+ const s = checkInstallState({ home: home });
816
+ const activeRoot = (s.record && s.record.active_root)
817
+ || (s.resolver && s.resolver.root)
818
+ || null;
819
+ const activeVersion = (s.record && s.record.active_version)
820
+ || (function () { try { return readInstalledPluginsVersion(home); } catch (_) { return null; } })();
821
+ const c = checkDeploymentSurfaces({
822
+ home: home,
823
+ topology: s.topology || 'not-found',
824
+ activeRoot: activeRoot,
825
+ activeVersion: activeVersion,
826
+ });
827
+ const ok = c.status === 'healthy' || c.status === 'skipped';
828
+ const failedSurfaces = Array.isArray(c.surfaces)
829
+ ? c.surfaces.filter(function (x) { return x.ok === false; })
830
+ : [];
831
+ const finding = ok ? null : (failedSurfaces.length + ' surface(s) drifted');
832
+ return { ok: ok, finding: finding, detail: { status: c.status, surfaces: c.surfaces } };
833
+ },
834
+ },
835
+ {
836
+ id: 'version-of-record-repo',
837
+ label: 'plugin.json / package.json / CHANGELOG top entry consistent (repo-file half of 5-way)',
838
+ severity: 'blocker',
839
+ applies_to: ['pre-tag', 'full'],
840
+ run: async function () {
841
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'version-of-record-repo') {
842
+ return { ok: false, finding: 'version-of-record-repo synthesized failure (test mode)', detail: {} };
843
+ }
844
+ try {
845
+ const pjPath = path.join(pluginRoot, '.claude-plugin', 'plugin.json');
846
+ const pkPath = path.join(pluginRoot, 'package.json');
847
+ const chPath = path.join(pluginRoot, 'CHANGELOG.md');
848
+ const pj = JSON.parse(fs.readFileSync(pjPath, 'utf8'));
849
+ const pk = JSON.parse(fs.readFileSync(pkPath, 'utf8'));
850
+ const ch = fs.readFileSync(chPath, 'utf8');
851
+ const chMatch = ch.match(/^## \[([^\]]+)\]/m);
852
+ const chTop = chMatch ? chMatch[1] : null;
853
+ const pjVer = pj.version;
854
+ const pkVer = pk.version;
855
+ // Accept "Unreleased" CHANGELOG heading (work in progress); the
856
+ // sub-check passes if pj==pk AND CHANGELOG either matches OR is
857
+ // an [Unreleased] heading naming the same version in its body
858
+ // (the release.sh Step 6 finalizes [Unreleased] -> [vN] before
859
+ // Step 6.6 calls us, so by then chTop is the version).
860
+ const allMatch = pjVer === pkVer && (pjVer === chTop || chTop === 'Unreleased');
861
+ const finding = allMatch ? null : ('version mismatch: plugin.json=' + pjVer + ', package.json=' + pkVer + ', CHANGELOG top=' + chTop);
862
+ return { ok: !!allMatch, finding: finding, detail: { pluginJson: pjVer, packageJson: pkVer, changelogTop: chTop } };
863
+ } catch (e) {
864
+ return { ok: false, finding: 'version-of-record-repo read failed: ' + e.message, detail: {} };
865
+ }
866
+ },
867
+ },
868
+ {
869
+ id: 'verify-release',
870
+ label: 'scripts/verify-release passes',
871
+ severity: 'blocker',
872
+ // PRE-TAG ONLY. Excluded from 'full' (post-publish) on purpose:
873
+ // release.sh Step 7.5 (Commit B) bumps the working tree to the next dev
874
+ // pre-release (beta.N+1) while the marketplace correctly stays at the
875
+ // published beta.N. scripts/verify-release Check 3 is a STRICT
876
+ // plugin==marketplace equality, so under 'full' it would compare
877
+ // beta.N+1 vs beta.N and false-fail (the Step 9.8 false abort, RCA
878
+ // 260605-h3p). Post-publish version state is OWNED by the
879
+ // 'version-of-record-published' module below (tag + marketplace
880
+ // source.ref + npm view, keyed off the LAST SHIPPED tag, not the dev
881
+ // placeholder). scripts/verify-release stays the strict PRE-tag gate
882
+ // (release.sh Step 2 standalone + the --pre-tag acceptance tier).
883
+ // Precedent: the 'verify-release-clean-tree' sibling was tier-moved
884
+ // from ['pre-tag','full'] to ['full'] for the mirror-image reason
885
+ // (see tests/test-doctor-acceptance-preflight-checks.cjs:233).
886
+ applies_to: ['pre-tag'],
887
+ run: async function () {
888
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'verify-release') {
889
+ return { ok: false, finding: 'verify-release synthesized failure (test mode)', detail: {} };
890
+ }
891
+ // Override path only honored in test mode (the test harness sets a logging shim).
892
+ const verifyPath = (inTestMode && process.env.DOCTOR_VERIFY_RELEASE_PATH)
893
+ ? process.env.DOCTOR_VERIFY_RELEASE_PATH
894
+ : path.join(pluginRoot, 'scripts', 'verify-release');
895
+ const cp = require('child_process');
896
+ const r = cp.spawnSync('bash', [verifyPath], { encoding: 'utf8', timeout: 60000 });
897
+ const ok = r.status === 0;
898
+ const finding = ok ? null : ('verify-release exited ' + r.status);
899
+ return { ok: ok, finding: finding, detail: { status: r.status, stdoutTail: (r.stdout || '').slice(-500), stderrTail: (r.stderr || '').slice(-500) } };
900
+ },
901
+ },
902
+ {
903
+ id: 'version-of-record-published',
904
+ label: 'git tag exists + marketplace source.version pinned + npm view returns the version',
905
+ severity: 'blocker',
906
+ applies_to: ['full'],
907
+ run: async function () {
908
+ if (inTestMode && process.env.DOCTOR_TEST_STUB_POST_PUBLISH === '1') {
909
+ throw new Error('post-publish stub invoked under --pre-tag -- bug in runner');
910
+ }
911
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'version-of-record-published') {
912
+ return { ok: false, finding: 'version-of-record-published synthesized failure (test mode)', detail: {} };
913
+ }
914
+ const cp = require('child_process');
915
+ try {
916
+ // Phase 127.2 Plan 02 Finding B: resolve "version of record" via
917
+ // last shipped git tag, NOT plugin.json. release.sh Commit B
918
+ // bumps main HEAD to the next pre-release placeholder (e.g.
919
+ // beta.27 after beta.26 ships), which by definition has no tag
920
+ // yet. The gate must verify the LAST SHIPPED version is live
921
+ // across all three records (tag + marketplace source + npm), not
922
+ // the placeholder.
923
+ const tagProbe = cp.spawnSync('git', ['-C', pluginRoot, 'describe', '--tags', '--abbrev=0', '--match=v*'], { encoding: 'utf8' });
924
+ let ver;
925
+ if (tagProbe.status === 0 && tagProbe.stdout && tagProbe.stdout.trim()) {
926
+ ver = tagProbe.stdout.trim().replace(/^v/, '');
927
+ } else {
928
+ // Fresh repo edge case (no tags yet): fall back to plugin.json.
929
+ const pj = JSON.parse(fs.readFileSync(path.join(pluginRoot, '.claude-plugin', 'plugin.json'), 'utf8'));
930
+ ver = pj.version;
931
+ }
932
+ // (a) git tag exists, reachable from origin/main.
933
+ const t = cp.spawnSync('git', ['-C', pluginRoot, 'rev-parse', '--verify', 'refs/tags/v' + ver], { encoding: 'utf8' });
934
+ if (t.status !== 0) return { ok: false, finding: 'git tag v' + ver + ' not found', detail: { stderr: (t.stderr || '').slice(-200) } };
935
+ // (b) marketplace source.version pinned to <ver> (D-01/D-06, npm
936
+ // source; no v prefix). During the git-to-npm transition this also
937
+ // accepts the legacy source.ref === 'v'+ver shape (the LIVE
938
+ // marketplace has not flipped yet as of this phase), via the pure
939
+ // helper below so a fixture-driven unit test can exercise every
940
+ // branch without touching the real marketplace.json.
941
+ const mpPath = path.join(home, 'mindrian-marketplace', '.claude-plugin', 'marketplace.json');
942
+ let mp;
943
+ try { mp = JSON.parse(fs.readFileSync(mpPath, 'utf8')); }
944
+ catch (e) { return { ok: false, finding: 'marketplace.json unreadable: ' + e.message, detail: { mpPath: mpPath } }; }
945
+ const source = mp.plugins && mp.plugins[0] && mp.plugins[0].source;
946
+ const sourceCheck = evaluateMarketplaceSourcePin(source, ver);
947
+ if (!sourceCheck.ok) return { ok: false, finding: sourceCheck.finding, detail: sourceCheck.detail };
948
+ // (c) npm view -- THIS IS THE ONE NETWORK CALL in this point.
949
+ const n = cp.spawnSync('npm', ['view', '@mindrian_os/cli@' + ver, 'version'], { encoding: 'utf8', timeout: 30000 });
950
+ if (n.status !== 0) return { ok: false, finding: 'npm view failed: ' + (n.stderr || '').slice(-200), detail: { status: n.status } };
951
+ const npmVer = (n.stdout || '').trim();
952
+ const ok = npmVer === ver;
953
+ return { ok: ok, finding: ok ? null : ('npm view returned ' + npmVer + ', expected ' + ver), detail: { npmVer: npmVer, expectedVer: ver } };
954
+ } catch (e) {
955
+ return { ok: false, finding: 'version-of-record-published threw: ' + e.message, detail: {} };
956
+ }
957
+ },
958
+ },
959
+ {
960
+ id: 'npx-roundtrip',
961
+ label: 'npx @mindrian_os/cli round-trip resolves cleanly',
962
+ severity: 'blocker',
963
+ applies_to: ['full'],
964
+ run: async function () {
965
+ if (inTestMode && process.env.DOCTOR_TEST_STUB_POST_PUBLISH === '1') {
966
+ throw new Error('post-publish stub invoked under --pre-tag -- bug in runner');
967
+ }
968
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'npx-roundtrip') {
969
+ return { ok: false, finding: 'npx-roundtrip synthesized failure (test mode)', detail: {} };
970
+ }
971
+ const cp = require('child_process');
972
+ // Phase 127.2 Plan 02 Finding B (mirrored): resolve target version
973
+ // via last shipped tag, not plugin.json placeholder. Same logic as
974
+ // version-of-record-published.
975
+ const tagProbe = cp.spawnSync('git', ['-C', pluginRoot, 'describe', '--tags', '--abbrev=0', '--match=v*'], { encoding: 'utf8' });
976
+ let ver;
977
+ if (tagProbe.status === 0 && tagProbe.stdout && tagProbe.stdout.trim()) {
978
+ ver = tagProbe.stdout.trim().replace(/^v/, '');
979
+ } else {
980
+ const pj = JSON.parse(fs.readFileSync(path.join(pluginRoot, '.claude-plugin', 'plugin.json'), 'utf8'));
981
+ ver = pj.version;
982
+ }
983
+ // RCA fix (release-step-9.7-npx-self-test-false-alarm, recommendation A,
984
+ // 2026-06-02): assert the published package INSTALLS + its bin is present
985
+ // and parseable, NOT the PATH-sensitive npx-by-name runtime exit. The
986
+ // launcher shells out to claude/git absent in the bare sandbox, and npm
987
+ // 10.9.7 npx-by-name does not reliably link the package bin -- so a
988
+ // perfectly healthy publish exits non-zero for an environment reason
989
+ // (the false alarm that blocked every cut since beta.37). `npm install`
990
+ // is the path that actually works; bin presence + `node --check` + the
991
+ // .bin symlink is the true publishable+installable signal.
992
+ const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'mos-acceptance-'));
993
+ try {
994
+ const env = Object.assign({}, process.env, {
995
+ HOME: sandbox,
996
+ USERPROFILE: sandbox,
997
+ npm_config_cache: path.join(sandbox, '.npm'),
998
+ });
999
+ if (flagLightNpx) {
1000
+ // Light path: metadata-only -- assert the published bin is declared. No install.
1001
+ const v = cp.spawnSync('npm', ['view', '@mindrian_os/cli@' + ver, 'bin.mindrian-os'], { encoding: 'utf8', env: env, timeout: 30000 });
1002
+ const declared = (v.stdout || '').trim();
1003
+ const ok = v.status === 0 && declared.length > 0;
1004
+ return { ok: ok, finding: ok ? null : ('npm view bin.mindrian-os empty/failed (' + v.status + ')'), detail: { declared: declared, status: v.status } };
1005
+ }
1006
+ cp.spawnSync('npm', ['init', '-y'], { cwd: sandbox, env: env, encoding: 'utf8', timeout: 30000 });
1007
+ const inst = cp.spawnSync('npm', ['install', '--prefer-online', '@mindrian_os/cli@' + ver], { cwd: sandbox, env: env, encoding: 'utf8', timeout: 120000 });
1008
+ if (inst.status !== 0) {
1009
+ return { ok: false, finding: ('npm install @mindrian_os/cli@' + ver + ' failed (' + inst.status + ')'), detail: { status: inst.status, stderrTail: (inst.stderr || '').slice(-300) } };
1010
+ }
1011
+ const binFile = path.join(sandbox, 'node_modules', '@mindrian_os', 'cli', 'bin', 'cli.js');
1012
+ const binLink = path.join(sandbox, 'node_modules', '.bin', 'mindrian-os');
1013
+ const present = fs.existsSync(binFile);
1014
+ const parses = present && cp.spawnSync(process.execPath, ['--check', binFile], { encoding: 'utf8' }).status === 0;
1015
+ const linked = fs.existsSync(binLink);
1016
+ const ok = present && parses && linked;
1017
+ return {
1018
+ ok: ok,
1019
+ finding: ok ? null : ('published package install verification failed (present=' + present + ' parses=' + parses + ' linked=' + linked + ')'),
1020
+ detail: { present: present, parses: parses, linked: linked },
1021
+ };
1022
+ } finally {
1023
+ try { fs.rmSync(sandbox, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
1024
+ }
1025
+ },
1026
+ },
1027
+ {
1028
+ id: 'doctor-all',
1029
+ label: 'doctor --all exits 0',
1030
+ severity: 'blocker',
1031
+ applies_to: ['pre-tag', 'full'],
1032
+ run: async function () {
1033
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'doctor-all') {
1034
+ return { ok: false, finding: 'doctor-all synthesized failure (test mode)', detail: {} };
1035
+ }
1036
+ // Spawn ourselves with --all (NOT --acceptance, to avoid recursion).
1037
+ // Scrub DOCTOR_TEST_FAIL_POINT so a top-level test fail-point env var
1038
+ // doesn't propagate into our self-spawn and re-trigger the synthesis
1039
+ // there (the synthesis above already covers this point).
1040
+ const cp = require('child_process');
1041
+ const childEnv = Object.assign({}, process.env, { DOCTOR_TEST_FAIL_POINT: '' });
1042
+ const r = cp.spawnSync('node', [path.join(pluginRoot, 'scripts', 'doctor.cjs'), '--all', '--json'], { encoding: 'utf8', timeout: 60000, env: childEnv });
1043
+ let j = null;
1044
+ try {
1045
+ if (r.stdout) {
1046
+ const start = r.stdout.indexOf('{');
1047
+ if (start !== -1) j = JSON.parse(r.stdout.slice(start));
1048
+ }
1049
+ } catch (_) { /* leave null */ }
1050
+ const driftCount = j && j.summary && typeof j.summary.drift === 'number' ? j.summary.drift : 0;
1051
+ const ok = r.status === 0 && driftCount === 0;
1052
+ let finding = null;
1053
+ if (r.status !== 0) finding = 'doctor --all exited ' + r.status;
1054
+ else if (driftCount > 0) finding = 'doctor --all reports ' + driftCount + ' drift item(s)';
1055
+ return { ok: ok, finding: finding, detail: { exit: r.status, summary: j && j.summary } };
1056
+ },
1057
+ },
1058
+ {
1059
+ // Phase 172-13 (Canon Part 11 R2/R9/INV-10 step 3): the born-wired coverage
1060
+ // gate as a release acceptance point. Runs BOTH --check gates -- the
1061
+ // connector registry (a surface neither WIRED nor EXCLUDED is a hard fail)
1062
+ // and the orchestration projection (a bare command counterpart neither
1063
+ // ranked nor excluded is a hard fail). A release with an accidental-dark
1064
+ // surface aborts here. Canon Part 8: both --check gates regenerate in
1065
+ // memory from LOCAL sources; zero Brain / network.
1066
+ id: 'coverage-gate',
1067
+ label: 'connector + orchestration-projection + framework-vocabulary + render-coverage + skill-mirrors gates pass (no dark surface); shape-declaration and tool-honesty are advisory (WARNs inline, never blocks)',
1068
+ severity: 'blocker',
1069
+ applies_to: ['pre-tag', 'full'],
1070
+ run: async function () {
1071
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'coverage-gate') {
1072
+ return { ok: false, finding: 'coverage-gate synthesized failure (test mode)', detail: {} };
1073
+ }
1074
+ const cp = require('child_process');
1075
+ const gates = [
1076
+ { id: 'connector', script: 'build-connector-registry.cjs' },
1077
+ { id: 'projection', script: 'build-orchestration-projection.cjs' },
1078
+ // Phase 254 Plan 03 (WIRE-04): the framework-vocabulary-drift gate rides
1079
+ // the SAME coverage-gate organ. A silent divergence between the three
1080
+ // committed framework vocabularies (KNOWN_FRAMEWORKS, the registry's
1081
+ // declared frameworks, and the projection's framework nodes) is the
1082
+ // measured root cause of the one-step-chain defect Plans 01/02 of Phase
1083
+ // 254 fix; this gate makes any FUTURE undeclared divergence a blocker
1084
+ // too. A declared entry with a stated reason never fails this gate.
1085
+ { id: 'framework-vocabulary', script: 'check-framework-vocabulary-drift.cjs' },
1086
+ // Phase 178-03 (Canon Part 11 render twin, C-3): the render-coverage gate
1087
+ // rides the SAME doctor --acceptance organ as the two CIRS gates. A render
1088
+ // GAP (a reachable Decision-Gate surface not routed through the SEED-020
1089
+ // card-emission door) is a blocker here too -- HARD-FAIL, never WARN (R-5).
1090
+ { id: 'render', script: 'check-render-coverage.cjs' },
1091
+ // Quick task 260705-sy9: the skill-mirror staleness gate rides the SAME
1092
+ // coverage-gate organ (the Phase 178-03 render-gate precedent). A stale
1093
+ // skills/<name>/SKILL.md mirror ships a wrong command body to machines
1094
+ // hit by the Claude Code registration bug (260705-ob7). Blocker semantics:
1095
+ // the script exits 1 on any missing/stale mirror or a deleted/reverted
1096
+ // SKIP_LIST skill. Canon Part 8: local byte compares only.
1097
+ { id: 'skill-mirrors', script: 'build-skill-mirrors.cjs' },
1098
+ // Phase 190-04 (SFD-04/SFD-05, Canon Part 11 R16), softened by Phase 210
1099
+ // (item 210-A, navigator decision, CONTEXT addendum 1): the born-declared-shape
1100
+ // gate is ADVISORY. The script's --check exits 0 by default even on a
1101
+ // violating tree (its WARN lines enumerate every violation in stderrTail),
1102
+ // so the r.status === 0 ok test below no longer blocks on shape-declaration
1103
+ // drift; --strict on the script restores the pre-210 hard-fail if a future
1104
+ // phase re-hardens. The other three gates above KEEP blocker semantics.
1105
+ // The gate still enumerates the tree at run time; no hardcoded count.
1106
+ { id: 'shape-declaration', script: 'check-shape-declaration.cjs' },
1107
+ // Quick task 260903-ljj: the MCP tool-honesty description-vs-behavior
1108
+ // scanner rides the SAME advisory-by-default posture as the
1109
+ // shape-declaration entry above. Its --check exits 0 by design even
1110
+ // on a tree carrying HIGH RISK findings (it WARNs and enumerates
1111
+ // each one in stderrTail instead of failing), so the r.status === 0
1112
+ // ok test below never fails the roll-up on an advisory finding;
1113
+ // --strict on the script restores hard-fail once the live findings
1114
+ // are triaged and the gate is deliberately re-hardened. The gate
1115
+ // still enumerates every server.tool() registration at run time;
1116
+ // no hardcoded tool/branch/command count.
1117
+ { id: 'tool-honesty', script: 'check-tool-honesty.cjs' },
1118
+ ];
1119
+ const results = [];
1120
+ for (const g of gates) {
1121
+ const scriptPath = path.join(pluginRoot, 'scripts', g.script);
1122
+ if (!fs.existsSync(scriptPath)) {
1123
+ results.push({ id: g.id, ok: false, status: null, note: 'script missing: ' + g.script });
1124
+ continue;
1125
+ }
1126
+ const r = cp.spawnSync('node', [scriptPath, '--check'], { encoding: 'utf8', timeout: 60000 });
1127
+ results.push({
1128
+ id: g.id,
1129
+ ok: r.status === 0,
1130
+ status: r.status,
1131
+ stderrTail: (r.stderr || '').slice(-400),
1132
+ });
1133
+ }
1134
+ const failed = results.filter(function (x) { return !x.ok; });
1135
+ const ok = failed.length === 0;
1136
+ const finding = ok ? null : (failed.map(function (x) { return x.id + ' gate exited ' + x.status; }).join('; '));
1137
+ return { ok: ok, finding: finding, detail: { gates: results } };
1138
+ },
1139
+ },
1140
+ // -- Phase 126 Plan 05: 5 release-flight pre-flight hot-patches --
1141
+ //
1142
+ // Phase 123's release cut (v1.13.0-beta.13) surfaced 5 hot-patches during
1143
+ // Plan-06 pre-flight. The operator applied them MANUALLY before tagging
1144
+ // the release. That tribal knowledge is release risk; Plan 05 absorbs each
1145
+ // hot-patch as a checklist entry so the gate enforces them automatically.
1146
+ // All 5 are pre-tag-applicable (they verify pre-release state). Each
1147
+ // honors the DOCTOR_TEST_FAIL_POINT injection (mirrors the established
1148
+ // pattern from Phase 123). Canon Part 6 (dog-fooding): each manually-
1149
+ // applied operator check is promoted to a structured gate.
1150
+ {
1151
+ id: 'session-start-active-version',
1152
+ label: 'session-start derives active_version correctly from installed_plugins.json',
1153
+ severity: 'blocker',
1154
+ applies_to: ['pre-tag', 'full'],
1155
+ run: async function () {
1156
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'session-start-active-version') {
1157
+ return { ok: false, finding: 'session-start-active-version synthesized failure (test mode)', detail: {} };
1158
+ }
1159
+ try {
1160
+ // Read installed_plugins.json + extract the mos@mindrian-marketplace
1161
+ // active version. Mirror the same name/structure scan as session-start
1162
+ // lines 178-186 (accept both "mos" and "mindrian-os" prefixes).
1163
+ const ipPath = path.join(home, '.claude', 'plugins', 'installed_plugins.json');
1164
+ if (!fs.existsSync(ipPath)) {
1165
+ return { ok: false, finding: 'installed_plugins.json absent', detail: { ipPath: ipPath } };
1166
+ }
1167
+ const ip = JSON.parse(fs.readFileSync(ipPath, 'utf8'));
1168
+ const plugins = (ip && ip.plugins) || {};
1169
+ let recordedVersion = null;
1170
+ for (const k of Object.keys(plugins)) {
1171
+ const name = String(k).split('@')[0];
1172
+ if (name !== 'mos' && name !== 'mindrian-os') continue;
1173
+ let entry = plugins[k];
1174
+ if (Array.isArray(entry)) entry = entry[0];
1175
+ if (entry && entry.version) { recordedVersion = entry.version; break; }
1176
+ }
1177
+ if (!recordedVersion) {
1178
+ return { ok: false, finding: 'no mos@mindrian-marketplace entry in installed_plugins.json', detail: {} };
1179
+ }
1180
+ // Resolve the active plugin root and compare its plugin.json version
1181
+ // to the recorded installed_plugins.json version. session-start's
1182
+ // line-202 derivation uses resolver-root-basename first, then this
1183
+ // value -- a mismatch means the two derivation paths disagree.
1184
+ const r = resolveActivePluginRoot({});
1185
+ if (!r || !r.root) {
1186
+ return { ok: false, finding: 'active plugin root unresolvable', detail: { source: r && r.source } };
1187
+ }
1188
+ const pjPath = path.join(r.root, '.claude-plugin', 'plugin.json');
1189
+ if (!fs.existsSync(pjPath)) {
1190
+ return { ok: false, finding: 'plugin.json missing in active root', detail: { pjPath: pjPath } };
1191
+ }
1192
+ const pj = JSON.parse(fs.readFileSync(pjPath, 'utf8'));
1193
+ const ok = pj.version === recordedVersion;
1194
+ return {
1195
+ ok: ok,
1196
+ finding: ok ? null : ('plugin.json says ' + pj.version + ' but installed_plugins.json says ' + recordedVersion),
1197
+ detail: { pluginJson: pj.version, recordedVersion: recordedVersion, activeRoot: r.root, topology: r.topology },
1198
+ };
1199
+ } catch (e) {
1200
+ return { ok: false, finding: 'session-start-active-version threw: ' + e.message, detail: {} };
1201
+ }
1202
+ },
1203
+ },
1204
+ {
1205
+ id: 'verify-release-clean-tree',
1206
+ label: 'verify-release Step 12 reports clean git tree (tracked files only)',
1207
+ severity: 'blocker',
1208
+ // Phase 126 Plan 05 absorbed this check from the Phase 123 cut pre-flight.
1209
+ // The 2026-05-14 beta.16 cut attempt surfaced that --pre-tag is the wrong
1210
+ // mode: release.sh Step 6.6 runs --pre-tag AFTER Steps 3-6 intentionally
1211
+ // bump package.json + plugin.json + CHANGELOG (3 expected tracked-file
1212
+ // mods), and this strict clean-tree check trips on them. The check is a
1213
+ // PRE-FLIGHT meant to run OUT-OF-BAND with release.sh (operator runs it
1214
+ // manually before kicking off the cut, OR release.sh runs it BEFORE
1215
+ // Step 3 -- which would be a release.sh refactor). For now: drop from
1216
+ // 'pre-tag', keep in 'full' (post-tag, everything should be committed).
1217
+ // Operators can still invoke doctor --acceptance manually pre-flight by
1218
+ // running it without --pre-tag (which runs the 'full' suite).
1219
+ //
1220
+ // Phase 126.1 hotfix (2026-05-15): added 'pre-flight' tier. release.sh
1221
+ // Step 2.5 calls --pre-flight BEFORE Step 3 mutates the tree, so this
1222
+ // check runs on a clean working tree before the cut starts bumping.
1223
+ // 'pre-tag' stays excluded because Step 6.6 still runs post-bump.
1224
+ applies_to: ['pre-flight', 'full'],
1225
+ run: async function () {
1226
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'verify-release-clean-tree') {
1227
+ return { ok: false, finding: 'verify-release-clean-tree synthesized failure (test mode)', detail: {} };
1228
+ }
1229
+ const cp = require('child_process');
1230
+ try {
1231
+ // Mirror verify-release Step 12 line 296: --untracked-files=no so
1232
+ // untracked files are allowed; tracked-dirty is the failure mode.
1233
+ const r = cp.spawnSync('git', ['-C', pluginRoot, 'status', '--porcelain', '--untracked-files=no'], { encoding: 'utf8', timeout: 10000 });
1234
+ if (r.status !== 0) {
1235
+ return { ok: false, finding: 'git status failed (exit ' + r.status + ')', detail: { stderr: (r.stderr || '').slice(-200) } };
1236
+ }
1237
+ const dirty = (r.stdout || '').trim();
1238
+ const ok = dirty === '';
1239
+ const dirtyCount = dirty ? dirty.split('\n').length : 0;
1240
+ return {
1241
+ ok: ok,
1242
+ finding: ok ? null : ('tracked-file drift: ' + dirtyCount + ' file(s)'),
1243
+ detail: { dirtyCount: dirtyCount, dirtyTail: dirty.slice(0, 500) },
1244
+ };
1245
+ } catch (e) {
1246
+ return { ok: false, finding: 'verify-release-clean-tree threw: ' + e.message, detail: {} };
1247
+ }
1248
+ },
1249
+ },
1250
+ {
1251
+ id: 'frontmatter-yaml-validity',
1252
+ label: 'commands/operator.md + commands/doctor.md frontmatter YAML parses cleanly',
1253
+ severity: 'blocker',
1254
+ applies_to: ['pre-tag', 'full'],
1255
+ run: async function () {
1256
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'frontmatter-yaml-validity') {
1257
+ return { ok: false, finding: 'frontmatter-yaml-validity synthesized failure (test mode)', detail: {} };
1258
+ }
1259
+ try {
1260
+ const targets = [
1261
+ path.join(pluginRoot, 'commands', 'operator.md'),
1262
+ path.join(pluginRoot, 'commands', 'doctor.md'),
1263
+ ];
1264
+ const failures = [];
1265
+ for (const t of targets) {
1266
+ if (!fs.existsSync(t)) { failures.push(t + ' (absent)'); continue; }
1267
+ const content = fs.readFileSync(t, 'utf8');
1268
+ // Frontmatter pattern: file starts with `---\n` and the next `---\n`
1269
+ // closes the block. We do not require a leading BOM.
1270
+ const m = content.match(/^---\n([\s\S]*?)\n---\n/);
1271
+ if (!m) { failures.push(t + ' (no YAML frontmatter)'); continue; }
1272
+ const yaml = m[1];
1273
+ const lines = yaml.split('\n');
1274
+ for (let i = 0; i < lines.length; i++) {
1275
+ const ln = lines[i];
1276
+ if (ln.trim() === '') continue;
1277
+ if (ln.indexOf('\t') !== -1) { failures.push(t + ' line ' + (i + 1) + ' contains tab'); break; }
1278
+ // Allowed shapes: `<key>: <value>`, ` <key>: <value>` (nested),
1279
+ // `- <item>` (list item), `# comment`. Anything else is drift.
1280
+ const isKeyValue = /^[A-Za-z_][A-Za-z0-9_-]*:/.test(ln);
1281
+ const isIndented = ln.startsWith(' ');
1282
+ const isListItem = ln.startsWith('-');
1283
+ const isComment = ln.startsWith('#');
1284
+ if (!isKeyValue && !isIndented && !isListItem && !isComment) {
1285
+ failures.push(t + ' line ' + (i + 1) + ' not key:value');
1286
+ break;
1287
+ }
1288
+ }
1289
+ }
1290
+ const ok = failures.length === 0;
1291
+ return {
1292
+ ok: ok,
1293
+ finding: ok ? null : ('YAML drift: ' + failures.join('; ')),
1294
+ detail: { failures: failures },
1295
+ };
1296
+ } catch (e) {
1297
+ return { ok: false, finding: 'frontmatter-yaml-validity threw: ' + e.message, detail: {} };
1298
+ }
1299
+ },
1300
+ },
1301
+ {
1302
+ id: 'release-dry-run-output',
1303
+ label: 'release.sh --dry-run produces all expected step names',
1304
+ severity: 'blocker',
1305
+ applies_to: ['pre-tag', 'full'],
1306
+ run: async function () {
1307
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'release-dry-run-output') {
1308
+ return { ok: false, finding: 'release-dry-run-output synthesized failure (test mode)', detail: {} };
1309
+ }
1310
+ const cp = require('child_process');
1311
+ try {
1312
+ // Pass an explicit bump mode ('patch') so this self-test is robust to
1313
+ // the CURRENT version state. A bare '--dry-run' defaults to --prerelease
1314
+ // only when the current version carries a '-beta.N' suffix; during a
1315
+ // --finalize run this self-test executes AFTER Step 3 has bumped the
1316
+ // version to a clean X.Y.Z, where release.sh correctly requires an
1317
+ // explicit mode and a bare dry-run exits 1 (the finalize-path self-test
1318
+ // bug, fixed 2026-06-02). 'patch' is valid on both clean and suffixed
1319
+ // versions and emits the same mode-independent step names this check asserts.
1320
+ const r = cp.spawnSync('bash', [path.join(pluginRoot, 'scripts', 'release.sh'), 'patch', '--dry-run'], { encoding: 'utf8', timeout: 30000 });
1321
+ if (r.status !== 0) {
1322
+ return { ok: false, finding: 'release.sh --dry-run exited ' + r.status, detail: { stderr: (r.stderr || '').slice(-200) } };
1323
+ }
1324
+ const out = r.stdout || '';
1325
+ // NOTE: Plan 04 (Phase 126 Wave 3) patched this list to add Step 5.5
1326
+ // / 9.7 / 9.8 and to reorder Step 9.6 -> 9.8 for the rename. The new
1327
+ // Step 9.6 is the install-minisite HARD lockstep; Step 9.7 is the
1328
+ // npx-publish self-test; Step 9.8 is the post-publish full
1329
+ // --acceptance gate (was Step 9.6 in Wave 2's scaffold). If you edit
1330
+ // this list, make sure release.sh --dry-run output still matches in
1331
+ // order. Phase 126.1 hotfix (2026-05-15) added Step 2.5 (the new
1332
+ // --pre-flight clean-tree gate that runs BEFORE Step 3 mutates).
1333
+ const expectedSteps = ['Step 2', 'Step 2.5', 'Step 3', 'Step 4', 'Step 5', 'Step 5b', 'Step 5.5', 'Step 6', 'Step 6.5', 'Step 6.6', 'Step 7', 'Step 9.5', 'Step 9.6', 'Step 9.7', 'Step 9.8'];
1334
+ const missing = expectedSteps.filter(function (s) { return out.indexOf(s) === -1; });
1335
+ const ok = missing.length === 0;
1336
+ return {
1337
+ ok: ok,
1338
+ finding: ok ? null : ('missing step names: ' + missing.join(', ')),
1339
+ detail: { missing: missing, expectedCount: expectedSteps.length },
1340
+ };
1341
+ } catch (e) {
1342
+ return { ok: false, finding: 'release-dry-run-output threw: ' + e.message, detail: {} };
1343
+ }
1344
+ },
1345
+ },
1346
+ {
1347
+ id: 'working-tree-housekeeping',
1348
+ label: 'no orphan .tmp/.bak/.swp files in tracked directories',
1349
+ severity: 'blocker',
1350
+ applies_to: ['pre-tag', 'full'],
1351
+ run: async function () {
1352
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'working-tree-housekeeping') {
1353
+ return { ok: false, finding: 'working-tree-housekeeping synthesized failure (test mode)', detail: {} };
1354
+ }
1355
+ const cp = require('child_process');
1356
+ try {
1357
+ const r = cp.spawnSync('git', ['-C', pluginRoot, 'ls-files'], { encoding: 'utf8', timeout: 10000 });
1358
+ if (r.status !== 0) {
1359
+ return { ok: false, finding: 'git ls-files failed (exit ' + r.status + ')', detail: { stderr: (r.stderr || '').slice(-200) } };
1360
+ }
1361
+ const tracked = (r.stdout || '').split('\n').filter(Boolean);
1362
+ const orphans = tracked.filter(function (p) { return /\.(tmp|bak|swp|swo)$/.test(p); });
1363
+ const ok = orphans.length === 0;
1364
+ const orphanSample = orphans.slice(0, 5).join(', ') + (orphans.length > 5 ? ' (+' + (orphans.length - 5) + ' more)' : '');
1365
+ return {
1366
+ ok: ok,
1367
+ finding: ok ? null : ('orphan tracked tmp/bak files: ' + orphanSample),
1368
+ detail: { orphanCount: orphans.length, orphans: orphans.slice(0, 20) },
1369
+ };
1370
+ } catch (e) {
1371
+ return { ok: false, finding: 'working-tree-housekeeping threw: ' + e.message, detail: {} };
1372
+ }
1373
+ },
1374
+ },
1375
+ {
1376
+ // QUICK-260906-t3s -- catches unregistered checkout directories piling
1377
+ // up under .claude/worktrees/: agent-session worktrees whose linked-
1378
+ // worktree admin record has gone stale (a moved repo root, a deleted
1379
+ // branch, a crashed cleanup) so `git worktree prune` can no longer see
1380
+ // them and they sit on disk forever. On 2026-09-06 this reached 46
1381
+ // orphans / 3.9GB, and an unscoped version search walked into one of
1382
+ // them and returned a months-old answer as if it were current (see
1383
+ // docs/autopsies/2026-09-06-worktree-version-contamination-incident.md).
1384
+ // BLOCKER, not advisory (DD-4 of that quick task's plan): the clean
1385
+ // state is exactly zero and the fix is a single command, so unlike the
1386
+ // large-disputed-violation-set advisory gates (shape-declaration,
1387
+ // tool-honesty), a hard fail here is honest and cheap. `--advisory` on
1388
+ // the underlying script exists for a release train that ever needs to
1389
+ // move past this anyway; it is not wired here on purpose.
1390
+ // Canon Part 8: every check this entry runs is a local filesystem read
1391
+ // or a local `git` plumbing call, spawned in-process. Zero network.
1392
+ id: 'worktree-hygiene',
1393
+ label: 'no unregistered checkout directories under .claude/worktrees/',
1394
+ severity: 'blocker',
1395
+ applies_to: ['pre-tag', 'full'],
1396
+ run: async function () {
1397
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'worktree-hygiene') {
1398
+ return { ok: false, finding: 'worktree-hygiene synthesized failure (test mode)', detail: {} };
1399
+ }
1400
+ const cp = require('child_process');
1401
+ const scriptPath = path.join(pluginRoot, 'scripts', 'check-worktree-hygiene.cjs');
1402
+ if (!fs.existsSync(scriptPath)) {
1403
+ return { ok: false, finding: 'worktree-hygiene: script missing at ' + scriptPath, detail: { note: 'script missing' } };
1404
+ }
1405
+ try {
1406
+ const r = cp.spawnSync('node', [scriptPath, '--check', '--json'], { encoding: 'utf8', timeout: 30000, cwd: pluginRoot });
1407
+ let parsed;
1408
+ try {
1409
+ parsed = JSON.parse(r.stdout || '{}');
1410
+ } catch (e) {
1411
+ return { ok: false, finding: 'worktree-hygiene: could not parse script output: ' + e.message, detail: { stdout: (r.stdout || '').slice(-500), stderr: (r.stderr || '').slice(-500) } };
1412
+ }
1413
+ const nonRegistered = Array.isArray(parsed.nonRegistered) ? parsed.nonRegistered : [];
1414
+ const ok = r.status === 0;
1415
+ const finding = ok
1416
+ ? null
1417
+ : nonRegistered.length + ' unregistered worktree checkout(s) under .claude/worktrees/; fix with: node scripts/check-worktree-hygiene.cjs --prune --confirm';
1418
+ return {
1419
+ ok: ok,
1420
+ finding: finding,
1421
+ detail: { orphanCount: nonRegistered.length, review: nonRegistered.filter(function (x) { return x.status !== 'safe-orphan'; }) },
1422
+ };
1423
+ } catch (e) {
1424
+ return { ok: false, finding: 'worktree-hygiene threw: ' + e.message, detail: {} };
1425
+ }
1426
+ },
1427
+ },
1428
+ {
1429
+ // Phase 298 Plan 15 (SEED-032, Harness-as-Code) -- the final rung on
1430
+ // the placement ladder: standalone (`run-harness --check`) becomes
1431
+ // embedded here (a doctor acceptance point) and chained (the widened
1432
+ // pre-commit guard). Wired LAST, only after slice 1 was confirmed
1433
+ // green (`node scripts/run-harness.cjs --check --json` exit 0 on a
1434
+ // clean tree), per the D-4 precedent from quick 260906-t3s: every
1435
+ // `buildAcceptanceChecklist` entry is `severity: 'blocker'` and
1436
+ // `--acceptance` hard-aborts with no `--allow` override, so a
1437
+ // blocker's first run must be a true green.
1438
+ //
1439
+ // WHY BLOCKER, NOT ADVISORY: a declared harness policy that stops
1440
+ // running is a silent governance hole -- every acceptance entry here
1441
+ // is a blocker by construction (see `eureka-fts-index-visible`'s own
1442
+ // comment above for the general reasoning); informational reporting
1443
+ // for this same surface already lives in the `voice-style-log`
1444
+ // doctor MODULE (plan 298-08), which is the advisory sibling.
1445
+ //
1446
+ // Canon Part 8: the only I/O below is a local filesystem existence
1447
+ // check plus one local `spawnSync('node', [...])` of a repo-relative
1448
+ // script, argv array (never a shell string). Zero network.
1449
+ id: 'harness-policies',
1450
+ label: 'every declared harness policy runs and no blocking policy failed',
1451
+ severity: 'blocker',
1452
+ applies_to: ['pre-tag', 'full'],
1453
+ run: async function () {
1454
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'harness-policies') {
1455
+ return { ok: false, finding: 'harness-policies synthesized failure (test mode)', detail: {} };
1456
+ }
1457
+ const cp = require('child_process');
1458
+ const scriptPath = path.join(pluginRoot, 'scripts', 'run-harness.cjs');
1459
+ if (!fs.existsSync(scriptPath)) {
1460
+ return { ok: false, finding: 'harness-policies: script missing at ' + scriptPath, detail: { note: 'script missing' } };
1461
+ }
1462
+ try {
1463
+ const r = cp.spawnSync('node', [scriptPath, '--check', '--json'], { encoding: 'utf8', timeout: 30000, cwd: pluginRoot });
1464
+ let parsed;
1465
+ try {
1466
+ parsed = JSON.parse(r.stdout || '{}');
1467
+ } catch (e) {
1468
+ return { ok: false, finding: 'harness-policies: could not parse script output: ' + e.message, detail: { stdout: (r.stdout || '').slice(-500), stderr: (r.stderr || '').slice(-500) } };
1469
+ }
1470
+ const counts = parsed.counts || {};
1471
+ const policies = Array.isArray(parsed.policies) ? parsed.policies : [];
1472
+ const failedIds = policies.filter(function (p) { return p.verdict === 'fail'; }).map(function (p) { return p.id; });
1473
+ const ok = r.status === 0;
1474
+ // The 2026-07-11 printer-must-match-counter lesson: the failing
1475
+ // finding echoes the SAME 'Totals: ...' verdict string
1476
+ // run-harness.cjs's own printReport() prints in text mode
1477
+ // (scripts/run-harness.cjs:413-416), built here from the same
1478
+ // parsed counts object, never a separately-computed tally, plus
1479
+ // the exact recovery command.
1480
+ const totalsLine = 'Totals: ' + (counts.pass || 0) + ' pass, ' + (counts.fail || 0) + ' fail, ' +
1481
+ (counts.ghost || 0) + ' ghost, ' + (counts.declared || 0) + ' declared, ' + (counts.total || 0) + ' total.';
1482
+ const finding = ok
1483
+ ? null
1484
+ : (failedIds.length + ' failing harness polic' + (failedIds.length === 1 ? 'y' : 'ies') +
1485
+ (failedIds.length ? ' (' + failedIds.join(', ') + ')' : '') + '; ' + totalsLine +
1486
+ ' Fix with: node scripts/run-harness.cjs --check');
1487
+ return {
1488
+ ok: ok,
1489
+ finding: finding,
1490
+ detail: { counts: counts, failedIds: failedIds },
1491
+ };
1492
+ } catch (e) {
1493
+ return { ok: false, finding: 'harness-policies threw: ' + e.message, detail: {} };
1494
+ }
1495
+ },
1496
+ },
1497
+ {
1498
+ // Phase 127.2 Plan 04 Instance #7 -- Class N acceptance point:
1499
+ // "activation reached the wire."
1500
+ //
1501
+ // Asserts that the live MCP server's version matches the
1502
+ // package.json version-of-record. Catches the silent-activation gap
1503
+ // where /mos:update lands bytes in cache + the active install is not
1504
+ // swapped, so the user thinks they are on beta.N+1 while every Brain
1505
+ // probe quietly hits beta.N. Before Class N, this defect could ship
1506
+ // a beta where the published bytes never activate on testers'
1507
+ // machines -- breaking the entire dog-fooding feedback loop because
1508
+ // tester "I tested beta.N" reports were filed against the previous
1509
+ // beta.
1510
+ //
1511
+ // applies_to: ['full'] only. The wire probe requires a live MCP
1512
+ // server reachable + the published version on npm/marketplace; this
1513
+ // is FALSE before tag + publish, so the pre-tag and pre-flight modes
1514
+ // skip this point. Step 9.8 of release.sh runs the full --acceptance
1515
+ // gate AFTER publish, which is where this point gates.
1516
+ id: 'activation-reached-the-wire',
1517
+ label: 'L4 MCP server version matches package.json version-of-record',
1518
+ severity: 'blocker',
1519
+ applies_to: ['full'],
1520
+ run: async function () {
1521
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'activation-reached-the-wire') {
1522
+ return { ok: false, finding: 'activation-reached-the-wire synthesized failure (test mode)', detail: {} };
1523
+ }
1524
+ // Defensive: opt-out for hermetic CI / offline mode. release.sh
1525
+ // Step 9.8 runs against a live network; CI smoke tests can set
1526
+ // DOCTOR_SKIP_ACTIVATION_GATE=1 to mark this point ok-as-skipped.
1527
+ if (process.env.DOCTOR_SKIP_ACTIVATION_GATE === '1') {
1528
+ return { ok: true, finding: null, detail: { skipped: true, reason: 'DOCTOR_SKIP_ACTIVATION_GATE=1' } };
1529
+ }
1530
+ const cp = require('child_process');
1531
+ try {
1532
+ // 1. Read version-of-record (package.json) from pluginRoot.
1533
+ const pkPath = path.join(pluginRoot, 'package.json');
1534
+ if (!fs.existsSync(pkPath)) {
1535
+ return { ok: false, finding: 'package.json not found at ' + pkPath, detail: {} };
1536
+ }
1537
+ const recordVersion = JSON.parse(fs.readFileSync(pkPath, 'utf8')).version;
1538
+ if (!recordVersion) {
1539
+ return { ok: false, finding: 'package.json contains no version field', detail: {} };
1540
+ }
1541
+ // 2. Spawn doctor --brain-smoke --json against ourselves to probe
1542
+ // the live MCP server. Reuses the Phase 127-02 Class M smoke
1543
+ // test (6-layer probe at lib/core/doctor/class-m-brain-smoke.cjs).
1544
+ const doctorPath = path.join(__dirname, 'doctor.cjs');
1545
+ const r = cp.spawnSync('node', [doctorPath, '--brain-smoke', '--json'], {
1546
+ encoding: 'utf8', timeout: 30000,
1547
+ });
1548
+ if (!r || typeof r.stdout !== 'string') {
1549
+ return { ok: false, finding: 'brain-smoke spawn produced no stdout', detail: { stderr: (r && r.stderr || '').slice(-200) } };
1550
+ }
1551
+ let payload = null;
1552
+ try { payload = JSON.parse(r.stdout); }
1553
+ catch (_) { return { ok: false, finding: 'brain-smoke stdout not parseable as JSON', detail: { stdoutTail: r.stdout.slice(-200) } }; }
1554
+ if (!payload || !Array.isArray(payload.layers)) {
1555
+ return { ok: false, finding: 'brain-smoke payload missing layers array', detail: { payloadKeys: payload ? Object.keys(payload) : [] } };
1556
+ }
1557
+ const L4 = payload.layers.find(function (l) { return l && (l.id === 'mcp_stdio_handshake' || l.name === 'L4 MCP stdio handshake'); });
1558
+ if (!L4) {
1559
+ return { ok: false, finding: 'L4 mcp_stdio_handshake layer absent', detail: { layerIds: payload.layers.map(function (l) { return l && l.id; }) } };
1560
+ }
1561
+ if (!L4.ok) {
1562
+ return { ok: false, finding: 'L4 mcp_stdio_handshake did not succeed: ' + (L4.reason || 'unknown'), detail: { L4: L4 } };
1563
+ }
1564
+ // 3. Parse server=mindrian-brain vX.Y.Z[-PR] from L4 reason.
1565
+ const match = String(L4.reason || '').match(/server=mindrian-brain v(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/);
1566
+ if (!match) {
1567
+ return { ok: false, finding: 'L4 reason missing server-version pattern: ' + L4.reason, detail: { L4: L4 } };
1568
+ }
1569
+ const wireVersion = match[1];
1570
+ // 4. Compare. Match = pass, drift = activation-gap.
1571
+ const ok = wireVersion === recordVersion;
1572
+ const finding = ok ? null : ('activation gap detected: live install serves v' + wireVersion + ' but version-of-record is v' + recordVersion);
1573
+ // 5. Store-identity guard (quick task 260819-c9b, WS-E1). Reuses
1574
+ // the SAME payload already parsed above -- no second spawn.
1575
+ // Two things wire-lock a release here: the store_identity
1576
+ // layer must not silently disappear from the payload (absent
1577
+ // is a not-ok finding), and a release must never be cut while
1578
+ // the wire is pointed at the frozen stale-replica signature
1579
+ // (also not-ok, quoting the layer's own named reason). Any
1580
+ // OTHER store-identity failure -- a transient count dip, an
1581
+ // unreachable Brain -- is carried into detail as information
1582
+ // only and does NOT flip ok: an absolute count gate would
1583
+ // brick the release train on ordinary graph churn, the same
1584
+ // reasoning that keeps the dual-graph gate in report-only
1585
+ // mode (scripts/check-dual-graph-health.cjs DEFAULT_MODE).
1586
+ const storeIdentity = payload.layers.find(function (l) { return l && l.id === 'store_identity'; });
1587
+ if (!storeIdentity) {
1588
+ return {
1589
+ ok: false,
1590
+ finding: 'store_identity layer absent from brain-smoke payload',
1591
+ detail: { wireVersion: wireVersion, recordVersion: recordVersion, layerIds: payload.layers.map(function (l) { return l && l.id; }) },
1592
+ };
1593
+ }
1594
+ if (!storeIdentity.ok && /stale_replica_signature/.test(String(storeIdentity.reason || ''))) {
1595
+ return {
1596
+ ok: false,
1597
+ finding: 'release is being cut against the stale replica: ' + storeIdentity.reason,
1598
+ detail: { wireVersion: wireVersion, recordVersion: recordVersion, storeIdentity: storeIdentity },
1599
+ };
1600
+ }
1601
+ return { ok: ok, finding: finding, detail: { wireVersion: wireVersion, recordVersion: recordVersion, storeIdentity: storeIdentity } };
1602
+ } catch (e) {
1603
+ return { ok: false, finding: 'activation-reached-the-wire threw: ' + e.message, detail: {} };
1604
+ }
1605
+ },
1606
+ },
1607
+ {
1608
+ // Phase 199-05 (AS-08) -- Class O acceptance point:
1609
+ // "AgentShield reports zero flagged findings across all 5 plugin surfaces."
1610
+ //
1611
+ // Runs the shipped 199-04 orchestrator (runAgentShieldScan) over the SAME
1612
+ // tree doctor.cjs lives in (dog-food, Canon Part 6 / AS-04) and fails the
1613
+ // release gate if any of the five non-Brain config surfaces (MCP tool
1614
+ // descriptions, hook commands, skill files, CLAUDE.md permissions,
1615
+ // package.json deps) carries an unremediated FLAGGED finding. This catches
1616
+ // a poisoned tool-description / hook / skill BEFORE the plugin ships,
1617
+ // closing the born-wired security loop for the /mos:agentshield surface.
1618
+ //
1619
+ // ok is keyed ONLY on totalFlagged === 0. An `ambiguous` verdict WARNS via
1620
+ // the finding string but does NOT trip the blocker (mirrors the
1621
+ // warn-vs-blocker distinction used elsewhere in this file). The scan opens
1622
+ // NO network wire and starts NO MCP server (Part 8): every gatherer reads
1623
+ // plugin-local files READ-ONLY.
1624
+ //
1625
+ // applies_to: ['pre-tag','full'] -- the surfaces are all repo-local and
1626
+ // present before tag, so this gates in both the pre-tag and full modes.
1627
+ // ADD-ONLY: no existing checklist entry's logic is touched.
1628
+ id: 'agentshield-all-surfaces-clean',
1629
+ label: 'Class O: AgentShield reports zero flagged findings across all 5 plugin surfaces',
1630
+ severity: 'blocker',
1631
+ applies_to: ['pre-tag', 'full'],
1632
+ run: async function () {
1633
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'agentshield-all-surfaces-clean') {
1634
+ return { ok: false, finding: 'agentshield-all-surfaces-clean synthesized failure (test mode)', detail: {} };
1635
+ }
1636
+ try {
1637
+ const runnerPath = path.join(__dirname, '..', 'lib', 'core', 'security', 'agentshield-run.cjs');
1638
+ const { runAgentShieldScan } = require(runnerPath);
1639
+ const result = runAgentShieldScan();
1640
+ const ok = result.totalFlagged === 0;
1641
+ let finding = null;
1642
+ if (!ok) {
1643
+ const first = Array.isArray(result.findings) && result.findings.length > 0
1644
+ ? result.findings[0]
1645
+ : null;
1646
+ finding = 'AgentShield flagged ' + result.totalFlagged + ' finding(s); first: ' +
1647
+ (first
1648
+ ? (first.surface + '/' + first.id + ' (' + (first.ruleId || first.reason) + ')')
1649
+ : 'unknown');
1650
+ } else if (result.totalAmbiguous > 0) {
1651
+ // WARN-not-fail: ambiguous surfaces surface via the finding string
1652
+ // but the blocker stays green (only flagged findings fail the gate).
1653
+ finding = 'WARN: ' + result.totalAmbiguous +
1654
+ ' ambiguous finding(s) across surfaces (no flagged; blocker not tripped)';
1655
+ }
1656
+ return {
1657
+ ok: ok,
1658
+ finding: finding,
1659
+ detail: {
1660
+ totalFlagged: result.totalFlagged,
1661
+ totalAmbiguous: result.totalAmbiguous,
1662
+ surfaces: result.surfaces,
1663
+ },
1664
+ };
1665
+ } catch (e) {
1666
+ return { ok: false, finding: 'agentshield-all-surfaces-clean threw: ' + e.message, detail: {} };
1667
+ }
1668
+ },
1669
+ },
1670
+ {
1671
+ // quick(260706-13z) D14(e) -- Class S acceptance point.
1672
+ // Phase 341 Plan 03 (D-11) split the probe into a blocker half (L1-L4:
1673
+ // "is the capability reachable") and an advisory half (L5
1674
+ // model_installed: "is the model actually installed"). Spawns
1675
+ // doctor --eureka-smoke --json against ourselves and gates on
1676
+ // payload.ok === true with all 5 layers present -- payload.ok already
1677
+ // excludes the advisory layer from its own reduction (checkEurekaSmoke),
1678
+ // so a slim install with the model absent still passes this gate
1679
+ // honestly instead of failing it or being skipped. The probe NEVER
1680
+ // downloads a model (cache miss is a graceful PASS, and the Pitfall-6
1681
+ // un-awaited isModelCached bug that made a cold cache download inside
1682
+ // this very gate is fixed as of this plan), so this is safe inside a
1683
+ // release gate on airgapped CI.
1684
+ //
1685
+ // applies_to: ['pre-tag','full'] -- the stack is entirely repo-local
1686
+ // (node_modules + local model cache), present before tag, no publish
1687
+ // needed. ADD-ONLY: no existing checklist entry's logic is touched.
1688
+ // DOCTOR_SKIP_EUREKA_SMOKE=1 remains the hermetic-CI opt-out (mirrors
1689
+ // DOCTOR_SKIP_ACTIVATION_GATE) for a tree where the optional eureka deps
1690
+ // were intentionally not installed; per D-11 it is never needed on the
1691
+ // shipped artifact itself, because the slim install now passes on its
1692
+ // own (the advisory layer never blocks it).
1693
+ id: 'eureka-smoke-stack-ready',
1694
+ label: 'Class S: local eureka capability reachable (blocker) + embedding model installed (advisory)',
1695
+ severity: 'blocker',
1696
+ applies_to: ['pre-tag', 'full'],
1697
+ run: async function () {
1698
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'eureka-smoke-stack-ready') {
1699
+ return { ok: false, finding: 'eureka-smoke-stack-ready synthesized failure (test mode)', detail: {} };
1700
+ }
1701
+ if (process.env.DOCTOR_SKIP_EUREKA_SMOKE === '1') {
1702
+ return { ok: true, finding: null, detail: { skipped: true, reason: 'DOCTOR_SKIP_EUREKA_SMOKE=1' } };
1703
+ }
1704
+ const cp = require('child_process');
1705
+ try {
1706
+ const doctorPath = path.join(__dirname, 'doctor.cjs');
1707
+ const r = cp.spawnSync('node', [doctorPath, '--eureka-smoke', '--json'], {
1708
+ encoding: 'utf8', timeout: 30000,
1709
+ });
1710
+ if (!r || typeof r.stdout !== 'string') {
1711
+ return { ok: false, finding: 'eureka-smoke spawn produced no stdout', detail: { stderr: (r && r.stderr || '').slice(-200) } };
1712
+ }
1713
+ let payload = null;
1714
+ try { payload = JSON.parse(r.stdout); }
1715
+ catch (_) { return { ok: false, finding: 'eureka-smoke stdout not parseable as JSON', detail: { stdoutTail: r.stdout.slice(-200) } }; }
1716
+ if (!payload || !Array.isArray(payload.layers)) {
1717
+ return { ok: false, finding: 'eureka-smoke payload missing layers array', detail: { payloadKeys: payload ? Object.keys(payload) : [] } };
1718
+ }
1719
+ if (payload.layers.length !== 5) {
1720
+ return { ok: false, finding: 'eureka-smoke expected 5 layers, got ' + payload.layers.length, detail: { layerIds: payload.layers.map(function (l) { return l && l.id; }) } };
1721
+ }
1722
+ if (payload.ok !== true) {
1723
+ // A non-advisory (blocker) failure -- capability itself is unreachable.
1724
+ const firstFail = payload.layers.find(function (l) { return l && !l.ok && !l.advisory; });
1725
+ return {
1726
+ ok: false,
1727
+ finding: 'eureka stack not ready: ' + (firstFail ? (firstFail.name + ' -- ' + firstFail.reason) : 'unknown layer'),
1728
+ detail: { layers: payload.layers },
1729
+ };
1730
+ }
1731
+ // Passing path (D-10 "never a silent no-op" applied to the gate
1732
+ // itself): the advisory layer's reason is INCLUDED here even though
1733
+ // it never blocked the gate, so "model not installed" is always
1734
+ // visible to the operator, never hidden behind a green result.
1735
+ return {
1736
+ ok: true,
1737
+ finding: null,
1738
+ detail: {
1739
+ layers: payload.layers.map(function (l) {
1740
+ const entry = { id: l.id, ok: l.ok };
1741
+ if (l.advisory) entry.reason = l.reason;
1742
+ return entry;
1743
+ }),
1744
+ },
1745
+ };
1746
+ } catch (e) {
1747
+ return { ok: false, finding: 'eureka-smoke-stack-ready threw: ' + e.message, detail: {} };
1748
+ }
1749
+ },
1750
+ },
1751
+ {
1752
+ // Phase 244 Plan 06 (closing ROADMAP SC1 / RESEARCH BLOCKER B-2):
1753
+ // "a presence/freshness check added to `node scripts/doctor.cjs
1754
+ // --acceptance`, so an unbuilt index is visible rather than silently
1755
+ // producing zero fires." This point calls the health module's check()
1756
+ // DIRECTLY in-process (a sqlite_master read is cheap; unlike Class S it
1757
+ // needs no separate process for model loading, so it never spawns).
1758
+ //
1759
+ // SEVERITY REASONING, stated explicitly so a future reader never assumes
1760
+ // this was careless: EVERY entry in this checklist carries severity
1761
+ // 'blocker' and --acceptance hard-aborts on any failed point. Absent is
1762
+ // the CORRECT DEFAULT state of every room today (verified live against
1763
+ // the 8.4 MB `rethinking-mindrianos` room.db, zero eureka_fts rows) --
1764
+ // failing on absent would fail every release until every registered
1765
+ // room has been rebuilt. Stale (rows pointing at nodes that no longer
1766
+ // exist) is a genuine correctness defect that produces ghost triggers,
1767
+ // so failing on stale is right. The LOGIC below, not a lowered severity,
1768
+ // is what makes this point visibility rather than a blanket gate: it
1769
+ // reports the full per-room census in `detail` on the PASSING path too
1770
+ // (absent/empty/ok all pass), and fails ONLY when a room is classified
1771
+ // index_stale.
1772
+ //
1773
+ // applies_to: ['pre-tag', 'full'] -- purely LOCAL sqlite_master reads
1774
+ // against registered rooms, present before tag, no publish needed.
1775
+ // DOCTOR_SKIP_EUREKA_FTS_HEALTH=1 is the hermetic-CI opt-out (mirrors
1776
+ // DOCTOR_SKIP_EUREKA_SMOKE) for a tree with no registered room registry.
1777
+ id: 'eureka-fts-index-visible',
1778
+ label: 'local lexical trigger index (eureka_fts) is present and not stale, per registered room',
1779
+ severity: 'blocker',
1780
+ applies_to: ['pre-tag', 'full'],
1781
+ run: async function () {
1782
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'eureka-fts-index-visible') {
1783
+ return { ok: false, finding: 'eureka-fts-index-visible synthesized failure (test mode)', detail: {} };
1784
+ }
1785
+ if (process.env.DOCTOR_SKIP_EUREKA_FTS_HEALTH === '1') {
1786
+ return { ok: true, finding: null, detail: { skipped: true, reason: 'DOCTOR_SKIP_EUREKA_FTS_HEALTH=1' } };
1787
+ }
1788
+ try {
1789
+ const ftsHealthMod = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'eureka-fts-health-module.cjs'));
1790
+ const result = ftsHealthMod.check({});
1791
+ const rooms = Array.isArray(result && result.rooms) ? result.rooms : [];
1792
+ const staleRoom = rooms.find(function (r) { return r && r.state === 'index_stale'; });
1793
+ if (staleRoom) {
1794
+ return {
1795
+ ok: false,
1796
+ finding: 'eureka_fts stale in room "' + staleRoom.room + '" ('
1797
+ + staleRoom.orphan_rows + ' orphan row(s) pointing at deleted nodes)',
1798
+ detail: { status: result.status, totals: result.totals, rooms: rooms },
1799
+ };
1800
+ }
1801
+ return {
1802
+ ok: true,
1803
+ finding: null,
1804
+ detail: { status: (result && result.status) || 'skip', totals: result && result.totals, rooms: rooms },
1805
+ };
1806
+ } catch (e) {
1807
+ return { ok: false, finding: 'eureka-fts-index-visible threw: ' + e.message, detail: {} };
1808
+ }
1809
+ },
1810
+ },
1811
+ {
1812
+ // Phase 265 Plan 01 (RADAR-01/RADAR-02): "node scripts/doctor.cjs
1813
+ // --acceptance surfaces ledger staleness through a registered doctor
1814
+ // module" (must_have truth). Calls the capability-ledger module's
1815
+ // check() DIRECTLY in-process (a single JSON read + one bounded
1816
+ // `claude --version` subprocess; no spawn of doctor.cjs itself).
1817
+ //
1818
+ // SEVERITY REASONING: unlike eureka-fts-index-visible's
1819
+ // visibility-not-gate design, this point IS a hard gate. T-265-02
1820
+ // (Repudiation: stale ledger presenting false assurance) names this
1821
+ // exact wiring as its mitigation -- "warn on unknown version, never
1822
+ // ok; wired to doctor --acceptance so it runs on the release gate
1823
+ // path." A ledger that has fallen behind the installed Claude Code
1824
+ // version by more than MINDRIAN_LEDGER_MAX_VERSION_LAG, or a version
1825
+ // the check could not read at all, must not let a release ship
1826
+ // silently -- that silent-pass is the exact rot pattern Phase 138's
1827
+ // markdown ledger died from.
1828
+ //
1829
+ // applies_to: ['pre-tag', 'full'] -- purely LOCAL (one JSON read, one
1830
+ // bounded local subprocess; Canon Part 8 zero network). Present before
1831
+ // tag, no publish needed. DOCTOR_SKIP_CAPABILITY_LEDGER=1 is the
1832
+ // hermetic-CI / offline-claude-binary opt-out, mirroring the sibling
1833
+ // DOCTOR_SKIP_* escapes above.
1834
+ id: 'capability-ledger-fresh',
1835
+ label: 'capability ledger freshness: data/capability-ledger.json tracks the installed claude --version',
1836
+ severity: 'blocker',
1837
+ applies_to: ['pre-tag', 'full'],
1838
+ run: async function () {
1839
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'capability-ledger-fresh') {
1840
+ return { ok: false, finding: 'capability-ledger-fresh synthesized failure (test mode)', detail: {} };
1841
+ }
1842
+ if (process.env.DOCTOR_SKIP_CAPABILITY_LEDGER === '1') {
1843
+ return { ok: true, finding: null, detail: { skipped: true, reason: 'DOCTOR_SKIP_CAPABILITY_LEDGER=1' } };
1844
+ }
1845
+ try {
1846
+ const ledgerMod = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'capability-ledger-module.cjs'));
1847
+ const result = ledgerMod.check({});
1848
+ if (!result || result.status !== 'ok') {
1849
+ return {
1850
+ ok: false,
1851
+ finding: 'capability-ledger-fresh: ' + (result && result.detail || 'check() returned no detail'),
1852
+ detail: { status: result && result.status },
1853
+ };
1854
+ }
1855
+ return { ok: true, finding: null, detail: { status: result.status, detail: result.detail } };
1856
+ } catch (e) {
1857
+ return { ok: false, finding: 'capability-ledger-fresh threw: ' + e.message, detail: {} };
1858
+ }
1859
+ },
1860
+ },
1861
+ {
1862
+ // Phase 265 Plan 23 (RADAR-30, R-7): surfaces the MCP surface doctor
1863
+ // organ (lib/core/doctor/mcp-surface-module.cjs) on the acceptance
1864
+ // path, mirroring the capability-ledger-fresh point immediately
1865
+ // above. Calls the organ's check() DIRECTLY in-process; the organ
1866
+ // itself owns both stdio spawns (Canon Part 8: zero network).
1867
+ //
1868
+ // SEVERITY REASONING: 'error' (a zero-tool tools/list response or a
1869
+ // wedged/crashed server) fails this gate -- that is the exact
1870
+ // 2.1.128 signal ("connected with 0 tools") this organ exists to
1871
+ // catch, and it must not ship silently. 'warn' (the measured
1872
+ // combined surface exceeds TOTAL_SURFACE_TOKEN_BUDGET) does NOT fail
1873
+ // this gate -- per the organ's own contract it is a measurement
1874
+ // warning, not a tool-count cap (the Brain's 10-15 figure does not
1875
+ // transfer to a 36-tool server; see the module's header comment).
1876
+ // The finding/detail carries the warn text either way so it is
1877
+ // visible in acceptance output without blocking a release on it.
1878
+ id: 'mcp-surface-tool-count',
1879
+ label: 'MCP surface: live per-server tool count for both servers, fails on zero tools',
1880
+ severity: 'blocker',
1881
+ applies_to: ['pre-tag', 'full'],
1882
+ run: async function () {
1883
+ if (inTestMode && process.env.DOCTOR_TEST_FAIL_POINT === 'mcp-surface-tool-count') {
1884
+ return { ok: false, finding: 'mcp-surface-tool-count synthesized failure (test mode)', detail: {} };
1885
+ }
1886
+ if (process.env.DOCTOR_SKIP_MCP_SURFACE === '1') {
1887
+ return { ok: true, finding: null, detail: { skipped: true, reason: 'DOCTOR_SKIP_MCP_SURFACE=1' } };
1888
+ }
1889
+ try {
1890
+ const mcpSurfaceMod = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'mcp-surface-module.cjs'));
1891
+ const result = mcpSurfaceMod.check({});
1892
+ if (!result || result.status === 'error') {
1893
+ return {
1894
+ ok: false,
1895
+ finding: 'mcp-surface-tool-count: ' + (result && result.detail || 'check() returned no detail'),
1896
+ detail: { status: result && result.status },
1897
+ };
1898
+ }
1899
+ return { ok: true, finding: null, detail: { status: result.status, detail: result.detail } };
1900
+ } catch (e) {
1901
+ return { ok: false, finding: 'mcp-surface-tool-count threw: ' + e.message, detail: {} };
1902
+ }
1903
+ },
1904
+ },
1905
+ ];
1906
+ }
1907
+
1908
+ async function runAcceptance(opts) {
1909
+ const home = opts.home;
1910
+ const pluginRoot = opts.pluginRoot;
1911
+ const flagPreTag = opts.flagPreTag;
1912
+ const flagPreFlight = opts.flagPreFlight;
1913
+ const flagLightNpx = opts.flagLightNpx;
1914
+ const checklist = buildAcceptanceChecklist({ home: home, pluginRoot: pluginRoot, flagLightNpx: flagLightNpx });
1915
+ // Phase 126.1 hotfix (2026-05-15): tri-state mode. --pre-tag wins when both
1916
+ // --pre-tag and --pre-flight are passed; --pre-flight is the strict-subset
1917
+ // tier release.sh Step 2.5 uses BEFORE Step 3 mutates the tree.
1918
+ const mode = flagPreTag ? 'pre-tag' : (flagPreFlight ? 'pre-flight' : 'full');
1919
+ const filtered = checklist.filter(function (p) { return p.applies_to.indexOf(mode) !== -1; });
1920
+ const results = [];
1921
+ const failed = [];
1922
+ for (const p of filtered) {
1923
+ let r;
1924
+ try { r = await p.run(); }
1925
+ catch (e) { r = { ok: false, finding: 'point ' + p.id + ' threw: ' + e.message, detail: {} }; }
1926
+ results.push({ id: p.id, label: p.label, ok: !!r.ok, finding: r.finding || null, detail: r.detail || null });
1927
+ if (!r.ok) failed.push(p.id);
1928
+ }
1929
+ return {
1930
+ mode: mode,
1931
+ points: results,
1932
+ failed_points: failed,
1933
+ summary: { total: results.length, passed: results.length - failed.length, failed: failed.length },
1934
+ };
1935
+ }
1936
+
1937
+ // -- Loop-fires gate: --dogfood-acceptance (Phase 146 Plan 04) -------
1938
+ //
1939
+ // The Phase 146 milestone gate. Composes the 6 ACPT dogfood drivers (Phase 146
1940
+ // Plans 01-03: ACPT-01 engine-fires, ACPT-02 websearch-hat-scoped, ACPT-03
1941
+ // first-material-explore, ACPT-04 filing-cascade-surfaces, ACPT-05
1942
+ // brain-derive-tier-rise; Phase 150.5-03: ACPT-06 dial-atomic-emission)
1943
+ // by spawning each as a child `node <driver>` process and aggregating
1944
+ // their exit codes. Modeled on runAcceptance (the checklist-point shape +
1945
+ // the spawnSync child-process model from the verify-release point), but with
1946
+ // its OWN exit-code contract: 0 = every ACPT hermetic leg passed; non-zero =
1947
+ // a leg failed. NOT a class flag -- the class-flag-always-exit-0 invariant
1948
+ // does NOT apply (per the ROADMAP mandate: exit 0 = milestone ships as "Larry
1949
+ // Reaches", non-zero = the milestone is renamed).
1950
+ //
1951
+ // Canon Part 8: --dogfood-acceptance adds ZERO network surface in doctor
1952
+ // itself -- it only spawns LOCAL node drivers. The ACPT-05 driver's LIVE
1953
+ // mode_a arm self-skips when Brain is unreachable (Plan 03), so the gate is
1954
+ // green on the hermetic legs WITHOUT a reachable Brain. The gate requires the
1955
+ // hermetic mode_b arm, NEVER the live mode_a arm (T-146-14): a down Brain can
1956
+ // never make the gate fake mode_a.
1957
+ //
1958
+ // Each ACPT point: { id, label, run: () -> { ok, finding, detail } } where run
1959
+ // spawns `node <driver>` via child_process.spawnSync with a 120000ms timeout
1960
+ // and ok = (status === 0).
1961
+
1962
+ const ACPT_DRIVERS = [
1963
+ { id: 'ACPT-01', file: 'test-acpt-01-engine-fires.cjs', label: 'navigation engine fires (routing_source legacy->engine on a real fired sensor)' },
1964
+ { id: 'ACPT-02', file: 'test-acpt-02-websearch-hat-scoped.cjs', label: 'WebSearch is hat-scoped (the external-tool affordance respects the hat boundary)' },
1965
+ { id: 'ACPT-03', file: 'test-acpt-03-first-material-explore.cjs', label: 'first material auto-explores (room non-empty by turn 2)' },
1966
+ { id: 'ACPT-04', file: 'test-acpt-04-filing-cascade-surfaces.cjs', label: 'filing surfaces a cross-relationship cascade mid-session' },
1967
+ { id: 'ACPT-05', file: 'test-acpt-05-brain-derive-tier-rise.cjs', label: 'BRAIN.md derive raises tier_mode above tier_0 (hermetic; live mode_a self-skips)' },
1968
+ { id: 'ACPT-06', file: 'test-acpt-06-dial-atomic-emission.cjs', label: 'dial text + card contract emit atomically on the engine arm (real sensor fired from the production turn shape); legacy emits neither' },
1969
+ ];
1970
+
1971
+ async function runDogfoodAcceptance(opts) {
1972
+ opts = opts || {};
1973
+ const pluginRoot = opts.pluginRoot || PLUGIN_ROOT;
1974
+ const cp = require('child_process');
1975
+ const testsDir = path.join(pluginRoot, 'tests');
1976
+
1977
+ const points = ACPT_DRIVERS.map(function (d) {
1978
+ return {
1979
+ id: d.id,
1980
+ label: d.label,
1981
+ run: function () {
1982
+ const driverPath = path.join(testsDir, d.file);
1983
+ if (!fs.existsSync(driverPath)) {
1984
+ return { ok: false, finding: 'driver missing: ' + d.file, detail: { file: d.file } };
1985
+ }
1986
+ const r = cp.spawnSync('node', [driverPath], { encoding: 'utf8', timeout: 120000 });
1987
+ const ok = r.status === 0;
1988
+ const finding = ok ? null : ('driver ' + d.file + ' exited ' + r.status + (r.signal ? ' (signal ' + r.signal + ')' : ''));
1989
+ return {
1990
+ ok: ok,
1991
+ finding: finding,
1992
+ detail: {
1993
+ file: d.file,
1994
+ status: r.status,
1995
+ signal: r.signal || null,
1996
+ stdoutTail: (r.stdout || '').slice(-500),
1997
+ stderrTail: (r.stderr || '').slice(-500),
1998
+ },
1999
+ };
2000
+ },
2001
+ };
2002
+ });
2003
+
2004
+ const results = [];
2005
+ const failed = [];
2006
+ for (const p of points) {
2007
+ let r;
2008
+ try { r = p.run(); }
2009
+ catch (e) { r = { ok: false, finding: 'point ' + p.id + ' threw: ' + e.message, detail: {} }; }
2010
+ results.push({ id: p.id, label: p.label, ok: !!r.ok, finding: r.finding || null, detail: r.detail || null });
2011
+ if (!r.ok) failed.push(p.id);
2012
+ }
2013
+
2014
+ return {
2015
+ gate: 'dogfood-acceptance',
2016
+ points: results,
2017
+ failed_points: failed,
2018
+ summary: { total: results.length, passed: results.length - failed.length, failed: failed.length },
2019
+ };
2020
+ }
2021
+
2022
+ // -- Phase 150-08 (MEM-09 / D-09): the claim harness gate (doctor --claims) -----
2023
+ //
2024
+ // A SIBLING of runAcceptance: it shells tests/claim-harness/run-all-claims.sh
2025
+ // (the C1..C7 falsifiable public-site-claim drivers + the gates) and returns its
2026
+ // OWN self-contained exit code, NOT folded into the class roster. Canon Part 8:
2027
+ // the harness is LOCAL/hermetic; the Brain LIVE arms self-skip via
2028
+ // class-m-brain-smoke, so the gate is CI-green without a reachable Brain.
2029
+ //
2030
+ // Self-test hook (mirrors DOCTOR_TEST_FAIL_POINT): when DOCTOR_CLAIM_FAIL_POINT
2031
+ // is set (any non-empty value) AND the in-test mode is active, the gate
2032
+ // synthesizes a failure of the harness so its own failure path is testable
2033
+ // WITHOUT corrupting the real harness. The accepted token is 'run-all-claims'
2034
+ // (the single point this gate owns); any other non-empty value also trips, so a
2035
+ // test can simply set DOCTOR_CLAIM_FAIL_POINT=run-all-claims.
2036
+ function runClaims(opts) {
2037
+ opts = opts || {};
2038
+ const pluginRoot = opts.pluginRoot || PLUGIN_ROOT;
2039
+ const cp = require('child_process');
2040
+ const harness = path.join(pluginRoot, 'tests', 'claim-harness', 'run-all-claims.sh');
2041
+
2042
+ // DOCTOR_CLAIM_FAIL_POINT self-test (mirrors the DOCTOR_TEST_FAIL_POINT
2043
+ // precedent at runAcceptance). inTestMode mirrors the acceptance self-test
2044
+ // gate: it is on when the env var is set (the test owns the process).
2045
+ const failPoint = process.env.DOCTOR_CLAIM_FAIL_POINT;
2046
+ if (typeof failPoint === 'string' && failPoint.length > 0) {
2047
+ return {
2048
+ gate: 'claims',
2049
+ ok: false,
2050
+ synthesized: true,
2051
+ fail_point: failPoint,
2052
+ status: null,
2053
+ finding: 'DOCTOR_CLAIM_FAIL_POINT synthesized a failure of the claim harness (' + failPoint + ')',
2054
+ summary: { total: 1, passed: 0, failed: 1 },
2055
+ };
2056
+ }
2057
+
2058
+ if (!fs.existsSync(harness)) {
2059
+ return {
2060
+ gate: 'claims',
2061
+ ok: false,
2062
+ synthesized: false,
2063
+ status: null,
2064
+ finding: 'claim harness missing: tests/claim-harness/run-all-claims.sh',
2065
+ summary: { total: 1, passed: 0, failed: 1 },
2066
+ };
2067
+ }
2068
+
2069
+ const r = cp.spawnSync('bash', [harness], { encoding: 'utf8', timeout: 300000 });
2070
+ const ok = r.status === 0;
2071
+ return {
2072
+ gate: 'claims',
2073
+ ok: ok,
2074
+ synthesized: false,
2075
+ status: r.status,
2076
+ signal: r.signal || null,
2077
+ finding: ok ? null : ('claim harness exited ' + r.status + (r.signal ? ' (signal ' + r.signal + ')' : '')),
2078
+ stdoutTail: (r.stdout || '').slice(-800),
2079
+ stderrTail: (r.stderr || '').slice(-400),
2080
+ summary: { total: 1, passed: ok ? 1 : 0, failed: ok ? 0 : 1 },
2081
+ };
2082
+ }
2083
+
2084
+
2085
+ // -- Renderers -------------------------------------------------------
2086
+
2087
+ // Phase 95.1-04 retrofit: 4-zone Shape E (Action Report) per skills/ui-system/SKILL.md.
2088
+ // Zone 1 header literal '-- MindrianOS -- doctor -- {stage} --' (compact form when
2089
+ // projected lines > 30 per D-16). Zone 2 body holds per-class drift findings using
2090
+ // only the 12-glyph vocabulary. Zone 3 reserved for room-proactive signals (D-17;
2091
+ // always empty in Phase 95.1, room-proactive integration deferred). F.1 Next Move
2092
+ // selector renders before Zone 4 when drift detected without --fix (D-18; structural
2093
+ // marker only, canonical AskUserQuestion implementation deferred to Phase 88.2 -- see
2094
+ // .planning/phases/95.1-mos-doctor-drift-detection-and-self-heal/f1-selector-deferred.md).
2095
+ // Zone 4 action footer always present (D-12). Renderer is purely structural; Larry
2096
+ // handles narrative interpretation per D-19.
2097
+ function renderHumanReport(report) {
2098
+ const lines = [];
2099
+
2100
+ // Compute stage label for Zone 1 per D-12 stage values.
2101
+ let stage = 'no-drift';
2102
+ if (report.fixRequested && report.classARecovered) stage = 'recovered';
2103
+ else if (report.fixRequested) stage = 'recovering';
2104
+ else if (report.drift && report.drift.detected) stage = 'drift-detected';
2105
+ // Additional class-driven drift detection added by 95.1 plans 05+06 will populate
2106
+ // report.checks; iterate and aggregate stage accordingly.
2107
+
2108
+ // Compute body rows BEFORE deciding header density (D-16).
2109
+ const bodyRows = [];
2110
+
2111
+ // Class A -- install-cache (existing logic preserved, glyphs swapped to vocabulary).
2112
+ //
2113
+ // Phase 126 Plan-01 fix: when classARecovered is set (recovery succeeded),
2114
+ // the renderer MUST emit BOTH `✓ recovered to <version>` AND `backup <path>`
2115
+ // lines per the commands/doctor.md Step 3 contract -- INCLUDING the
2116
+ // missing-install case (installResult.status === 'missing') where the
2117
+ // pre-fix renderer fell into the `cannot read state` branch and never
2118
+ // emitted the contract lines (2026-05-13 Windows dogfood finding 3a).
2119
+ // Recovery-succeeded takes precedence over status-shape branching.
2120
+ if (report.classARecovered) {
2121
+ // Drift was detected (either status==='ok' + cmp<0 OR status==='missing'
2122
+ // with available cache; see drift detection at line ~2634-2649) and was
2123
+ // closed by performRecoveryAtomic. Use the drift-detected layout for
2124
+ // continuity with the live-stale recovery case.
2125
+ bodyRows.push(` ${C.yellow}■${C.reset} install-cache ${C.yellow}⚠${C.reset} drift detected`);
2126
+ // Show the live -> recovered transition. For the missing-install case
2127
+ // report.install.version is undefined; show `missing` instead so the
2128
+ // line is still informative.
2129
+ const liveLabel = report.install.version || (report.install.status === 'missing' ? 'missing' : 'unknown');
2130
+ bodyRows.push(` ${C.dim}live${C.reset} ${C.yellow}${liveLabel}${C.reset} ${C.dim}→${C.reset} ${C.green}${report.cache.latest || report.classARecovered.recoveredVersion}${C.reset}`);
2131
+ bodyRows.push(` ${C.green}✓${C.reset} recovered to ${C.green}${report.classARecovered.recoveredVersion}${C.reset}`);
2132
+ // Backup line: omit when backup is null (missing-install case -- nothing
2133
+ // existed to rename; performRecoveryAtomic returned backup=null at
2134
+ // line ~370 of this file). The contract example specifies the line is
2135
+ // emitted WHEN a backup exists, not unconditionally.
2136
+ if (report.classARecovered.backup) {
2137
+ bodyRows.push(` ${C.dim}backup ${report.classARecovered.backup}${C.reset}`);
2138
+ }
2139
+ } else if (report.install.status === 'ok' && report.cache.status === 'ok') {
2140
+ if (report.drift.detected) {
2141
+ bodyRows.push(` ${C.yellow}■${C.reset} install-cache ${C.yellow}⚠${C.reset} drift detected`);
2142
+ bodyRows.push(` ${C.dim}live${C.reset} ${C.yellow}${report.install.version}${C.reset} ${C.dim}→${C.reset} ${C.green}${report.cache.latest}${C.reset}`);
2143
+ if (report.fixRequested) {
2144
+ // RCA doctor-marketplace-cache-drift-deadlock (2026-06-12): three-way
2145
+ // branch replaces the single `recovery failed: <err || 'unknown'>`
2146
+ // line. The literal 'unknown' rendered whenever recovery was never
2147
+ // ATTEMPTED (gated/skipped) -- classARecovered and recoveryError both
2148
+ // null is not a failure, it is an un-run.
2149
+ if (report.recoveryError) {
2150
+ bodyRows.push(` ${C.red}⚠${C.reset} recovery failed: ${report.recoveryError}`);
2151
+ } else if (report.recoverySkipped) {
2152
+ bodyRows.push(` ${C.yellow}⚠${C.reset} recovery skipped (topology marketplace-cache -- legacy install dir is vestigial)`);
2153
+ } else {
2154
+ bodyRows.push(` ${C.dim}recovery not attempted (gated)${C.reset}`);
2155
+ }
2156
+ }
2157
+ } else {
2158
+ bodyRows.push(` ${C.green}■${C.reset} install-cache ${C.green}✓${C.reset} healthy ${C.dim}(${report.install.version})${C.reset}`);
2159
+ }
2160
+ } else {
2161
+ bodyRows.push(` ${C.red}■${C.reset} install-cache ${C.red}⚠${C.reset} cannot read state`);
2162
+ if (report.install.status !== 'ok') bodyRows.push(` ${C.dim}install: ${report.install.detail || report.install.status}${C.reset}`);
2163
+ if (report.cache.status !== 'ok') bodyRows.push(` ${C.dim}cache: ${report.cache.detail || report.cache.status}${C.reset}`);
2164
+ }
2165
+
2166
+ // Dev source consistency.
2167
+ if (report.dev.status === 'ok') {
2168
+ bodyRows.push(` ${C.green}■${C.reset} dev-source ${C.green}✓${C.reset} consistent ${C.dim}(${report.dev.pluginJson})${C.reset}`);
2169
+ } else if (report.dev.status === 'mismatch') {
2170
+ bodyRows.push(` ${C.yellow}■${C.reset} dev-source ${C.yellow}⚠${C.reset} version mismatch`);
2171
+ bodyRows.push(` ${C.dim}plugin.json ${report.dev.pluginJson}${C.reset}`);
2172
+ bodyRows.push(` ${C.dim}package.json ${report.dev.packageJson}${C.reset}`);
2173
+ }
2174
+ // status=skip silently omitted (acceptable for end users without dev clone).
2175
+
2176
+ // Slot for class B/C/D/E/F/G rows -- populated by Plans 95.1-05 + 95.1-06
2177
+ // and Plan 106-03 via report.checks.* extension. Each new check reads from
2178
+ // report.checks[name] and pushes rows in the same Shape-E format:
2179
+ // filled-square + class label + status glyph + detail. Glyphs match the
2180
+ // existing class A pattern: green check for ok, yellow warn for warn,
2181
+ // red warn for error (mirrors line 1067 'cannot read state'), dim slash
2182
+ // for skip. Class F UI compliance scanner approves these glyphs (U+2717
2183
+ // BALLOT X is forbidden; U+26A0 WARN and U+2298 CIRCLED DIVISION SLASH
2184
+ // are approved per the 12-glyph vocabulary).
2185
+ if (report.checks) {
2186
+ // Class G (statusline-visibility) + class H (install-incomplete) hand-coded
2187
+ // render branches RETIRED (Phase 217 Plan 05): both rows -- and class H's
2188
+ // hint sub-line -- now come from the generic loop below + the runner's
2189
+ // action_lines[]. After this plan the ONLY non-generic render path left is
2190
+ // class A (install-cache), the sanctioned carve-out (Plan 06).
2191
+ for (const [name, check] of Object.entries(report.checks)) {
2192
+ if (name === 'install-cache') continue; // already handled above.
2193
+ // Skip entries are omitted, matching the plugin-enabled-state precedent
2194
+ // above and computeSummary's skip omission, so visible rows stay in exact
2195
+ // parity with the Summary tally.
2196
+ if (!check || check.status === 'skip') continue;
2197
+ let glyph;
2198
+ let color;
2199
+ if (check.status === 'ok') { glyph = '✓'; color = C.green; }
2200
+ else if (check.status === 'warn') { glyph = '⚠'; color = C.yellow; }
2201
+ else if (check.status === 'error') { glyph = '⚠'; color = C.red; }
2202
+ else { glyph = '⊘'; color = C.dim; }
2203
+ bodyRows.push(' ' + color + '■' + C.reset + ' ' + name.padEnd(28) + color + glyph + C.reset + ' ' + (check.detail || check.status));
2204
+ // Phase 217 Plan-01: action_lines sub-line support. The structural home
2205
+ // for the hand-coded hint sub-lines (class H/K/N) that later migration
2206
+ // plans delete: a cadence:always module returns action_lines[] and the
2207
+ // generic renderer prints one dim `-> ` sub-line per entry.
2208
+ if (Array.isArray(check.action_lines)) {
2209
+ for (const line of check.action_lines) {
2210
+ bodyRows.push(' ' + C.dim + '-> ' + line + C.reset);
2211
+ }
2212
+ }
2213
+ }
2214
+ }
2215
+
2216
+ // Render --fix outcomes so a fix attempt is never invisible. Every --fix
2217
+ // dispatch pushes its result into report.recovered; class A recovery already
2218
+ // renders via classARecovered above, so the install-cache tool is guarded to
2219
+ // avoid a double line.
2220
+ if (report.fixRequested && Array.isArray(report.recovered) && report.recovered.length > 0) {
2221
+ for (const entry of report.recovered) {
2222
+ if (!entry) continue;
2223
+ if (entry.status === 'skip') continue; // skip entries omitted.
2224
+ if (entry.tool === 'install-cache') continue; // already rendered via classARecovered.
2225
+ let glyph;
2226
+ let color;
2227
+ if (entry.status === 'ok') { glyph = '✓'; color = C.green; }
2228
+ else if (entry.status === 'partial') { glyph = '⚠'; color = C.yellow; }
2229
+ else if (entry.status === 'error') { glyph = '⚠'; color = C.red; }
2230
+ else { glyph = '⊘'; color = C.dim; }
2231
+ bodyRows.push(' ' + color + glyph + C.reset + ' fix ' + (entry.tool || 'unknown-tool') + ': ' + (entry.detail || entry.status));
2232
+ }
2233
+ }
2234
+
2235
+ // Summary line. Aggregate counts from install/dev/drift signals plus any
2236
+ // report.checks.* rows that Plans 05+06 will register.
2237
+ const summary = computeSummary(report);
2238
+ bodyRows.push('');
2239
+ bodyRows.push(` ${C.dim}Summary: ${C.green}${summary.healthy}${C.dim} healthy / ${C.yellow}${summary.drift}${C.dim} drift / ${C.red}${summary.warnings}${C.dim} warnings${C.reset}`);
2240
+
2241
+ // F.1 Next Move selector (D-18) -- only when drift AND !fix. Phase 95.1 ships
2242
+ // a STRUCTURAL marker. Larry handles conversational selection per D-19.
2243
+ // Phase 88.2 will replace this with the canonical AskUserQuestion primitive.
2244
+ // See .planning/phases/95.1-.../f1-selector-deferred.md for the deferral note.
2245
+ if (!report.fixRequested && (report.drift.detected || summary.drift > 0)) {
2246
+ bodyRows.push('');
2247
+ bodyRows.push(` ${C.cyan}[F.1 Next Move]${C.reset}`);
2248
+ bodyRows.push(` ${C.cyan}▶${C.reset} Run ${C.cyan}/mos:doctor --fix${C.reset}`);
2249
+ bodyRows.push(` ${C.dim}▷${C.reset} Defer`);
2250
+ bodyRows.push(` ${C.dim}▷${C.reset} Free-Text`);
2251
+ }
2252
+
2253
+ // Zone 4 action footer -- ALWAYS present (D-12 mandate). 2-3 grounded /mos:
2254
+ // commands. Primary action uses U+25B6, alternatives use U+25B7.
2255
+ bodyRows.push('');
2256
+ if (report.drift.detected || summary.drift > 0) {
2257
+ bodyRows.push(` ${C.cyan}▶ /mos:doctor --fix --all${C.reset} ${C.dim}# auto-recover all drift classes${C.reset}`);
2258
+ bodyRows.push(` ${C.dim}▷${C.reset} ${C.cyan}/mos:rooms${C.reset} ${C.dim}# inspect known rooms${C.reset}`);
2259
+ bodyRows.push(` ${C.dim}▷${C.reset} ${C.cyan}/mos:doctor --json${C.reset} ${C.dim}# machine-readable output${C.reset}`);
2260
+ } else {
2261
+ bodyRows.push(` ${C.cyan}▶ /mos:status${C.reset} ${C.dim}# room state overview${C.reset}`);
2262
+ bodyRows.push(` ${C.dim}▷${C.reset} ${C.cyan}/mos:doctor --all${C.reset} ${C.dim}# re-run all classes${C.reset}`);
2263
+ bodyRows.push(` ${C.dim}▷${C.reset} ${C.cyan}/mos:doctor --json${C.reset} ${C.dim}# machine-readable output${C.reset}`);
2264
+ }
2265
+
2266
+ // Zone 1 header (decided AFTER body rows for density rule, D-16).
2267
+ // Zone 3 (Intelligence Strip) stays empty in 95.1 per D-17 -- room-proactive
2268
+ // integration deferred. Default behavior: omit Zone 3 entirely.
2269
+ const projectedLineCount = bodyRows.length + 4; // header + blank + body + trailing blank.
2270
+ if (projectedLineCount > 30) {
2271
+ // Compact header per D-16 density rule.
2272
+ const classCount = Object.keys(report.checks || { 'install-cache': 1 }).length;
2273
+ lines.push(`${C.dim}-- doctor: ${classCount} drift classes checked --${C.reset}`);
2274
+ } else {
2275
+ // Standard Zone 1 header per D-12. Literal prefix '-- MindrianOS -- doctor'
2276
+ // is asserted by tests/test-doctor-ui-self-compliant.cjs scenario 3.
2277
+ lines.push(`${C.dim}-- MindrianOS -- doctor -- ${stage} --${C.reset}`);
2278
+ }
2279
+ lines.push('');
2280
+ for (const row of bodyRows) lines.push(row);
2281
+ lines.push('');
2282
+
2283
+ return lines.join('\n');
2284
+ }
2285
+
2286
+ // Helper for Zone 2 summary aggregation. Walks install/dev/drift signals plus
2287
+ // any report.checks.* rows that Plans 05+06 will register and returns
2288
+ // healthy / drift / warning totals.
2289
+ function computeSummary(report) {
2290
+ let healthy = 0;
2291
+ let drift = 0;
2292
+ let warnings = 0;
2293
+ // Class A -- install-cache + drift signal.
2294
+ //
2295
+ // Phase 126 Plan-01 fix: after a successful classA recovery, the drift was
2296
+ // RESOLVED -- the summary must NOT report `1 drift` while the header says
2297
+ // `recovered` (Option A: decrement-after-recovery, minimum surface delta
2298
+ // per Canon Part 7). 2026-05-13 Windows dogfood finding 3a flagged the
2299
+ // `0 healthy / 1 drift / 0 warnings` line as semantically inconsistent
2300
+ // with the recovered header. After recovery the install is on the cache's
2301
+ // latest version; classify as healthy.
2302
+ if (report.classARecovered) {
2303
+ healthy += 1;
2304
+ } else if (report.install.status === 'ok' && report.cache.status === 'ok' && !report.drift.detected) {
2305
+ healthy += 1;
2306
+ } else if (report.drift.detected) {
2307
+ drift += 1;
2308
+ } else {
2309
+ warnings += 1;
2310
+ }
2311
+ // Dev source.
2312
+ if (report.dev.status === 'ok') healthy += 1;
2313
+ else if (report.dev.status === 'mismatch') warnings += 1;
2314
+ // Future class B/C/D/E/F: walk report.checks.
2315
+ if (report.checks) {
2316
+ for (const [name, check] of Object.entries(report.checks)) {
2317
+ if (name === 'install-cache') continue;
2318
+ if (check && check.status === 'ok') healthy += 1;
2319
+ else if (check && (check.status === 'warn' || check.status === 'error')) drift += 1;
2320
+ // status='skip' omitted from totals.
2321
+ }
2322
+ }
2323
+ return { healthy, drift, warnings };
2324
+ }
2325
+
2326
+ // -- Accumulative engine (Phase 139-02, S2) --------------------------
2327
+ //
2328
+ // The version-dispatch substrate that converts doctor's frozen Phase-95 class
2329
+ // roster into a forward-healing engine. Generalizes the proven
2330
+ // lib/core/install-state.cjs::migrateIfNeeded shape: iterate the hand-maintained
2331
+ // module registry (data/doctor-modules.json), run every module whose
2332
+ // introduced_version is in the (applied_through, running] window, DEFER any
2333
+ // future-version module (introduced > running), and never re-run an
2334
+ // already-applied one (the persisted watermark at ~/.mindrian/doctor-applied.json
2335
+ // never regresses -> re-running is a no-op).
2336
+ //
2337
+ // In THIS plan the registry is seeded EMPTY of organ modules, so the loop runs
2338
+ // zero modules -- but the window math + runner-dispatch wiring is complete, so
2339
+ // Plan 03 only adds a registry entry + a runner file, no engine code.
2340
+ //
2341
+ // Canon Part 8: LOCAL file reads only. Zero network surface; runs no Brain query.
2342
+
2343
+ // Normalize a version string for semver comparison. Prefer semver.valid (which
2344
+ // PRESERVES prerelease labels so 1.13.1-beta.4 vs 1.13.1-beta.3 compare
2345
+ // correctly) and fall back to semver.coerce only for sloppy input that is not
2346
+ // already valid semver. Returns null when neither resolves.
2347
+ function _normalizeVersion(v) {
2348
+ if (typeof v !== 'string' || !v) return null;
2349
+ const valid = semver.valid(v);
2350
+ if (valid) return valid;
2351
+ const coerced = semver.coerce(v);
2352
+ return coerced ? coerced.version : null;
2353
+ }
2354
+
2355
+ /**
2356
+ * Run the accumulative-engine selector.
2357
+ *
2358
+ * @param {object} [flags] CLI flags (fix, dryRun, json, flags, classFlagsActive)
2359
+ * OR a test opts seam ({ home, running, registry, dryRun }). The test
2360
+ * seam keys take precedence so the test never depends on the live
2361
+ * registry. `flags` is the full parsed CLI flags object and
2362
+ * `classFlagsActive` the class-flag-mode boolean; both drive the
2363
+ * Phase 217 cadence/flag gates (default {} + false => baseWanted true,
2364
+ * keeping every pre-217 caller byte-compatible).
2365
+ * @returns {{
2366
+ * status: 'ok'|'skip',
2367
+ * detail?: string,
2368
+ * running: string|null,
2369
+ * applied_through: string|null,
2370
+ * selected: Array<{id, introduced_version}>,
2371
+ * deferred: Array<{id, introduced_version}>,
2372
+ * findings: Array<object>,
2373
+ * advanced: boolean,
2374
+ * checks: Object<string, object>,
2375
+ * recovered: Array<object>
2376
+ * }}
2377
+ */
2378
+ // resolveAlwaysRunner(mod): resolve the check/fix pair for a cadence:always
2379
+ // module. Test seams (mod._check / mod._fix) win; otherwise require the runner
2380
+ // file relative to the plugin root and read its exported check/fix. Never
2381
+ // throws (a require failure leaves the fn null -> the caller records an error
2382
+ // finding), so one bad module cannot crash the always-pass (T-217-01).
2383
+ function resolveAlwaysRunner(mod) {
2384
+ let checkFn = (typeof mod._check === 'function') ? mod._check : null;
2385
+ let fixFn = (typeof mod._fix === 'function') ? mod._fix : null;
2386
+ if ((!checkFn || !fixFn) && mod.runner) {
2387
+ try {
2388
+ const runnerPath = path.isAbsolute(mod.runner)
2389
+ ? mod.runner
2390
+ : path.join(PLUGIN_ROOT, mod.runner);
2391
+ const runnerMod = require(runnerPath);
2392
+ if (!checkFn && typeof runnerMod.check === 'function') checkFn = runnerMod.check;
2393
+ if (!fixFn && typeof runnerMod.fix === 'function') fixFn = runnerMod.fix;
2394
+ } catch (_) { /* leave null; the caller soft-fails to an error finding */ }
2395
+ }
2396
+ return { checkFn, fixFn };
2397
+ }
2398
+
2399
+ function runAccumulativeEngine(flags) {
2400
+ const opts = flags || {};
2401
+ const home = opts.home || process.env.HOME || process.env.USERPROFILE || HOME;
2402
+ const dryRun = !!opts.dryRun;
2403
+ const wantFix = !!opts.fix;
2404
+ // Phase 217 Plan-01: the full parsed CLI flags object + the class-flag-mode
2405
+ // boolean, both carried so the engine can reproduce the exact gate that
2406
+ // today lives at the main() call site. Defaults keep every pre-217 caller
2407
+ // (the hermetic selector tests pass only { home, running, registry })
2408
+ // byte-compatible: flags {} + classFlagsActive false => baseWanted true.
2409
+ const callerFlags = (opts.flags && typeof opts.flags === 'object') ? opts.flags : {};
2410
+ const classFlagsActive = !!opts.classFlagsActive;
2411
+ // baseWanted is the class-A/N "runs on a bare / --all / --fix run, but a
2412
+ // class-flag-only run (e.g. --room-md) skips it" gate -- the exact predicate
2413
+ // that used to guard the engine call in main() (line ~5534).
2414
+ const baseWanted = !!(callerFlags.all || callerFlags.fix || !classFlagsActive);
2415
+
2416
+ // 1. Resolve the RUNNING version of record. Test seam: opts.running wins.
2417
+ // Otherwise read the active plugin.json via checkInstallVersion().
2418
+ let runningRaw = (typeof opts.running !== 'undefined') ? opts.running : null;
2419
+ if (runningRaw === null && typeof opts.running === 'undefined') {
2420
+ try {
2421
+ const iv = checkInstallVersion();
2422
+ runningRaw = iv && iv.version ? iv.version : null;
2423
+ } catch (_) {
2424
+ runningRaw = null;
2425
+ }
2426
+ // Phase 217 Plan 05/06: when checkInstallVersion cannot resolve the install
2427
+ // (e.g. a hermetic test that overrides HOME so ~/.claude/plugins is not
2428
+ // where checkInstallVersion looked, or an install loaded from an unexpected
2429
+ // path) OR resolves a NON-CLEAN-SEMVER version (a 4-component ship version
2430
+ // like 1.12.5.1 that only survives via semver.coerce, which DROPS the 4th
2431
+ // component -- 1.12.5.1 -> 1.12.5 -- and would then place the install BELOW
2432
+ // a diagnostic's introduced_version on the deferred-guard number line, so a
2433
+ // cadence:always check that ships WITH this code would be wrongly DEFERRED),
2434
+ // fall back to the version of record of the CODE that is actually running --
2435
+ // PLUGIN_ROOT/.claude-plugin/plugin.json. That path is HOME-independent, so
2436
+ // the accumulative engine never goes dark (the Pitfall-1 silence the
2437
+ // always-cadence design exists to prevent, and the regression class I test
2438
+ // i.6 pins: a 1.12.5.1 install must still surface report.checks
2439
+ // ['install-state']). Real installs with CLEAN semver resolve via
2440
+ // checkInstallVersion first, so this is a no-op for them. Only reached when
2441
+ // opts.running was undefined (the real CLI path); an EXPLICIT opts.running:
2442
+ // null still yields the soft-fail skip. semver.valid rejects 4-component and
2443
+ // accepts prereleases (1.13.0-beta.13 stays as-resolved), which is exactly
2444
+ // the clean-vs-coerced distinction the deferred-guard needs.
2445
+ if (!semver.valid(typeof runningRaw === 'string' ? runningRaw : '')) {
2446
+ try {
2447
+ const devPluginJson = path.join(PLUGIN_ROOT, '.claude-plugin', 'plugin.json');
2448
+ if (fs.existsSync(devPluginJson)) {
2449
+ const j = JSON.parse(fs.readFileSync(devPluginJson, 'utf8'));
2450
+ runningRaw = j && j.version ? j.version : null;
2451
+ }
2452
+ } catch (_) {
2453
+ runningRaw = null;
2454
+ }
2455
+ }
2456
+ }
2457
+ const running = _normalizeVersion(runningRaw);
2458
+ if (!running) {
2459
+ return {
2460
+ status: 'skip',
2461
+ detail: 'running version unresolved',
2462
+ running: null,
2463
+ applied_through: null,
2464
+ selected: [],
2465
+ deferred: [],
2466
+ findings: [],
2467
+ advanced: false,
2468
+ checks: {},
2469
+ recovered: [],
2470
+ };
2471
+ }
2472
+
2473
+ // 2. Read the registry (soft-fail to { modules: [] } on parse error).
2474
+ // Test seam: opts.registry wins.
2475
+ let registry;
2476
+ if (opts.registry && typeof opts.registry === 'object') {
2477
+ registry = opts.registry;
2478
+ } else {
2479
+ try {
2480
+ registry = JSON.parse(fs.readFileSync(DOCTOR_MODULES_PATH, 'utf8'));
2481
+ } catch (_) {
2482
+ registry = { modules: [] };
2483
+ }
2484
+ }
2485
+ const modules = Array.isArray(registry.modules) ? registry.modules : [];
2486
+
2487
+ // 2b. Split registry modules by CADENCE (Phase 217 Plan-01, Pitfall 1):
2488
+ // - cadence:'always' = a per-invocation DIAGNOSTIC. Its check() runs on
2489
+ // EVERY doctor invocation, immune to the watermark. This is what stops
2490
+ // a migrated D-01 check from going permanently silent after one run.
2491
+ // - cadence:'once' (or ABSENT, back-compat) = a watermark-gated HEAL
2492
+ // (umbilical's existing semantics). The Plan 02 contract test forces the
2493
+ // registry file itself to declare cadence explicitly.
2494
+ const onceModules = [];
2495
+ const alwaysModules = [];
2496
+ for (const mod of modules) {
2497
+ if (!mod || typeof mod.id !== 'string') continue;
2498
+ if (mod.cadence === 'always') alwaysModules.push(mod);
2499
+ else onceModules.push(mod);
2500
+ }
2501
+
2502
+ // 3. Read the watermark. null applied_through means "first run; window is
2503
+ // (null, running] = everything <= running". Read unconditionally so the
2504
+ // return shape reports applied_through even when the once-pass is gated off.
2505
+ const wm = readDoctorApplied(home);
2506
+ const appliedThrough = wm.applied_through; // semver string | null
2507
+
2508
+ const selected = [];
2509
+ const deferred = [];
2510
+ const findings = [];
2511
+ const ranModuleIds = [];
2512
+ let anyHardError = false;
2513
+ let advanced = false;
2514
+
2515
+ // 4. ONCE pass (watermark-gated heals; umbilical). Behavior byte-identical to
2516
+ // pre-217 with ONE relocation: the entire once-pass (window selection +
2517
+ // runner invocation + watermark advance) is now gated behind baseWanted --
2518
+ // the exact `flags.all || flags.fix || !classFlagsActive` predicate that
2519
+ // used to live at the main() call site. A class-flag-only run (--room-md)
2520
+ // therefore never runs the once-heals and never advances the watermark,
2521
+ // preserving the pre-217 invariant. When the gate is false the once-pass
2522
+ // yields selected:[], advanced:false.
2523
+ if (baseWanted) {
2524
+ // SELECT once modules where introduced_version is in (applied_through, running].
2525
+ // Lower bound EXCLUSIVE (already-applied), upper bound INCLUSIVE (current).
2526
+ // A future-version module (introduced > running) is DEFERRED, never run --
2527
+ // the future-version-DEFER shape from install-state.migrateIfNeeded.
2528
+ const lower = appliedThrough || '0.0.0';
2529
+ for (const mod of onceModules) {
2530
+ const introduced = _normalizeVersion(mod.introduced_version);
2531
+ if (!introduced) {
2532
+ // Malformed introduced_version -> skip (cannot place in the window).
2533
+ continue;
2534
+ }
2535
+ const aboveLower = semver.gt(introduced, _normalizeVersion(lower) || '0.0.0');
2536
+ const atOrBelowRunning = semver.lte(introduced, running);
2537
+ if (!atOrBelowRunning) {
2538
+ deferred.push(mod);
2539
+ continue;
2540
+ }
2541
+ if (aboveLower) {
2542
+ selected.push(mod);
2543
+ }
2544
+ // introduced <= applied_through -> already applied -> skip silently.
2545
+ }
2546
+
2547
+ // 5. Run each selected module's runner, wrapped in try/catch so one bad
2548
+ // module cannot crash doctor (soft-fail -> error finding, loop continues).
2549
+ // --fix runs the module fixer; read-only runs the checker only.
2550
+ for (const mod of selected) {
2551
+ try {
2552
+ // Resolve the runner. Test seam: an in-test runner is attached as
2553
+ // mod._check / mod._fix. Otherwise require the runner file relative to
2554
+ // the plugin root and invoke its exported check(opts)/fix(opts) entry.
2555
+ let entryResult;
2556
+ if (wantFix && typeof mod._fix === 'function') {
2557
+ entryResult = mod._fix({ home, running, dryRun });
2558
+ } else if (typeof mod._check === 'function') {
2559
+ entryResult = mod._check({ home, running, dryRun });
2560
+ } else if (mod.runner) {
2561
+ const runnerPath = path.isAbsolute(mod.runner)
2562
+ ? mod.runner
2563
+ : path.join(PLUGIN_ROOT, mod.runner);
2564
+ const runnerMod = require(runnerPath);
2565
+ const entry = (wantFix && typeof runnerMod.fix === 'function')
2566
+ ? runnerMod.fix
2567
+ : runnerMod.check;
2568
+ if (typeof entry !== 'function') {
2569
+ throw new Error('runner ' + mod.runner + ' exposes no ' + (wantFix ? 'fix/check' : 'check') + ' function');
2570
+ }
2571
+ entryResult = entry({ home, running, dryRun, fix: wantFix });
2572
+ } else {
2573
+ throw new Error('module ' + mod.id + ' has no runner');
2574
+ }
2575
+ findings.push(Object.assign({ id: mod.id, status: 'ok' }, entryResult && typeof entryResult === 'object' ? entryResult : {}));
2576
+ ranModuleIds.push(mod.id);
2577
+ } catch (err) {
2578
+ anyHardError = true;
2579
+ findings.push({ id: mod.id, status: 'error', detail: (err && err.message) || 'runner threw' });
2580
+ }
2581
+ }
2582
+
2583
+ // 6. Idempotency: advance the watermark to running ONLY on a non-dry-run with
2584
+ // no hard runner error. Re-running with the watermark already at running
2585
+ // selects ZERO modules (window (running, running] is empty) -> no-op.
2586
+ // An EMPTY window (zero selected) on a clean run still advances so the
2587
+ // ledger records the heal-through point.
2588
+ if (!dryRun && !anyHardError) {
2589
+ advanced = writeDoctorApplied(home, { appliedThrough: running, ranModuleIds });
2590
+ }
2591
+ }
2592
+
2593
+ // 7. ALWAYS pass (Phase 217 Plan-01, Pitfall 1). For each cadence:always
2594
+ // module in registry array order: flag-gate, deferred-guard (upper bound
2595
+ // only -- NO watermark lower bound, that is the whole point), run check()
2596
+ // on EVERY invocation, optional fix-then-recheck. Results land ONLY in
2597
+ // alwaysChecks (never in the once-path findings[]), so the selector-test
2598
+ // assertions on findings stay valid and Pitfall 3 is impossible.
2599
+ const alwaysChecks = {};
2600
+ const recovered = [];
2601
+ for (const mod of alwaysModules) {
2602
+ // a. Flag gate. null/undefined flag => the class-A/N baseWanted gate (runs
2603
+ // under bare / --all / --fix when no class flag is active). A named flag
2604
+ // => the module runs only when that parseArgs flag is set.
2605
+ const flagKey = mod.flag;
2606
+ const wanted = (flagKey === null || flagKey === undefined)
2607
+ ? baseWanted
2608
+ : !!callerFlags[flagKey];
2609
+ if (!wanted) continue; // no report entry -- matches today's absent-when-not-requested behavior.
2610
+
2611
+ // b. Deferred guard (registry/code-skew safety ONLY): a module introduced in
2612
+ // a FUTURE version is deferred. There is NO lower-bound / watermark check
2613
+ // for always modules -- that immunity is the point (Pitfall 1).
2614
+ const introduced = _normalizeVersion(mod.introduced_version);
2615
+ if (introduced && semver.gt(introduced, running)) {
2616
+ deferred.push(mod);
2617
+ continue;
2618
+ }
2619
+
2620
+ // c. Build ctx. `checks` is the accumulating always-result map so a later
2621
+ // module can read an earlier same-invocation result (Plan 06 reads
2622
+ // ctx.checks['install-state']).
2623
+ const ctx = { home, running, dryRun, fix: wantFix, flags: callerFlags, checks: alwaysChecks };
2624
+
2625
+ // d. Resolve + run check() inside try/catch (soft-fail -> error row,
2626
+ // self-DoS mitigation T-217-01).
2627
+ const { checkFn, fixFn } = resolveAlwaysRunner(mod);
2628
+ let result;
2629
+ try {
2630
+ if (typeof checkFn !== 'function') {
2631
+ throw new Error('module ' + mod.id + ' exposes no check function');
2632
+ }
2633
+ result = checkFn(ctx);
2634
+ } catch (err) {
2635
+ result = { status: 'error', detail: (err && err.message) || 'runner threw' };
2636
+ }
2637
+
2638
+ // e. Fix-then-recheck flow (mirrors the ad-hoc class E/G/H pattern). Only
2639
+ // when --fix was requested, the module declares fix_supported === true,
2640
+ // a fixer exists, the check surfaced a warn/error, and the result is not
2641
+ // explicitly marked unrecoverable.
2642
+ if (wantFix && mod.fix_supported === true && typeof fixFn === 'function'
2643
+ && result && (result.status === 'warn' || result.status === 'error')
2644
+ && result.recoverable !== false) {
2645
+ let fixRes;
2646
+ try {
2647
+ fixRes = fixFn(Object.assign({}, ctx, { check_result: result }));
2648
+ } catch (err) {
2649
+ fixRes = { status: 'error', detail: (err && err.message) || 'fixer threw' };
2650
+ }
2651
+ // Re-run the check so the reported result reflects the post-fix state.
2652
+ try {
2653
+ result = checkFn(ctx);
2654
+ } catch (err) {
2655
+ result = { status: 'error', detail: (err && err.message) || 'runner threw' };
2656
+ }
2657
+ result.fix_result = fixRes;
2658
+ // Recovered plumbing. Object.assign order matters: the fixer's own `tool`
2659
+ // field wins when present (preserves class E's existing recovery record
2660
+ // shape, e.g. tool:'generate-section-intelligence').
2661
+ if (fixRes && Array.isArray(fixRes.recoveries)) {
2662
+ for (const rec of fixRes.recoveries) {
2663
+ recovered.push(Object.assign({ tool: mod.id }, rec));
2664
+ }
2665
+ } else {
2666
+ recovered.push(Object.assign({ tool: mod.id }, fixRes));
2667
+ }
2668
+ }
2669
+
2670
+ // f. Always results go ONLY into alwaysChecks, NEVER the once-path findings[].
2671
+ alwaysChecks[mod.id] = result;
2672
+ }
2673
+
2674
+ return {
2675
+ status: 'ok',
2676
+ running,
2677
+ applied_through: appliedThrough,
2678
+ selected: selected.map((m) => ({ id: m.id, introduced_version: m.introduced_version })),
2679
+ deferred: deferred.map((m) => ({ id: m.id, introduced_version: m.introduced_version })),
2680
+ findings,
2681
+ advanced: !!advanced,
2682
+ checks: alwaysChecks,
2683
+ recovered,
2684
+ };
2685
+ }
2686
+
2687
+ // -- Phase 150.9 Class Q / heal-arm helpers --------------------------
2688
+ //
2689
+ // resolveGsdTools(): locate gsd-tools.cjs mirroring the workflows/health.md:52
2690
+ // _GSD_TOOLS resolver order. NEVER hardcodes a single path. Honors the
2691
+ // MINDRIAN_DOCTOR_GSD_TOOLS env override FIRST (hermetic-test injection +
2692
+ // operator override). Returns an absolute path or null (-> Class Q emits a
2693
+ // 'skip' finding; the run never crashes). Canon Part 8: locates a LOCAL tool;
2694
+ // no network.
2695
+ function resolveGsdTools() {
2696
+ const fsLocal = require('fs');
2697
+ // 0. Explicit override (tests + operators).
2698
+ const override = process.env.MINDRIAN_DOCTOR_GSD_TOOLS;
2699
+ if (override) {
2700
+ return fsLocal.existsSync(override) ? override : null;
2701
+ }
2702
+ const SHIM = 'gsd-tools.cjs';
2703
+ const candidates = [];
2704
+ // 1. RUNTIME_DIR / repo-root gsd-core.
2705
+ const runtimeRoot = process.env.RUNTIME_DIR || path.resolve(__dirname, '..');
2706
+ candidates.push(path.join(runtimeRoot, 'gsd-core', 'bin', SHIM));
2707
+ candidates.push(path.join(runtimeRoot, '.claude', 'gsd-core', 'bin', SHIM));
2708
+ // 2. HOME-installed gsd-core (the common dev/CI topology).
2709
+ const home = process.env.HOME || process.env.USERPROFILE;
2710
+ if (home) candidates.push(path.join(home, '.claude', 'gsd-core', 'bin', SHIM));
2711
+ for (const c of candidates) {
2712
+ try { if (fsLocal.existsSync(c)) return c; } catch (_e) { /* keep looking */ }
2713
+ }
2714
+ return null;
2715
+ }
2716
+
2717
+ // extractPhaseToken(message): pull a phase label (digits + dots, e.g. "150.7")
2718
+ // out of a W007 ROADMAP-gap message. Returns the token or null.
2719
+ function extractPhaseToken(message) {
2720
+ const s = String(message || '');
2721
+ // "Phase 150.7 exists on disk but not in ROADMAP.md"
2722
+ let m = s.match(/Phase\s+([0-9][0-9.]*)\b/i);
2723
+ if (m) return m[1];
2724
+ // Fallback: a leading "<NN>-<name>" directory token.
2725
+ m = s.match(/\b([0-9][0-9.]*)-[a-z]/i);
2726
+ return m ? m[1] : null;
2727
+ }
2728
+
2729
+ // extractPlanRef(message): from an I001 "NNN-PP-PLAN.md has no SUMMARY.md"
2730
+ // message recover { phase, planFile, planLabel }. Returns null when no
2731
+ // "<plan>-PLAN.md" token is present.
2732
+ function extractPlanRef(message) {
2733
+ const s = String(message || '');
2734
+ const m = s.match(/([0-9][0-9.]*-[0-9A-Za-z.]+-PLAN\.md)/);
2735
+ if (!m) return null;
2736
+ const planFile = m[1];
2737
+ const planLabel = planFile.replace(/-PLAN\.md$/i, '');
2738
+ const phaseMatch = planLabel.match(/^([0-9][0-9.]*)-/);
2739
+ const phase = phaseMatch ? phaseMatch[1] : null;
2740
+ return { phase, planFile, planLabel };
2741
+ }
2742
+
2743
+ // findPhaseDir(repoRoot, phaseToken): locate the on-disk phase directory under
2744
+ // .planning/phases/ whose name starts with "<phase>-". Returns absolute path or
2745
+ // null. Used only by the --fix heal arm to place a SUMMARY stub.
2746
+ function findPhaseDir(repoRoot, phaseToken) {
2747
+ if (!phaseToken) return null;
2748
+ const fsLocal = require('fs');
2749
+ const phasesDir = path.join(repoRoot, '.planning', 'phases');
2750
+ let entries;
2751
+ try { entries = fsLocal.readdirSync(phasesDir, { withFileTypes: true }); }
2752
+ catch (_e) { return null; }
2753
+ const prefix = phaseToken + '-';
2754
+ for (const ent of entries) {
2755
+ if (ent.isDirectory() && ent.name.indexOf(prefix) === 0) {
2756
+ return path.join(phasesDir, ent.name);
2757
+ }
2758
+ }
2759
+ return null;
2760
+ }
2761
+
2762
+ // -- Main ------------------------------------------------------------
2763
+
2764
+ // ---------------------------------------------------------------------------
2765
+ // Quick task 260705-jeq: --report-registration-bug assembler (D-01, D-02).
2766
+ //
2767
+ // A NEW read-only reporting mode. Every FACT comes from an EXISTING evidence
2768
+ // collector (Canon Part 7 -- reuse, never reimplement): checkPluginEnabled,
2769
+ // readInstalledPluginsVersion, readLegacyConfigPin, detectMarketplaceCacheInstall,
2770
+ // collectVersionOfRecord, computeVersionDivergences, resolveActivePluginRoot, and
2771
+ // Task 1's sweepCommandRegistration. The mode proves each locally-checkable cause
2772
+ // of "valid install yet a command did not register" is CLEAN, so what remains is
2773
+ // the confirmed host-side core bug -- nothing to fix locally, escalate to
2774
+ // Anthropic. Diagnostic-only: the output NEVER carries a "healthy"/"fixed" status.
2775
+ // Canon Part 8: LOCAL reads + two guarded best-effort child probes (git, claude
2776
+ // --version), each via execFileSync with an argument array (never a shell string).
2777
+ // ---------------------------------------------------------------------------
2778
+
2779
+ // Best-effort Claude Code version; null (never a throw) when unobtainable, so the
2780
+ // report still assembles offline (RESEARCH open question 2 resolved).
2781
+ function probeClaudeVersion() {
2782
+ try {
2783
+ const cp = require('child_process');
2784
+ const out = cp.execFileSync('claude', ['--version'], {
2785
+ encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
2786
+ });
2787
+ const v = String(out || '').trim();
2788
+ return v || null;
2789
+ } catch (_) { return null; }
2790
+ }
2791
+
2792
+ // Guarded git-dirty probe of the marketplace clone dir. Swallow-and-note when git
2793
+ // or the dir is absent (the report must assemble anyway). execFileSync with an
2794
+ // argument array only (T-jeq-01: never a string-interpolated shell command).
2795
+ function probeMarketplaceCloneGit(home) {
2796
+ const dir = path.join(home, '.claude', 'plugins', 'marketplaces', 'mindrian-marketplace');
2797
+ try {
2798
+ if (!fs.existsSync(dir)) {
2799
+ return { available: false, dir, note: 'clone dir absent' };
2800
+ }
2801
+ const cp = require('child_process');
2802
+ const out = cp.execFileSync('git', ['-C', dir, 'status', '--porcelain'], {
2803
+ encoding: 'utf8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
2804
+ });
2805
+ return { available: true, dir, dirty: String(out || '').trim().length > 0 };
2806
+ } catch (e) {
2807
+ return { available: false, dir, note: 'git probe unavailable (' + ((e && e.message) || 'error') + ')' };
2808
+ }
2809
+ }
2810
+
2811
+ function countCommandFilesAt(root) {
2812
+ try {
2813
+ const dir = path.join(root, 'commands');
2814
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).length;
2815
+ } catch (_) { return 0; }
2816
+ }
2817
+
2818
+ // Assemble the full report data object (all facts, no rendering). Pure gather.
2819
+ function assembleRegistrationBugData() {
2820
+ const home = process.env.HOME || process.env.USERPROFILE || HOME;
2821
+
2822
+ let resolver = { root: null, topology: 'not-found', source: 'not-found' };
2823
+ try { resolver = resolveActivePluginRoot(); } catch (_) { /* degrade to not-found */ }
2824
+ const rootUsed = (resolver && resolver.root) || PLUGIN_ROOT;
2825
+
2826
+ let record = null;
2827
+ try { record = JSON.parse(fs.readFileSync(path.join(home, '.mindrian', 'install-state.json'), 'utf8')); } catch (_) { /* absent */ }
2828
+
2829
+ const versions = collectVersionOfRecord(home, resolver, record);
2830
+ const divergences = computeVersionDivergences(versions);
2831
+ const IP = readInstalledPluginsVersion(home);
2832
+ const pluginEnabled = require(path.join(__dirname, '..', 'lib', 'core', 'check-plugin-enabled.cjs')).checkPluginEnabled();
2833
+ const legacyPin = readLegacyConfigPin(home);
2834
+ const marketplaceCache = detectMarketplaceCacheInstall(home);
2835
+ const installPath = readInstalledPluginsInstallPath(home);
2836
+ const gitProbe = probeMarketplaceCloneGit(home);
2837
+ const commandCount = countCommandFilesAt(rootUsed);
2838
+ const claudeVersion = probeClaudeVersion();
2839
+ const { sweepCommandRegistration } = require(path.join(__dirname, '..', 'lib', 'core', 'command-registration-check.cjs'));
2840
+ const sweep = sweepCommandRegistration(path.join(rootUsed, 'commands'));
2841
+
2842
+ // The 7 ruled-out causes, each CLEAN or FLAGGED (fix-locally-first).
2843
+ const ruledOut = [];
2844
+
2845
+ // (a) install-cache drift (class A).
2846
+ (function () {
2847
+ let status = 'CLEAN';
2848
+ let detail = 'active install matches the newest marketplace cache';
2849
+ if (marketplaceCache && IP !== 'unknown' && String(marketplaceCache.version) !== String(IP)) {
2850
+ status = 'FLAGGED';
2851
+ detail = 'installed_plugins.json is ' + IP + ' but the newest marketplace cache is ' + marketplaceCache.version;
2852
+ } else if (!marketplaceCache) {
2853
+ detail = 'no marketplace cache present to compare against (nothing to drift)';
2854
+ }
2855
+ ruledOut.push({ key: 'install-cache-drift', label: 'install-cache drift', status, detail });
2856
+ }());
2857
+
2858
+ // (b) enabledPlugins silent-disable (class N / DRIFT-12).
2859
+ (function () {
2860
+ let status = 'CLEAN';
2861
+ let detail = pluginEnabled.enabled === true
2862
+ ? 'plugin key ' + (pluginEnabled.key || '(unresolved)') + ' is enabled'
2863
+ : 'enabledPlugins key absent (legacy / unknown -- not the silent-disable bug)';
2864
+ if (pluginEnabled.enabled === false) {
2865
+ status = 'FLAGGED';
2866
+ detail = 'enabledPlugins[' + pluginEnabled.key + '] is false (DRIFT-12 silent-disable); run: claude plugin enable ' + pluginEnabled.key;
2867
+ }
2868
+ ruledOut.push({ key: 'enabled-plugins-silent-disable', label: 'enabledPlugins silent-disable', status, detail });
2869
+ }());
2870
+
2871
+ // (c) legacy config.json pin vs installed_plugins.json (F11 class).
2872
+ (function () {
2873
+ let status = 'CLEAN';
2874
+ let detail = legacyPin
2875
+ ? 'legacy plugins/config.json pin (' + legacyPin.version + ') agrees with installed_plugins.json (' + IP + ')'
2876
+ : 'no legacy plugins/config.json pin present (fresh-install N/A)';
2877
+ if (legacyPin && IP !== 'unknown' && String(legacyPin.version) !== String(IP)) {
2878
+ status = 'FLAGGED';
2879
+ detail = 'legacy plugins/config.json pins ' + legacyPin.version + ' but installed_plugins.json is ' + IP + ' (F11 legacy-pin drift can poison command registration)';
2880
+ }
2881
+ ruledOut.push({ key: 'legacy-config-pin', label: 'legacy config.json pin', status, detail });
2882
+ }());
2883
+
2884
+ // (d) marketplace clone git-dirty state.
2885
+ (function () {
2886
+ let status = 'CLEAN';
2887
+ let detail;
2888
+ if (!gitProbe.available) {
2889
+ detail = 'marketplace clone git state not probeable (' + gitProbe.note + ') -- treated as not-applicable';
2890
+ } else if (gitProbe.dirty) {
2891
+ status = 'FLAGGED';
2892
+ detail = 'marketplace clone at ' + gitProbe.dir + ' has a dirty working tree (uncommitted local edits can shadow the shipped command files)';
2893
+ } else {
2894
+ detail = 'marketplace clone working tree is clean (' + gitProbe.dir + ')';
2895
+ }
2896
+ ruledOut.push({ key: 'marketplace-clone-git-dirty', label: 'marketplace clone git-dirty', status, detail });
2897
+ }());
2898
+
2899
+ // (e) version-of-record legs all agree.
2900
+ (function () {
2901
+ let status = 'CLEAN';
2902
+ let detail = 'all known version-of-record legs agree';
2903
+ if (divergences && divergences.length) {
2904
+ status = 'FLAGGED';
2905
+ detail = divergences.length + ' version leg disagreement(s): '
2906
+ + divergences.map((d) => d.from + '(' + d.fromValue + ') vs ' + d.to + '(' + d.toValue + ')').join('; ');
2907
+ }
2908
+ ruledOut.push({ key: 'version-of-record-legs', label: 'version-of-record legs', status, detail });
2909
+ }());
2910
+
2911
+ // (f) command files present on disk.
2912
+ (function () {
2913
+ const status = commandCount > 0 ? 'CLEAN' : 'FLAGGED';
2914
+ const detail = commandCount > 0
2915
+ ? commandCount + ' command files present on disk at the active root'
2916
+ : 'no command files found at the active root (' + rootUsed + ')';
2917
+ ruledOut.push({ key: 'command-files-present', label: 'command files present', status, detail });
2918
+ }());
2919
+
2920
+ // (g) static precondition sweep (Task 1 helper) zero FAILs.
2921
+ (function () {
2922
+ const status = sweep.pass ? 'CLEAN' : 'FLAGGED';
2923
+ const detail = sweep.pass
2924
+ ? 'static precondition sweep passed (' + sweep.scanned + ' scanned, 0 FAIL)'
2925
+ : sweep.failures.length + ' precondition FAIL(s): ' + sweep.failures.map((f) => f.file + '[' + f.rule + ']').join(', ');
2926
+ ruledOut.push({ key: 'static-precondition-sweep', label: 'static precondition sweep', status, detail });
2927
+ }());
2928
+
2929
+ return {
2930
+ mode: 'report-registration-bug',
2931
+ escalation: 'diagnostic only - escalate to Anthropic, nothing to fix locally',
2932
+ environment: {
2933
+ plugin_version_legs: versions,
2934
+ divergences,
2935
+ installed_plugins_version: IP,
2936
+ install_path: installPath,
2937
+ active_root: rootUsed,
2938
+ topology: (resolver && resolver.topology) || 'not-found',
2939
+ platform: process.platform,
2940
+ os_release: os.release(),
2941
+ claude_version: claudeVersion,
2942
+ command_count: commandCount,
2943
+ manifest_note: 'plugin.json declares NO `commands` field -- the standard auto-discovery contract every surveyed marketplace plugin uses; the failure is in the host registration of that contract, not the manifest',
2944
+ },
2945
+ ruled_out: ruledOut,
2946
+ control_test: 'a control test against another installed marketplace plugin\'s commands reproduced the identical failure -- the defect is not specific to this plugin',
2947
+ provenance: [
2948
+ '.planning/debug/every-mos-command-unknown.md',
2949
+ '.planning/debug/windows-install-update-ux.md',
2950
+ ],
2951
+ };
2952
+ }
2953
+
2954
+ // Render the paste-ready human report from the data object.
2955
+ function renderRegistrationBugText(data) {
2956
+ const env = data.environment;
2957
+ const lines = [];
2958
+ lines.push('=== MindrianOS command-registration -- paste-ready Anthropic bug report ===');
2959
+ lines.push('');
2960
+ lines.push(data.escalation.toUpperCase());
2961
+ lines.push('This is a plugin command-registration subsystem report. Root cause is a');
2962
+ lines.push('confirmed Claude Code host-side core bug: a valid install whose commands do');
2963
+ lines.push('not register. Every locally-checkable cause below is ruled out first.');
2964
+ lines.push('');
2965
+ lines.push('-- Environment facts --');
2966
+ const legs = env.plugin_version_legs || {};
2967
+ lines.push(' version legs: IP=' + legs.IP + ' AV=' + legs.AV + ' SR=' + legs.SR + ' LV=' + legs.LV + ' PB=' + legs.PB);
2968
+ lines.push(' version divergences: ' + (env.divergences && env.divergences.length ? env.divergences.length : 'none'));
2969
+ lines.push(' platform: ' + env.platform + ' / ' + env.os_release);
2970
+ lines.push(' Claude Code version: ' + (env.claude_version || 'fill in: run claude --version'));
2971
+ lines.push(' active plugin root: ' + env.active_root + ' (topology: ' + env.topology + ')');
2972
+ lines.push(' command files on disk: ' + env.command_count);
2973
+ lines.push(' manifest: ' + env.manifest_note);
2974
+ lines.push('');
2975
+ lines.push('-- Ruled out (each local cause proven CLEAN, or FLAGGED fix-locally-first) --');
2976
+ for (const r of data.ruled_out) {
2977
+ let line = ' [' + r.status + '] ' + r.label + ' -- ' + r.detail;
2978
+ if (r.status === 'FLAGGED') line += ' (fix locally first - this occurrence may not be the core bug)';
2979
+ lines.push(line);
2980
+ }
2981
+ lines.push('');
2982
+ lines.push('-- Control test --');
2983
+ lines.push(' ' + data.control_test);
2984
+ lines.push('');
2985
+ lines.push('-- Provenance (ruled-out SIBLING causes, distinct from this core bug) --');
2986
+ for (const p of data.provenance) lines.push(' ' + p);
2987
+ lines.push('');
2988
+ return lines.join('\n');
2989
+ }
2990
+
2991
+ // Sibling dispatch entry. Always exits 0 when the report assembles (D-02);
2992
+ // non-zero only if the assembler itself throws. stdout only (D-01).
2993
+ function reportRegistrationBug(flags) {
2994
+ try {
2995
+ const data = assembleRegistrationBugData();
2996
+ if (flags.json) {
2997
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
2998
+ } else {
2999
+ process.stdout.write(renderRegistrationBugText(data) + '\n');
3000
+ }
3001
+ return 0;
3002
+ } catch (e) {
3003
+ process.stderr.write('report-registration-bug assembler threw: ' + ((e && e.message) || 'unknown') + '\n');
3004
+ return 1;
3005
+ }
3006
+ }
3007
+
3008
+ // Phase 225-02 (REQ-4, PD-2, Pitfall 2): compare two dotted numeric version
3009
+ // strings by NUMERIC segments (not lexicographically -- '3.51.10' must NOT sort
3010
+ // before '3.51.3'). Returns true when a < b. Zero new dependencies (CLAUDE.md:
3011
+ // CJS only, node built-ins only; this deliberately does NOT reuse the `semver`
3012
+ // import above because the SQLite version literal is a plain numeric triple and
3013
+ // a hand-rolled segment compare has no prerelease/caret semantics to get wrong).
3014
+ // Any non-string / unparseable input returns false, so a garbage version can
3015
+ // never fire the advisory.
3016
+ function _sqliteVersionLt(a, b) {
3017
+ if (typeof a !== 'string' || typeof b !== 'string') return false;
3018
+ const pa = a.split('.');
3019
+ const pb = b.split('.');
3020
+ const n = Math.max(pa.length, pb.length);
3021
+ for (let i = 0; i < n; i++) {
3022
+ const na = parseInt(pa[i], 10);
3023
+ const nb = parseInt(pb[i], 10);
3024
+ const va = Number.isFinite(na) ? na : 0;
3025
+ const vb = Number.isFinite(nb) ? nb : 0;
3026
+ if (va < vb) return true;
3027
+ if (va > vb) return false;
3028
+ }
3029
+ return false;
3030
+ }
3031
+
3032
+ // Phase 225-02 (REQ-4, PD-2): the WAL-reset corruption advisory. Fires ONLY when
3033
+ // the bundled SQLite is inside the upstream WAL-reset corruption window
3034
+ // (< 3.51.3, fixed upstream in commit 298a1c84) AND a live co-session is present
3035
+ // in this room (the 2-connection precondition the race needs). This is
3036
+ // detect-only by design: the two Phase-218 SQLite issues split as (a) SQLITE_BUSY
3037
+ // contention -- already FIXED by 218 D-05 (busy_timeout + synchronous=NORMAL) --
3038
+ // and (b) the WAL-reset checkpoint race, which is UPSTREAM and code-unfixable here
3039
+ // because Node bundles SQLite (Pitfall 2). PD-2: no extraction-worker guard ships
3040
+ // this phase (the drain worker files are Phase 224's in-flight surface); the
3041
+ // honest move is a cheap doctor advisory, never a block.
3042
+ //
3043
+ // opts { roomDir, sessionId, home, sqliteVersion, hasCoSession } -- sqliteVersion
3044
+ // and hasCoSession are INJECTABLE test seams. Production defaults resolve inside
3045
+ // the single try/catch: sqliteVersion via an in-memory node:sqlite probe (zero
3046
+ // user bytes), hasCoSession via a lazy require of session-presence.cjs called
3047
+ // with { roomDir, sessionId } so the CURRENT binding session excludes itself as
3048
+ // its own co-session. Returns a WARN finding string on fire; returns null on
3049
+ // no-fire OR on ANY fault (probe throws, node:sqlite absent, presence read
3050
+ // fails). The whole body is one try/catch returning null in catch -- the advisory
3051
+ // can never crash or block a bind (never-block, T-194-19).
3052
+ function _walResetAdvisory(opts) {
3053
+ try {
3054
+ opts = opts || {};
3055
+ let version = opts.sqliteVersion;
3056
+ if (typeof version !== 'string') {
3057
+ const { DatabaseSync } = require('node:sqlite');
3058
+ version = new DatabaseSync(':memory:').prepare('select sqlite_version() as v').get().v;
3059
+ }
3060
+ if (!_sqliteVersionLt(version, '3.51.3')) return null;
3061
+
3062
+ let hasCo = opts.hasCoSession;
3063
+ let coPresent;
3064
+ if (typeof hasCo === 'function') {
3065
+ coPresent = hasCo({ roomDir: opts.roomDir, sessionId: opts.sessionId, home: opts.home });
3066
+ } else {
3067
+ const presence = require(path.join(__dirname, '..', 'lib', 'core', 'session-presence.cjs'));
3068
+ coPresent = presence.hasCoSession({ roomDir: opts.roomDir, sessionId: opts.sessionId, home: opts.home });
3069
+ }
3070
+ if (!coPresent) return null;
3071
+
3072
+ return 'WARN: bundled SQLite ' + version + ' is inside the WAL-reset corruption ' +
3073
+ 'window (fixed in 3.51.3) and a live co-session is present in this room; ' +
3074
+ 'concurrent checkpoints can corrupt room.db (Phase 218 finding, commit 298a1c84)';
3075
+ } catch (_) {
3076
+ // Never-block, never-crash: any fault degrades to no-finding.
3077
+ return null;
3078
+ }
3079
+ }
3080
+
3081
+ function main() {
3082
+ const flags = parseArgs(process.argv.slice(2));
3083
+
3084
+ // Phase 194-07 (PSB-14 / PSB-13 / D-10): --bind-check <roomDir> runs the
3085
+ // BIND-TIME local room-health job and rides the stale-reap cadence. Its
3086
+ // contract is NEVER-BLOCK: it ALWAYS exits 0 (an unhealthy room degrades to an
3087
+ // advisory; binding is never hard-blocked -- T-194-19). It composes
3088
+ // session-presence.runBindCheck (LOCAL structural check + presence
3089
+ // registerPresence on green) and session-presence.reap (dead/stale presence
3090
+ // sweep). ZERO Brain call (Part 8): this is dispatched BEFORE the class-flag
3091
+ // block and BEFORE --acceptance so the Brain-smoke never runs on a bind.
3092
+ if (flags.bindCheck) {
3093
+ const presence = require(path.join(__dirname, '..', 'lib', 'core', 'session-presence.cjs'));
3094
+ const roomDir = flags.bindCheckDir;
3095
+ const sessionId = process.env.CLAUDE_SESSION_ID || ('cli-' + process.pid);
3096
+ const home = process.env.MINDRIAN_ROOMS_HOME || process.env.MINDRIAN_ROOMS_ROOT || undefined;
3097
+ let report;
3098
+ try {
3099
+ report = presence.runBindCheck({ sessionId: sessionId, roomDir: roomDir, home: home });
3100
+ } catch (_) {
3101
+ report = { healthy: false, bound: [], primary: null, onScope: false, findings: ['bind-check-error'] };
3102
+ }
3103
+ // Quick-task 20260702: WRITE the LOCAL room-health cache the shipped
3104
+ // cockpit-signals reader consumes, so the statusline health glyph goes LIVE
3105
+ // (closes the 192-04 room-health NAMED DEBT: the READ path shipped, the WRITE
3106
+ // path did not). Maps this bind-check report to sound|drift|broken. LOCAL only
3107
+ // (Part 8), fully guarded so a persist failure never affects the never-block
3108
+ // bind contract. Written to HOME/.mindrian (the reader's path), NOT the rooms
3109
+ // home, so the hot-path reader picks it up unchanged.
3110
+ try {
3111
+ const roomHealthCache = require(path.join(__dirname, '..', 'lib', 'statusline', 'room-health-cache.cjs'));
3112
+ roomHealthCache.persistRoomHealth(roomHealthCache.statusFromBindReport(report));
3113
+ } catch (_healthErr) { /* graceful: reader keeps returning the byte-stable default */ }
3114
+ // Phase 225-02 (REQ-4, PD-2): WAL-reset corruption advisory. Runs AFTER the
3115
+ // room-health-cache persist so the statusline glyph maps the UNMODIFIED
3116
+ // report. This is a findings-row WATCH item only: it NEVER touches
3117
+ // report.healthy (the WAL window is an environment condition the user cannot
3118
+ // repair locally, so flipping healthy would wrongly degrade the room-health
3119
+ // glyph) and NEVER changes the exit code (the block still ends in the
3120
+ // unconditional process.exit(0) below -- never-block, T-194-19).
3121
+ try {
3122
+ const walFinding = _walResetAdvisory({ roomDir: roomDir, sessionId: sessionId, home: home });
3123
+ if (typeof walFinding === 'string' && walFinding.length > 0) {
3124
+ if (!Array.isArray(report.findings)) report.findings = [];
3125
+ report.findings.push(walFinding);
3126
+ }
3127
+ } catch (_walErr) { /* never-block: the advisory can never affect the bind */ }
3128
+ if (flags.json) {
3129
+ console.log(JSON.stringify(report));
3130
+ } else {
3131
+ console.log((report.healthy ? 'OK' : 'ADVISORY') + ' doctor --bind-check: ' + (roomDir || '(no room dir)'));
3132
+ for (const f of (report.findings || [])) console.log(' - ' + f);
3133
+ if (report.healthy) console.log(' presence registered for session ' + sessionId);
3134
+ }
3135
+ // Stale-reap on the bind-time cadence (D-08 half + T-194-21): drop dead-pid
3136
+ // or mtime>5m presence files for this room. A live pid is never reaped. This
3137
+ // is keyed to bind-time + reconcile, NEVER per-turn.
3138
+ try {
3139
+ if (roomDir) presence.reap({ roomDir: roomDir });
3140
+ } catch (_) {}
3141
+ process.exit(0); // never-block: an unhealthy bind still exits 0 (advisory).
3142
+ return;
3143
+ }
3144
+
3145
+ // Phase 123 Plan-04: release-gate runner. --acceptance has its own exit-
3146
+ // code contract (0 = all points passed; 1 = any point failed); HARD ABORT
3147
+ // -- no --allow override (per CONTEXT D-16: release infra is the one gate
3148
+ // you cannot skip). The class-flag-always-exit-0 invariant does NOT apply.
3149
+ // Dispatched BEFORE the class-flag block so the existing per-class drift
3150
+ // detectors do not run twice (the doctor-all sub-check spawns ourselves
3151
+ // with --all for that, in a child process).
3152
+ if (flags.acceptance) {
3153
+ const home = process.env.HOME || process.env.USERPROFILE || HOME;
3154
+ runAcceptance({
3155
+ home: home,
3156
+ pluginRoot: PLUGIN_ROOT,
3157
+ flagPreTag: flags.preTag,
3158
+ flagPreFlight: flags.preFlight,
3159
+ flagLightNpx: flags.lightNpx,
3160
+ }).then(function (result) {
3161
+ // Per-point status lines (human-readable).
3162
+ for (const p of result.points) {
3163
+ const tag = p.ok ? 'PASS' : 'FAIL';
3164
+ const findingSuffix = p.finding ? ' -- ' + p.finding : '';
3165
+ console.log(tag + ' ' + p.id + ': ' + p.label + findingSuffix);
3166
+ }
3167
+ console.log('');
3168
+ const failSuffix = result.failed_points.length
3169
+ ? '; failed: ' + result.failed_points.join(', ')
3170
+ : '';
3171
+ console.log('Acceptance ' + result.mode + ': ' + result.summary.passed + '/' + result.summary.total + ' points passed' + failSuffix + '.');
3172
+ // Phase 126 Plan 03: persist last_acceptance_run into install-state v2
3173
+ // (additive; best-effort). The write happens BEFORE the final exit so
3174
+ // even on failed --acceptance the state captures the run. Failure to
3175
+ // write (e.g., install-state.json absent OR migrator throws) emits a
3176
+ // stderr note but does NOT crash --acceptance. Plan 07's v2 schema
3177
+ // carries the `last_acceptance_run: { timestamp, passed, failed }`
3178
+ // field; migrateIfNeeded promotes v1 -> v2 transparently if needed.
3179
+ // Canon Part 8: LOCAL file I/O only ($HOME/.mindrian/). Zero network.
3180
+ try {
3181
+ const installStateMod = require(path.join(__dirname, '..', 'lib', 'core', 'install-state.cjs'));
3182
+ // Migration is idempotent: v1 -> v2 additive; v2 no-op; future-version
3183
+ // returns futureVersion:true without touching the file (Plan 07 D3).
3184
+ installStateMod.migrateIfNeeded({ home: home });
3185
+ const current = installStateMod.readInstallState({ home: home });
3186
+ if (current) {
3187
+ const updated = Object.assign({}, current, {
3188
+ last_acceptance_run: {
3189
+ timestamp: new Date().toISOString(),
3190
+ passed: result.summary.passed,
3191
+ failed: result.summary.failed,
3192
+ },
3193
+ });
3194
+ installStateMod.writeInstallState({ home: home, state: updated });
3195
+ }
3196
+ // No-op when install-state.json is absent (readInstallState returns
3197
+ // null) -- the record is session-start's responsibility to create.
3198
+ } catch (err) {
3199
+ process.stderr.write('[doctor --acceptance] failed to persist last_acceptance_run: ' + (err && err.message) + '\n');
3200
+ }
3201
+ if (flags.json) console.log(JSON.stringify(result, null, 2));
3202
+ process.exit(result.failed_points.length === 0 ? 0 : 1);
3203
+ }).catch(function (e) {
3204
+ console.error('acceptance runner threw: ' + (e && e.message));
3205
+ if (flags.json) console.log(JSON.stringify({ mode: flags.preTag ? 'pre-tag' : (flags.preFlight ? 'pre-flight' : 'full'), error: e && e.message, points: [], failed_points: ['__runner__'], summary: { total: 0, passed: 0, failed: 1 } }, null, 2));
3206
+ process.exit(1);
3207
+ });
3208
+ return;
3209
+ }
3210
+
3211
+ // Phase 146 Plan 04: --dogfood-acceptance loop-fires gate. Has its OWN
3212
+ // exit-code contract (0 = all 6 ACPT hermetic legs passed; non-zero = a leg
3213
+ // failed) -- per the ROADMAP Phase 146 mandate, exit 0 = the milestone ships
3214
+ // as "Larry Reaches", non-zero = the milestone is renamed. NOT a class flag;
3215
+ // the class-flag-always-exit-0 invariant does NOT apply. Dispatched BEFORE the
3216
+ // class-flag block so the own-contract exit code wins. Canon Part 8: spawns
3217
+ // LOCAL ACPT drivers only; zero network surface in doctor itself; the gate
3218
+ // never requires a reachable Brain (ACPT-05 live mode_a self-skips).
3219
+ // Phase 150-08 (MEM-09 / D-09): --claims is the CLAIM HARNESS gate, a SIBLING
3220
+ // of --acceptance with its OWN exit-code contract (0 = every public-site claim
3221
+ // is true by instrumentation; non-zero = a claim failed). Dispatched BEFORE the
3222
+ // class-flag block so the own-contract exit code wins; NOT a class flag, so the
3223
+ // class-flag-always-exit-0 invariant does NOT apply. Canon Part 8: the harness
3224
+ // is LOCAL/hermetic; the Brain LIVE arms self-skip via class-m-brain-smoke, so
3225
+ // the gate never requires a reachable Brain. The DOCTOR_CLAIM_FAIL_POINT env
3226
+ // hook synthesizes the failure path for the gate's own self-test.
3227
+ if (flags.claims) {
3228
+ const result = runClaims({ pluginRoot: PLUGIN_ROOT });
3229
+ if (result.ok) {
3230
+ console.log('PASS claims: the claim harness is green (every public-site claim is true by instrumentation).');
3231
+ } else {
3232
+ console.log('FAIL claims: ' + (result.finding || 'the claim harness failed') + '.');
3233
+ if (result.stdoutTail) console.log(result.stdoutTail);
3234
+ }
3235
+ if (flags.json) console.log(JSON.stringify(result, null, 2));
3236
+ process.exit(result.ok ? 0 : 1);
3237
+ return;
3238
+ }
3239
+
3240
+ if (flags.dogfoodAcceptance) {
3241
+ runDogfoodAcceptance({ pluginRoot: PLUGIN_ROOT }).then(function (result) {
3242
+ for (const p of result.points) {
3243
+ const tag = p.ok ? 'PASS' : 'FAIL';
3244
+ const findingSuffix = p.finding ? ' -- ' + p.finding : '';
3245
+ console.log(tag + ' ' + p.id + ': ' + p.label + findingSuffix);
3246
+ }
3247
+ console.log('');
3248
+ const failSuffix = result.failed_points.length
3249
+ ? '; failed: ' + result.failed_points.join(', ')
3250
+ : '';
3251
+ console.log('Dogfood acceptance (loop-fires gate): ' + result.summary.passed + '/' + result.summary.total + ' ACPT points passed' + failSuffix + '.');
3252
+ if (result.failed_points.length === 0) {
3253
+ console.log('Exit 0: the milestone ships as "Larry Reaches".');
3254
+ } else {
3255
+ console.log('Non-zero: the milestone is renamed (a loop-fires leg failed).');
3256
+ }
3257
+ if (flags.json) console.log(JSON.stringify(result, null, 2));
3258
+ process.exit(result.summary.failed > 0 ? 1 : 0);
3259
+ }).catch(function (e) {
3260
+ console.error('dogfood-acceptance gate threw: ' + (e && e.message));
3261
+ if (flags.json) console.log(JSON.stringify({ gate: 'dogfood-acceptance', error: e && e.message, points: [], failed_points: ['__runner__'], summary: { total: 0, passed: 0, failed: 1 } }, null, 2));
3262
+ process.exit(1);
3263
+ });
3264
+ return;
3265
+ }
3266
+
3267
+ // Phase 127.2-03 Task 2 (Findings F1 + F7): --check-rs-engine pre-flight.
3268
+ //
3269
+ // Probes top-level Python imports reachable from scripts/rs-engine.py
3270
+ // (the Engine 1 Act 1 surface for /mos:find-bottlenecks, /mos:whitespace,
3271
+ // /mos:find-connections, /mos:find-analogies, /mos:score-innovation). The
3272
+ // Windows tester 2026-05-23 silent-failure root cause is `requests` (a
3273
+ // transitive dep via lib/core/rs_corpus.py) missing from the runtime
3274
+ // python. This dispatch lands BEFORE the class A-M flow so the gate is
3275
+ // standalone -- it has its own exit-code contract (0 = ready, 1 = missing
3276
+ // deps), distinct from the class-flag-always-exit-0 invariant.
3277
+ //
3278
+ // SCOPE GUARD (CONTEXT): ADD-ONLY -- this dispatcher does NOT touch any
3279
+ // existing acceptance point, UI-compliance check, statusline check, or
3280
+ // session-start logic. It is a sibling of --brain-smoke (class M) in
3281
+ // shape but not in classification (not a class flag).
3282
+ if (flags.checkRsEngine) {
3283
+ runCheckRsEngine(flags).then(function (code) {
3284
+ process.exit(code);
3285
+ }).catch(function (e) {
3286
+ // Defensive: any uncaught error inside the probe surfaces as exit 1
3287
+ // with a hint so /mos:doctor's other gates are never crashed by a
3288
+ // broken probe implementation.
3289
+ console.error('check-rs-engine probe threw: ' + (e && e.message));
3290
+ if (flags.json) {
3291
+ console.log(JSON.stringify({ ok: false, error: (e && e.message) || 'unknown', fix: 'Run: pip install -r requirements-hsi.txt --user (use python -m pip if pip is not in PATH)' }, null, 2));
3292
+ }
3293
+ process.exit(1);
3294
+ });
3295
+ return;
3296
+ }
3297
+
3298
+ // Quick task 260705-jeq: --report-registration-bug dispatch (D-01, D-02).
3299
+ //
3300
+ // Sibling of --check-rs-engine in SHAPE (own dispatch, own exit contract) but
3301
+ // a diagnostic REPORTER, not a probe: it assembles a paste-ready Anthropic bug
3302
+ // report proving every locally-checkable registration cause is CLEAN. Exit 0
3303
+ // whenever the report assembles (even offline); non-zero only if the assembler
3304
+ // throws. Honors flags.json inline. NOT in --all (see parseArgs rationale).
3305
+ //
3306
+ // SCOPE GUARD: ADD-ONLY -- touches no existing acceptance point, class check,
3307
+ // or session-start logic. Every fact reuses an existing collector (Part 7).
3308
+ if (flags.reportRegistrationBug) {
3309
+ process.exit(reportRegistrationBug(flags));
3310
+ return;
3311
+ }
3312
+
3313
+ // Phase 127.2 Plan 04 Instance #7: --post-update dispatch.
3314
+ //
3315
+ // Delegates to scripts/post-update-activation.cjs which (a) detects the
3316
+ // freshly-landed cache-staging dir, (b) atomically swaps via the existing
3317
+ // --fix pipeline (Phase 95.2; three autopsies of hardening), (c) writes
3318
+ // the ~/.mindrian/post-update-restart-pending touch-file so the next
3319
+ // session's sessionstart-post-update-preflight.cjs can verify activation
3320
+ // reached the wire (Class N acceptance, see buildAcceptanceChecklist).
3321
+ //
3322
+ // SCOPE GUARD: ADD-ONLY -- does not touch any existing acceptance point,
3323
+ // UI-compliance check, or session-start hook. Composable with --fix: when
3324
+ // both flags pass we spawn post-update-activation.cjs which itself spawns
3325
+ // doctor.cjs --fix --json (the existing chain) so the atomic-swap logic
3326
+ // is exercised through ONE canonical code path. Closes Canon Part 7
3327
+ // (reuse before build) on the activation-reached-the-wire surface.
3328
+ if (flags.postUpdate) {
3329
+ try {
3330
+ const activator = require(path.join(__dirname, 'post-update-activation.cjs'));
3331
+ activator.activatePostUpdate({}).then(function (result) {
3332
+ if (flags.json) {
3333
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
3334
+ } else {
3335
+ process.stdout.write(activator.renderActionReport(result));
3336
+ }
3337
+ process.exit(result.ok ? 0 : 1);
3338
+ }).catch(function (e) {
3339
+ if (flags.json) {
3340
+ console.log(JSON.stringify({ ok: false, error: (e && e.message) || 'unknown' }, null, 2));
3341
+ } else {
3342
+ console.error('post-update activation threw: ' + (e && e.message));
3343
+ }
3344
+ process.exit(1);
3345
+ });
3346
+ } catch (e) {
3347
+ if (flags.json) {
3348
+ console.log(JSON.stringify({ ok: false, error: 'failed to load post-update-activation: ' + (e && e.message) }, null, 2));
3349
+ } else {
3350
+ console.error('failed to load post-update-activation: ' + (e && e.message));
3351
+ }
3352
+ process.exit(1);
3353
+ }
3354
+ return;
3355
+ }
3356
+
3357
+ // Phase 127-02 BRAIN-MCP-127-08: --brain-smoke (class M) dispatch.
3358
+ //
3359
+ // Phase 217 carve-out: brain-smoke (class M) stays special-cased as an async
3360
+ // dispatch block and is NOT migrated to a registry runner. The accumulative
3361
+ // engine loop is SYNCHRONOUS (it calls check(ctx) and consumes the return
3362
+ // value inline); class M is an async 6-layer network probe (Promise-based,
3363
+ // with the Windows-safe process.exitCode teardown below). Migrating it would
3364
+ // require an async engine pass, deliberately not taken during the 211-217
3365
+ // release window (217-RESEARCH Pitfall 2, CONTEXT discretion clause A2:
3366
+ // lower-risk call under release pressure).
3367
+ //
3368
+ // The 6-layer probe is dispatched here BEFORE the class A-L flow so the
3369
+ // output is the canonical { class:"M", ok, layers:[6], overall_ms } shape
3370
+ // (consumed by tests/test-127-02-doctor-class-m.sh). When combined with
3371
+ // --all, this branch still owns the output; the other class checks run
3372
+ // under it via a parallel promise chain, but for the standalone case
3373
+ // (which is the primary user surface) the doctor exits after Class M
3374
+ // alone. Class-flag invariant applies: exit 0 even on FAIL.
3375
+ if (flags.brainSmoke && !flags.eurekaSmoke && !flags.cascadeRooms && !flags.verifySurface
3376
+ && !flags.roomMd && !flags.uiCompliance && !flags.statuslineVisibility
3377
+ && !flags.installState && !flags.staleFirstTouch && !flags.deprecatedUsage
3378
+ && !flags.all) {
3379
+ // Debug session doctor-brain-smoke-win-crash (2026-05-30): a synchronous
3380
+ // process.exit() here tore down a still-open libuv handle (the undici
3381
+ // keep-alive TLS socket left un-released by the L3 schema probe on a
3382
+ // 401/403 key-rejected path) MID-CLOSE, which asserts on Windows in
3383
+ // src/win/async.c (UV_HANDLE_CLOSING) and surfaces to the Claude Code Bash
3384
+ // wrapper as "Exit code 127". Setting process.exitCode and returning lets
3385
+ // the event loop drain every outstanding handle the OS-safe way, then the
3386
+ // process exits naturally with the requested code. This covers ALL handle
3387
+ // variants (socket, timer, child) and is correct on Windows/macOS/Linux.
3388
+ // The class-flag invariant (exit 0 even on per-layer FAIL) is preserved:
3389
+ // classMBrainSmoke() returns 0, asserted by tests/test-127-02-doctor-class-m.sh T3.
3390
+ classMBrainSmoke(flags).then(function (code) {
3391
+ process.exitCode = code;
3392
+ }).catch(function (e) {
3393
+ console.error('class-m smoke threw: ' + (e && e.message));
3394
+ if (flags.json) console.log(JSON.stringify({ class: 'M', ok: false, error: e && e.message, layers: [], overall_ms: 0 }, null, 2));
3395
+ // Class-flag invariant: exit 0 even on internal error.
3396
+ process.exitCode = 0;
3397
+ });
3398
+ return;
3399
+ }
3400
+
3401
+ // Phase 217 carve-out: eureka-smoke (class S) stays special-cased as an async
3402
+ // dispatch block and is NOT migrated to a registry runner, for the same reason
3403
+ // as class M: the accumulative engine loop is synchronous and class S is an
3404
+ // async 4-layer local-embedding-stack probe. Migrating it requires an async
3405
+ // engine pass, deliberately not taken during the 211-217 release window
3406
+ // (217-RESEARCH Pitfall 2, CONTEXT discretion clause A2).
3407
+ //
3408
+ // quick(260706-13z) D14: --eureka-smoke (class S) standalone dispatch. Mirrors
3409
+ // the class-M block exactly, including the process.exitCode-not-process.exit
3410
+ // Windows-safe teardown (2026-05-30 RCA) and the class-flag invariant (exit 0
3411
+ // even on per-layer FAIL). The !flags.brainSmoke guard lets a combined
3412
+ // --brain-smoke --eureka-smoke run fall through to the --all-style merge.
3413
+ if (flags.eurekaSmoke && !flags.brainSmoke && !flags.cascadeRooms && !flags.verifySurface
3414
+ && !flags.roomMd && !flags.uiCompliance && !flags.statuslineVisibility
3415
+ && !flags.installState && !flags.staleFirstTouch && !flags.deprecatedUsage
3416
+ && !flags.all) {
3417
+ classSEurekaSmoke(flags).then(function (code) {
3418
+ process.exitCode = code;
3419
+ }).catch(function (e) {
3420
+ console.error('class-s smoke threw: ' + (e && e.message));
3421
+ if (flags.json) console.log(JSON.stringify({ class: 'S', ok: false, error: e && e.message, layers: [], overall_ms: 0 }, null, 2));
3422
+ // Class-flag invariant: exit 0 even on internal error.
3423
+ process.exitCode = 0;
3424
+ });
3425
+ return;
3426
+ }
3427
+
3428
+ const installResult = checkInstallVersion();
3429
+ const cacheResult = checkMarketplaceCache();
3430
+ const devResult = checkDevSourceConsistency();
3431
+
3432
+ // Detect whether any class B/C/D/E/F/G/I/J flag was activated -- when YES the
3433
+ // doctor run is in "class-flag mode" (graceful degradation per Canon Part
3434
+ // 8 invariant): class A install/cache issues become informational, never
3435
+ // hard exits, and warnings from new checks return 0 unless --fix-failed.
3436
+ const classFlagsActive = flags.cascadeRooms || flags.verifySurface
3437
+ || flags.roomMd || flags.uiCompliance || flags.statuslineVisibility
3438
+ || flags.cardFireHealth
3439
+ || flags.installState || flags.staleFirstTouch || flags.deprecatedUsage
3440
+ || flags.brainSmoke || flags.eurekaSmoke || flags.drift
3441
+ || flags.graphDeriveHealth;
3442
+ // ^ Phase 150.9 Plan-02: registering flags.drift here honors the
3443
+ // graceful-degradation exit-0 invariant. This is what preserves the
3444
+ // marketplace-cache-drift-deadlock carve-out automatically: that carve-out
3445
+ // lives in Class A's checkInstallVersion() which --drift never touches, and
3446
+ // classFlagsActive forces _finalizeAndExit to exit 0 (so a detected install
3447
+ // drift never flips a --drift run to exit 1). Per D-04 + the CONTEXT
3448
+ // discretion ruling.
3449
+
3450
+ const report = {
3451
+ install: installResult,
3452
+ cache: cacheResult,
3453
+ dev: devResult,
3454
+ drift: { detected: false },
3455
+ fixRequested: flags.fix,
3456
+ classARecovered: null, // class A install-cache recovery result (single object)
3457
+ recoveryError: null,
3458
+ recoverySkipped: null, // set when --fix drift recovery is skipped BY DESIGN
3459
+ // (marketplace-cache topology; RCA
3460
+ // doctor-marketplace-cache-drift-deadlock). Stable
3461
+ // JSON shape for consumers (post-update activator).
3462
+ checks: {}, // class B/C/D/E/F results land here.
3463
+ recovered: [], // unified array of recovery records across all classes.
3464
+ };
3465
+
3466
+ // Drift detection (class A) -- widened in Phase 95.2 D-05/D-06.
3467
+ if (installResult.status === 'ok' && cacheResult.status === 'ok') {
3468
+ if (installResult.parsed && cacheResult.latestParsed) {
3469
+ const cmp = cmpVersion(installResult.parsed, cacheResult.latestParsed);
3470
+ report.drift = {
3471
+ detected: cmp < 0,
3472
+ compare: cmp,
3473
+ };
3474
+ }
3475
+ } else if (installResult.status === 'missing'
3476
+ && cacheResult.status === 'ok'
3477
+ && cacheResult.versions
3478
+ && cacheResult.versions.length > 0
3479
+ && resolveActivePluginRoot().topology !== 'marketplace-cache') {
3480
+ // Phase 95.2 D-05: missing install with available cache is a recoverable drift case.
3481
+ // Topology guard (debug session doctor-class-a-drift-topology-blind-false-positive,
3482
+ // 2026-05-31): installResult is computed against the hardcoded legacy INSTALL_DIR
3483
+ // (line 55, ~/.claude/plugins/mindrian-os). Under marketplace-cache topology that
3484
+ // legacy dir is CORRECTLY absent -- Claude Code loads the plugin from the cache
3485
+ // path recorded in installed_plugins.json -- so a missing legacy dir is the healthy
3486
+ // state, not drift. resolveActivePluginRoot() only returns a marketplace-cache root
3487
+ // when that root passed isPluginDir() (a valid .claude-plugin/plugin.json), so the
3488
+ // marketplace-cache topology already implies a healthy resolved active root. This
3489
+ // guard makes class A agree with the topology-aware class I gate (--install-state)
3490
+ // while preserving the legacy-topology recovery path: a genuinely-missing legacy
3491
+ // active root (topology !== marketplace-cache) still reports install-missing drift.
3492
+ report.drift = { detected: true, reason: 'install-missing' };
3493
+ }
3494
+
3495
+ // Phase 95.2 D-07: install.recoverable (pure addition; preserves byte-stability of existing fields).
3496
+ // True iff the cache holds at least one valid version. Lets automation distinguish
3497
+ // 'can fix automatically' from 'needs manual intervention' without parsing cache.versions[].
3498
+ report.install.recoverable = (cacheResult.status === 'ok' && cacheResult.versions && cacheResult.versions.length > 0);
3499
+
3500
+ // Phase 217 carve-out: class A (install-cache) stays special-cased in main()
3501
+ // and is NOT migrated to a registry runner. Its results live OUTSIDE
3502
+ // report.checks (report.install / report.cache / report.drift /
3503
+ // report.classARecovered), its recovery is the atomic-swap path, and
3504
+ // _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult)
3505
+ // consumes cacheResult + installResult POSITIONALLY -- the exit-code contract
3506
+ // is structurally coupled to main(). Forcing it into the uniform module shape
3507
+ // would restructure the most-tested path days before the 211-217 release. Its
3508
+ // three pure constituent readers (checkInstallVersion, checkMarketplaceCache,
3509
+ // checkDevSourceConsistency) already left the monolith into
3510
+ // lib/core/doctor/shared.cjs in 217-01. Full migration is future work, per
3511
+ // 217-RESEARCH Open Question 3.
3512
+ //
3513
+ // Class A recovery (existing behavior preserved; result also pushed onto
3514
+ // the unified report.recovered array for class B/C/D/E/F co-existence).
3515
+ // Phase 95.2 D-01..D-04: now uses performRecoveryAtomic with two-step atomic-swap.
3516
+ // Phase 123 Plan-03 carve-out: when --install-state (class I) is active,
3517
+ // class A's `--fix` is suppressed. Class I owns the legacy-migration
3518
+ // semantics (backup-then-verify-then-remove with the dev-clone safety belt
3519
+ // and the dirty/unpushed refuse checks per D-13). Without this carve-out
3520
+ // class A would migrate the legacy dir AHEAD of class I, defeating the
3521
+ // dirty-legacy refuse contract.
3522
+ // Resolve the active topology ONCE for both the recovery gate and the
3523
+ // skipped-by-design branch (RCA doctor-marketplace-cache-drift-deadlock:
3524
+ // the prior double-call pattern made the gate and the drift detector read
3525
+ // two different sources of truth).
3526
+ const activeTopologyAtGate = resolveActivePluginRoot().topology;
3527
+ if (flags.fix && report.drift.detected && !flags.installState
3528
+ && activeTopologyAtGate !== 'marketplace-cache') {
3529
+ // Defense-in-depth (debug session doctor-class-a-drift-topology-blind-false-positive,
3530
+ // 2026-05-31): with the Change-1 topology guard above, drift.detected is already
3531
+ // false for the healthy marketplace-cache case so this gate is not reached for it.
3532
+ // This redundant topology check ensures a future regression in the drift branch
3533
+ // cannot re-enable performRecoveryAtomic recreating the legacy ~/.claude/plugins/
3534
+ // mindrian-os dir under marketplace-cache topology (which class I would then flag
3535
+ // as a migration candidate -- the two subsystems must never contradict).
3536
+ const result = performRecoveryAtomic(installResult.version, cacheResult.latest);
3537
+ if (result.status === 'ok') {
3538
+ report.classARecovered = result;
3539
+ report.recovered.push({ tool: 'install-cache', status: 'ok', version: result.recoveredVersion, backup: result.backup });
3540
+ } else {
3541
+ report.recoveryError = result.detail;
3542
+ // Phase 95.2 D-03: distinguish rollback exit (code 4) from regular failure (code 1).
3543
+ if (result.exitCode === 4 || result.stage === 'commit-rollback') {
3544
+ report.recoveryRolledBack = true;
3545
+ }
3546
+ }
3547
+ } else if (flags.fix && report.drift.detected && !flags.installState
3548
+ && activeTopologyAtGate === 'marketplace-cache') {
3549
+ // RCA doctor-marketplace-cache-drift-deadlock (2026-06-12): the topology
3550
+ // check is what blocked recovery. Record the skip instead of silently
3551
+ // falling through -- a silent skip left classARecovered AND recoveryError
3552
+ // both null, which rendered as the useless 'recovery failed: unknown' and
3553
+ // tripped the unconditional drift exit-1 in _finalizeAndExit (the
3554
+ // permanent /mos:update Step 7 deadlock). Skipped-by-design is NOT
3555
+ // failure: the renderer and the exit finalizer both read this field.
3556
+ report.recoverySkipped = {
3557
+ reason: 'topology-marketplace-cache',
3558
+ detail: 'recovery skipped by design: active topology is marketplace-cache; the legacy install dir is vestigial and class A will not touch it (class I owns legacy migration)',
3559
+ };
3560
+ }
3561
+
3562
+ // Class B + C: cascade-rooms (sentinel + active-room guard silence) migrated to
3563
+ // registry-driven cadence:always runners (Phase 217 Plan 04). The accumulative
3564
+ // engine now owns cascade-rooms + cascade-rooms-active under the --cascade-rooms
3565
+ // flag; class B's --fix (never wired pre-217) lives in the runner's fix(ctx).
3566
+
3567
+ // Class D (verify-surface), class G (statusline-visibility), and class H
3568
+ // (install-incomplete) migrated to registry-driven cadence:always runners
3569
+ // (Phase 217 Plan 05). The accumulative engine now owns:
3570
+ // - verify-surface under --verify-surface (check-only, fix_supported false);
3571
+ // - statusline-visibility + install-incomplete under --statusline-visibility
3572
+ // (H shares G's flag, today's exact behavior: --statusline-visibility
3573
+ // surfaces both, --all surfaces both);
3574
+ // and the engine's fix-then-recheck flow owns what the old hand-wired G/H --fix
3575
+ // dispatches did (G gate: warn AND recoverable !== false; H's stricter
3576
+ // recoverable === true gate is enforced runner-internally in its fix(ctx)).
3577
+
3578
+ // Class I (install-state) + Class J (deployment-surfaces) migrated to
3579
+ // registry-driven cadence:always runners (Phase 217 Plan 06). The
3580
+ // accumulative engine now owns both under the --install-state flag (they share
3581
+ // it, exactly as this block did): install-state runs first so class J reads
3582
+ // its same-invocation result via ctx.checks['install-state']; each fix's
3583
+ // recoveries[] land on report.recovered through the engine glue. The class-A
3584
+ // BUG 7 reinterpretation that used to close this block (it reads class I's
3585
+ // topology to downgrade an expected-absent legacy clone) is relocated to AFTER
3586
+ // the engine populates report.checks['install-state'] (see below).
3587
+
3588
+
3589
+ // Class P: prose-vs-code / product-claims drift (Phase 150.9 Plan-02;
3590
+ // Fable v1.13.1 audit track 1). REPORT-ONLY (D-03): NEVER edits any prose
3591
+ // .md file, even under --fix. Wraps the two shipped checkers (Canon Part 7
3592
+ // reuse -- repoints, does NOT reimplement):
3593
+ // - check-skill-vs-code-drift.cjs : require().check() (clean, no exit)
3594
+ // - check-first-touch-drift.cjs : spawned as a SUBPROCESS (Pitfall 1 --
3595
+ // it is CLI-only and self-exits, so a
3596
+ // require() would kill doctor mid-run).
3597
+ // OPT-IN ONLY (D-04): gated strictly behind flags.drift, NEVER behind
3598
+ // !classFlagsActive. Pure LOCAL: file reads + a LOCAL node subprocess; zero
3599
+ // network, zero Brain, zero telemetry (Canon Part 8). Soft-fail: a throw
3600
+ // records an error finding, never crashes doctor.
3601
+ if (flags.drift) {
3602
+ try {
3603
+ // (a) skill-vs-code drift -- require-clean.
3604
+ let skillResult = null;
3605
+ let skillErr = null;
3606
+ try {
3607
+ const { check } = require(path.join(__dirname, 'check-skill-vs-code-drift.cjs'));
3608
+ skillResult = check();
3609
+ } catch (e) {
3610
+ skillErr = (e && e.message) || 'skill-vs-code check threw';
3611
+ }
3612
+
3613
+ // (b) first-touch drift -- SUBPROCESS (Pitfall 1). Capture exit code
3614
+ // (1 = drift, 0 = clean) + parse the "DRIFT:" stdout lines. We pass the
3615
+ // child the SAME process.env so a test's MOS_TEST_SCAN_DIR fixture flows
3616
+ // through. Fixed arg array (no shell string).
3617
+ const ftPath = path.join(__dirname, 'check-first-touch-drift.cjs');
3618
+ const cp = require('child_process');
3619
+ const ft = cp.spawnSync('node', [ftPath], { encoding: 'utf8', timeout: 30000 });
3620
+ const ftStdout = (ft && ft.stdout) || '';
3621
+ const firstTouchHits = ftStdout
3622
+ .split('\n')
3623
+ .filter((ln) => ln.indexOf('DRIFT:') === 0).length;
3624
+ const firstTouchDrift = ft && ft.status === 1;
3625
+
3626
+ // Aggregate. skill drift = !valid; first-touch drift = exit 1 / hits > 0.
3627
+ const skillDrift = skillResult ? skillResult.valid === false : false;
3628
+ const anyDrift = skillDrift || firstTouchDrift;
3629
+
3630
+ const detailParts = [];
3631
+ if (skillErr) detailParts.push('skill-vs-code check error: ' + skillErr);
3632
+ else if (skillDrift) detailParts.push('skill-vs-code drift (SKILL.md disagrees with shipped code)');
3633
+ if (firstTouchDrift) detailParts.push('first-touch drift: ' + firstTouchHits + ' hit(s)');
3634
+ if (!anyDrift && !skillErr) detailParts.push('prose-vs-code surfaces clean');
3635
+
3636
+ report.checks['prose-vs-code'] = {
3637
+ class: 'P',
3638
+ // REPORT-ONLY: status is ok|warn (never a --fix that rewrites prose).
3639
+ // A checker that errored is surfaced as warn-with-detail, not a false ok.
3640
+ status: (anyDrift || skillErr) ? 'warn' : 'ok',
3641
+ detail: detailParts.join('; '),
3642
+ skill_findings: skillResult || null,
3643
+ skill_error: skillErr,
3644
+ first_touch_hits: firstTouchHits,
3645
+ first_touch_drift: !!firstTouchDrift,
3646
+ report_only: true,
3647
+ };
3648
+ } catch (err) {
3649
+ report.checks['prose-vs-code'] = {
3650
+ class: 'P',
3651
+ status: 'error',
3652
+ detail: (err && err.message) || 'class P threw',
3653
+ first_touch_hits: 0,
3654
+ report_only: true,
3655
+ };
3656
+ }
3657
+ }
3658
+
3659
+ // Class Q: GSD execution-record drift (Phase 150.9 Plan-02; the W007/I001
3660
+ // class Fable ran by hand). SHELLS OUT to `gsd-tools validate health --raw`
3661
+ // (D-02 -- single source of truth; does NOT reimplement the ROADMAP/SUMMARY
3662
+ // scan) and parses W007 (ROADMAP drift) + I001 (missing SUMMARYs). OPT-IN
3663
+ // ONLY (D-04): gated behind flags.drift. Pure LOCAL: spawns a LOCAL node
3664
+ // subprocess (gsd-tools), zero network / zero Brain (Canon Part 8 -- the
3665
+ // spawnSync('node', [...'validate','health']) is a local subprocess, not a
3666
+ // network call). Pitfalls handled: 2 (shape-assert before reading; error not
3667
+ // false-clean), 3 (cwd = repo root, treat broken/error health as
3668
+ // indeterminate). Soft-fail + locator-skip: never crashes doctor.
3669
+ if (flags.drift) {
3670
+ try {
3671
+ const driftRepoRoot = path.resolve(__dirname, '..');
3672
+ const gsdToolsPath = resolveGsdTools();
3673
+ if (!gsdToolsPath) {
3674
+ report.checks['gsd-record-drift'] = {
3675
+ class: 'Q',
3676
+ status: 'skip',
3677
+ detail: 'gsd-health unavailable (gsd-tools.cjs not found via the resolver order) -- W007/I001 scan skipped',
3678
+ w007: [],
3679
+ i001: [],
3680
+ };
3681
+ } else {
3682
+ const cp = require('child_process');
3683
+ // T-150.9-03 mitigation: FIXED arg array (never a shell string); no
3684
+ // interpolation of untrusted input. cwd = repo root (Pitfall 3).
3685
+ const hr = cp.spawnSync('node', [gsdToolsPath, 'validate', 'health', '--raw'], {
3686
+ cwd: driftRepoRoot,
3687
+ encoding: 'utf8',
3688
+ timeout: 60000,
3689
+ });
3690
+ let health = null;
3691
+ let parseErr = null;
3692
+ try {
3693
+ health = JSON.parse((hr && hr.stdout) || '');
3694
+ } catch (e) {
3695
+ parseErr = (e && e.message) || 'JSON parse failed';
3696
+ }
3697
+
3698
+ if (parseErr || !health || typeof health !== 'object'
3699
+ || !Array.isArray(health.warnings) || !Array.isArray(health.info)) {
3700
+ // Pitfall 2: shape unrecognized -> error, NOT a false clean.
3701
+ report.checks['gsd-record-drift'] = {
3702
+ class: 'Q',
3703
+ status: 'error',
3704
+ detail: 'gsd-health output shape unrecognized -- W007/I001 parse skipped'
3705
+ + (parseErr ? ' (' + parseErr + ')' : ''),
3706
+ w007: [],
3707
+ i001: [],
3708
+ };
3709
+ } else {
3710
+ const w007 = health.warnings.filter((w) => w && w.code === 'W007');
3711
+ const i001 = health.info.filter((i) => i && i.code === 'I001');
3712
+ // Pitfall 3: a broken/error top-level status is indeterminate -- surface
3713
+ // it rather than reporting clean when there are no parsed findings.
3714
+ const topStatus = String(health.status || '');
3715
+ const indeterminate = (topStatus === 'broken' || topStatus === 'error');
3716
+ let qStatus;
3717
+ if (w007.length || i001.length) qStatus = 'warn';
3718
+ else if (indeterminate) qStatus = 'error';
3719
+ else qStatus = 'ok';
3720
+ report.checks['gsd-record-drift'] = {
3721
+ class: 'Q',
3722
+ status: qStatus,
3723
+ detail: qStatus === 'warn'
3724
+ ? ('gsd-record drift: ' + w007.length + ' W007 (ROADMAP gap) + ' + i001.length + ' I001 (missing SUMMARY)')
3725
+ : (qStatus === 'error'
3726
+ ? ('gsd-health status "' + topStatus + '" -- record-drift indeterminate')
3727
+ : 'gsd execution records clean (no W007 / I001)'),
3728
+ health_status: topStatus,
3729
+ w007,
3730
+ i001,
3731
+ };
3732
+ }
3733
+ }
3734
+ } catch (err) {
3735
+ report.checks['gsd-record-drift'] = {
3736
+ class: 'Q',
3737
+ status: 'error',
3738
+ detail: (err && err.message) || 'class Q threw',
3739
+ w007: [],
3740
+ i001: [],
3741
+ };
3742
+ }
3743
+ }
3744
+
3745
+ // Class R: RUNTIME-REACHABILITY drift (Phase 185, DRIFT-01). The assertion
3746
+ // Classes P + Q lack: P/Q do MERGE-TIME marking (prose-vs-code + gsd-record)
3747
+ // only -- neither checks whether a WIRED capability is actually REACHABLE by
3748
+ // decide() at runtime. Class R closes that gap (CIRS R9 -- doctor --drift is
3749
+ // the SCHEDULED reconciliation surface): it FAILS when a capability is WIRED
3750
+ // in the connector registry but the Phase-184 decide()-time projection READER
3751
+ // can never surface it. Unlike P (report-only prose) this is a HARD signal --
3752
+ // a runtime-unreachable WIRED capability flips doctor --drift to non-zero (the
3753
+ // narrow exit branch in _finalizeAndExit keys on this check's 'warn' status).
3754
+ //
3755
+ // Pure LOCAL (Canon Part 8): reads the connector registry + the orchestration
3756
+ // projection (both committed LOCAL artifacts) via the Phase-184 reader; zero
3757
+ // network egress. Reachability is computed DETERMINISTICALLY in code,
3758
+ // never by an LLM-judge. Soft-fail: a throw records an 'error' finding (exit
3759
+ // stays 0 -- an indeterminate computation is not a drift FAIL), never crashes
3760
+ // doctor. Hermetic test seams: MOS_TEST_REACHABILITY_REGISTRY /
3761
+ // MOS_TEST_REACHABILITY_PROJECTION inject fixture artifact paths.
3762
+ if (flags.drift) {
3763
+ try {
3764
+ const { checkRuntimeReachability } = require(
3765
+ path.join(__dirname, '..', 'lib', 'core', 'drift-runtime-reachability.cjs'));
3766
+ const rrOpts = {};
3767
+ if (process.env.MOS_TEST_REACHABILITY_REGISTRY) {
3768
+ rrOpts.registryPath = process.env.MOS_TEST_REACHABILITY_REGISTRY;
3769
+ }
3770
+ if (process.env.MOS_TEST_REACHABILITY_PROJECTION) {
3771
+ rrOpts.projectionPath = process.env.MOS_TEST_REACHABILITY_PROJECTION;
3772
+ }
3773
+ const rr = checkRuntimeReachability(rrOpts);
3774
+ report.checks['runtime-reachability'] = Object.assign({ class: 'R' }, rr);
3775
+ } catch (err) {
3776
+ report.checks['runtime-reachability'] = {
3777
+ class: 'R',
3778
+ status: 'error',
3779
+ detail: (err && err.message) || 'class R threw',
3780
+ wired_eligible: 0,
3781
+ reachable: 0,
3782
+ unreachable: [],
3783
+ report_only: false,
3784
+ };
3785
+ }
3786
+ }
3787
+
3788
+ // --drift --fix HEAL ARM (D-03 heal-where-safe split). MECHANICAL drift
3789
+ // auto-heals: write the DRIFT.md baseline (root index + per-folder detail)
3790
+ // from the Class P + Class Q findings, and stub a missing SUMMARY for each
3791
+ // I001 record. JUDGMENT-CALL drift stays REPORT-ONLY: Class P prose findings
3792
+ // are recorded, NEVER auto-fixed. This arm MUST NOT call process.exit (exit
3793
+ // is _finalizeAndExit-only -- preserves the exit-0 invariant + the deadlock
3794
+ // carve-out). Soft-fail: a throw records a heal error, never crashes doctor.
3795
+ if (flags.drift && flags.fix) {
3796
+ try {
3797
+ const driftRepoRoot = path.resolve(__dirname, '..');
3798
+ const planningDir = path.join(driftRepoRoot, '.planning');
3799
+ const {
3800
+ writeDriftBaseline,
3801
+ stubMissingSummary,
3802
+ } = require(path.join(__dirname, '..', 'lib', 'core', 'drift-baseline.cjs'));
3803
+
3804
+ // Build the finding set from Class P + Class Q results.
3805
+ const findings = [];
3806
+ const qRes = report.checks['gsd-record-drift'];
3807
+ if (qRes && Array.isArray(qRes.w007)) {
3808
+ for (const w of qRes.w007) {
3809
+ const phase = extractPhaseToken(w && w.message);
3810
+ findings.push({
3811
+ finding_id: 'W007-' + (phase || 'unknown'),
3812
+ track: 'gsd_record',
3813
+ severity: 'warn',
3814
+ status: 'open',
3815
+ source_class: 'Q',
3816
+ detail: (w && w.message) || 'ROADMAP gap',
3817
+ phase: phase,
3818
+ per_folder: !!phase,
3819
+ });
3820
+ }
3821
+ }
3822
+ const summaryStubs = [];
3823
+ if (qRes && Array.isArray(qRes.i001)) {
3824
+ for (const i of qRes.i001) {
3825
+ const planRef = extractPlanRef(i && i.message);
3826
+ const phase = planRef ? planRef.phase : null;
3827
+ findings.push({
3828
+ finding_id: 'I001-' + (planRef ? planRef.planLabel : 'unknown'),
3829
+ track: 'gsd_record',
3830
+ severity: 'info',
3831
+ status: 'open',
3832
+ source_class: 'Q',
3833
+ detail: (i && i.message) || 'missing SUMMARY',
3834
+ phase: phase,
3835
+ per_folder: !!phase,
3836
+ });
3837
+ if (planRef) summaryStubs.push(planRef);
3838
+ }
3839
+ }
3840
+ // Class P prose findings are recorded REPORT-ONLY in the root index; they
3841
+ // carry per_folder:false so the writer renders a (report-only) pointer and
3842
+ // never writes a prose file.
3843
+ const pRes = report.checks['prose-vs-code'];
3844
+ if (pRes && (pRes.status === 'warn')) {
3845
+ findings.push({
3846
+ finding_id: 'P-prose-vs-code',
3847
+ track: 'prose_vs_code',
3848
+ severity: 'high',
3849
+ status: 'open',
3850
+ source_class: 'P',
3851
+ detail: pRes.detail || 'prose-vs-code drift (report-only)',
3852
+ phase: null,
3853
+ per_folder: false,
3854
+ });
3855
+ }
3856
+
3857
+ const healResult = { drift_md: null, summary_stubs: [], errors: [] };
3858
+ try {
3859
+ const wr = writeDriftBaseline({
3860
+ planningDir,
3861
+ findings,
3862
+ meta: {
3863
+ audit_date: new Date().toISOString().slice(0, 10),
3864
+ auditor: 'doctor --drift --fix (Class P + Q)',
3865
+ status: (qRes && qRes.health_status) || 'unknown',
3866
+ process_note: 'Bound per-agent scope, fan wider (Fable 600s watchdog lesson).',
3867
+ },
3868
+ });
3869
+ healResult.drift_md = { rootPath: wr.rootPath, wrote: wr.wrote };
3870
+ } catch (e) {
3871
+ healResult.errors.push('drift-baseline write: ' + ((e && e.message) || e));
3872
+ }
3873
+ // Stub missing SUMMARYs (I001 mechanical auto-heal).
3874
+ for (const ref of summaryStubs) {
3875
+ try {
3876
+ const phaseDir = findPhaseDir(driftRepoRoot, ref.phase);
3877
+ if (!phaseDir) {
3878
+ healResult.errors.push('no phase dir for ' + ref.phase + ' (' + ref.planFile + ')');
3879
+ continue;
3880
+ }
3881
+ const st = stubMissingSummary({ phaseDir, planFile: ref.planFile });
3882
+ healResult.summary_stubs.push({ path: st.path, created: st.created });
3883
+ } catch (e) {
3884
+ healResult.errors.push('summary stub ' + ref.planFile + ': ' + ((e && e.message) || e));
3885
+ }
3886
+ }
3887
+ report.checks['drift-heal'] = {
3888
+ class: 'Q',
3889
+ status: healResult.errors.length ? 'warn' : 'ok',
3890
+ detail: 'DRIFT.md baseline written; ' + healResult.summary_stubs.filter((s) => s.created).length
3891
+ + ' SUMMARY stub(s) created'
3892
+ + (healResult.errors.length ? '; ' + healResult.errors.length + ' heal error(s)' : ''),
3893
+ drift_md: healResult.drift_md,
3894
+ summary_stubs: healResult.summary_stubs,
3895
+ errors: healResult.errors,
3896
+ report_only_prose: true,
3897
+ };
3898
+ } catch (err) {
3899
+ report.checks['drift-heal'] = {
3900
+ class: 'Q',
3901
+ status: 'error',
3902
+ detail: (err && err.message) || 'drift heal arm threw',
3903
+ summary_stubs: [],
3904
+ errors: [(err && err.message) || 'threw'],
3905
+ };
3906
+ }
3907
+ }
3908
+
3909
+ // Phase 127-02 BRAIN-MCP-127-08: Class M (Brain end-to-end smoke) under
3910
+ // combined runs (e.g. --all). The standalone --brain-smoke path is handled
3911
+ // earlier with its own canonical output shape; here we attach the result
3912
+ // into report.checks['brain-smoke'] so --all sees all classes in one
3913
+ // report. Async-to-sync bridge: run-and-finalize via promise chain.
3914
+ // Phase 139 Plan-02 (S2): the accumulative-engine selector. It is the ENGINE,
3915
+ // not a class -- so it is NOT gated behind a class A-M flag. It runs under
3916
+ // --all (every doctor sweep heals the install forward) and standalone (a bare
3917
+ // `doctor` run). With the registry seeded EMPTY of organ modules it selects
3918
+ // zero modules and stays a no-op; the wiring is what Plan 03 builds on.
3919
+ // Soft-fail: a throw here records an error finding but never crashes doctor.
3920
+ // Phase 217 Plan-01: the engine call is now UNCONDITIONAL (cadence:always
3921
+ // modules must run on every invocation via their own flag gates). The engine
3922
+ // itself reproduces the old class-A/N gate internally for the once-pass +
3923
+ // self-report row via opts.classFlagsActive. main() spreads the engine's
3924
+ // per-module always results into report.checks (so the generic renderer +
3925
+ // computeSummary treat each as a top-level row) and appends any recovered
3926
+ // records; the self-report row is attached ONLY under the old gate so a
3927
+ // --room-md-only run does not grow a new accumulative-engine row.
3928
+ try {
3929
+ const engineResult = runAccumulativeEngine({
3930
+ fix: flags.fix,
3931
+ dryRun: flags.dryRun,
3932
+ flags,
3933
+ classFlagsActive,
3934
+ });
3935
+ const { checks: engineChecks, recovered: engineRecovered, ...engineSelf } = engineResult;
3936
+ Object.assign(report.checks, engineChecks || {});
3937
+ if (Array.isArray(engineRecovered)) {
3938
+ for (const rec of engineRecovered) report.recovered.push(rec);
3939
+ }
3940
+ if (flags.all || flags.fix || !classFlagsActive) {
3941
+ report.checks['accumulative-engine'] = engineSelf;
3942
+ }
3943
+ } catch (err) {
3944
+ report.checks['accumulative-engine'] = { status: 'error', detail: (err && err.message) || 'engine threw', selected: [], deferred: [], findings: [], advanced: false };
3945
+ }
3946
+
3947
+ // BUG 7 reinterpretation (relocated from the retired class I/J main() block,
3948
+ // Phase 217 Plan 06): class A (install-cache) x class I (install-state)
3949
+ // interplay. When class A reports install status:'missing' AND class I
3950
+ // resolves topology=='marketplace-cache', the legacy clone path is EXPECTED
3951
+ // absent (Claude Code loads from the cache), so downgrade class A to a note
3952
+ // and neutralize an install-missing-based drift. Class I now runs inside the
3953
+ // accumulative engine, so this reads the engine-populated
3954
+ // report.checks['install-state']. Gated on flags.installState (the flag that
3955
+ // activates class I), matching the pre-migration guard.
3956
+ if (flags.installState) {
3957
+ const stateAfter = report.checks['install-state'];
3958
+ if (report.install && report.install.status === 'missing'
3959
+ && stateAfter && stateAfter.topology === 'marketplace-cache') {
3960
+ report.install = Object.assign({}, report.install, {
3961
+ status: 'ok',
3962
+ note: 'legacy clone path expected absent on a marketplace-cache install (Bug 7 fix)',
3963
+ bug7_fix: true,
3964
+ });
3965
+ if (report.drift && report.drift.reason === 'install-missing') {
3966
+ report.drift = { detected: false, reason: 'bug7-fix-marketplace-cache' };
3967
+ }
3968
+ }
3969
+ }
3970
+
3971
+
3972
+ if (flags.brainSmoke) {
3973
+ const { checkBrainSmoke } = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'class-m-brain-smoke.cjs'));
3974
+ checkBrainSmoke().then(function (result) {
3975
+ report.checks['brain-smoke'] = Object.assign({ class: 'M' }, result);
3976
+ _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult);
3977
+ }).catch(function (err) {
3978
+ report.checks['brain-smoke'] = { class: 'M', status: 'error', detail: err && err.message };
3979
+ _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult);
3980
+ });
3981
+ return;
3982
+ }
3983
+
3984
+ // quick(260706-13z) D14: Class S (Eureka smoke) under combined runs (e.g. a
3985
+ // --brain-smoke --eureka-smoke pair, or --all). The standalone --eureka-smoke
3986
+ // path is handled earlier with its own canonical output; here we attach the
3987
+ // result into report.checks['eureka-smoke'] beside the brain-smoke block.
3988
+ if (flags.eurekaSmoke) {
3989
+ const { checkEurekaSmoke } = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'class-s-eureka-smoke.cjs'));
3990
+ checkEurekaSmoke().then(function (result) {
3991
+ report.checks['eureka-smoke'] = Object.assign({ class: 'S' }, result);
3992
+ _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult);
3993
+ }).catch(function (err) {
3994
+ report.checks['eureka-smoke'] = { class: 'S', status: 'error', detail: err && err.message };
3995
+ _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult);
3996
+ });
3997
+ return;
3998
+ }
3999
+
4000
+ _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult);
4001
+ }
4002
+
4003
+ // -- --check-rs-engine probe (Phase 127.2 Plan 03, Findings F1 + F7) ---------
4004
+ //
4005
+ // Pre-flights Python deps reachable from scripts/rs-engine.py for the Engine 1
4006
+ // Act 1 surface. Probes:
4007
+ // - `requests` (transitive via lib/core/rs_corpus.py; the Windows 2026-05-23
4008
+ // silent-failure root cause)
4009
+ // - `numpy` (direct top-level import in rs-engine.py + rs_math)
4010
+ // Plus best-effort umbrella probe of `sentence_transformers` + `sklearn`
4011
+ // (declared in requirements-hsi.txt; used by sibling Engine 1 surfaces).
4012
+ //
4013
+ // Python resolution: `python3` preferred (POSIX convention + Aryeh's tester
4014
+ // machine has both py3.13 + py3.14 reachable via `python3`); falls back to
4015
+ // `python` (Windows / older installs). Honors MINDRIAN_PYTHON env override
4016
+ // for tester / CI use, matching lib/agents/reverse-salient-agent.cjs:181.
4017
+ //
4018
+ // JSON exit (always emits on --json): { ok, ready, missing, python, fix }.
4019
+ // Human exit: actionable fix line on FAIL; concise OK line on PASS.
4020
+ //
4021
+ // Phase-134-python-elimination-false-complete RCA, Change 1 (2026-08-27):
4022
+ // `--fix` composes with `--check-rs-engine` (same convention as `--drift --fix`
4023
+ // and `--graph-derive-health --fix` / `--heal-room`) to actually install the
4024
+ // missing deps via `python -m pip install --user`, instead of only printing
4025
+ // the manual instruction. Explicit `--fix` invocation IS the consent gate --
4026
+ // same precedent as every other `--fix` composition in this file. Without
4027
+ // `--fix`, behavior is unchanged: probe-only, printed instruction.
4028
+ async function runCheckRsEngine(flags) {
4029
+ const cp = require('node:child_process');
4030
+ // Critical deps (rs-engine.py path -- failure here = /mos:find-bottlenecks down).
4031
+ const critical = ['requests', 'numpy'];
4032
+ // Umbrella deps (broader Engine 1 Act 1 surface; missing -> warn but not fail).
4033
+ const umbrella = ['sentence_transformers', 'sklearn'];
4034
+ const all = critical.concat(umbrella);
4035
+ const fix = 'Run: pip install -r requirements-hsi.txt --user (use python -m pip if pip is not in PATH), or `/mos:doctor --check-rs-engine --fix` to install automatically';
4036
+ // import-name -> pip-package-name (they differ for two of the four deps).
4037
+ const pipName = {
4038
+ requests: 'requests',
4039
+ numpy: 'numpy',
4040
+ sentence_transformers: 'sentence-transformers',
4041
+ sklearn: 'scikit-learn',
4042
+ };
4043
+
4044
+ // Resolve python interpreter: MINDRIAN_PYTHON > python3 > python.
4045
+ // Each candidate gets a probe; first one that responds wins.
4046
+ function probePythonAvailable(bin) {
4047
+ try {
4048
+ const r = cp.spawnSync(bin, ['-c', 'import sys; print(sys.version)'], {
4049
+ encoding: 'utf8',
4050
+ timeout: 5000,
4051
+ });
4052
+ return r && r.status === 0;
4053
+ } catch (_e) {
4054
+ return false;
4055
+ }
4056
+ }
4057
+ let python = process.env.MINDRIAN_PYTHON || null;
4058
+ if (!python || !probePythonAvailable(python)) {
4059
+ if (probePythonAvailable('python3')) python = 'python3';
4060
+ else if (probePythonAvailable('python')) python = 'python';
4061
+ else python = null;
4062
+ }
4063
+
4064
+ if (!python) {
4065
+ const payload = {
4066
+ ok: false,
4067
+ ready: false,
4068
+ missing: all.slice(),
4069
+ python: null,
4070
+ fix: 'Python 3 is not on PATH. Install Python 3.10+ then: ' + fix,
4071
+ };
4072
+ if (flags.json) {
4073
+ console.log(JSON.stringify(payload, null, 2));
4074
+ } else {
4075
+ console.log('Engine 1 Act 1 (/mos:find-bottlenecks et al.) cannot start: Python is not on PATH.');
4076
+ console.log(payload.fix);
4077
+ }
4078
+ return 1;
4079
+ }
4080
+
4081
+ // Per-dep probe via `python -c "import <name>"`. Each probe is timeout-fenced
4082
+ // so a hung interpreter cannot stall /mos:doctor. Status code 0 = present.
4083
+ function probeImport(dep) {
4084
+ try {
4085
+ const r = cp.spawnSync(python, ['-c', 'import ' + dep], {
4086
+ encoding: 'utf8',
4087
+ timeout: 10000,
4088
+ });
4089
+ const ok = r && r.status === 0;
4090
+ // Capture stderr tail on failure for diagnostic surface (mirror F2 pattern).
4091
+ const stderrTail = (!ok && r && r.stderr) ? String(r.stderr).slice(-200) : '';
4092
+ return { dep: dep, ok: ok, stderrTail: stderrTail };
4093
+ } catch (e) {
4094
+ return { dep: dep, ok: false, stderrTail: String((e && e.message) || '').slice(-200) };
4095
+ }
4096
+ }
4097
+
4098
+ let results = all.map(probeImport);
4099
+ let missingCritical = results.filter(function (r) {
4100
+ return critical.indexOf(r.dep) !== -1 && !r.ok;
4101
+ }).map(function (r) { return r.dep; });
4102
+ let missingUmbrella = results.filter(function (r) {
4103
+ return umbrella.indexOf(r.dep) !== -1 && !r.ok;
4104
+ }).map(function (r) { return r.dep; });
4105
+ let ok = missingCritical.length === 0;
4106
+
4107
+ // Self-remediation (--fix): attempt `python -m pip install --user` for
4108
+ // every currently-missing dep, then re-probe. Explicit `--fix` is the
4109
+ // consent gate (matches --drift --fix / --heal-room precedent elsewhere
4110
+ // in this file) -- no prompt, no dry-run-first, same posture as the
4111
+ // already-shipped scripts/lib/ensure_ml_deps.py auto-installer.
4112
+ let remediation = null;
4113
+ if (!ok || missingUmbrella.length > 0) {
4114
+ if (flags.fix) {
4115
+ const toInstall = missingCritical.concat(missingUmbrella);
4116
+ const pipArgs = ['-m', 'pip', 'install', '--user', '--quiet']
4117
+ .concat(toInstall.map(function (dep) { return pipName[dep] || dep; }));
4118
+ if (!flags.json) {
4119
+ console.log('Engine 1 Act 1: installing missing Python deps (' + toInstall.join(', ') + ')...');
4120
+ }
4121
+ let installResult;
4122
+ try {
4123
+ installResult = cp.spawnSync(python, pipArgs, { encoding: 'utf8', timeout: 300000 });
4124
+ } catch (e) {
4125
+ installResult = { status: -1, error: e };
4126
+ }
4127
+ const installOk = installResult && installResult.status === 0;
4128
+ // Re-probe regardless of the reported install exit code -- the ground
4129
+ // truth is whether the interpreter can import the module afterward,
4130
+ // not pip's exit status (mirrors ensure_ml_deps.py's own re-check).
4131
+ results = all.map(probeImport);
4132
+ missingCritical = results.filter(function (r) {
4133
+ return critical.indexOf(r.dep) !== -1 && !r.ok;
4134
+ }).map(function (r) { return r.dep; });
4135
+ missingUmbrella = results.filter(function (r) {
4136
+ return umbrella.indexOf(r.dep) !== -1 && !r.ok;
4137
+ }).map(function (r) { return r.dep; });
4138
+ ok = missingCritical.length === 0;
4139
+ remediation = {
4140
+ attempted: true,
4141
+ installed: toInstall,
4142
+ pip_exit_code: installResult ? installResult.status : null,
4143
+ resolved: installOk && missingCritical.concat(missingUmbrella).length === 0,
4144
+ still_missing: missingCritical.concat(missingUmbrella),
4145
+ stderr_tail: (installResult && installResult.stderr) ? String(installResult.stderr).slice(-200) : '',
4146
+ };
4147
+ } else {
4148
+ remediation = { attempted: false };
4149
+ }
4150
+ }
4151
+
4152
+ if (flags.json) {
4153
+ const payload = {
4154
+ ok: ok,
4155
+ ready: ok,
4156
+ python: python,
4157
+ probes: results.map(function (r) { return { dep: r.dep, ok: r.ok }; }),
4158
+ missing: missingCritical.concat(missingUmbrella),
4159
+ missing_critical: missingCritical,
4160
+ missing_umbrella: missingUmbrella,
4161
+ fix: ok ? null : fix,
4162
+ remediation: remediation,
4163
+ };
4164
+ console.log(JSON.stringify(payload, null, 2));
4165
+ } else {
4166
+ if (ok) {
4167
+ console.log('Engine 1 Act 1 ready -- Python (' + python + ') has all critical deps: ' + critical.join(', ') + '.');
4168
+ if (remediation && remediation.attempted) {
4169
+ console.log('Auto-installed: ' + remediation.installed.join(', ') + '.');
4170
+ }
4171
+ if (missingUmbrella.length > 0) {
4172
+ console.log('Note: umbrella deps missing (' + missingUmbrella.join(', ') + '); affects sibling Engine 1 surfaces. ' + fix);
4173
+ }
4174
+ } else if (remediation && remediation.attempted) {
4175
+ console.log('Engine 1 Act 1 (/mos:find-bottlenecks et al.) still needs Python deps after auto-install attempt.');
4176
+ console.log('Still missing: ' + missingCritical.join(', ') + '.');
4177
+ if (remediation.stderr_tail) console.log('pip stderr (tail): ' + remediation.stderr_tail);
4178
+ console.log('Manual fallback -- pip install -r requirements-hsi.txt --user (use python -m pip if pip is not in PATH)');
4179
+ } else {
4180
+ console.log('Engine 1 Act 1 (/mos:find-bottlenecks et al.) needs Python deps. Missing: ' + missingCritical.join(', ') + '.');
4181
+ console.log(fix);
4182
+ }
4183
+ }
4184
+
4185
+ return ok ? 0 : 1;
4186
+ }
4187
+
4188
+ // -- Class M dispatcher (Phase 127-02) ---------------------------------------
4189
+ //
4190
+ // Mirrors the Class I dispatcher shape (scripts/doctor.cjs Class I precedent).
4191
+ // Returns the process exit code; per the class-flag invariant (line 1447) this
4192
+ // is 0 even on probe FAIL. The standalone --brain-smoke caller awaits this and
4193
+ // exits with whatever this returns; the --all combined caller goes through
4194
+ // _finalizeAndExit instead.
4195
+ async function classMBrainSmoke(flags) {
4196
+ const { checkBrainSmoke } = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'class-m-brain-smoke.cjs'));
4197
+ const result = await checkBrainSmoke();
4198
+ if (flags.json) {
4199
+ console.log(JSON.stringify(Object.assign({ class: 'M' }, result), null, 2));
4200
+ } else {
4201
+ console.log('Class M -- Brain end-to-end smoke (6-layer probe)');
4202
+ console.log(' Overall: ' + (result.ok ? 'PASS' : 'FAIL') + ' (' + result.overall_ms + 'ms)');
4203
+ for (const layer of result.layers) {
4204
+ const marker = layer.ok ? 'PASS' : 'FAIL';
4205
+ console.log(' [' + marker + '] ' + layer.name + ' -- ' + layer.reason + ' (' + layer.ms + 'ms)');
4206
+ // Handoff section 7 item g: report which endpoint the wire resolved
4207
+ // to and whether it is canon. Guarded on payload being present so
4208
+ // layers 1-5 (which never carry one) print exactly as before.
4209
+ if (layer.payload) {
4210
+ const p = layer.payload;
4211
+ console.log(' endpoint=' + p.endpoint + ' node_count=' + p.node_count
4212
+ + ' canon=' + p.canon + (p.override ? ' override=true' : ''));
4213
+ if (p.stamp) {
4214
+ const stampParts = [];
4215
+ if (p.stamp.schema_version != null) stampParts.push('schema_version=' + p.stamp.schema_version);
4216
+ if (p.stamp.last_reconciled != null) stampParts.push('last_reconciled=' + p.stamp.last_reconciled);
4217
+ if (p.stamp.refreshed_at != null) stampParts.push('refreshed_at=' + p.stamp.refreshed_at);
4218
+ console.log(' GraphRagMeta stamp: ' + stampParts.join(' '));
4219
+ }
4220
+ }
4221
+ }
4222
+ }
4223
+ // Class-flag invariant: exit 0 even on per-layer FAIL.
4224
+ return 0;
4225
+ }
4226
+
4227
+ // -- Class S dispatcher (quick 260706-13z) -----------------------------------
4228
+ //
4229
+ // Mirrors the Class M dispatcher shape. Returns the process exit code; per the
4230
+ // class-flag invariant this is 0 even on probe FAIL. The standalone
4231
+ // --eureka-smoke caller awaits this; the combined caller goes through
4232
+ // _finalizeAndExit instead.
4233
+ async function classSEurekaSmoke(flags) {
4234
+ const { checkEurekaSmoke } = require(path.join(__dirname, '..', 'lib', 'core', 'doctor', 'class-s-eureka-smoke.cjs'));
4235
+ const result = await checkEurekaSmoke();
4236
+ if (flags.json) {
4237
+ console.log(JSON.stringify(Object.assign({ class: 'S' }, result), null, 2));
4238
+ } else {
4239
+ console.log('Class S -- Eureka local-embedding-stack smoke (4-layer probe)');
4240
+ console.log(' Overall: ' + (result.ok ? 'PASS' : 'FAIL') + ' (' + result.overall_ms + 'ms)');
4241
+ for (const layer of result.layers) {
4242
+ const marker = layer.ok ? 'PASS' : 'FAIL';
4243
+ console.log(' [' + marker + '] ' + layer.name + ' -- ' + layer.reason + ' (' + layer.ms + 'ms)');
4244
+ }
4245
+ }
4246
+ // Class-flag invariant: exit 0 even on per-layer FAIL.
4247
+ return 0;
4248
+ }
4249
+
4250
+ // -- Output + exit finalizer (extracted Phase 127-02 to allow async injection)
4251
+ function _finalizeAndExit(flags, report, classFlagsActive, cacheResult, installResult) {
4252
+ // Output
4253
+ if (flags.json) {
4254
+ console.log(JSON.stringify(report, null, 2));
4255
+ } else {
4256
+ console.log(renderHumanReport(report));
4257
+ }
4258
+
4259
+ // Exit code.
4260
+ // Phase 185 (DRIFT-01): runtime-reachability is the ONE --drift check that
4261
+ // produces a HARD non-zero signal. A capability WIRED in the connector
4262
+ // registry but UNREACHABLE by decide() at runtime is a CIRS R9 drift FAIL, not
4263
+ // a report-only warning -- so a 'warn' here flips doctor --drift to exit 1.
4264
+ // This is evaluated BEFORE the classFlagsActive graceful-degradation exit-0,
4265
+ // but it is narrowly scoped: report.checks['runtime-reachability'] is
4266
+ // populated ONLY under flags.drift, so this branch can never alter the exit
4267
+ // code of any other class-flag run (--all does not set --drift), and it leaves
4268
+ // the marketplace-cache-drift-deadlock carve-out (Class A) untouched. A clean
4269
+ // reachability check (status 'ok') OR an indeterminate one ('error') falls
4270
+ // through to the normal exit-0 invariant, so the real registry+projection
4271
+ // (fully reachable today) keeps doctor --drift at exit 0 -- no regression.
4272
+ const reach = report.checks && report.checks['runtime-reachability'];
4273
+ if (reach && reach.status === 'warn') {
4274
+ process.exit(1);
4275
+ }
4276
+ // When class flags are active we honor the graceful-degradation invariant
4277
+ // (Canon Part 8): the doctor run NEVER aborts with non-zero unless an
4278
+ // explicit --fix attempt failed. Tests rely on this for hermetic scratch-
4279
+ // registry runs that have no real install directory.
4280
+ if (classFlagsActive) {
4281
+ process.exit(0);
4282
+ }
4283
+ // Phase 95.2 exit-code chain (additive; preserves existing 0/1/2/3 semantics, adds 4).
4284
+ // 0 = healthy (no drift detected).
4285
+ // 1 = drift detected (read-only mode OR --fix didn't run).
4286
+ // 2 = drift detected and recovered via --fix.
4287
+ // 3 = internal error (cache unreadable, or install in 'error' state -- not 'missing').
4288
+ // 4 = recovery attempted but rolled back to backup state (D-03).
4289
+ // 0 = drift detected but recovery skipped by design (marketplace-cache
4290
+ // topology; report.recoverySkipped set -- only ever under --fix, so
4291
+ // read-only drift keeps exiting 1). RCA:
4292
+ // doctor-marketplace-cache-drift-deadlock (2026-06-12).
4293
+ if (cacheResult.status !== 'ok') process.exit(3);
4294
+ if (installResult.status === 'error') process.exit(3);
4295
+ if (report.recoveryRolledBack) process.exit(4);
4296
+ if (report.classARecovered) process.exit(2);
4297
+ if (report.drift.detected && !report.recoverySkipped) process.exit(1);
4298
+ // Class N (DRIFT-12): the plugin-DISABLED silent-disable state is a drift
4299
+ // signal on a bare / non-class-flag run. enabled===false -> exit 1 so
4300
+ // `mindrian-os doctor` returns non-zero for the one failure mode that no
4301
+ // SessionStart hook can ever report (the hooks do not run while disabled).
4302
+ // ok / null / skip leave the exit at 0. Under class-flag mode the run already
4303
+ // returned 0 above (graceful-degradation invariant) -- the finding is still
4304
+ // visible in report.checks for --all consumers.
4305
+ const pen = report.checks && report.checks['plugin-enabled-state'];
4306
+ if (pen && pen.status === 'warn') process.exit(1);
4307
+ process.exit(0);
4308
+ }
4309
+
4310
+ // Phase 139 Plan-02: export the accumulative-engine selector for hermetic unit
4311
+ // testing (tests/test-doctor-module-selector.cjs requires this module and calls
4312
+ // runAccumulativeEngine with an injected registry + watermark HOME). Guard the
4313
+ // CLI entry so `require('doctor.cjs')` does NOT run main(); the script still
4314
+ // runs main() when invoked directly or spawned as a subprocess (the standard
4315
+ // `node scripts/doctor.cjs ...` usage, where require.main === module).
4316
+ // renderHumanReport + computeSummary are additively exported so
4317
+ // tests/test-doctor-fix-renderer.cjs can drive them as hermetic unit sub-tests
4318
+ // (warn-row visibility, Summary parity, recovered-line rendering) without
4319
+ // spawning a subprocess. The require.main guard below still prevents main().
4320
+ // Phase 225-02 (REQ-4): additive export of the WAL-reset advisory helpers for
4321
+ // hermetic unit testing via injected seams (tests/test-225-wal-advisory.cjs).
4322
+ // Additive-only append -- existing keys are never reordered.
4323
+ module.exports = { runAccumulativeEngine, renderHumanReport, computeSummary, _walResetAdvisory, _sqliteVersionLt, evaluateMarketplaceSourcePin };
4324
+
4325
+ if (require.main === module) {
4326
+ main();
4327
+ }