@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,223 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /*
5
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
6
+ *
7
+ * Phase 88.1-08 -- async artifact auto-commit throttle acceptance tests.
8
+ *
9
+ * Verifies the pure shouldThrottle + updateLedger + pruneOldEntries contract
10
+ * for the auto-commit throttle module at lib/core/auto-commit-throttle.cjs.
11
+ * The throttle module is consumed by the PostToolUse hook at
12
+ * scripts/async-artifact-auto-commit.cjs, which handles fs I/O, stdin envelope
13
+ * parsing, and the detached git spawn on the isolated data-room-autocommit
14
+ * branch.
15
+ *
16
+ * Canon references:
17
+ * Part 4 Every Choice Is Graph Data -- auto-commits preserve the provenance
18
+ * trail of every artifact write; the filesystem IS the graph.
19
+ * Part 6 Product-as-Venture -- dog-fooding: our own rooms get auto-commits,
20
+ * same as user rooms.
21
+ * Part 8 Graph Boundary -- auto-commit happens in the LOCAL workspace's
22
+ * room git; never pushes anywhere. Pure throttle is LOCAL by construction.
23
+ *
24
+ * Test map (10 cases, one-to-one with the PLAN <behavior> block):
25
+ * 1. Empty ledger, shouldThrottle returns false
26
+ * 2. Ledger has entry for path, now - lastTs < 5000ms -> true (throttle)
27
+ * 3. Ledger has entry for path, now - lastTs >= 5000ms -> false (no throttle)
28
+ * 4. Ledger has entry for OTHER path; shouldThrottle for current path false
29
+ * 5. updateLedger returns NEW object with updated ts (immutability check)
30
+ * 6. pruneOldEntries drops entries older than 86400000ms (1 day)
31
+ * 7. pruneOldEntries keeps entries within 1 day
32
+ * 8. THROTTLE_WINDOW_MS exported constant equals 5000
33
+ * 9. Pure function: zero fs imports in throttle module
34
+ * 10. Ledger with 1000 entries + prune runs in < 5ms (performance sanity)
35
+ *
36
+ * Registered in lib/memory/run-feynman-tests.cjs.
37
+ */
38
+
39
+ const assert = require('node:assert/strict');
40
+ const fs = require('node:fs');
41
+ const path = require('node:path');
42
+
43
+ const REPO = path.resolve(__dirname, '..', '..');
44
+ const THROTTLE_PATH = path.join(REPO, 'lib/core/auto-commit-throttle.cjs');
45
+
46
+ // Defensive clear so repeated invocations reload fresh.
47
+ try { delete require.cache[require.resolve(THROTTLE_PATH)]; } catch (_) {}
48
+ const throttle = require(THROTTLE_PATH);
49
+
50
+ assert.equal(typeof throttle.shouldThrottle, 'function',
51
+ 'module must export shouldThrottle()');
52
+ assert.equal(typeof throttle.updateLedger, 'function',
53
+ 'module must export updateLedger()');
54
+ assert.equal(typeof throttle.pruneOldEntries, 'function',
55
+ 'module must export pruneOldEntries()');
56
+ assert.equal(typeof throttle.THROTTLE_WINDOW_MS, 'number',
57
+ 'module must export THROTTLE_WINDOW_MS as a number');
58
+
59
+ // ---------- Test 1: empty ledger -> no throttle ----------
60
+
61
+ (function test1EmptyLedger() {
62
+ const ledger = {};
63
+ const now = 1000000;
64
+ const filePath = '/room/artifacts/a.md';
65
+ const result = throttle.shouldThrottle(filePath, now, ledger);
66
+ assert.equal(result, false,
67
+ 'empty ledger must not throttle');
68
+ console.log('PASS test 1: empty ledger -> no throttle');
69
+ })();
70
+
71
+ // ---------- Test 2: recent entry -> throttle ----------
72
+
73
+ (function test2RecentEntry() {
74
+ const filePath = '/room/artifacts/a.md';
75
+ const now = 1000000;
76
+ const ledger = { [filePath]: now - 1000 }; // 1s ago (< 5000ms)
77
+ const result = throttle.shouldThrottle(filePath, now, ledger);
78
+ assert.equal(result, true,
79
+ 'entry within 5000ms window must throttle');
80
+ console.log('PASS test 2: recent entry -> throttle');
81
+ })();
82
+
83
+ // ---------- Test 3: stale entry -> no throttle ----------
84
+
85
+ (function test3StaleEntry() {
86
+ const filePath = '/room/artifacts/a.md';
87
+ const now = 1000000;
88
+ const ledger = { [filePath]: now - 6000 }; // 6s ago (>= 5000ms)
89
+ const result = throttle.shouldThrottle(filePath, now, ledger);
90
+ assert.equal(result, false,
91
+ 'entry beyond 5000ms window must NOT throttle');
92
+ // Exact boundary: now - 5000 is not throttled (>= is the un-throttle side).
93
+ const boundary = { [filePath]: now - 5000 };
94
+ assert.equal(throttle.shouldThrottle(filePath, now, boundary), false,
95
+ 'exactly 5000ms boundary must NOT throttle (>= 5000 -> allow)');
96
+ console.log('PASS test 3: stale entry -> no throttle');
97
+ })();
98
+
99
+ // ---------- Test 4: per-path throttling ----------
100
+
101
+ (function test4PerPath() {
102
+ const fileA = '/room/artifacts/a.md';
103
+ const fileB = '/room/artifacts/b.md';
104
+ const now = 1000000;
105
+ const ledger = { [fileA]: now - 100 }; // A recently committed
106
+ const result = throttle.shouldThrottle(fileB, now, ledger);
107
+ assert.equal(result, false,
108
+ 'throttle must be per-path: fileA recent does not throttle fileB');
109
+ console.log('PASS test 4: per-path throttling');
110
+ })();
111
+
112
+ // ---------- Test 5: updateLedger immutability ----------
113
+
114
+ (function test5Immutability() {
115
+ const filePath = '/room/artifacts/a.md';
116
+ const now = 1234567;
117
+ const original = { '/other.md': 500 };
118
+ const originalSnapshot = JSON.stringify(original);
119
+ const next = throttle.updateLedger(filePath, now, original);
120
+ // Original unchanged
121
+ assert.equal(JSON.stringify(original), originalSnapshot,
122
+ 'updateLedger must not mutate the input ledger');
123
+ // Returned object has the new entry
124
+ assert.equal(next[filePath], now,
125
+ 'returned ledger must carry the updated timestamp');
126
+ // Returned object preserves other entries
127
+ assert.equal(next['/other.md'], 500,
128
+ 'returned ledger must preserve pre-existing entries');
129
+ // Distinct object references
130
+ assert.notEqual(next, original,
131
+ 'updateLedger must return a NEW object reference');
132
+ console.log('PASS test 5: updateLedger immutability');
133
+ })();
134
+
135
+ // ---------- Test 6: pruneOldEntries drops entries > 1 day ----------
136
+
137
+ (function test6PruneDrops() {
138
+ const ONE_DAY_MS = 86400000;
139
+ const now = 10 * ONE_DAY_MS; // arbitrary "now"
140
+ const stale = now - (ONE_DAY_MS + 1); // just over 1 day
141
+ const ledger = {
142
+ '/stale1.md': stale,
143
+ '/stale2.md': now - (ONE_DAY_MS * 2),
144
+ };
145
+ const pruned = throttle.pruneOldEntries(ledger, now);
146
+ assert.equal(pruned['/stale1.md'], undefined,
147
+ 'entry older than 1 day must be pruned');
148
+ assert.equal(pruned['/stale2.md'], undefined,
149
+ 'entry much older than 1 day must be pruned');
150
+ console.log('PASS test 6: pruneOldEntries drops old entries');
151
+ })();
152
+
153
+ // ---------- Test 7: pruneOldEntries keeps entries within 1 day ----------
154
+
155
+ (function test7PruneKeeps() {
156
+ const ONE_DAY_MS = 86400000;
157
+ const now = 10 * ONE_DAY_MS;
158
+ const recent = now - 1000; // 1s ago
159
+ const mid = now - (ONE_DAY_MS - 1000); // just under 1 day
160
+ const ledger = {
161
+ '/recent.md': recent,
162
+ '/mid.md': mid,
163
+ };
164
+ const pruned = throttle.pruneOldEntries(ledger, now);
165
+ assert.equal(pruned['/recent.md'], recent,
166
+ 'recent entry must survive prune');
167
+ assert.equal(pruned['/mid.md'], mid,
168
+ 'entry within 1 day must survive prune');
169
+ console.log('PASS test 7: pruneOldEntries keeps recent entries');
170
+ })();
171
+
172
+ // ---------- Test 8: THROTTLE_WINDOW_MS constant is 5000 ----------
173
+
174
+ (function test8ConstantValue() {
175
+ assert.equal(throttle.THROTTLE_WINDOW_MS, 5000,
176
+ 'THROTTLE_WINDOW_MS must equal 5000 per PLAN spec');
177
+ console.log('PASS test 8: THROTTLE_WINDOW_MS === 5000');
178
+ })();
179
+
180
+ // ---------- Test 9: pure -- zero fs imports in throttle module ----------
181
+
182
+ (function test9Pure() {
183
+ const src = fs.readFileSync(THROTTLE_PATH, 'utf8');
184
+ // Reject any require of node:fs or fs
185
+ const fsRequireRegex = /require\s*\(\s*['"](?:node:)?fs['"]/;
186
+ assert.equal(fsRequireRegex.test(src), false,
187
+ 'throttle module must NOT require fs (it is a pure function)');
188
+ // Also reject http / https / child_process for good measure (pure is pure).
189
+ const forbiddenRegex = /require\s*\(\s*['"](?:node:)?(?:http|https|child_process|net)['"]/;
190
+ assert.equal(forbiddenRegex.test(src), false,
191
+ 'throttle module must NOT require http/https/child_process/net');
192
+ console.log('PASS test 9: pure -- no fs/net imports');
193
+ })();
194
+
195
+ // ---------- Test 10: prune 1000 entries in < 5ms ----------
196
+
197
+ (function test10Performance() {
198
+ const ONE_DAY_MS = 86400000;
199
+ const now = 10 * ONE_DAY_MS;
200
+ const ledger = {};
201
+ for (let i = 0; i < 1000; i++) {
202
+ // Half stale, half fresh.
203
+ const ts = (i % 2 === 0) ? now - (ONE_DAY_MS * 2) : now - 1000;
204
+ ledger['/f' + i + '.md'] = ts;
205
+ }
206
+ const start = process.hrtime.bigint();
207
+ const pruned = throttle.pruneOldEntries(ledger, now);
208
+ const endNs = process.hrtime.bigint();
209
+ const elapsedMs = Number(endNs - start) / 1e6;
210
+ // Sanity: half the entries remain.
211
+ const keptCount = Object.keys(pruned).length;
212
+ assert.equal(keptCount, 500,
213
+ 'half of the 1000 entries must survive (odd indices are fresh)');
214
+ // Performance budget -- 5ms is generous for a 1000-entry O(n) scan on
215
+ // any modern CI runner. Keep it tight but tolerant.
216
+ assert.ok(elapsedMs < 5,
217
+ 'pruneOldEntries on 1000 entries must complete in < 5ms (was ' +
218
+ elapsedMs.toFixed(3) + 'ms)');
219
+ console.log('PASS test 10: prune 1000 entries in ' +
220
+ elapsedMs.toFixed(3) + 'ms');
221
+ })();
222
+
223
+ console.log('\nasync-artifact-auto-commit: 10/10 tests passed');
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * bearer-token.test.cjs -- Phase 87-09 integration + unit fence.
6
+ *
7
+ * BSL 1.1. Copyright (c) Mindrian 2026.
8
+ * (Business Source License 1.1; SPDX BUSL-1.1; see LICENSE.)
9
+ *
10
+ * Covers every R-87-09-CSRF gap plus the nominal Bearer flow:
11
+ * Unit (bearer-token.cjs):
12
+ * - 64 hex Bearer + 32 hex CSRF returned by createToken
13
+ * - Origin-bound lookupToken (gap 3): mismatch -> null
14
+ * - TTL expiration -> null
15
+ * - revokeToken deletes
16
+ * - lookupCsrfForToken returns the bound CSRF
17
+ *
18
+ * Integration (spawn serve-dashboard-live on a non-default port with
19
+ * --no-open and --room <tmpdir>):
20
+ * - POST /api/auth/session with valid Origin -> 200 + token + csrf +
21
+ * Set-Cookie mos_csrf (SameSite=Strict) + 4 security headers (gap 4)
22
+ * - Origin: null -> 403 (gap 1)
23
+ * - Origin: https://evil.com -> 403 (gap 1)
24
+ * - Host: evil.com:<port> -> 403 (gap 2)
25
+ * - /api/room/chat with api_key in body -> 401
26
+ * - /api/room/chat with no Bearer -> 401
27
+ * - /api/room/chat with no X-CSRF-Token -> 403 (gap 6)
28
+ * - /api/room/chat with X-CSRF-Token != cookie -> 403 (gap 6)
29
+ * - /api/room/chat with token bound to localhost but used from file://
30
+ * -> non-200 (gap 3)
31
+ * - 11 rapid POSTs to /api/auth/session -> 11th is 429 (rate limit)
32
+ * - Server stderr NEVER contains the api_key (zero-log + gap 5 coverage:
33
+ * every error path, including unhandledRejection and nested
34
+ * err.request.headers leaks)
35
+ *
36
+ * Exit codes:
37
+ * 0 -> PASS
38
+ * 1 -> FAIL (hard regression)
39
+ */
40
+
41
+ const assert = require('node:assert/strict');
42
+ const http = require('node:http');
43
+ const fs = require('node:fs');
44
+ const os = require('node:os');
45
+ const path = require('node:path');
46
+ const { spawn } = require('node:child_process');
47
+
48
+ const bearer = require('../core/bearer-token.cjs');
49
+
50
+ const SERVER_PATH = path.resolve(__dirname, '../../scripts/serve-dashboard-live');
51
+ const PORT = 3192;
52
+ const FAKE_KEY = 'sk-test-fake-key-for-unit-tests-do-not-use-' + Date.now();
53
+
54
+ function post(target, { headers = {}, body = '' } = {}) {
55
+ return new Promise((resolve, reject) => {
56
+ const u = new URL(target);
57
+ const payload = typeof body === 'string' ? body : JSON.stringify(body);
58
+ const req = http.request({
59
+ hostname: u.hostname,
60
+ port: u.port,
61
+ path: u.pathname + u.search,
62
+ method: 'POST',
63
+ headers: Object.assign({
64
+ 'Content-Type': 'application/json',
65
+ 'Content-Length': Buffer.byteLength(payload),
66
+ }, headers),
67
+ timeout: 6000,
68
+ }, (res) => {
69
+ let b = '';
70
+ res.on('data', (c) => { b += c; });
71
+ res.on('end', () => resolve({
72
+ statusCode: res.statusCode,
73
+ body: b,
74
+ headers: res.headers,
75
+ }));
76
+ });
77
+ req.on('error', reject);
78
+ req.on('timeout', () => { req.destroy(new Error('timeout')); });
79
+ req.write(payload);
80
+ req.end();
81
+ });
82
+ }
83
+
84
+ async function waitForBind(port, maxMs = 8000) {
85
+ const start = Date.now();
86
+ while (Date.now() - start < maxMs) {
87
+ try {
88
+ const r = await post('http://127.0.0.1:' + port + '/api/auth/session', {
89
+ body: '{}',
90
+ headers: { 'Origin': 'http://localhost:3131' },
91
+ });
92
+ if (r.statusCode >= 200 && r.statusCode < 600) return true;
93
+ } catch (_) { /* retry */ }
94
+ await new Promise((r) => setTimeout(r, 150));
95
+ }
96
+ return false;
97
+ }
98
+
99
+ async function run() {
100
+ /* ═════ UNIT tests ═════ */
101
+ bearer.clearAll();
102
+ const r1 = bearer.createToken(FAKE_KEY, 'http://localhost:3131');
103
+ assert.strictEqual(r1.token.length, 64, 'token must be 64 hex chars');
104
+ assert.strictEqual(r1.csrfToken.length, 32, 'csrf must be 32 hex chars');
105
+ assert.strictEqual(r1.expiresIn, 1800, 'expiresIn must be 1800 (30 min)');
106
+ assert.strictEqual(bearer.lookupToken(r1.token, 'http://localhost:3131'), FAKE_KEY);
107
+ // R-87-09-CSRF gap 3: Origin-mismatch lookup returns null.
108
+ // Token was created with Origin 'http://localhost:3131' so a request
109
+ // presenting Origin 'file://' must be rejected.
110
+ assert.strictEqual(bearer.lookupToken(r1.token, 'file://'), null,
111
+ 'Origin-mismatch lookup must return null');
112
+ assert.strictEqual(bearer.lookupToken('nonexistent', 'http://localhost:3131'), null);
113
+ assert.strictEqual(bearer.lookupCsrfForToken(r1.token), r1.csrfToken,
114
+ 'lookupCsrfForToken must return the bound CSRF');
115
+
116
+ // TTL expiry
117
+ const r2 = bearer.createToken(FAKE_KEY, 'http://localhost:3131', 50);
118
+ await new Promise((r) => setTimeout(r, 100));
119
+ assert.strictEqual(bearer.lookupToken(r2.token, 'http://localhost:3131'), null,
120
+ 'expired token must return null');
121
+
122
+ // Explicit revoke
123
+ assert.ok(bearer.revokeToken(r1.token));
124
+ assert.strictEqual(bearer.lookupToken(r1.token, 'http://localhost:3131'), null);
125
+
126
+ /* ═════ INTEGRATION tests: spawn server ═════ */
127
+ // Use an explicit --room tmpdir so the server doesn't depend on a
128
+ // user's real MindrianRooms or the project registry.
129
+ const tmpRoom = fs.mkdtempSync(path.join(os.tmpdir(), 'bearer-test-'));
130
+ const child = spawn(
131
+ 'node',
132
+ [SERVER_PATH, '--port', String(PORT), '--no-open', '--room', tmpRoom],
133
+ { stdio: ['ignore', 'pipe', 'pipe'], env: Object.assign({}, process.env, { NODE_ENV: 'test' }) }
134
+ );
135
+ let stdout = '';
136
+ let stderr = '';
137
+ child.stdout.on('data', (d) => { stdout += d; });
138
+ child.stderr.on('data', (d) => { stderr += d; });
139
+
140
+ try {
141
+ const bound = await waitForBind(PORT, 8000);
142
+ assert.ok(bound, 'server did not bind within 8s. stderr: ' + stderr);
143
+
144
+ /* 1. Valid auth -> 200 + token + csrf + Set-Cookie mos_csrf */
145
+ const authRes = await post('http://127.0.0.1:' + PORT + '/api/auth/session', {
146
+ headers: { 'Origin': 'http://localhost:3131' },
147
+ body: JSON.stringify({ api_key: FAKE_KEY }),
148
+ });
149
+ assert.strictEqual(authRes.statusCode, 200,
150
+ 'auth must return 200 (got ' + authRes.statusCode + ': ' + authRes.body + ')');
151
+ const parsed = JSON.parse(authRes.body);
152
+ const token = parsed.token;
153
+ const csrfBody = parsed.csrf_token;
154
+ assert.ok(token && token.length === 64, 'token must be 64 hex chars');
155
+ assert.ok(csrfBody && csrfBody.length === 32, 'csrf_token must be 32 hex chars');
156
+ const setCookieArr = authRes.headers['set-cookie'] || [];
157
+ const setCookie = Array.isArray(setCookieArr) ? setCookieArr.join('|') : String(setCookieArr);
158
+ // R-87-09-CSRF gap 6: Set-Cookie mos_csrf present with SameSite=Strict
159
+ assert.match(setCookie, /mos_csrf=/, 'Set-Cookie must include mos_csrf');
160
+ assert.match(setCookie, /SameSite=Strict/, 'mos_csrf cookie must have SameSite=Strict');
161
+ const csrfCookie = (setCookie.match(/mos_csrf=([^;|]+)/) || [])[1];
162
+ assert.strictEqual(csrfCookie, csrfBody, 'cookie mos_csrf must equal body csrf_token');
163
+
164
+ // R-87-09-CSRF gap 4: security headers present on EVERY response
165
+ assert.strictEqual(authRes.headers['x-frame-options'], 'DENY',
166
+ 'X-Frame-Options: DENY required');
167
+ assert.match(authRes.headers['content-security-policy'] || '', /frame-ancestors 'none'/,
168
+ 'CSP frame-ancestors none required');
169
+ assert.strictEqual(authRes.headers['x-content-type-options'], 'nosniff',
170
+ 'nosniff required');
171
+ assert.strictEqual(authRes.headers['referrer-policy'], 'no-referrer',
172
+ 'no-referrer required');
173
+
174
+ /* 2. R-87-09-CSRF gap 1: Origin: null -> 403. The null-origin sentinel
175
+ * is intentionally absent from the default allowlist. */
176
+ const nullOriginRes = await post('http://127.0.0.1:' + PORT + '/api/auth/session', {
177
+ headers: { 'Origin': 'null' },
178
+ body: JSON.stringify({ api_key: FAKE_KEY }),
179
+ });
180
+ assert.strictEqual(nullOriginRes.statusCode, 403,
181
+ 'Origin: null must be rejected by default (got ' + nullOriginRes.statusCode + ')');
182
+
183
+ /* 3. R-87-09-CSRF gap 1: Origin: evil.com -> 403 */
184
+ const evilOriginRes = await post('http://127.0.0.1:' + PORT + '/api/auth/session', {
185
+ headers: { 'Origin': 'https://evil.com' },
186
+ body: JSON.stringify({ api_key: FAKE_KEY }),
187
+ });
188
+ assert.strictEqual(evilOriginRes.statusCode, 403,
189
+ 'evil.com origin must be rejected');
190
+
191
+ /* 4. R-87-09-CSRF gap 2: Host: evil.com:PORT -> 403 (DNS-rebinding) */
192
+ const badHostRes = await post('http://127.0.0.1:' + PORT + '/api/auth/session', {
193
+ headers: { 'Origin': 'http://localhost:3131', 'Host': 'evil.com:' + PORT },
194
+ body: JSON.stringify({ api_key: FAKE_KEY }),
195
+ });
196
+ assert.strictEqual(badHostRes.statusCode, 403,
197
+ 'evil.com host must be rejected (DNS rebinding guard)');
198
+
199
+ /* 5. /api/room/chat with api_key in body -> 401 */
200
+ const badKeyRes = await post('http://127.0.0.1:' + PORT + '/api/room/chat', {
201
+ headers: {
202
+ 'Origin': 'http://localhost:3131',
203
+ 'Authorization': 'Bearer ' + token,
204
+ 'Cookie': 'mos_csrf=' + csrfBody,
205
+ 'X-CSRF-Token': csrfBody,
206
+ },
207
+ body: JSON.stringify({ api_key: FAKE_KEY, message: 'hi' }),
208
+ });
209
+ assert.strictEqual(badKeyRes.statusCode, 401,
210
+ 'raw api_key in body must be rejected');
211
+
212
+ /* 6. /api/room/chat with no Bearer -> 401 */
213
+ const noAuthRes = await post('http://127.0.0.1:' + PORT + '/api/room/chat', {
214
+ headers: {
215
+ 'Origin': 'http://localhost:3131',
216
+ 'Cookie': 'mos_csrf=' + csrfBody,
217
+ 'X-CSRF-Token': csrfBody,
218
+ },
219
+ body: JSON.stringify({ message: 'hi' }),
220
+ });
221
+ assert.strictEqual(noAuthRes.statusCode, 401,
222
+ 'missing Bearer must be rejected');
223
+
224
+ /* 7. R-87-09-CSRF gap 6: no X-CSRF-Token -> 403 */
225
+ const noCsrfRes = await post('http://127.0.0.1:' + PORT + '/api/room/chat', {
226
+ headers: {
227
+ 'Origin': 'http://localhost:3131',
228
+ 'Authorization': 'Bearer ' + token,
229
+ 'Cookie': 'mos_csrf=' + csrfBody,
230
+ },
231
+ body: JSON.stringify({ message: 'hi' }),
232
+ });
233
+ assert.strictEqual(noCsrfRes.statusCode, 403,
234
+ 'missing X-CSRF-Token must be rejected');
235
+
236
+ /* 8. R-87-09-CSRF gap 6: mismatched X-CSRF-Token vs cookie -> 403 */
237
+ const mismatchRes = await post('http://127.0.0.1:' + PORT + '/api/room/chat', {
238
+ headers: {
239
+ 'Origin': 'http://localhost:3131',
240
+ 'Authorization': 'Bearer ' + token,
241
+ 'Cookie': 'mos_csrf=' + csrfBody,
242
+ 'X-CSRF-Token': 'wrong-csrf-value',
243
+ },
244
+ body: JSON.stringify({ message: 'hi' }),
245
+ });
246
+ assert.strictEqual(mismatchRes.statusCode, 403,
247
+ 'mismatched CSRF must be rejected');
248
+
249
+ /* 9. R-87-09-CSRF gap 3: token obtained with Origin A, used with
250
+ * Origin B -> non-200. file:// is in the server's allowlist but
251
+ * the token's bound origin is http://localhost:3131, so
252
+ * lookupToken returns null. 401 or 403 both acceptable;
253
+ * rejecting 200 is the invariant. A nested err.request.headers
254
+ * leak attempt (FAKE_KEY in body alongside unhandledRejection
255
+ * path exercises during fetch) also stays redacted. */
256
+ const crossOriginRes = await post('http://127.0.0.1:' + PORT + '/api/room/chat', {
257
+ headers: {
258
+ 'Origin': 'file://',
259
+ 'Authorization': 'Bearer ' + token,
260
+ 'Cookie': 'mos_csrf=' + csrfBody,
261
+ 'X-CSRF-Token': csrfBody,
262
+ },
263
+ body: JSON.stringify({ message: 'hi' }),
264
+ });
265
+ assert.ok(crossOriginRes.statusCode === 401 || crossOriginRes.statusCode === 403,
266
+ 'token-origin mismatch must reject (got ' + crossOriginRes.statusCode + ')');
267
+
268
+ /* 10. MEDIUM rate limit: 11 rapid auth attempts -> at least one 429.
269
+ * Earlier tests already used 3 of the 10 slots with Origin
270
+ * http://localhost:3131; firing 11 more rolls us over the window. */
271
+ let saw429 = false;
272
+ for (let i = 0; i < 11; i += 1) {
273
+ const rl = await post('http://127.0.0.1:' + PORT + '/api/auth/session', {
274
+ headers: { 'Origin': 'http://localhost:3131' },
275
+ body: JSON.stringify({ api_key: FAKE_KEY }),
276
+ });
277
+ if (rl.statusCode === 429) { saw429 = true; break; }
278
+ }
279
+ assert.ok(saw429,
280
+ 'auth rate limit must return 429 within 11 requests (rate-limit hardening)');
281
+
282
+ /* 11. R-87-09-CSRF gap 5: zero-log assertion across ALL error paths.
283
+ *
284
+ * The api_key (real or fabricated) must NEVER appear in server logs,
285
+ * INCLUDING paths where an error carries nested headers such as
286
+ * err.request.headers['x-api-key'] or err.cause.config.headers.
287
+ * The server's safeLogError only accesses err.message + err.code,
288
+ * and knownSecrets replaces any exact api_key substring in any
289
+ * logged string. After all the above traffic (success, 4xx errors,
290
+ * 403 guards, 429 rate limit), inspect stdout+stderr for FAKE_KEY.
291
+ * Also covers the unhandledRejection handler (same safeLogError
292
+ * discipline applied for process-level rejections). */
293
+ const combined = stdout + stderr;
294
+ assert.ok(!combined.includes(FAKE_KEY),
295
+ 'api_key must NEVER appear in server logs ' +
296
+ '(covers nested err.request.headers + unhandledRejection leak paths)');
297
+
298
+ process.stdout.write(
299
+ 'bearer-token: all tests passed (all six R-87-09-CSRF gaps + rate limit + zero-log)\n'
300
+ );
301
+ child.kill('SIGINT');
302
+ await new Promise((r) => setTimeout(r, 200));
303
+ try { fs.rmSync(tmpRoom, { recursive: true, force: true }); } catch (_) { /* best-effort */ }
304
+ process.exit(0);
305
+ } catch (e) {
306
+ try { child.kill('SIGKILL'); } catch (_) { /* ignore */ }
307
+ try { fs.rmSync(tmpRoom, { recursive: true, force: true }); } catch (_) { /* ignore */ }
308
+ process.stderr.write('STDOUT: ' + stdout + '\n');
309
+ process.stderr.write('STDERR: ' + stderr + '\n');
310
+ process.stderr.write((e && e.stack) ? e.stack + '\n' : String(e) + '\n');
311
+ process.exit(1);
312
+ }
313
+ }
314
+
315
+ run();