@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,407 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
4
+ *
5
+ * Phase 88-02 -- minto-debouncer tests (RED -> GREEN)
6
+ * ====================================================
7
+ * Ships the debounced queue that coalesces post-write regen intents into
8
+ * one regen per section per 10s window. Queue persists at
9
+ * .mindrian/minto-queue.json and survives session crashes. Concurrent
10
+ * producers (post-write hook + on-stop + intent-classifier drain) cannot
11
+ * corrupt the queue file because every mutation composes with the
12
+ * Phase 87-02 atomic write-lock (acquireLock / releaseLock).
13
+ *
14
+ * Test map (12 tests, one per PLAN <behavior> case):
15
+ * Test 1: first enqueue creates .mindrian/minto-queue.json with 1 entry
16
+ * Test 2: second enqueue on same section within 10s -> length stays 1
17
+ * (earliest-wins: enqueued_at preserved on FIRST entry)
18
+ * Test 3: two enqueues on different sections -> length 2
19
+ * Test 4: enqueue after 10s+ on same section -> length 2 (window expired)
20
+ * Test 5: drain(olderThanMs:30000) -> only entries older than 30s drained
21
+ * Test 6: drain crash-safe: simulated mid-drain failure leaves valid JSON
22
+ * Test 7: 5 concurrent forked enqueues on SAME section -> final length 1
23
+ * Test 8: 5 concurrent forked enqueues on 5 DISTINCT sections -> length 5
24
+ * Test 9: peek is read-only (peek twice -> identical result, no mutation)
25
+ * Test 10: malformed JSON self-heals; warning to stderr; no throw
26
+ * Test 11: CLI invocation: enqueue subcommand exits 0, produces queue file
27
+ * Test 12: drain wall-clock timeout: 100ms bound honored under load
28
+ *
29
+ * BSL 1.1. Zero npm deps. Node built-ins only. Three-surface by construction.
30
+ */
31
+
32
+ 'use strict';
33
+
34
+ const assert = require('node:assert/strict');
35
+ const fs = require('node:fs');
36
+ const os = require('node:os');
37
+ const path = require('node:path');
38
+ const { fork, spawnSync } = require('node:child_process');
39
+
40
+ const debouncer = require('../../scripts/minto-debouncer.cjs');
41
+
42
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
43
+ const DEBOUNCER_CLI = path.join(REPO_ROOT, 'scripts', 'minto-debouncer.cjs');
44
+ const ENQUEUE_WORKER = path.join(__dirname, 'minto-debouncer.worker.cjs');
45
+
46
+ const TMPDIRS = [];
47
+ function mkTmp(prefix) {
48
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
49
+ // Mark as a room so downstream resolve-room style walkers could find it.
50
+ fs.writeFileSync(path.join(d, '.room-root'), '');
51
+ TMPDIRS.push(d);
52
+ return d;
53
+ }
54
+ process.on('exit', () => {
55
+ for (const d of TMPDIRS) {
56
+ try { fs.rmSync(d, { recursive: true, force: true }); } catch (_) {}
57
+ }
58
+ });
59
+
60
+ function queuePath(roomDir) {
61
+ return path.join(roomDir, '.mindrian', 'minto-queue.json');
62
+ }
63
+
64
+ function readQueue(roomDir) {
65
+ return JSON.parse(fs.readFileSync(queuePath(roomDir), 'utf-8'));
66
+ }
67
+
68
+ let passed = 0;
69
+ let failed = 0;
70
+ function run(name, fn) {
71
+ try {
72
+ fn();
73
+ process.stdout.write(' OK ' + name + '\n');
74
+ passed += 1;
75
+ } catch (e) {
76
+ process.stderr.write(' FAIL ' + name + ': ' + (e && e.message || e) + '\n');
77
+ if (e && e.stack) process.stderr.write(e.stack + '\n');
78
+ failed += 1;
79
+ }
80
+ }
81
+
82
+ async function runAsync(name, fn) {
83
+ try {
84
+ await fn();
85
+ process.stdout.write(' OK ' + name + '\n');
86
+ passed += 1;
87
+ } catch (e) {
88
+ process.stderr.write(' FAIL ' + name + ': ' + (e && e.message || e) + '\n');
89
+ if (e && e.stack) process.stderr.write(e.stack + '\n');
90
+ failed += 1;
91
+ }
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Tests
96
+ // ---------------------------------------------------------------------------
97
+
98
+ async function main() {
99
+ process.stdout.write('minto-debouncer.test.cjs:\n');
100
+
101
+ // Sanity: worker fork helper exists.
102
+ assert.ok(
103
+ fs.existsSync(ENQUEUE_WORKER),
104
+ 'minto-debouncer.worker.cjs missing (concurrency tests depend on it)'
105
+ );
106
+
107
+ // -------------------------------------------------------------------------
108
+ // Test 1: first enqueue creates the queue file with one entry
109
+ // -------------------------------------------------------------------------
110
+ run('Test 1: first enqueue creates queue with 1 entry', () => {
111
+ const room = mkTmp('mi-deb-t1-');
112
+ debouncer.enqueue(room, 'market-analysis', 'post-write');
113
+ assert.ok(fs.existsSync(queuePath(room)), 'queue file should be created');
114
+ const q = readQueue(room);
115
+ assert.strictEqual(q.version, 1, 'schema version should be 1');
116
+ assert.strictEqual(q.entries.length, 1, 'entries length 1');
117
+ assert.strictEqual(q.entries[0].section, 'market-analysis');
118
+ assert.strictEqual(q.entries[0].reason, 'post-write');
119
+ assert.strictEqual(q.entries[0].attempts, 0);
120
+ assert.ok(
121
+ typeof q.entries[0].enqueued_at === 'string' &&
122
+ !Number.isNaN(Date.parse(q.entries[0].enqueued_at)),
123
+ 'enqueued_at should be a valid ISO-8601 string'
124
+ );
125
+ });
126
+
127
+ // -------------------------------------------------------------------------
128
+ // Test 2: coalesce -- second enqueue on same section within 10s stays at 1
129
+ // earliest-wins: enqueued_at preserved from the FIRST entry
130
+ // -------------------------------------------------------------------------
131
+ run('Test 2: coalesce within 10s (earliest-wins enqueued_at)', () => {
132
+ const room = mkTmp('mi-deb-t2-');
133
+ debouncer.enqueue(room, 'problem-definition', 'post-write');
134
+ const firstTs = readQueue(room).entries[0].enqueued_at;
135
+ // small real-time delay to ensure a second call would see a different "now"
136
+ const deadline = Date.now() + 5;
137
+ while (Date.now() < deadline) {/* burn */}
138
+ debouncer.enqueue(room, 'problem-definition', 'post-write');
139
+ const q = readQueue(room);
140
+ assert.strictEqual(q.entries.length, 1, 'coalesce should keep length 1');
141
+ assert.strictEqual(
142
+ q.entries[0].enqueued_at,
143
+ firstTs,
144
+ 'earliest-wins: first enqueued_at must be preserved'
145
+ );
146
+ });
147
+
148
+ // -------------------------------------------------------------------------
149
+ // Test 3: two different sections -> length 2
150
+ // -------------------------------------------------------------------------
151
+ run('Test 3: distinct sections both enqueue', () => {
152
+ const room = mkTmp('mi-deb-t3-');
153
+ debouncer.enqueue(room, 'market-analysis', 'post-write');
154
+ debouncer.enqueue(room, 'financial-model', 'post-write');
155
+ const q = readQueue(room);
156
+ assert.strictEqual(q.entries.length, 2);
157
+ const sections = q.entries.map((e) => e.section).sort();
158
+ assert.deepStrictEqual(sections, ['financial-model', 'market-analysis']);
159
+ });
160
+
161
+ // -------------------------------------------------------------------------
162
+ // Test 4: after window expires, same section appends new entry
163
+ // We simulate time travel by rewriting the queue file with a stale
164
+ // enqueued_at (older than 10s). That exercises the same code path
165
+ // as real elapsed time without the 10s test delay.
166
+ // -------------------------------------------------------------------------
167
+ run('Test 4: window expired -> new entry appended', () => {
168
+ const room = mkTmp('mi-deb-t4-');
169
+ debouncer.enqueue(room, 'business-model', 'post-write');
170
+ const q1 = readQueue(room);
171
+ assert.strictEqual(q1.entries.length, 1);
172
+ // Backdate the entry 15s so the next enqueue falls OUTSIDE the 10s window.
173
+ q1.entries[0].enqueued_at = new Date(Date.now() - 15000).toISOString();
174
+ fs.writeFileSync(queuePath(room), JSON.stringify(q1));
175
+ debouncer.enqueue(room, 'business-model', 'on-stop');
176
+ const q2 = readQueue(room);
177
+ assert.strictEqual(q2.entries.length, 2, 'stale entry + fresh entry = 2');
178
+ const reasons = q2.entries.map((e) => e.reason).sort();
179
+ assert.deepStrictEqual(reasons, ['on-stop', 'post-write']);
180
+ });
181
+
182
+ // -------------------------------------------------------------------------
183
+ // Test 5: drain(olderThanMs:30000) -- only entries older than 30s drained
184
+ // -------------------------------------------------------------------------
185
+ run('Test 5: drain honors olderThanMs', () => {
186
+ const room = mkTmp('mi-deb-t5-');
187
+ debouncer.enqueue(room, 'old-section', 'post-write');
188
+ debouncer.enqueue(room, 'new-section', 'post-write');
189
+ // Backdate old-section to 45s ago.
190
+ const q = readQueue(room);
191
+ const old = q.entries.find((e) => e.section === 'old-section');
192
+ old.enqueued_at = new Date(Date.now() - 45000).toISOString();
193
+ fs.writeFileSync(queuePath(room), JSON.stringify(q));
194
+ const drained = debouncer.drain(room, { timeoutMs: 5000, olderThanMs: 30000 });
195
+ assert.strictEqual(drained.length, 1, 'exactly 1 entry older than 30s');
196
+ assert.strictEqual(drained[0].section, 'old-section');
197
+ const remaining = readQueue(room);
198
+ assert.strictEqual(remaining.entries.length, 1, 'fresh entry remains');
199
+ assert.strictEqual(remaining.entries[0].section, 'new-section');
200
+ });
201
+
202
+ // -------------------------------------------------------------------------
203
+ // Test 6: drain is atomic-safe -- even if the queue file is truncated
204
+ // mid-write, the previous committed queue remains valid JSON.
205
+ // We validate the temp-file-then-rename pattern by inspecting the
206
+ // queue file between enqueue calls (it must always parse).
207
+ // -------------------------------------------------------------------------
208
+ run('Test 6: atomic write via tmp+rename keeps queue JSON-valid', () => {
209
+ const room = mkTmp('mi-deb-t6-');
210
+ debouncer.enqueue(room, 'sec-a', 'post-write');
211
+ // At any point after enqueue, reading the queue must parse cleanly.
212
+ const raw = fs.readFileSync(queuePath(room), 'utf-8');
213
+ const parsed = JSON.parse(raw);
214
+ assert.strictEqual(parsed.version, 1);
215
+ // Drain and re-check.
216
+ debouncer.drain(room, { timeoutMs: 1000, olderThanMs: 0 });
217
+ const raw2 = fs.readFileSync(queuePath(room), 'utf-8');
218
+ const parsed2 = JSON.parse(raw2);
219
+ assert.strictEqual(parsed2.version, 1);
220
+ assert.strictEqual(parsed2.entries.length, 0, 'drain empties when olderThanMs=0');
221
+ // Verify no .tmp.PID leftovers exist in the .mindrian dir.
222
+ const mindrianDir = path.join(room, '.mindrian');
223
+ const leftovers = fs.readdirSync(mindrianDir).filter((f) => f.includes('.tmp.'));
224
+ assert.strictEqual(leftovers.length, 0, 'no tmp artifacts left behind');
225
+ });
226
+
227
+ // -------------------------------------------------------------------------
228
+ // Test 7: 5 concurrent forked enqueues on SAME section -> final length 1
229
+ // This proves the write-lock composition prevents duplicate entries.
230
+ // -------------------------------------------------------------------------
231
+ await runAsync('Test 7: 5 concurrent same-section -> length 1 (coalesced)', async () => {
232
+ const room = mkTmp('mi-deb-t7-');
233
+ const forks = [];
234
+ for (let i = 0; i < 5; i++) {
235
+ forks.push(new Promise((resolve) => {
236
+ const child = fork(ENQUEUE_WORKER, [room, 'market-analysis', 'post-write'], { silent: true });
237
+ let stderrBuf = '';
238
+ if (child.stderr) child.stderr.on('data', (c) => { stderrBuf += c.toString(); });
239
+ child.on('exit', (code) => resolve({ code, stderr: stderrBuf }));
240
+ }));
241
+ }
242
+ const results = await Promise.all(forks);
243
+ const failures = results.filter((r) => r.code !== 0);
244
+ if (failures.length) {
245
+ for (const r of failures) process.stderr.write('[worker stderr] ' + r.stderr);
246
+ }
247
+ assert.strictEqual(failures.length, 0, 'all 5 workers must exit 0');
248
+ const q = readQueue(room);
249
+ assert.strictEqual(
250
+ q.entries.length,
251
+ 1,
252
+ 'coalesced to 1 entry (got ' + q.entries.length + ')'
253
+ );
254
+ assert.strictEqual(q.entries[0].section, 'market-analysis');
255
+ });
256
+
257
+ // -------------------------------------------------------------------------
258
+ // Test 8: 5 concurrent forked enqueues on 5 DISTINCT sections -> length 5
259
+ // Zero data loss under concurrency.
260
+ // -------------------------------------------------------------------------
261
+ await runAsync('Test 8: 5 concurrent distinct sections -> length 5 (no loss)', async () => {
262
+ const room = mkTmp('mi-deb-t8-');
263
+ const sections = ['s1', 's2', 's3', 's4', 's5'];
264
+ const forks = sections.map((s) => new Promise((resolve) => {
265
+ const child = fork(ENQUEUE_WORKER, [room, s, 'post-write'], { silent: true });
266
+ let stderrBuf = '';
267
+ if (child.stderr) child.stderr.on('data', (c) => { stderrBuf += c.toString(); });
268
+ child.on('exit', (code) => resolve({ code, stderr: stderrBuf }));
269
+ }));
270
+ const results = await Promise.all(forks);
271
+ const failures = results.filter((r) => r.code !== 0);
272
+ if (failures.length) {
273
+ for (const r of failures) process.stderr.write('[worker stderr] ' + r.stderr);
274
+ }
275
+ assert.strictEqual(failures.length, 0, 'all 5 workers must exit 0');
276
+ const q = readQueue(room);
277
+ assert.strictEqual(q.entries.length, 5, 'all 5 distinct sections present');
278
+ const got = q.entries.map((e) => e.section).sort();
279
+ assert.deepStrictEqual(got, sections);
280
+ });
281
+
282
+ // -------------------------------------------------------------------------
283
+ // Test 9: peek is read-only (two peeks return identical results,
284
+ // queue file mtime unchanged).
285
+ // -------------------------------------------------------------------------
286
+ run('Test 9: peek is read-only (idempotent)', () => {
287
+ const room = mkTmp('mi-deb-t9-');
288
+ debouncer.enqueue(room, 'only-section', 'post-write');
289
+ const mtime1 = fs.statSync(queuePath(room)).mtimeMs;
290
+ const p1 = debouncer.peek(room);
291
+ const p2 = debouncer.peek(room);
292
+ assert.deepStrictEqual(p1, p2, 'two peeks should return deeply equal result');
293
+ assert.strictEqual(p1.entries.length, 1);
294
+ assert.strictEqual(p1.entries[0].section, 'only-section');
295
+ const mtime2 = fs.statSync(queuePath(room)).mtimeMs;
296
+ assert.strictEqual(mtime1, mtime2, 'peek must not touch the queue file');
297
+ });
298
+
299
+ // -------------------------------------------------------------------------
300
+ // Test 10: malformed JSON self-heals; warning to stderr; no throw.
301
+ // -------------------------------------------------------------------------
302
+ run('Test 10: malformed JSON self-heals with stderr warning', () => {
303
+ const room = mkTmp('mi-deb-t10-');
304
+ fs.mkdirSync(path.join(room, '.mindrian'), { recursive: true });
305
+ // Write garbage to the queue path.
306
+ fs.writeFileSync(queuePath(room), '{not-valid-json}}}');
307
+ // Capture stderr during the enqueue call.
308
+ const origWrite = process.stderr.write.bind(process.stderr);
309
+ let stderrBuf = '';
310
+ process.stderr.write = (chunk) => { stderrBuf += String(chunk); return true; };
311
+ try {
312
+ assert.doesNotThrow(
313
+ () => debouncer.enqueue(room, 'healed-section', 'post-write'),
314
+ 'enqueue must not throw on corrupted queue'
315
+ );
316
+ } finally {
317
+ process.stderr.write = origWrite;
318
+ }
319
+ assert.ok(
320
+ stderrBuf.toLowerCase().includes('warn') ||
321
+ stderrBuf.toLowerCase().includes('corrupt') ||
322
+ stderrBuf.toLowerCase().includes('reset') ||
323
+ stderrBuf.toLowerCase().includes('heal'),
324
+ 'should log a recovery warning to stderr (got: ' + JSON.stringify(stderrBuf) + ')'
325
+ );
326
+ const q = readQueue(room);
327
+ assert.strictEqual(q.version, 1);
328
+ assert.strictEqual(q.entries.length, 1);
329
+ assert.strictEqual(q.entries[0].section, 'healed-section');
330
+ });
331
+
332
+ // -------------------------------------------------------------------------
333
+ // Test 11: CLI invocation works and produces the queue file.
334
+ // -------------------------------------------------------------------------
335
+ run('Test 11: CLI enqueue subcommand produces queue file', () => {
336
+ const room = mkTmp('mi-deb-t11-');
337
+ const res = spawnSync(process.execPath, [
338
+ DEBOUNCER_CLI, 'enqueue', room, 'market-analysis', 'post-write',
339
+ ], { encoding: 'utf-8' });
340
+ assert.strictEqual(res.status, 0, 'CLI must exit 0 (stderr: ' + res.stderr + ')');
341
+ assert.ok(fs.existsSync(queuePath(room)), 'queue file should exist after CLI enqueue');
342
+ const q = readQueue(room);
343
+ assert.strictEqual(q.entries.length, 1);
344
+ assert.strictEqual(q.entries[0].section, 'market-analysis');
345
+ // peek subcommand also works.
346
+ const peekRes = spawnSync(process.execPath, [DEBOUNCER_CLI, 'peek', room], { encoding: 'utf-8' });
347
+ assert.strictEqual(peekRes.status, 0, 'peek exit 0 (stderr: ' + peekRes.stderr + ')');
348
+ assert.ok(
349
+ peekRes.stdout.includes('market-analysis'),
350
+ 'peek stdout should mention the section'
351
+ );
352
+ });
353
+
354
+ // -------------------------------------------------------------------------
355
+ // Test 12: drain has a wall-clock timeout bound (returns within timeoutMs
356
+ // even under synthetic load).
357
+ // -------------------------------------------------------------------------
358
+ run('Test 12: drain honors wall-clock timeoutMs', () => {
359
+ const room = mkTmp('mi-deb-t12-');
360
+ // Pre-populate queue with many entries (simulate a burst).
361
+ fs.mkdirSync(path.join(room, '.mindrian'), { recursive: true });
362
+ const entries = [];
363
+ const now = Date.now();
364
+ for (let i = 0; i < 500; i++) {
365
+ entries.push({
366
+ section: 'sec-' + i,
367
+ enqueued_at: new Date(now - 60000).toISOString(),
368
+ reason: 'post-write',
369
+ attempts: 0,
370
+ });
371
+ }
372
+ fs.writeFileSync(queuePath(room), JSON.stringify({ version: 1, entries }));
373
+ const t0 = Date.now();
374
+ const drained = debouncer.drain(room, { timeoutMs: 100, olderThanMs: 30000 });
375
+ const elapsed = Date.now() - t0;
376
+ // Wall-clock bound: allow generous overhead for lock acquire + rename on slow CI.
377
+ // The invariant we're testing is "drain returns in bounded time" -- not ms-precise.
378
+ assert.ok(
379
+ elapsed < 2000,
380
+ 'drain should return in bounded time; took ' + elapsed + 'ms'
381
+ );
382
+ // drain under timeout pressure is allowed to return empty OR partial OR full; the
383
+ // invariant is "no hang, no throw, queue still valid".
384
+ assert.ok(Array.isArray(drained), 'drained must be an array');
385
+ const rem = readQueue(room);
386
+ assert.strictEqual(rem.version, 1);
387
+ assert.ok(Array.isArray(rem.entries));
388
+ });
389
+
390
+ // -------------------------------------------------------------------------
391
+ // Summary
392
+ // -------------------------------------------------------------------------
393
+ process.stdout.write('\nminto-debouncer: ' + passed + '/' + (passed + failed) + ' passed');
394
+ if (failed > 0) {
395
+ process.stdout.write(', ' + failed + ' failed\n');
396
+ process.exit(1);
397
+ } else {
398
+ process.stdout.write('\n');
399
+ process.exit(0);
400
+ }
401
+ }
402
+
403
+ main().catch((e) => {
404
+ process.stderr.write('minto-debouncer.test.cjs FATAL: ' + (e && e.message || e) + '\n');
405
+ if (e && e.stack) process.stderr.write(e.stack + '\n');
406
+ process.exit(1);
407
+ });
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2026 Mindrian. BSL 1.1.
4
+ *
5
+ * Phase 88-02 -- minto-debouncer concurrency worker
6
+ * ==================================================
7
+ * Forked by lib/memory/minto-debouncer.test.cjs Tests 7 + 8 to get true
8
+ * OS-level concurrency (not fake "parallel" promises in one event loop).
9
+ *
10
+ * Usage:
11
+ * node minto-debouncer.worker.cjs <roomDir> <section> <reason>
12
+ *
13
+ * Exit codes:
14
+ * 0 -- enqueue succeeded (expected happy path for all 5 workers)
15
+ * 1 -- unexpected error (fails the parent test)
16
+ *
17
+ * Standalone file (not inline template string in the test) to match the
18
+ * Phase 87-02 write-lock-atomic.worker.cjs pattern: avoids Windows/Linux
19
+ * path-escape ambiguity in parent test source.
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const path = require('node:path');
25
+
26
+ // Resolve debouncer relative to THIS worker's location.
27
+ // __dirname = lib/memory/, target = scripts/minto-debouncer.cjs
28
+ const debouncer = require(path.resolve(__dirname, '..', '..', 'scripts', 'minto-debouncer.cjs'));
29
+
30
+ const roomDir = process.argv[2];
31
+ const section = process.argv[3];
32
+ const reason = process.argv[4];
33
+
34
+ if (!roomDir || !section || !reason) {
35
+ process.stderr.write('minto-debouncer.worker: missing argv (roomDir, section, reason)\n');
36
+ process.exit(1);
37
+ }
38
+
39
+ try {
40
+ debouncer.enqueue(roomDir, section, reason);
41
+ process.exit(0);
42
+ } catch (e) {
43
+ process.stderr.write('minto-debouncer.worker ERROR: ' + (e && e.message || e) + '\n');
44
+ if (e && e.stack) process.stderr.write(e.stack + '\n');
45
+ process.exit(1);
46
+ }