@mindrian_os/install 1.13.0-beta.11

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 (597) hide show
  1. package/.claude-plugin/plugin.json +21 -0
  2. package/.mcp.json +9 -0
  3. package/CHANGELOG.md +3333 -0
  4. package/LICENSE +123 -0
  5. package/README.md +673 -0
  6. package/agents/brain-query.md +80 -0
  7. package/agents/framework-runner.md +237 -0
  8. package/agents/grading.md +188 -0
  9. package/agents/investor.md +128 -0
  10. package/agents/larry-extended.md +135 -0
  11. package/agents/opportunity-scanner.md +91 -0
  12. package/agents/persona-analyst.md +132 -0
  13. package/agents/research.md +89 -0
  14. package/agents/reverse-salient-agent.md +27 -0
  15. package/bin/cli.js +142 -0
  16. package/bin/mindrian-mcp-server.cjs +182 -0
  17. package/bin/mindrian-tools.cjs +765 -0
  18. package/commands/act.md +439 -0
  19. package/commands/admin.md +404 -0
  20. package/commands/analyze-needs.md +42 -0
  21. package/commands/analyze-systems.md +39 -0
  22. package/commands/analyze-timing.md +42 -0
  23. package/commands/auto-explore.md +64 -0
  24. package/commands/beautiful-question.md +40 -0
  25. package/commands/brain-derive.md +78 -0
  26. package/commands/build-knowledge.md +42 -0
  27. package/commands/build-thesis.md +46 -0
  28. package/commands/causal.md +234 -0
  29. package/commands/challenge-assumptions.md +33 -0
  30. package/commands/compare-ventures.md +83 -0
  31. package/commands/dashboard.md +110 -0
  32. package/commands/deep-grade.md +82 -0
  33. package/commands/diagnose.md +58 -0
  34. package/commands/diagnostics.md +151 -0
  35. package/commands/doctor.md +151 -0
  36. package/commands/dominant-designs.md +40 -0
  37. package/commands/explain-decision.md +87 -0
  38. package/commands/explore-domains.md +42 -0
  39. package/commands/explore-futures.md +40 -0
  40. package/commands/explore-trends.md +42 -0
  41. package/commands/export.md +103 -0
  42. package/commands/file-meeting.md +724 -0
  43. package/commands/find-analogies.md +188 -0
  44. package/commands/find-bottlenecks.md +62 -0
  45. package/commands/find-connections.md +76 -0
  46. package/commands/funding.md +81 -0
  47. package/commands/grade.md +203 -0
  48. package/commands/graph.md +128 -0
  49. package/commands/hat-briefing.md +125 -0
  50. package/commands/heal.md +196 -0
  51. package/commands/help.md +399 -0
  52. package/commands/hmi-status.md +172 -0
  53. package/commands/jtbd.md +241 -0
  54. package/commands/leadership.md +73 -0
  55. package/commands/lean-canvas.md +40 -0
  56. package/commands/macro-trends.md +40 -0
  57. package/commands/map-unknowns.md +40 -0
  58. package/commands/memory.md +173 -0
  59. package/commands/models.md +175 -0
  60. package/commands/mos-reason.md +285 -0
  61. package/commands/mullins.md +120 -0
  62. package/commands/new-project.md +481 -0
  63. package/commands/onboard.md +434 -0
  64. package/commands/operator.md +149 -0
  65. package/commands/opportunities.md +144 -0
  66. package/commands/organize.md +497 -0
  67. package/commands/persona.md +198 -0
  68. package/commands/pipeline.md +112 -0
  69. package/commands/present.md +91 -0
  70. package/commands/publish.md +201 -0
  71. package/commands/query.md +124 -0
  72. package/commands/radar.md +72 -0
  73. package/commands/reanalyze.md +91 -0
  74. package/commands/research.md +196 -0
  75. package/commands/room.md +352 -0
  76. package/commands/rooms.md +598 -0
  77. package/commands/root-cause.md +40 -0
  78. package/commands/rs-experts.md +85 -0
  79. package/commands/rs-explain.md +100 -0
  80. package/commands/rs-fetch.md +94 -0
  81. package/commands/rs-thesis.md +85 -0
  82. package/commands/scenario-plan.md +40 -0
  83. package/commands/scheduled-tasks.md +285 -0
  84. package/commands/score-innovation.md +43 -0
  85. package/commands/scout.md +239 -0
  86. package/commands/setup.md +618 -0
  87. package/commands/snapshot.md +147 -0
  88. package/commands/speakers.md +84 -0
  89. package/commands/splash.md +28 -0
  90. package/commands/status.md +75 -0
  91. package/commands/structure-argument.md +42 -0
  92. package/commands/suggest-next.md +80 -0
  93. package/commands/systems-thinking.md +40 -0
  94. package/commands/think-hats.md +42 -0
  95. package/commands/update.md +181 -0
  96. package/commands/user-needs.md +40 -0
  97. package/commands/validate.md +40 -0
  98. package/commands/value-proposition.md +61 -0
  99. package/commands/vault.md +180 -0
  100. package/commands/visualize.md +52 -0
  101. package/commands/whitespace.md +507 -0
  102. package/commands/wiki.md +69 -0
  103. package/hooks/hooks.json +381 -0
  104. package/hooks/run-hook.cmd +64 -0
  105. package/lib/__init__.py +0 -0
  106. package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
  107. package/lib/agents/auto-explore-agent.cjs +1043 -0
  108. package/lib/agents/reverse-salient-agent.cjs +679 -0
  109. package/lib/agents/tension-hook-agent.cjs +544 -0
  110. package/lib/brain/ROOM.md +44 -0
  111. package/lib/brain/chain-recommender.cjs +301 -0
  112. package/lib/chat/chat-context.js +185 -0
  113. package/lib/chat/chat-panel.js +721 -0
  114. package/lib/chat/fabric-chat.cjs +288 -0
  115. package/lib/chat/generative-tools.js +219 -0
  116. package/lib/conversation/ROOM.md +39 -0
  117. package/lib/conversation/classifier-rules.json +38 -0
  118. package/lib/conversation/classifier.cjs +264 -0
  119. package/lib/conversation/operator.cjs +287 -0
  120. package/lib/copy/115-spec-strings.cjs +55 -0
  121. package/lib/core/__init__.py +0 -0
  122. package/lib/core/__nav-stub.cjs +14 -0
  123. package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
  125. package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
  126. package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
  127. package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
  128. package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
  129. package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
  130. package/lib/core/artifact-id.cjs +148 -0
  131. package/lib/core/asset-ops.cjs +151 -0
  132. package/lib/core/auto-commit-throttle.cjs +129 -0
  133. package/lib/core/bearer-token.cjs +199 -0
  134. package/lib/core/brain-client.cjs +865 -0
  135. package/lib/core/brain-derivation-prompts.cjs +326 -0
  136. package/lib/core/brain-derivation-queue.cjs +431 -0
  137. package/lib/core/brain-derivation.cjs +580 -0
  138. package/lib/core/brain-md-schema.cjs +528 -0
  139. package/lib/core/brain-md-staleness.cjs +357 -0
  140. package/lib/core/brain-response-sanitize.cjs +188 -0
  141. package/lib/core/bridge-writer.cjs +477 -0
  142. package/lib/core/chat-context-builder.cjs +253 -0
  143. package/lib/core/cross-room-aggregator.cjs +762 -0
  144. package/lib/core/daily-briefing.cjs +438 -0
  145. package/lib/core/decision-capture.cjs +618 -0
  146. package/lib/core/deep-links.cjs +82 -0
  147. package/lib/core/dispatch-optimizer.cjs +354 -0
  148. package/lib/core/dual-path-detector.cjs +84 -0
  149. package/lib/core/dual-path-detector.test.cjs +334 -0
  150. package/lib/core/exports-log.cjs +79 -0
  151. package/lib/core/feynman-minto-invariants.cjs +605 -0
  152. package/lib/core/folder-memory-async.cjs +338 -0
  153. package/lib/core/folder-memory-shared.cjs +890 -0
  154. package/lib/core/folder-memory.cjs +416 -0
  155. package/lib/core/framework-chain-composer.cjs +411 -0
  156. package/lib/core/frontmatter-schemas.cjs +330 -0
  157. package/lib/core/git-ops.cjs +141 -0
  158. package/lib/core/graph-ops.cjs +258 -0
  159. package/lib/core/hat-persistence.cjs +362 -0
  160. package/lib/core/index.cjs +60 -0
  161. package/lib/core/integration-registry.cjs +232 -0
  162. package/lib/core/intelligence-cascade.cjs +661 -0
  163. package/lib/core/lazygraph-ops.cjs +1057 -0
  164. package/lib/core/lru-cache.cjs +139 -0
  165. package/lib/core/mcp-profiles.cjs +182 -0
  166. package/lib/core/meeting-ops.cjs +54 -0
  167. package/lib/core/memory-ops.cjs +600 -0
  168. package/lib/core/migrations/ROOM.md +33 -0
  169. package/lib/core/migrations/phase-109-nodes-provenance.cjs +339 -0
  170. package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
  171. package/lib/core/model-profiles.cjs +246 -0
  172. package/lib/core/mullins-scaffold.cjs +160 -0
  173. package/lib/core/nav-dial.cjs +316 -0
  174. package/lib/core/navigation/ROOM.md +15 -0
  175. package/lib/core/navigation/explanation.cjs +43 -0
  176. package/lib/core/navigation/focus.cjs +135 -0
  177. package/lib/core/navigation/ingestion.cjs +82 -0
  178. package/lib/core/navigation/insights.cjs +350 -0
  179. package/lib/core/navigation/memory-events.cjs +118 -0
  180. package/lib/core/navigation/neighborhood.cjs +78 -0
  181. package/lib/core/navigation/packet.cjs +182 -0
  182. package/lib/core/navigation/room-home.cjs +127 -0
  183. package/lib/core/navigation/transitions.cjs +82 -0
  184. package/lib/core/navigation-engine-shared.cjs +242 -0
  185. package/lib/core/navigation-engine.cjs +664 -0
  186. package/lib/core/navigation.cjs +60 -0
  187. package/lib/core/nl-graph-queries.cjs +164 -0
  188. package/lib/core/offer-presenter.cjs +406 -0
  189. package/lib/core/opportunity-extractor.cjs +183 -0
  190. package/lib/core/opportunity-ops.cjs +1371 -0
  191. package/lib/core/persona-ops.cjs +537 -0
  192. package/lib/core/persona-taxonomy.cjs +190 -0
  193. package/lib/core/platform-gates.cjs +120 -0
  194. package/lib/core/platform.cjs +257 -0
  195. package/lib/core/proactive-intelligence.cjs +528 -0
  196. package/lib/core/problem-type-router.cjs +315 -0
  197. package/lib/core/reasoning-ops.cjs +639 -0
  198. package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
  199. package/lib/core/room-classifier-strict-mode.cjs +229 -0
  200. package/lib/core/room-db.cjs +127 -0
  201. package/lib/core/room-ops-async.cjs +92 -0
  202. package/lib/core/room-ops-shared.cjs +64 -0
  203. package/lib/core/room-ops-sync.cjs +70 -0
  204. package/lib/core/room-ops.cjs +32 -0
  205. package/lib/core/room-type-detector.cjs +386 -0
  206. package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
  207. package/lib/core/rs-brain-substrate.cjs +570 -0
  208. package/lib/core/rs-breakthrough-scorer.cjs +255 -0
  209. package/lib/core/rs-canon-violations.cjs +82 -0
  210. package/lib/core/rs-chain-feeder.cjs +343 -0
  211. package/lib/core/rs-commercial-assessor.cjs +280 -0
  212. package/lib/core/rs-differential-scorer.cjs +376 -0
  213. package/lib/core/rs-domain-analyzer.cjs +385 -0
  214. package/lib/core/rs-egress-prompts.cjs +113 -0
  215. package/lib/core/rs-egress-telemetry.cjs +225 -0
  216. package/lib/core/rs-egress-violations.cjs +53 -0
  217. package/lib/core/rs-expert-mapper.cjs +467 -0
  218. package/lib/core/rs-fetcher-academic.cjs +697 -0
  219. package/lib/core/rs-fetcher-experts.cjs +314 -0
  220. package/lib/core/rs-fetcher-industry.cjs +731 -0
  221. package/lib/core/rs-fetcher-patents.cjs +564 -0
  222. package/lib/core/rs-innovation-classifier.cjs +194 -0
  223. package/lib/core/rs-mind-map.cjs +656 -0
  224. package/lib/core/rs-neo4j-writer.cjs +388 -0
  225. package/lib/core/rs-nl-to-query.cjs +425 -0
  226. package/lib/core/rs-pinecone-bridge.cjs +303 -0
  227. package/lib/core/rs-preprocessor.cjs +350 -0
  228. package/lib/core/rs-query-matrix.cjs +316 -0
  229. package/lib/core/rs-query-to-text.cjs +438 -0
  230. package/lib/core/rs-sqlite-mirror.cjs +443 -0
  231. package/lib/core/rs-thesis-generator.cjs +188 -0
  232. package/lib/core/rs_cache.py +479 -0
  233. package/lib/core/rs_corpus.py +468 -0
  234. package/lib/core/rs_hybrid.py +586 -0
  235. package/lib/core/rs_math.py +287 -0
  236. package/lib/core/rs_rooms.py +193 -0
  237. package/lib/core/scheduled-scanner.cjs +463 -0
  238. package/lib/core/scratchpad-ops.cjs +201 -0
  239. package/lib/core/section-8-trace-schema.cjs +138 -0
  240. package/lib/core/section-registry.cjs +111 -0
  241. package/lib/core/session-state.cjs +144 -0
  242. package/lib/core/shallow-doc-parser.cjs +174 -0
  243. package/lib/core/shallow-doc-parser.test.cjs +226 -0
  244. package/lib/core/skill-activation-router.cjs +284 -0
  245. package/lib/core/state-ops.cjs +46 -0
  246. package/lib/core/statusline-cache.cjs +266 -0
  247. package/lib/core/token-estimator.cjs +348 -0
  248. package/lib/core/user-archetype.cjs +239 -0
  249. package/lib/core/user-md-ops.cjs +524 -0
  250. package/lib/core/visual-ops.cjs +624 -0
  251. package/lib/core/write-lock.cjs +149 -0
  252. package/lib/graph/canvas-graph.js +467 -0
  253. package/lib/graph/constellation-config.cjs +299 -0
  254. package/lib/graph/graph-detail-panel.js +165 -0
  255. package/lib/hmi/ROOM.md +47 -0
  256. package/lib/hmi/across-session-memory.cjs +604 -0
  257. package/lib/hmi/cross-room-memory.cjs +575 -0
  258. package/lib/hmi/decoy-tier.cjs +395 -0
  259. package/lib/hmi/jtbd-classifier.cjs +219 -0
  260. package/lib/hmi/jtbd-state.cjs +199 -0
  261. package/lib/hmi/jtbd-taxonomy.json +392 -0
  262. package/lib/hmi/selector-dispatcher.cjs +546 -0
  263. package/lib/hmi/selector-telemetry.cjs +263 -0
  264. package/lib/hmi/shape-f0-renderer.cjs +139 -0
  265. package/lib/hmi/shape-f1-fallback.cjs +80 -0
  266. package/lib/hmi/shape-f1-renderer.cjs +138 -0
  267. package/lib/hmi/shape-f2-renderer.cjs +132 -0
  268. package/lib/hmi/shape-f3-renderer.cjs +66 -0
  269. package/lib/hmi/shape-f4-renderer.cjs +72 -0
  270. package/lib/hmi/shape-f5-renderer.cjs +155 -0
  271. package/lib/hmi/shape-f6-plan-review-renderer.cjs +312 -0
  272. package/lib/hmi/shape-f6-renderer.cjs +144 -0
  273. package/lib/hmi/shape-g-renderer.cjs +219 -0
  274. package/lib/hmi/shape-h-renderer.cjs +222 -0
  275. package/lib/hmi/tier-check.cjs +63 -0
  276. package/lib/import/PRECONDITIONS.md +41 -0
  277. package/lib/import/branding.cjs +210 -0
  278. package/lib/import/branding.test.cjs +235 -0
  279. package/lib/import/classifications-sync.cjs +104 -0
  280. package/lib/import/classifications-sync.test.cjs +129 -0
  281. package/lib/import/enricher.cjs +296 -0
  282. package/lib/import/enricher.test.cjs +273 -0
  283. package/lib/import/integration.test.cjs +376 -0
  284. package/lib/import/manifest.cjs +129 -0
  285. package/lib/import/manifest.schema.json +185 -0
  286. package/lib/import/manifest.test.cjs +123 -0
  287. package/lib/import/meeting-detector.cjs +92 -0
  288. package/lib/import/meeting-detector.test.cjs +100 -0
  289. package/lib/import/person-detector.cjs +229 -0
  290. package/lib/import/person-detector.test.cjs +149 -0
  291. package/lib/import/report.cjs +186 -0
  292. package/lib/import/report.test.cjs +186 -0
  293. package/lib/import/room-md-scaffolder.cjs +49 -0
  294. package/lib/import/router.cjs +224 -0
  295. package/lib/import/router.test.cjs +356 -0
  296. package/lib/import/run-all-tests.cjs +36 -0
  297. package/lib/import/smoke-test.cjs +213 -0
  298. package/lib/import/smoke-test.test.cjs +148 -0
  299. package/lib/import/test-fixtures/collision-vault/preexisting-room/STATE.md +8 -0
  300. package/lib/import/test-fixtures/collision-vault/preexisting-room/problem-definition/onboarding/onboarding.md +7 -0
  301. package/lib/import/test-fixtures/collision-vault/source/onboarding.md +5 -0
  302. package/lib/import/test-fixtures/obsidian-vault/.obsidian/workspace.json +1 -0
  303. package/lib/import/test-fixtures/obsidian-vault/notes/with-wikilinks.md +4 -0
  304. package/lib/import/test-fixtures/tiny-vault/notes/2026-01-15-team-sync.md +9 -0
  305. package/lib/import/test-fixtures/tiny-vault/notes/empty.md +3 -0
  306. package/lib/import/test-fixtures/tiny-vault/notes/onboarding.md +5 -0
  307. package/lib/import/test-fixtures/tiny-vault/notes/pricing.md +5 -0
  308. package/lib/import/test-fixtures/tiny-vault/notes/random.md +4 -0
  309. package/lib/import/undo.test.cjs +199 -0
  310. package/lib/import/vault-scanner.cjs +105 -0
  311. package/lib/import/vault-scanner.test.cjs +67 -0
  312. package/lib/mcp/app-html/dashboard.html +316 -0
  313. package/lib/mcp/app-html/graph.html +428 -0
  314. package/lib/mcp/app-html/mindrian-platform.html +1841 -0
  315. package/lib/mcp/app-html/wiki.html +383 -0
  316. package/lib/mcp/app-views.cjs +322 -0
  317. package/lib/mcp/brain-router.cjs +418 -0
  318. package/lib/mcp/capability-registry.cjs +62 -0
  319. package/lib/mcp/larry-context.cjs +46 -0
  320. package/lib/mcp/larry-server-instructions.md +114 -0
  321. package/lib/mcp/pipeline-state.cjs +275 -0
  322. package/lib/mcp/prompts.cjs +302 -0
  323. package/lib/mcp/resources.cjs +227 -0
  324. package/lib/mcp/session-catchup.cjs +327 -0
  325. package/lib/mcp/surface-detect.cjs +75 -0
  326. package/lib/mcp/tool-router.cjs +1034 -0
  327. package/lib/memory/aaak-compress.cjs +403 -0
  328. package/lib/memory/aaak-compress.test.cjs +288 -0
  329. package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
  330. package/lib/memory/bearer-token.test.cjs +315 -0
  331. package/lib/memory/brain-cache-lru.test.cjs +259 -0
  332. package/lib/memory/brain-client-query-shape.test.cjs +160 -0
  333. package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
  334. package/lib/memory/brain-derivation-queue.test.cjs +539 -0
  335. package/lib/memory/brain-derivation.test.cjs +634 -0
  336. package/lib/memory/brain-derive-command.test.cjs +534 -0
  337. package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
  338. package/lib/memory/brain-md-schema.test.cjs +467 -0
  339. package/lib/memory/brain-md-staleness.test.cjs +525 -0
  340. package/lib/memory/brain-server-resolution.test.cjs +314 -0
  341. package/lib/memory/chain-recommender.test.cjs +233 -0
  342. package/lib/memory/chat-context.test.cjs +128 -0
  343. package/lib/memory/command-registry.test.cjs +220 -0
  344. package/lib/memory/cross-room-aggregator.test.cjs +909 -0
  345. package/lib/memory/dashboard-server.test.cjs +256 -0
  346. package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
  347. package/lib/memory/decision-capture.test.cjs +632 -0
  348. package/lib/memory/decision-capture.worker.cjs +70 -0
  349. package/lib/memory/explain-decision-command.test.cjs +521 -0
  350. package/lib/memory/explain-decision-footer.test.cjs +316 -0
  351. package/lib/memory/explored-materials-store.cjs +392 -0
  352. package/lib/memory/feynman-minto-guardian.test.cjs +736 -0
  353. package/lib/memory/feynman-minto-invariants.test.cjs +511 -0
  354. package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
  355. package/lib/memory/feynman-prompts.cjs +151 -0
  356. package/lib/memory/feynman-prompts.test.cjs +96 -0
  357. package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
  358. package/lib/memory/folder-memory.test.cjs +503 -0
  359. package/lib/memory/framework-chain-composer.test.cjs +515 -0
  360. package/lib/memory/frontmatter-schema-validator.test.cjs +290 -0
  361. package/lib/memory/heal-command.test.cjs +604 -0
  362. package/lib/memory/index-artifact-transaction.test.cjs +333 -0
  363. package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
  364. package/lib/memory/mcp-input-validation.test.cjs +240 -0
  365. package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
  366. package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
  367. package/lib/memory/minto-debouncer.test.cjs +407 -0
  368. package/lib/memory/minto-debouncer.worker.cjs +46 -0
  369. package/lib/memory/minto-migration-v88.test.cjs +265 -0
  370. package/lib/memory/minto-schema-v88.test.cjs +390 -0
  371. package/lib/memory/mos-status-renderer.test.cjs +631 -0
  372. package/lib/memory/narrative-schema.cjs +376 -0
  373. package/lib/memory/narrative-schema.test.cjs +209 -0
  374. package/lib/memory/nav-dial.test.cjs +414 -0
  375. package/lib/memory/navigation-engine-core.test.cjs +722 -0
  376. package/lib/memory/navigation-invariants.test.cjs +483 -0
  377. package/lib/memory/offer-presenter.test.cjs +554 -0
  378. package/lib/memory/on-stop-snapshot.test.cjs +404 -0
  379. package/lib/memory/pending-tension-store.cjs +373 -0
  380. package/lib/memory/post-compact-reinjection.test.cjs +854 -0
  381. package/lib/memory/post-write-triple.test.cjs +317 -0
  382. package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
  383. package/lib/memory/problem-type-router.test.cjs +656 -0
  384. package/lib/memory/query-efficiency-telemetry.test.cjs +370 -0
  385. package/lib/memory/recompile-room-references.test.cjs +392 -0
  386. package/lib/memory/recompile-room-references.worker.cjs +42 -0
  387. package/lib/memory/record-decision-dual-write.test.cjs +454 -0
  388. package/lib/memory/room-classifier-strict-mode.test.cjs +417 -0
  389. package/lib/memory/room-minto-hook.test.cjs +398 -0
  390. package/lib/memory/rs-discovery-engine.test.cjs +323 -0
  391. package/lib/memory/run-feynman-tests.cjs +1247 -0
  392. package/lib/memory/security-trifecta.test.cjs +312 -0
  393. package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
  394. package/lib/memory/session-start-triple-injection.test.cjs +514 -0
  395. package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
  396. package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
  397. package/lib/memory/skill-activation-router.test.cjs +419 -0
  398. package/lib/memory/stamp-artifact-write.test.cjs +304 -0
  399. package/lib/memory/statusline-active-room.test.cjs +315 -0
  400. package/lib/memory/statusline-minto-segment.test.cjs +292 -0
  401. package/lib/memory/sync-async-entry-points.test.cjs +204 -0
  402. package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
  403. package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
  404. package/lib/memory/test-rs-brain-substrate.cjs +636 -0
  405. package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
  406. package/lib/memory/test-rs-canon-violations.cjs +218 -0
  407. package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
  408. package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
  409. package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
  410. package/lib/memory/test-rs-differential-scorer.cjs +480 -0
  411. package/lib/memory/test-rs-discovery-engine.cjs +603 -0
  412. package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
  413. package/lib/memory/test-rs-egress-primitives.cjs +420 -0
  414. package/lib/memory/test-rs-expert-mapper.cjs +547 -0
  415. package/lib/memory/test-rs-explain-command.cjs +443 -0
  416. package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
  417. package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
  418. package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
  419. package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
  420. package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
  421. package/lib/memory/test-rs-mind-map.cjs +646 -0
  422. package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
  423. package/lib/memory/test-rs-nl-to-query.cjs +449 -0
  424. package/lib/memory/test-rs-pinecone-bridge.cjs +277 -0
  425. package/lib/memory/test-rs-preprocessor.cjs +433 -0
  426. package/lib/memory/test-rs-query-matrix.cjs +391 -0
  427. package/lib/memory/test-rs-query-to-text.cjs +551 -0
  428. package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
  429. package/lib/memory/test-rs-thesis-generator.cjs +360 -0
  430. package/lib/memory/triple-context-formatter.cjs +473 -0
  431. package/lib/memory/triple-context-formatter.test.cjs +442 -0
  432. package/lib/memory/user-md-persona.test.cjs +565 -0
  433. package/lib/memory/userpromptsubmit-integration.test.cjs +690 -0
  434. package/lib/memory/validators/README.md +157 -0
  435. package/lib/memory/validators/brain-md-invariants.cjs +475 -0
  436. package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
  437. package/lib/memory/validators/external-academic-invariants.cjs +249 -0
  438. package/lib/memory/validators/external-industry-invariants.cjs +271 -0
  439. package/lib/memory/validators/external-patents-invariants.cjs +266 -0
  440. package/lib/memory/validators/minto-invariants.cjs +62 -0
  441. package/lib/memory/validators/navigation-invariants.cjs +340 -0
  442. package/lib/memory/validators/queue-health.cjs +95 -0
  443. package/lib/memory/validators/snapshot-integrity.cjs +129 -0
  444. package/lib/memory/validators/stale-lifecycle.cjs +116 -0
  445. package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
  446. package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
  447. package/lib/memory/write-lock-atomic.test.cjs +137 -0
  448. package/lib/memory/write-lock-atomic.worker.cjs +55 -0
  449. package/lib/parity/check-parity.cjs +83 -0
  450. package/lib/presentation/presentation-server.cjs +101 -0
  451. package/lib/presentation/presentation-watcher.cjs +123 -0
  452. package/lib/quickview/hub-server.cjs +719 -0
  453. package/lib/quickview/server.cjs +533 -0
  454. package/lib/render/JTBD-PALETTES.md +145 -0
  455. package/lib/render/ROOM.md +59 -0
  456. package/lib/render/render-v2.cjs +486 -0
  457. package/lib/render/render-v2.test.cjs +267 -0
  458. package/lib/render/render.cjs +65 -0
  459. package/lib/state/ROOM.md +46 -0
  460. package/lib/state/state-md-parser.cjs +215 -0
  461. package/lib/statusline/ROOM.md +38 -0
  462. package/lib/statusline/banner-suppression.cjs +50 -0
  463. package/lib/statusline/surface-detect.cjs +85 -0
  464. package/lib/update-bootstrap.sh.template +145 -0
  465. package/lib/vault/frontmatter-schema.cjs +297 -0
  466. package/lib/vault/room-scanner.cjs +352 -0
  467. package/lib/vault/wikilink-builder.cjs +231 -0
  468. package/lib/vault/wikilink-builder.test.cjs +182 -0
  469. package/lib/wiki/graph-links.cjs +281 -0
  470. package/lib/wiki/page-renderer.cjs +229 -0
  471. package/lib/wiki/wiki-chat.cjs +81 -0
  472. package/lib/wiki/wiki-layout.cjs +1459 -0
  473. package/lib/wiki/wiki-search.cjs +142 -0
  474. package/lib/wiki/wiki-server.cjs +678 -0
  475. package/lib/wiki/wiki-watcher.cjs +105 -0
  476. package/lib/workflow/ROOM.md +47 -0
  477. package/lib/workflow/command-resolver.cjs +155 -0
  478. package/lib/workflow/command-resolver.test.cjs +235 -0
  479. package/package.json +44 -0
  480. package/pipelines/analogy/01-decompose.md +80 -0
  481. package/pipelines/analogy/02-abstract.md +87 -0
  482. package/pipelines/analogy/03-search.md +135 -0
  483. package/pipelines/analogy/04-transfer.md +101 -0
  484. package/pipelines/analogy/05-validate.md +106 -0
  485. package/pipelines/analogy/CHAIN.md +56 -0
  486. package/pipelines/discovery/01-explore-domains.md +44 -0
  487. package/pipelines/discovery/02-think-hats.md +50 -0
  488. package/pipelines/discovery/03-analyze-needs.md +54 -0
  489. package/pipelines/discovery/CHAIN.md +37 -0
  490. package/pipelines/thesis/01-structure-argument.md +45 -0
  491. package/pipelines/thesis/02-challenge-assumptions.md +48 -0
  492. package/pipelines/thesis/03-build-thesis.md +54 -0
  493. package/pipelines/thesis/CHAIN.md +37 -0
  494. package/references/brain/causal-directives.md +91 -0
  495. package/references/brain/causal-enrichment.cypher +165 -0
  496. package/references/brain/command-triggers-schema.md +226 -0
  497. package/references/brain/graph-architecture.md +317 -0
  498. package/references/brain/query-patterns.md +460 -0
  499. package/references/brain/room-hierarchy-schema.md +218 -0
  500. package/references/brain/schema.md +76 -0
  501. package/references/capability-radar/capabilities-index.md +241 -0
  502. package/references/capability-radar/changelog-cache.md +81 -0
  503. package/references/causal/causal-schema.md +103 -0
  504. package/references/design/email-template-standard.md +155 -0
  505. package/references/design/graph-visualization-standard.md +178 -0
  506. package/references/document-generation.md +179 -0
  507. package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
  508. package/references/import-config.md +141 -0
  509. package/references/integrations/detection-patterns.md +101 -0
  510. package/references/meeting/artifact-template.md +377 -0
  511. package/references/meeting/cross-meeting-intelligence.md +216 -0
  512. package/references/meeting/cross-relationship-patterns.md +202 -0
  513. package/references/meeting/live-join-interface.md +244 -0
  514. package/references/meeting/section-mapping.md +192 -0
  515. package/references/meeting/segment-classification.md +258 -0
  516. package/references/meeting/speaker-profile-template.md +219 -0
  517. package/references/meeting/summary-template.md +348 -0
  518. package/references/meeting/transcript-patterns.md +226 -0
  519. package/references/methodology/analyze-needs.md +135 -0
  520. package/references/methodology/analyze-systems.md +121 -0
  521. package/references/methodology/analyze-timing.md +149 -0
  522. package/references/methodology/beautiful-question.md +109 -0
  523. package/references/methodology/build-knowledge.md +161 -0
  524. package/references/methodology/build-thesis.md +237 -0
  525. package/references/methodology/challenge-assumptions.md +127 -0
  526. package/references/methodology/diagnose.md +169 -0
  527. package/references/methodology/dominant-designs.md +212 -0
  528. package/references/methodology/explore-domains.md +147 -0
  529. package/references/methodology/explore-futures.md +163 -0
  530. package/references/methodology/explore-trends.md +129 -0
  531. package/references/methodology/find-bottlenecks.md +131 -0
  532. package/references/methodology/grade.md +211 -0
  533. package/references/methodology/index.md +97 -0
  534. package/references/methodology/leadership.md +200 -0
  535. package/references/methodology/lean-canvas.md +116 -0
  536. package/references/methodology/macro-trends.md +192 -0
  537. package/references/methodology/map-unknowns.md +137 -0
  538. package/references/methodology/mullins-7-domains.md +104 -0
  539. package/references/methodology/problem-types.md +65 -0
  540. package/references/methodology/root-cause.md +178 -0
  541. package/references/methodology/sapphire-encoding.md +355 -0
  542. package/references/methodology/scenario-plan.md +178 -0
  543. package/references/methodology/score-innovation.md +154 -0
  544. package/references/methodology/structure-argument.md +158 -0
  545. package/references/methodology/systems-thinking.md +159 -0
  546. package/references/methodology/think-hats.md +147 -0
  547. package/references/methodology/triz-matrix.json +751 -0
  548. package/references/methodology/triz-principles.md +501 -0
  549. package/references/methodology/user-needs.md +199 -0
  550. package/references/methodology/validate.md +163 -0
  551. package/references/methodology/value-proposition.md +244 -0
  552. package/references/opportunities/funding-lifecycle.md +103 -0
  553. package/references/opportunities/grant-api-patterns.md +99 -0
  554. package/references/opportunities/opportunity-template.md +84 -0
  555. package/references/personality/assessment-philosophy.md +72 -0
  556. package/references/personality/lexicon.md +100 -0
  557. package/references/personality/persona-chains.md +56 -0
  558. package/references/personality/pws-lexicon-full.md +499 -0
  559. package/references/personality/voice-dna.md +156 -0
  560. package/references/personas/hat-perspectives.md +76 -0
  561. package/references/personas/persona-template.md +63 -0
  562. package/references/pipeline/act-output-contract.md +88 -0
  563. package/references/pipeline/chains-index.md +39 -0
  564. package/references/pws-profile-generation.md +79 -0
  565. package/references/reasoning/reasoning-schema.md +143 -0
  566. package/references/reasoning/reasoning-template.md +68 -0
  567. package/references/reasoning/run-template.md +38 -0
  568. package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
  569. package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
  570. package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
  571. package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
  572. package/references/taxonomy/TAXONOMY.md +192 -0
  573. package/references/templates/MINTO.md +36 -0
  574. package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
  575. package/references/vault-kit/README.md +35 -0
  576. package/references/vault-kit/app.json +12 -0
  577. package/references/vault-kit/appearance.json +12 -0
  578. package/references/vault-kit/graph.json +35 -0
  579. package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
  580. package/references/vault-kit/templates/new-artifact.md +37 -0
  581. package/references/vault-kit/templates/new-meeting-note.md +35 -0
  582. package/references/vault-kit/templates/new-team-profile.md +29 -0
  583. package/references/vault-kit/templates/new-xref.md +35 -0
  584. package/references/visual/symbol-system.md +151 -0
  585. package/skills/MOSDeckEngine/SKILL.md +325 -0
  586. package/skills/brain-connector/SKILL.md +114 -0
  587. package/skills/context-engine/SKILL.md +147 -0
  588. package/skills/conversation-mode/SKILL.md +102 -0
  589. package/skills/larry-personality/SKILL.md +219 -0
  590. package/skills/larry-personality/framework-chains.md +92 -0
  591. package/skills/larry-personality/mode-engine.md +185 -0
  592. package/skills/mullins-scaffold/SKILL.md +61 -0
  593. package/skills/mullins-scaffold/scaffold.json +146 -0
  594. package/skills/pws-methodology/SKILL.md +49 -0
  595. package/skills/room-passive/SKILL.md +165 -0
  596. package/skills/room-proactive/SKILL.md +250 -0
  597. package/skills/ui-system/SKILL.md +277 -0
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
6
+ * Phase 89.4 Plan 02 -- rs-chain-feeder core fixture suite.
7
+ *
8
+ * 8 scenarios + A1 sweep covering the Wave-2 core module:
9
+ * lib/core/rs-chain-feeder.cjs
10
+ *
11
+ * Test 1 (Brain unreachable graceful degradation): mock brainClient
12
+ * with isAvailable() returning false; lookupUpstream returns
13
+ * {state: ready}; stderr captures warning containing chain-feeder
14
+ * + Brain unreachable.
15
+ *
16
+ * Test 2 (Pause-state with missing upstream): mock brainClient.query()
17
+ * to return records with fresh:false flags; opts.upstreamFreshness
18
+ * Map keyed false; lookupUpstream returns {state: pause,
19
+ * missing_upstream: [...], suggested_action: Run Methodology}.
20
+ *
21
+ * Test 3 (Ready-state with all upstream present): mock brainClient.query()
22
+ * to return records; default freshness map -> all upstream fresh;
23
+ * lookupUpstream returns {state: ready}.
24
+ *
25
+ * Test 4 (emitChainMetadata happy-path shape): structural_transfer + 8 +
26
+ * empty active_context produces recommended_verb in CANONICAL_VERBS,
27
+ * feeds_into === PWS VP, spawn_skill === null.
28
+ *
29
+ * Test 5 (emitChainMetadata feeds_into branch coverage):
30
+ * structural_transfer -> PWS VP, semantic_implementation ->
31
+ * Causal Loop, hybrid -> Navigation Engine.
32
+ *
33
+ * Test 6 (Canon Part 8 adversarial -- lookupUpstream input):
34
+ * problem_type containing 'meeting with' throws
35
+ * ExternalEgressViolation with err.meta.surface ===
36
+ * rs-chain-feeder-lookup-upstream.
37
+ *
38
+ * Test 7 (Canon Part 8 adversarial -- emitChainMetadata active_context):
39
+ * active_context.meta.contact === 'john@acme.com' throws
40
+ * ExternalEgressViolation with err.meta.surface ===
41
+ * rs-chain-feeder-emit-metadata.
42
+ *
43
+ * Test 8 (chokepoint exclusivity): brainClient.query is invoked at least
44
+ * once via injected spy; ZERO direct global.fetch() calls.
45
+ *
46
+ * Test 9 (recommendSkillSpawn STUB shape).
47
+ *
48
+ * A1 sweep: 5 happy-path emitChainMetadata calls produce zero
49
+ * forbidden-pattern hits in the JSON.stringify of returned blocks.
50
+ *
51
+ * Pure CJS, zero npm deps, node built-ins only (assert).
52
+ */
53
+
54
+ const assert = require('node:assert/strict');
55
+
56
+ // ---------- Suite bookkeeping ----------
57
+
58
+ let passed = 0;
59
+ let failed = 0;
60
+ const failures = [];
61
+ const tests = [];
62
+
63
+ // Register an async or sync test under a name. The runner awaits each
64
+ // in series so stderr captures and brainClient spies do not interleave.
65
+ function record(name, fn) {
66
+ tests.push({ name, fn });
67
+ }
68
+
69
+ async function runAll() {
70
+ for (const t of tests) {
71
+ try {
72
+ const maybe = t.fn();
73
+ if (maybe && typeof maybe.then === 'function') await maybe;
74
+ passed++;
75
+ process.stdout.write('PASS ' + t.name + '\n');
76
+ } catch (err) {
77
+ failed++;
78
+ failures.push({ name: t.name, message: err && err.message ? err.message : String(err) });
79
+ process.stdout.write('FAIL ' + t.name + ': ' + (err && err.message ? err.message : String(err)) + '\n');
80
+ }
81
+ }
82
+ }
83
+
84
+ // Stderr capture helper. Returns a thunk that, after fn settles, returns
85
+ // what was written to process.stderr. Restores original write at the end.
86
+ async function captureStderrAsync(fn) {
87
+ const chunks = [];
88
+ const origWrite = process.stderr.write.bind(process.stderr);
89
+ process.stderr.write = function (chunk) {
90
+ chunks.push(typeof chunk === 'string' ? chunk : chunk.toString());
91
+ return true;
92
+ };
93
+ try {
94
+ const value = await fn();
95
+ return { value, stderr: chunks.join('') };
96
+ } finally {
97
+ process.stderr.write = origWrite;
98
+ }
99
+ }
100
+
101
+ // Build a Brain client stub with a configurable query() return + spy.
102
+ function makeBrainClientStub(opts) {
103
+ opts = opts || {};
104
+ const queryReturn = opts.queryReturn !== undefined ? opts.queryReturn : { records: [] };
105
+ const isAvailableValue = opts.isAvailable !== undefined ? opts.isAvailable : true;
106
+ const queryThrows = opts.queryThrows || null;
107
+ const callLog = [];
108
+ const stub = {
109
+ isAvailable: function () { return isAvailableValue; },
110
+ query: async function (cypher) {
111
+ callLog.push({ method: 'query', cypher });
112
+ if (queryThrows) throw queryThrows;
113
+ return queryReturn;
114
+ },
115
+ search: async function () { callLog.push({ method: 'search' }); return null; },
116
+ _callLog: callLog,
117
+ };
118
+ return stub;
119
+ }
120
+
121
+ // ---------- Module under test ----------
122
+
123
+ const mod = require('../core/rs-chain-feeder.cjs');
124
+ const {
125
+ lookupUpstream,
126
+ emitChainMetadata,
127
+ recommendSkillSpawn,
128
+ CANONICAL_VERBS,
129
+ } = mod;
130
+
131
+ // ---------- Tests ----------
132
+
133
+ // Test 1: Brain unreachable graceful degradation
134
+ record('T1 Brain unreachable -> lookupUpstream returns {state: ready} + stderr warning', async function () {
135
+ const brainClient = makeBrainClientStub({ isAvailable: false });
136
+ const captured = await captureStderrAsync(function () {
137
+ return lookupUpstream('IDP', 'opportunity_identified', { brainClient: brainClient });
138
+ });
139
+ assert.deepEqual(captured.value, { state: 'ready' },
140
+ 'expected {state: ready}, got ' + JSON.stringify(captured.value));
141
+ assert.ok(captured.stderr.indexOf('chain-feeder') !== -1,
142
+ 'expected stderr to contain chain-feeder, got ' + JSON.stringify(captured.stderr));
143
+ assert.ok(captured.stderr.indexOf('Brain unreachable') !== -1,
144
+ 'expected stderr to contain Brain unreachable, got ' + JSON.stringify(captured.stderr));
145
+ });
146
+
147
+ // Test 2: Pause-state with missing upstream
148
+ record('T2 Brain reachable + missing upstream -> {state: pause, missing_upstream, suggested_action}', async function () {
149
+ const brainClient = makeBrainClientStub({
150
+ queryReturn: {
151
+ records: [
152
+ { name: 'systems-thinking' },
153
+ { name: 'hsi_semantic_surprise_analysis' },
154
+ ],
155
+ },
156
+ });
157
+ const freshness = new Map();
158
+ freshness.set('systems-thinking', false);
159
+ freshness.set('hsi_semantic_surprise_analysis', false);
160
+ const result = await lookupUpstream('IDP', 'opportunity_identified', {
161
+ brainClient: brainClient,
162
+ upstreamFreshness: freshness,
163
+ });
164
+ assert.equal(result.state, 'pause', 'expected state: pause, got ' + result.state);
165
+ assert.ok(Array.isArray(result.missing_upstream), 'missing_upstream must be array');
166
+ assert.equal(result.missing_upstream.length, 2, 'expected 2 missing_upstream entries');
167
+ assert.ok(result.missing_upstream.indexOf('systems-thinking') !== -1,
168
+ 'expected systems-thinking in missing_upstream');
169
+ assert.ok(result.missing_upstream.indexOf('hsi_semantic_surprise_analysis') !== -1,
170
+ 'expected hsi_semantic_surprise_analysis in missing_upstream');
171
+ assert.equal(result.suggested_action, 'Run Methodology',
172
+ 'expected suggested_action: Run Methodology, got ' + result.suggested_action);
173
+ });
174
+
175
+ // Test 3: Ready-state with all upstream present
176
+ record('T3 Brain reachable + all upstream fresh (no freshness Map) -> {state: ready}', async function () {
177
+ const brainClient = makeBrainClientStub({
178
+ queryReturn: {
179
+ records: [
180
+ { name: 'systems-thinking' },
181
+ { name: 'Cynefin' },
182
+ ],
183
+ },
184
+ });
185
+ const result = await lookupUpstream('IDP', 'opportunity_identified', { brainClient: brainClient });
186
+ assert.deepEqual(result, { state: 'ready' },
187
+ 'expected {state: ready}, got ' + JSON.stringify(result));
188
+ });
189
+
190
+ // Test 4: emitChainMetadata happy-path shape
191
+ // Plan 89.4-03 update: spawn_skill assertion broadened from "must be null"
192
+ // (STUB) to "must be string-or-null with /mos: prefix when non-null"
193
+ // (additive-shape invariant). The 89.4-02 stub returned null; the 89.4-03
194
+ // rule-table wires Rule 1 (structural_transfer + score 8 ->
195
+ // /mos:find-analogies). The shape contract (string|null) is preserved.
196
+ record('T4 emitChainMetadata structural_transfer + 8 -> shape with verb in CANONICAL_VERBS + PWS VP + spawn_skill string|null', function () {
197
+ const block = emitChainMetadata('structural_transfer', 8, {});
198
+ assert.ok(block && typeof block === 'object', 'expected object return');
199
+ assert.ok(typeof block.recommended_verb === 'string', 'recommended_verb must be string');
200
+ assert.ok(CANONICAL_VERBS.indexOf(block.recommended_verb) !== -1,
201
+ 'recommended_verb must be in CANONICAL_VERBS, got ' + JSON.stringify(block.recommended_verb));
202
+ assert.equal(block.feeds_into, 'PWS VP',
203
+ 'feeds_into must be PWS VP for structural_transfer, got ' + JSON.stringify(block.feeds_into));
204
+ assert.ok(block.spawn_skill === null || typeof block.spawn_skill === 'string',
205
+ 'spawn_skill must be string|null, got ' + JSON.stringify(block.spawn_skill));
206
+ if (typeof block.spawn_skill === 'string') {
207
+ assert.ok(block.spawn_skill.indexOf('/mos:') === 0,
208
+ 'when non-null, spawn_skill must start with /mos:, got ' + JSON.stringify(block.spawn_skill));
209
+ }
210
+ });
211
+
212
+ // Test 5: feeds_into branch coverage
213
+ record('T5 emitChainMetadata feeds_into branches: structural_transfer -> PWS VP, semantic_implementation -> Causal Loop, hybrid -> Navigation Engine', function () {
214
+ const a = emitChainMetadata('structural_transfer', 8, {});
215
+ assert.equal(a.feeds_into, 'PWS VP', 'structural_transfer -> PWS VP');
216
+ const b = emitChainMetadata('semantic_implementation', 8, {});
217
+ assert.equal(b.feeds_into, 'Causal Loop', 'semantic_implementation -> Causal Loop');
218
+ const c = emitChainMetadata('hybrid', 8, {});
219
+ assert.equal(c.feeds_into, 'Navigation Engine', 'hybrid -> Navigation Engine');
220
+ });
221
+
222
+ // Test 6: Canon Part 8 adversarial -- forbidden pattern in lookupUpstream input
223
+ record('T6 Canon Part 8 adversarial -- forbidden pattern in problem_type throws ExternalEgressViolation', async function () {
224
+ const brainClient = makeBrainClientStub({});
225
+ let caught = null;
226
+ try {
227
+ await lookupUpstream('IDP-meeting with john', 'opportunity_identified', { brainClient: brainClient });
228
+ } catch (e) {
229
+ caught = e;
230
+ }
231
+ assert.ok(caught !== null, 'expected throw');
232
+ assert.equal(caught.name, 'ExternalEgressViolation',
233
+ 'expected ExternalEgressViolation, got ' + caught.name);
234
+ assert.equal(caught.meta.surface, 'rs-chain-feeder-lookup-upstream',
235
+ 'expected meta.surface rs-chain-feeder-lookup-upstream, got ' + caught.meta.surface);
236
+ assert.equal(brainClient._callLog.length, 0,
237
+ 'brain query must NOT be invoked when audit trips (got ' + brainClient._callLog.length + ' calls)');
238
+ });
239
+
240
+ // Test 7: Canon Part 8 adversarial -- forbidden pattern nested in active_context
241
+ record('T7 Canon Part 8 adversarial -- email nested in active_context.meta throws ExternalEgressViolation', function () {
242
+ let caught = null;
243
+ try {
244
+ emitChainMetadata('structural_transfer', 8, { meta: { contact: 'john@acme.com' } });
245
+ } catch (e) {
246
+ caught = e;
247
+ }
248
+ assert.ok(caught !== null, 'expected throw');
249
+ assert.equal(caught.name, 'ExternalEgressViolation',
250
+ 'expected ExternalEgressViolation, got ' + caught.name);
251
+ assert.equal(caught.meta.surface, 'rs-chain-feeder-emit-metadata',
252
+ 'expected meta.surface rs-chain-feeder-emit-metadata, got ' + caught.meta.surface);
253
+ });
254
+
255
+ // Test 8: chokepoint exclusivity -- brainClient.query invoked, no direct fetch
256
+ record('T8 chokepoint exclusivity -- brainClient.query called >=1 time + zero direct global.fetch calls', async function () {
257
+ const fetchCalls = [];
258
+ const originalFetch = global.fetch;
259
+ global.fetch = function () {
260
+ fetchCalls.push(Array.prototype.slice.call(arguments));
261
+ return Promise.resolve(null);
262
+ };
263
+ try {
264
+ const brainClient = makeBrainClientStub({
265
+ queryReturn: { records: [{ name: 'systems-thinking' }] },
266
+ });
267
+ await lookupUpstream('IDP', 'opportunity_identified', { brainClient: brainClient });
268
+ assert.ok(brainClient._callLog.length >= 1,
269
+ 'expected brainClient.query called >=1 time, got ' + brainClient._callLog.length);
270
+ assert.equal(brainClient._callLog[0].method, 'query',
271
+ 'expected first method call to be query, got ' + brainClient._callLog[0].method);
272
+ assert.equal(fetchCalls.length, 0,
273
+ 'expected ZERO direct fetch() calls; got ' + fetchCalls.length);
274
+ } finally {
275
+ if (originalFetch === undefined) delete global.fetch;
276
+ else global.fetch = originalFetch;
277
+ }
278
+ });
279
+
280
+ // Test 9: recommendSkillSpawn shape invariant
281
+ // Plan 89.4-03 update: assertion broadened from STUB-specific "spawn_skill
282
+ // null + 89.4-02 reasoning string" to the SHAPE invariant -- result is an
283
+ // object with spawn_skill (string|null), numeric confidence in [0, 1], and
284
+ // non-empty reasoning string. The 89.4-02 stub returned null/0/stub-text;
285
+ // the 89.4-03 rule-table fires Rule 1 here -> /mos:find-analogies / 0.85.
286
+ // Shape is byte-identical across both implementations.
287
+ record('T9 recommendSkillSpawn shape invariant -- {spawn_skill: string|null, confidence: 0..1, reasoning: non-empty string}', function () {
288
+ const result = recommendSkillSpawn('structural_transfer', 8, {});
289
+ assert.ok(result && typeof result === 'object', 'expected object');
290
+ assert.ok(result.spawn_skill === null || typeof result.spawn_skill === 'string',
291
+ 'spawn_skill must be string|null, got ' + typeof result.spawn_skill);
292
+ if (typeof result.spawn_skill === 'string') {
293
+ assert.ok(result.spawn_skill.indexOf('/mos:') === 0,
294
+ 'when non-null, spawn_skill must start with /mos:, got ' + JSON.stringify(result.spawn_skill));
295
+ }
296
+ assert.equal(typeof result.confidence, 'number', 'confidence must be a number');
297
+ assert.ok(result.confidence >= 0 && result.confidence <= 1,
298
+ 'confidence must be in [0, 1], got ' + result.confidence);
299
+ assert.ok(typeof result.reasoning === 'string' && result.reasoning.length > 0,
300
+ 'reasoning must be non-empty string');
301
+ });
302
+
303
+ // A1 sweep: 5 happy-path emitChainMetadata calls produce zero
304
+ // forbidden-pattern hits in JSON.stringify of returned blocks.
305
+ record('A1 sweep: 5 happy-path emitChainMetadata calls produce zero forbidden-pattern hits in returned blocks', function () {
306
+ const calls = [
307
+ ['structural_transfer', 9, {}],
308
+ ['semantic_implementation', 7, { industry_signals_count: 3 }],
309
+ ['hybrid', 5, { cross_methodology_substrates: ['SAPPhIRE', 'TRIZ'] }],
310
+ ['structural_transfer', 4, {}],
311
+ ['semantic_implementation', 6, { resolved_experts_count: 8 }],
312
+ ];
313
+ const { FORBIDDEN_PATTERNS } = require('../core/rs-egress-prompts.cjs');
314
+ for (const [rs_type, score, ctx] of calls) {
315
+ const block = emitChainMetadata(rs_type, score, ctx);
316
+ const json = JSON.stringify(block);
317
+ for (const re of FORBIDDEN_PATTERNS) {
318
+ assert.ok(!re.test(json),
319
+ 'A1 sweep: emitChainMetadata output for (' + rs_type + ', ' + score + ') matched forbidden pattern '
320
+ + re.source + '; output was ' + json);
321
+ }
322
+ assert.ok(CANONICAL_VERBS.indexOf(block.recommended_verb) !== -1,
323
+ 'A1 sweep: recommended_verb ' + JSON.stringify(block.recommended_verb)
324
+ + ' not in CANONICAL_VERBS for (' + rs_type + ', ' + score + ')');
325
+ }
326
+ });
327
+
328
+ // ---------- Suite report ----------
329
+
330
+ (async function main() {
331
+ await runAll();
332
+ const total = passed + failed;
333
+ process.stdout.write('\n');
334
+ if (failed === 0) {
335
+ process.stdout.write('=== 89.4-02 rs-chain-feeder-core suite: ' + passed + '/' + total + ' passed ===\n');
336
+ process.exit(0);
337
+ } else {
338
+ process.stdout.write('=== 89.4-02 rs-chain-feeder-core suite: ' + passed + '/' + total + ' passed (' + failed + ' failed) ===\n');
339
+ for (const f of failures) {
340
+ process.stdout.write(' FAIL ' + f.name + ': ' + f.message + '\n');
341
+ }
342
+ process.exit(1);
343
+ }
344
+ })();
@@ -0,0 +1,297 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
6
+ * Phase 89.4 Plan 03 -- rs-chain-feeder skill-spawn rule table fixture suite.
7
+ *
8
+ * 11 scenarios covering the Wave-3 skill-spawn rule table per kickoff
9
+ * section 6.4 (LOCKED deterministic 5-rule table). Tests are Plan-89.4-03
10
+ * RED-then-GREEN: written against the swapped recommendSkillSpawn body and
11
+ * SKILL_SPAWN_RULES Object.freeze export. Plan 89.4-02 stub MUST fail
12
+ * Tests 1-5 + Test 7 + Test 9 + Test 11 because the stub returns
13
+ * spawn_skill: null for every input.
14
+ *
15
+ * Test 1 (Rule 1 positive): recommendSkillSpawn(structural_transfer, 8, {})
16
+ * returns {spawn_skill: /mos:find-analogies, confidence: 0.85,
17
+ * reasoning includes SAPPhIRE}.
18
+ *
19
+ * Test 2 (Rule 2 positive): recommendSkillSpawn(semantic_implementation,
20
+ * 7, {industry_signals_count: 3}) returns {spawn_skill:
21
+ * /mos:lean-canvas, confidence: 0.80, reasoning includes
22
+ * market signal}.
23
+ *
24
+ * Test 3 (Rule 3 positive): recommendSkillSpawn(hybrid, 6,
25
+ * {cross_methodology_substrates: [JTBD, Causal Loop]}) returns
26
+ * {spawn_skill: /mos:pipeline, confidence: 0.75, reasoning
27
+ * includes cross-methodology}.
28
+ *
29
+ * Test 4 (Rule 4 positive): recommendSkillSpawn(semantic_implementation,
30
+ * 5, {resolved_experts_count: 10, resolved_institutions_count: 3})
31
+ * returns {spawn_skill: /mos:persona, confidence: 0.70,
32
+ * reasoning includes expert network}.
33
+ *
34
+ * Test 5 (Rule 5 positive): recommendSkillSpawn(semantic_implementation,
35
+ * 2, {cynefin: complex}) returns {spawn_skill: /mos:think-hats,
36
+ * confidence: 0.65, reasoning includes perspective diversity}.
37
+ *
38
+ * Test 6 (no-match fallback): recommendSkillSpawn(semantic_implementation,
39
+ * 5, {}) returns {spawn_skill: null, confidence: 0, reasoning:
40
+ * no rule matched}.
41
+ *
42
+ * Test 7 (first-match-wins): recommendSkillSpawn(structural_transfer, 8,
43
+ * {industry_signals_count: 3, cross_methodology_substrates:
44
+ * [A, B]}) returns Rule 1 (find-analogies) NOT Rule 2 NOT Rule 3.
45
+ *
46
+ * Test 8 (Canon Part 8 adversarial): opts.contact carrying an email
47
+ * string throws ExternalEgressViolation with
48
+ * err.meta.surface === rs-chain-feeder-recommend-skill-spawn.
49
+ *
50
+ * Test 9 (integration smoke): emitChainMetadata(structural_transfer, 8,
51
+ * {}) returns object with spawn_skill === /mos:find-analogies,
52
+ * confidence: 0.85, recommended_verb: Bank Opportunity,
53
+ * feeds_into: PWS VP.
54
+ *
55
+ * Test 10 (regression): emitChainMetadata(structural_transfer, 3, {})
56
+ * returns spawn_skill === null (no rule matches; Rule 5 needs
57
+ * cynefin) + recommended_verb === Reformulate (score < 5).
58
+ *
59
+ * Test 11 (frozen rule table): SKILL_SPAWN_RULES is exported as an
60
+ * array; Array.isArray true; length 5; Object.isFrozen true;
61
+ * each rule entry is also frozen.
62
+ *
63
+ * Pure CJS, zero npm deps, node built-ins only (assert).
64
+ */
65
+
66
+ const assert = require('node:assert/strict');
67
+
68
+ // ---------- Suite bookkeeping ----------
69
+
70
+ let passed = 0;
71
+ let failed = 0;
72
+ const failures = [];
73
+ const tests = [];
74
+
75
+ function record(name, fn) {
76
+ tests.push({ name, fn });
77
+ }
78
+
79
+ async function runAll() {
80
+ for (const t of tests) {
81
+ try {
82
+ const maybe = t.fn();
83
+ if (maybe && typeof maybe.then === 'function') await maybe;
84
+ passed++;
85
+ process.stdout.write('PASS ' + t.name + '\n');
86
+ } catch (err) {
87
+ failed++;
88
+ failures.push({ name: t.name, message: err && err.message ? err.message : String(err) });
89
+ process.stdout.write('FAIL ' + t.name + ': ' + (err && err.message ? err.message : String(err)) + '\n');
90
+ }
91
+ }
92
+ }
93
+
94
+ // ---------- Module under test ----------
95
+
96
+ const mod = require('../core/rs-chain-feeder.cjs');
97
+ const {
98
+ recommendSkillSpawn,
99
+ emitChainMetadata,
100
+ SKILL_SPAWN_RULES,
101
+ } = mod;
102
+
103
+ // ---------- Tests ----------
104
+
105
+ // Test 1: Rule 1 positive -- structural_transfer + score 8
106
+ record('T1 Rule 1 -- structural_transfer + score 8 -> /mos:find-analogies (0.85)', function () {
107
+ const result = recommendSkillSpawn('structural_transfer', 8, {});
108
+ assert.ok(result && typeof result === 'object', 'expected object return');
109
+ assert.equal(result.spawn_skill, '/mos:find-analogies',
110
+ 'expected /mos:find-analogies, got ' + JSON.stringify(result.spawn_skill));
111
+ assert.equal(result.confidence, 0.85,
112
+ 'expected confidence 0.85, got ' + result.confidence);
113
+ assert.ok(typeof result.reasoning === 'string' && result.reasoning.length > 0,
114
+ 'reasoning must be non-empty string');
115
+ assert.ok(result.reasoning.indexOf('SAPPhIRE') !== -1 || result.reasoning.indexOf('TRIZ') !== -1
116
+ || result.reasoning.indexOf('structural') !== -1,
117
+ 'reasoning should reference SAPPhIRE / TRIZ / structural; got ' + JSON.stringify(result.reasoning));
118
+ });
119
+
120
+ // Test 2: Rule 2 positive -- score 7 + industry signals
121
+ record('T2 Rule 2 -- score 7 + industry_signals_count 3 -> /mos:lean-canvas (0.80)', function () {
122
+ const result = recommendSkillSpawn('semantic_implementation', 7, { industry_signals_count: 3 });
123
+ assert.equal(result.spawn_skill, '/mos:lean-canvas',
124
+ 'expected /mos:lean-canvas, got ' + JSON.stringify(result.spawn_skill));
125
+ assert.equal(result.confidence, 0.80,
126
+ 'expected confidence 0.80, got ' + result.confidence);
127
+ assert.ok(result.reasoning.indexOf('market signal') !== -1
128
+ || result.reasoning.indexOf('business model') !== -1,
129
+ 'reasoning should reference market signal / business model; got ' + JSON.stringify(result.reasoning));
130
+ });
131
+
132
+ // Test 3: Rule 3 positive -- cross-methodology substrates
133
+ record('T3 Rule 3 -- cross_methodology_substrates length > 1 -> /mos:pipeline (0.75)', function () {
134
+ const result = recommendSkillSpawn('hybrid', 6, {
135
+ cross_methodology_substrates: ['JTBD', 'Causal Loop'],
136
+ });
137
+ assert.equal(result.spawn_skill, '/mos:pipeline',
138
+ 'expected /mos:pipeline, got ' + JSON.stringify(result.spawn_skill));
139
+ assert.equal(result.confidence, 0.75,
140
+ 'expected confidence 0.75, got ' + result.confidence);
141
+ assert.ok(result.reasoning.indexOf('cross-methodology') !== -1
142
+ || result.reasoning.indexOf('chained pipeline') !== -1,
143
+ 'reasoning should reference cross-methodology / chained pipeline; got '
144
+ + JSON.stringify(result.reasoning));
145
+ });
146
+
147
+ // Test 4: Rule 4 positive -- expert network dense
148
+ record('T4 Rule 4 -- experts > 5 + institutions > 1 -> /mos:persona (0.70)', function () {
149
+ const result = recommendSkillSpawn('semantic_implementation', 5, {
150
+ resolved_experts_count: 10,
151
+ resolved_institutions_count: 3,
152
+ });
153
+ assert.equal(result.spawn_skill, '/mos:persona',
154
+ 'expected /mos:persona, got ' + JSON.stringify(result.spawn_skill));
155
+ assert.equal(result.confidence, 0.70,
156
+ 'expected confidence 0.70, got ' + result.confidence);
157
+ assert.ok(result.reasoning.indexOf('expert network') !== -1
158
+ || result.reasoning.indexOf('team composition') !== -1,
159
+ 'reasoning should reference expert network / team composition; got '
160
+ + JSON.stringify(result.reasoning));
161
+ });
162
+
163
+ // Test 5: Rule 5 positive -- low breakthrough + Cynefin complex
164
+ record('T5 Rule 5 -- score 2 + cynefin complex -> /mos:think-hats (0.65)', function () {
165
+ const result = recommendSkillSpawn('semantic_implementation', 2, { cynefin: 'complex' });
166
+ assert.equal(result.spawn_skill, '/mos:think-hats',
167
+ 'expected /mos:think-hats, got ' + JSON.stringify(result.spawn_skill));
168
+ assert.equal(result.confidence, 0.65,
169
+ 'expected confidence 0.65, got ' + result.confidence);
170
+ assert.ok(result.reasoning.indexOf('perspective diversity') !== -1
171
+ || result.reasoning.indexOf('Cynefin') !== -1,
172
+ 'reasoning should reference perspective diversity / Cynefin; got '
173
+ + JSON.stringify(result.reasoning));
174
+ });
175
+
176
+ // Test 6: no-match fallback
177
+ record('T6 no-match -- score 5 + empty opts -> {spawn_skill: null, confidence: 0, reasoning: no rule matched}', function () {
178
+ const result = recommendSkillSpawn('semantic_implementation', 5, {});
179
+ assert.equal(result.spawn_skill, null,
180
+ 'expected null spawn_skill, got ' + JSON.stringify(result.spawn_skill));
181
+ assert.equal(result.confidence, 0,
182
+ 'expected confidence 0, got ' + result.confidence);
183
+ assert.equal(result.reasoning, 'no rule matched',
184
+ 'expected reasoning "no rule matched", got ' + JSON.stringify(result.reasoning));
185
+ });
186
+
187
+ // Test 7: first-match-wins -- Rule 1 wins over Rule 2 + Rule 3
188
+ record('T7 first-match-wins -- Rule 1 inputs + Rule 2 + Rule 3 inputs -> Rule 1 wins (find-analogies)', function () {
189
+ const result = recommendSkillSpawn('structural_transfer', 8, {
190
+ industry_signals_count: 3,
191
+ cross_methodology_substrates: ['A', 'B'],
192
+ });
193
+ assert.equal(result.spawn_skill, '/mos:find-analogies',
194
+ 'first-match-wins violated: expected /mos:find-analogies (Rule 1), got '
195
+ + JSON.stringify(result.spawn_skill));
196
+ assert.equal(result.confidence, 0.85,
197
+ 'expected Rule 1 confidence 0.85, got ' + result.confidence);
198
+ });
199
+
200
+ // Test 8: Canon Part 8 adversarial -- email in opts
201
+ record('T8 Canon Part 8 adversarial -- opts.contact carries email -> ExternalEgressViolation', function () {
202
+ let caught = null;
203
+ try {
204
+ recommendSkillSpawn('structural_transfer', 8, {
205
+ industry_signals_count: 3,
206
+ contact: 'john@acme.com',
207
+ });
208
+ } catch (e) {
209
+ caught = e;
210
+ }
211
+ assert.ok(caught !== null, 'expected throw');
212
+ assert.equal(caught.name, 'ExternalEgressViolation',
213
+ 'expected ExternalEgressViolation, got ' + caught.name);
214
+ assert.equal(caught.meta.surface, 'rs-chain-feeder-recommend-skill-spawn',
215
+ 'expected meta.surface rs-chain-feeder-recommend-skill-spawn, got '
216
+ + caught.meta.surface);
217
+ });
218
+
219
+ // Test 9: integration smoke -- rule propagates through emitChainMetadata
220
+ record('T9 integration smoke -- emitChainMetadata(structural_transfer, 8) carries Rule 1 fields', function () {
221
+ const block = emitChainMetadata('structural_transfer', 8, {});
222
+ assert.equal(block.spawn_skill, '/mos:find-analogies',
223
+ 'expected spawn_skill /mos:find-analogies in chain metadata, got '
224
+ + JSON.stringify(block.spawn_skill));
225
+ assert.equal(block.confidence, 0.85,
226
+ 'expected confidence 0.85 in chain metadata, got ' + block.confidence);
227
+ assert.equal(block.recommended_verb, 'Bank Opportunity',
228
+ 'expected recommended_verb Bank Opportunity, got '
229
+ + JSON.stringify(block.recommended_verb));
230
+ assert.equal(block.feeds_into, 'PWS VP',
231
+ 'expected feeds_into PWS VP, got ' + JSON.stringify(block.feeds_into));
232
+ assert.ok(typeof block.reasoning === 'string' && block.reasoning.length > 0,
233
+ 'reasoning must be non-empty string in chain metadata');
234
+ });
235
+
236
+ // Test 10: regression -- score 3 + structural_transfer -> no rule matches
237
+ record('T10 regression -- emitChainMetadata(structural_transfer, 3) -> spawn_skill null + Reformulate verb', function () {
238
+ const block = emitChainMetadata('structural_transfer', 3, {});
239
+ assert.equal(block.spawn_skill, null,
240
+ 'expected null spawn_skill (no rule matches at score 3), got '
241
+ + JSON.stringify(block.spawn_skill));
242
+ assert.equal(block.recommended_verb, 'Reformulate',
243
+ 'expected recommended_verb Reformulate (score < 5), got '
244
+ + JSON.stringify(block.recommended_verb));
245
+ assert.equal(block.feeds_into, 'PWS VP',
246
+ 'expected feeds_into PWS VP (structural_transfer), got '
247
+ + JSON.stringify(block.feeds_into));
248
+ assert.equal(block.confidence, 0,
249
+ 'expected confidence 0 (no rule matched), got ' + block.confidence);
250
+ });
251
+
252
+ // Test 11: SKILL_SPAWN_RULES is frozen + exported with correct shape
253
+ record('T11 SKILL_SPAWN_RULES exported + frozen + length 5 + each entry frozen', function () {
254
+ assert.ok(Array.isArray(SKILL_SPAWN_RULES),
255
+ 'SKILL_SPAWN_RULES must be an array, got ' + typeof SKILL_SPAWN_RULES);
256
+ assert.equal(SKILL_SPAWN_RULES.length, 5,
257
+ 'expected 5 rules per kickoff section 6.4, got ' + SKILL_SPAWN_RULES.length);
258
+ assert.equal(Object.isFrozen(SKILL_SPAWN_RULES), true,
259
+ 'SKILL_SPAWN_RULES must be Object.freeze\'d');
260
+ for (let i = 0; i < SKILL_SPAWN_RULES.length; i++) {
261
+ const rule = SKILL_SPAWN_RULES[i];
262
+ assert.ok(rule && typeof rule === 'object',
263
+ 'rule ' + i + ' must be object');
264
+ assert.equal(typeof rule.match, 'function',
265
+ 'rule ' + i + ' must have match() function');
266
+ assert.equal(typeof rule.spawn, 'string',
267
+ 'rule ' + i + ' must have spawn string');
268
+ assert.ok(rule.spawn.indexOf('/mos:') === 0,
269
+ 'rule ' + i + ' spawn must start with /mos:, got ' + rule.spawn);
270
+ assert.equal(typeof rule.confidence, 'number',
271
+ 'rule ' + i + ' must have numeric confidence');
272
+ assert.ok(rule.confidence > 0 && rule.confidence <= 1,
273
+ 'rule ' + i + ' confidence must be (0, 1], got ' + rule.confidence);
274
+ assert.equal(typeof rule.reasoning, 'string',
275
+ 'rule ' + i + ' must have reasoning string');
276
+ assert.equal(Object.isFrozen(rule), true,
277
+ 'rule ' + i + ' entry must also be Object.freeze\'d');
278
+ }
279
+ });
280
+
281
+ // ---------- Suite report ----------
282
+
283
+ (async function main() {
284
+ await runAll();
285
+ const total = passed + failed;
286
+ process.stdout.write('\n');
287
+ if (failed === 0) {
288
+ process.stdout.write('=== 89.4-03 rs-chain-feeder-skill-spawn suite: ' + passed + '/' + total + ' passed ===\n');
289
+ process.exit(0);
290
+ } else {
291
+ process.stdout.write('=== 89.4-03 rs-chain-feeder-skill-spawn suite: ' + passed + '/' + total + ' passed (' + failed + ' failed) ===\n');
292
+ for (const f of failures) {
293
+ process.stdout.write(' FAIL ' + f.name + ': ' + f.message + '\n');
294
+ }
295
+ process.exit(1);
296
+ }
297
+ })();