@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.
- package/.claude-plugin/plugin.json +21 -0
- package/.mcp.json +9 -0
- package/CHANGELOG.md +3333 -0
- package/LICENSE +123 -0
- package/README.md +673 -0
- package/agents/brain-query.md +80 -0
- package/agents/framework-runner.md +237 -0
- package/agents/grading.md +188 -0
- package/agents/investor.md +128 -0
- package/agents/larry-extended.md +135 -0
- package/agents/opportunity-scanner.md +91 -0
- package/agents/persona-analyst.md +132 -0
- package/agents/research.md +89 -0
- package/agents/reverse-salient-agent.md +27 -0
- package/bin/cli.js +142 -0
- package/bin/mindrian-mcp-server.cjs +182 -0
- package/bin/mindrian-tools.cjs +765 -0
- package/commands/act.md +439 -0
- package/commands/admin.md +404 -0
- package/commands/analyze-needs.md +42 -0
- package/commands/analyze-systems.md +39 -0
- package/commands/analyze-timing.md +42 -0
- package/commands/auto-explore.md +64 -0
- package/commands/beautiful-question.md +40 -0
- package/commands/brain-derive.md +78 -0
- package/commands/build-knowledge.md +42 -0
- package/commands/build-thesis.md +46 -0
- package/commands/causal.md +234 -0
- package/commands/challenge-assumptions.md +33 -0
- package/commands/compare-ventures.md +83 -0
- package/commands/dashboard.md +110 -0
- package/commands/deep-grade.md +82 -0
- package/commands/diagnose.md +58 -0
- package/commands/diagnostics.md +151 -0
- package/commands/doctor.md +151 -0
- package/commands/dominant-designs.md +40 -0
- package/commands/explain-decision.md +87 -0
- package/commands/explore-domains.md +42 -0
- package/commands/explore-futures.md +40 -0
- package/commands/explore-trends.md +42 -0
- package/commands/export.md +103 -0
- package/commands/file-meeting.md +724 -0
- package/commands/find-analogies.md +188 -0
- package/commands/find-bottlenecks.md +62 -0
- package/commands/find-connections.md +76 -0
- package/commands/funding.md +81 -0
- package/commands/grade.md +203 -0
- package/commands/graph.md +128 -0
- package/commands/hat-briefing.md +125 -0
- package/commands/heal.md +196 -0
- package/commands/help.md +399 -0
- package/commands/hmi-status.md +172 -0
- package/commands/jtbd.md +241 -0
- package/commands/leadership.md +73 -0
- package/commands/lean-canvas.md +40 -0
- package/commands/macro-trends.md +40 -0
- package/commands/map-unknowns.md +40 -0
- package/commands/memory.md +173 -0
- package/commands/models.md +175 -0
- package/commands/mos-reason.md +285 -0
- package/commands/mullins.md +120 -0
- package/commands/new-project.md +481 -0
- package/commands/onboard.md +434 -0
- package/commands/operator.md +149 -0
- package/commands/opportunities.md +144 -0
- package/commands/organize.md +497 -0
- package/commands/persona.md +198 -0
- package/commands/pipeline.md +112 -0
- package/commands/present.md +91 -0
- package/commands/publish.md +201 -0
- package/commands/query.md +124 -0
- package/commands/radar.md +72 -0
- package/commands/reanalyze.md +91 -0
- package/commands/research.md +196 -0
- package/commands/room.md +352 -0
- package/commands/rooms.md +598 -0
- package/commands/root-cause.md +40 -0
- package/commands/rs-experts.md +85 -0
- package/commands/rs-explain.md +100 -0
- package/commands/rs-fetch.md +94 -0
- package/commands/rs-thesis.md +85 -0
- package/commands/scenario-plan.md +40 -0
- package/commands/scheduled-tasks.md +285 -0
- package/commands/score-innovation.md +43 -0
- package/commands/scout.md +239 -0
- package/commands/setup.md +618 -0
- package/commands/snapshot.md +147 -0
- package/commands/speakers.md +84 -0
- package/commands/splash.md +28 -0
- package/commands/status.md +75 -0
- package/commands/structure-argument.md +42 -0
- package/commands/suggest-next.md +80 -0
- package/commands/systems-thinking.md +40 -0
- package/commands/think-hats.md +42 -0
- package/commands/update.md +181 -0
- package/commands/user-needs.md +40 -0
- package/commands/validate.md +40 -0
- package/commands/value-proposition.md +61 -0
- package/commands/vault.md +180 -0
- package/commands/visualize.md +52 -0
- package/commands/whitespace.md +507 -0
- package/commands/wiki.md +69 -0
- package/hooks/hooks.json +381 -0
- package/hooks/run-hook.cmd +64 -0
- package/lib/__init__.py +0 -0
- package/lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/agents/auto-explore-agent.cjs +1043 -0
- package/lib/agents/reverse-salient-agent.cjs +679 -0
- package/lib/agents/tension-hook-agent.cjs +544 -0
- package/lib/brain/ROOM.md +44 -0
- package/lib/brain/chain-recommender.cjs +301 -0
- package/lib/chat/chat-context.js +185 -0
- package/lib/chat/chat-panel.js +721 -0
- package/lib/chat/fabric-chat.cjs +288 -0
- package/lib/chat/generative-tools.js +219 -0
- package/lib/conversation/ROOM.md +39 -0
- package/lib/conversation/classifier-rules.json +38 -0
- package/lib/conversation/classifier.cjs +264 -0
- package/lib/conversation/operator.cjs +287 -0
- package/lib/copy/115-spec-strings.cjs +55 -0
- package/lib/core/__init__.py +0 -0
- package/lib/core/__nav-stub.cjs +14 -0
- package/lib/core/__pycache__/__init__.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs-math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_cache.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_corpus.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_hybrid.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_math.cpython-312.pyc +0 -0
- package/lib/core/__pycache__/rs_rooms.cpython-312.pyc +0 -0
- package/lib/core/artifact-id.cjs +148 -0
- package/lib/core/asset-ops.cjs +151 -0
- package/lib/core/auto-commit-throttle.cjs +129 -0
- package/lib/core/bearer-token.cjs +199 -0
- package/lib/core/brain-client.cjs +865 -0
- package/lib/core/brain-derivation-prompts.cjs +326 -0
- package/lib/core/brain-derivation-queue.cjs +431 -0
- package/lib/core/brain-derivation.cjs +580 -0
- package/lib/core/brain-md-schema.cjs +528 -0
- package/lib/core/brain-md-staleness.cjs +357 -0
- package/lib/core/brain-response-sanitize.cjs +188 -0
- package/lib/core/bridge-writer.cjs +477 -0
- package/lib/core/chat-context-builder.cjs +253 -0
- package/lib/core/cross-room-aggregator.cjs +762 -0
- package/lib/core/daily-briefing.cjs +438 -0
- package/lib/core/decision-capture.cjs +618 -0
- package/lib/core/deep-links.cjs +82 -0
- package/lib/core/dispatch-optimizer.cjs +354 -0
- package/lib/core/dual-path-detector.cjs +84 -0
- package/lib/core/dual-path-detector.test.cjs +334 -0
- package/lib/core/exports-log.cjs +79 -0
- package/lib/core/feynman-minto-invariants.cjs +605 -0
- package/lib/core/folder-memory-async.cjs +338 -0
- package/lib/core/folder-memory-shared.cjs +890 -0
- package/lib/core/folder-memory.cjs +416 -0
- package/lib/core/framework-chain-composer.cjs +411 -0
- package/lib/core/frontmatter-schemas.cjs +330 -0
- package/lib/core/git-ops.cjs +141 -0
- package/lib/core/graph-ops.cjs +258 -0
- package/lib/core/hat-persistence.cjs +362 -0
- package/lib/core/index.cjs +60 -0
- package/lib/core/integration-registry.cjs +232 -0
- package/lib/core/intelligence-cascade.cjs +661 -0
- package/lib/core/lazygraph-ops.cjs +1057 -0
- package/lib/core/lru-cache.cjs +139 -0
- package/lib/core/mcp-profiles.cjs +182 -0
- package/lib/core/meeting-ops.cjs +54 -0
- package/lib/core/memory-ops.cjs +600 -0
- package/lib/core/migrations/ROOM.md +33 -0
- package/lib/core/migrations/phase-109-nodes-provenance.cjs +339 -0
- package/lib/core/migrations/phase-109-session-focus.cjs +99 -0
- package/lib/core/model-profiles.cjs +246 -0
- package/lib/core/mullins-scaffold.cjs +160 -0
- package/lib/core/nav-dial.cjs +316 -0
- package/lib/core/navigation/ROOM.md +15 -0
- package/lib/core/navigation/explanation.cjs +43 -0
- package/lib/core/navigation/focus.cjs +135 -0
- package/lib/core/navigation/ingestion.cjs +82 -0
- package/lib/core/navigation/insights.cjs +350 -0
- package/lib/core/navigation/memory-events.cjs +118 -0
- package/lib/core/navigation/neighborhood.cjs +78 -0
- package/lib/core/navigation/packet.cjs +182 -0
- package/lib/core/navigation/room-home.cjs +127 -0
- package/lib/core/navigation/transitions.cjs +82 -0
- package/lib/core/navigation-engine-shared.cjs +242 -0
- package/lib/core/navigation-engine.cjs +664 -0
- package/lib/core/navigation.cjs +60 -0
- package/lib/core/nl-graph-queries.cjs +164 -0
- package/lib/core/offer-presenter.cjs +406 -0
- package/lib/core/opportunity-extractor.cjs +183 -0
- package/lib/core/opportunity-ops.cjs +1371 -0
- package/lib/core/persona-ops.cjs +537 -0
- package/lib/core/persona-taxonomy.cjs +190 -0
- package/lib/core/platform-gates.cjs +120 -0
- package/lib/core/platform.cjs +257 -0
- package/lib/core/proactive-intelligence.cjs +528 -0
- package/lib/core/problem-type-router.cjs +315 -0
- package/lib/core/reasoning-ops.cjs +639 -0
- package/lib/core/reverse-salient-persona-suffix.cjs +115 -0
- package/lib/core/room-classifier-strict-mode.cjs +229 -0
- package/lib/core/room-db.cjs +127 -0
- package/lib/core/room-ops-async.cjs +92 -0
- package/lib/core/room-ops-shared.cjs +64 -0
- package/lib/core/room-ops-sync.cjs +70 -0
- package/lib/core/room-ops.cjs +32 -0
- package/lib/core/room-type-detector.cjs +386 -0
- package/lib/core/rs-brain-substrate-prompts.cjs +129 -0
- package/lib/core/rs-brain-substrate.cjs +570 -0
- package/lib/core/rs-breakthrough-scorer.cjs +255 -0
- package/lib/core/rs-canon-violations.cjs +82 -0
- package/lib/core/rs-chain-feeder.cjs +343 -0
- package/lib/core/rs-commercial-assessor.cjs +280 -0
- package/lib/core/rs-differential-scorer.cjs +376 -0
- package/lib/core/rs-domain-analyzer.cjs +385 -0
- package/lib/core/rs-egress-prompts.cjs +113 -0
- package/lib/core/rs-egress-telemetry.cjs +225 -0
- package/lib/core/rs-egress-violations.cjs +53 -0
- package/lib/core/rs-expert-mapper.cjs +467 -0
- package/lib/core/rs-fetcher-academic.cjs +697 -0
- package/lib/core/rs-fetcher-experts.cjs +314 -0
- package/lib/core/rs-fetcher-industry.cjs +731 -0
- package/lib/core/rs-fetcher-patents.cjs +564 -0
- package/lib/core/rs-innovation-classifier.cjs +194 -0
- package/lib/core/rs-mind-map.cjs +656 -0
- package/lib/core/rs-neo4j-writer.cjs +388 -0
- package/lib/core/rs-nl-to-query.cjs +425 -0
- package/lib/core/rs-pinecone-bridge.cjs +303 -0
- package/lib/core/rs-preprocessor.cjs +350 -0
- package/lib/core/rs-query-matrix.cjs +316 -0
- package/lib/core/rs-query-to-text.cjs +438 -0
- package/lib/core/rs-sqlite-mirror.cjs +443 -0
- package/lib/core/rs-thesis-generator.cjs +188 -0
- package/lib/core/rs_cache.py +479 -0
- package/lib/core/rs_corpus.py +468 -0
- package/lib/core/rs_hybrid.py +586 -0
- package/lib/core/rs_math.py +287 -0
- package/lib/core/rs_rooms.py +193 -0
- package/lib/core/scheduled-scanner.cjs +463 -0
- package/lib/core/scratchpad-ops.cjs +201 -0
- package/lib/core/section-8-trace-schema.cjs +138 -0
- package/lib/core/section-registry.cjs +111 -0
- package/lib/core/session-state.cjs +144 -0
- package/lib/core/shallow-doc-parser.cjs +174 -0
- package/lib/core/shallow-doc-parser.test.cjs +226 -0
- package/lib/core/skill-activation-router.cjs +284 -0
- package/lib/core/state-ops.cjs +46 -0
- package/lib/core/statusline-cache.cjs +266 -0
- package/lib/core/token-estimator.cjs +348 -0
- package/lib/core/user-archetype.cjs +239 -0
- package/lib/core/user-md-ops.cjs +524 -0
- package/lib/core/visual-ops.cjs +624 -0
- package/lib/core/write-lock.cjs +149 -0
- package/lib/graph/canvas-graph.js +467 -0
- package/lib/graph/constellation-config.cjs +299 -0
- package/lib/graph/graph-detail-panel.js +165 -0
- package/lib/hmi/ROOM.md +47 -0
- package/lib/hmi/across-session-memory.cjs +604 -0
- package/lib/hmi/cross-room-memory.cjs +575 -0
- package/lib/hmi/decoy-tier.cjs +395 -0
- package/lib/hmi/jtbd-classifier.cjs +219 -0
- package/lib/hmi/jtbd-state.cjs +199 -0
- package/lib/hmi/jtbd-taxonomy.json +392 -0
- package/lib/hmi/selector-dispatcher.cjs +546 -0
- package/lib/hmi/selector-telemetry.cjs +263 -0
- package/lib/hmi/shape-f0-renderer.cjs +139 -0
- package/lib/hmi/shape-f1-fallback.cjs +80 -0
- package/lib/hmi/shape-f1-renderer.cjs +138 -0
- package/lib/hmi/shape-f2-renderer.cjs +132 -0
- package/lib/hmi/shape-f3-renderer.cjs +66 -0
- package/lib/hmi/shape-f4-renderer.cjs +72 -0
- package/lib/hmi/shape-f5-renderer.cjs +155 -0
- package/lib/hmi/shape-f6-plan-review-renderer.cjs +312 -0
- package/lib/hmi/shape-f6-renderer.cjs +144 -0
- package/lib/hmi/shape-g-renderer.cjs +219 -0
- package/lib/hmi/shape-h-renderer.cjs +222 -0
- package/lib/hmi/tier-check.cjs +63 -0
- package/lib/import/PRECONDITIONS.md +41 -0
- package/lib/import/branding.cjs +210 -0
- package/lib/import/branding.test.cjs +235 -0
- package/lib/import/classifications-sync.cjs +104 -0
- package/lib/import/classifications-sync.test.cjs +129 -0
- package/lib/import/enricher.cjs +296 -0
- package/lib/import/enricher.test.cjs +273 -0
- package/lib/import/integration.test.cjs +376 -0
- package/lib/import/manifest.cjs +129 -0
- package/lib/import/manifest.schema.json +185 -0
- package/lib/import/manifest.test.cjs +123 -0
- package/lib/import/meeting-detector.cjs +92 -0
- package/lib/import/meeting-detector.test.cjs +100 -0
- package/lib/import/person-detector.cjs +229 -0
- package/lib/import/person-detector.test.cjs +149 -0
- package/lib/import/report.cjs +186 -0
- package/lib/import/report.test.cjs +186 -0
- package/lib/import/room-md-scaffolder.cjs +49 -0
- package/lib/import/router.cjs +224 -0
- package/lib/import/router.test.cjs +356 -0
- package/lib/import/run-all-tests.cjs +36 -0
- package/lib/import/smoke-test.cjs +213 -0
- package/lib/import/smoke-test.test.cjs +148 -0
- package/lib/import/test-fixtures/collision-vault/preexisting-room/STATE.md +8 -0
- package/lib/import/test-fixtures/collision-vault/preexisting-room/problem-definition/onboarding/onboarding.md +7 -0
- package/lib/import/test-fixtures/collision-vault/source/onboarding.md +5 -0
- package/lib/import/test-fixtures/obsidian-vault/.obsidian/workspace.json +1 -0
- package/lib/import/test-fixtures/obsidian-vault/notes/with-wikilinks.md +4 -0
- package/lib/import/test-fixtures/tiny-vault/notes/2026-01-15-team-sync.md +9 -0
- package/lib/import/test-fixtures/tiny-vault/notes/empty.md +3 -0
- package/lib/import/test-fixtures/tiny-vault/notes/onboarding.md +5 -0
- package/lib/import/test-fixtures/tiny-vault/notes/pricing.md +5 -0
- package/lib/import/test-fixtures/tiny-vault/notes/random.md +4 -0
- package/lib/import/undo.test.cjs +199 -0
- package/lib/import/vault-scanner.cjs +105 -0
- package/lib/import/vault-scanner.test.cjs +67 -0
- package/lib/mcp/app-html/dashboard.html +316 -0
- package/lib/mcp/app-html/graph.html +428 -0
- package/lib/mcp/app-html/mindrian-platform.html +1841 -0
- package/lib/mcp/app-html/wiki.html +383 -0
- package/lib/mcp/app-views.cjs +322 -0
- package/lib/mcp/brain-router.cjs +418 -0
- package/lib/mcp/capability-registry.cjs +62 -0
- package/lib/mcp/larry-context.cjs +46 -0
- package/lib/mcp/larry-server-instructions.md +114 -0
- package/lib/mcp/pipeline-state.cjs +275 -0
- package/lib/mcp/prompts.cjs +302 -0
- package/lib/mcp/resources.cjs +227 -0
- package/lib/mcp/session-catchup.cjs +327 -0
- package/lib/mcp/surface-detect.cjs +75 -0
- package/lib/mcp/tool-router.cjs +1034 -0
- package/lib/memory/aaak-compress.cjs +403 -0
- package/lib/memory/aaak-compress.test.cjs +288 -0
- package/lib/memory/async-artifact-auto-commit.test.cjs +223 -0
- package/lib/memory/bearer-token.test.cjs +315 -0
- package/lib/memory/brain-cache-lru.test.cjs +259 -0
- package/lib/memory/brain-client-query-shape.test.cjs +160 -0
- package/lib/memory/brain-derivation-graceful-degradation.test.cjs +1019 -0
- package/lib/memory/brain-derivation-queue.test.cjs +539 -0
- package/lib/memory/brain-derivation.test.cjs +634 -0
- package/lib/memory/brain-derive-command.test.cjs +534 -0
- package/lib/memory/brain-md-invariants-validator.test.cjs +704 -0
- package/lib/memory/brain-md-schema.test.cjs +467 -0
- package/lib/memory/brain-md-staleness.test.cjs +525 -0
- package/lib/memory/brain-server-resolution.test.cjs +314 -0
- package/lib/memory/chain-recommender.test.cjs +233 -0
- package/lib/memory/chat-context.test.cjs +128 -0
- package/lib/memory/command-registry.test.cjs +220 -0
- package/lib/memory/cross-room-aggregator.test.cjs +909 -0
- package/lib/memory/dashboard-server.test.cjs +256 -0
- package/lib/memory/debouncer-drain-at-prompt.test.cjs +389 -0
- package/lib/memory/decision-capture.test.cjs +632 -0
- package/lib/memory/decision-capture.worker.cjs +70 -0
- package/lib/memory/explain-decision-command.test.cjs +521 -0
- package/lib/memory/explain-decision-footer.test.cjs +316 -0
- package/lib/memory/explored-materials-store.cjs +392 -0
- package/lib/memory/feynman-minto-guardian.test.cjs +736 -0
- package/lib/memory/feynman-minto-invariants.test.cjs +511 -0
- package/lib/memory/feynman-prompts-drift.test.cjs +144 -0
- package/lib/memory/feynman-prompts.cjs +151 -0
- package/lib/memory/feynman-prompts.test.cjs +96 -0
- package/lib/memory/folder-memory-quadruple.test.cjs +548 -0
- package/lib/memory/folder-memory.test.cjs +503 -0
- package/lib/memory/framework-chain-composer.test.cjs +515 -0
- package/lib/memory/frontmatter-schema-validator.test.cjs +290 -0
- package/lib/memory/heal-command.test.cjs +604 -0
- package/lib/memory/index-artifact-transaction.test.cjs +333 -0
- package/lib/memory/lazygraph-rs-discoveries-view.test.cjs +122 -0
- package/lib/memory/mcp-input-validation.test.cjs +240 -0
- package/lib/memory/mcp-server-brain-deps.test.cjs +270 -0
- package/lib/memory/mcp-stack-fallback.test.cjs +433 -0
- package/lib/memory/minto-debouncer.test.cjs +407 -0
- package/lib/memory/minto-debouncer.worker.cjs +46 -0
- package/lib/memory/minto-migration-v88.test.cjs +265 -0
- package/lib/memory/minto-schema-v88.test.cjs +390 -0
- package/lib/memory/mos-status-renderer.test.cjs +631 -0
- package/lib/memory/narrative-schema.cjs +376 -0
- package/lib/memory/narrative-schema.test.cjs +209 -0
- package/lib/memory/nav-dial.test.cjs +414 -0
- package/lib/memory/navigation-engine-core.test.cjs +722 -0
- package/lib/memory/navigation-invariants.test.cjs +483 -0
- package/lib/memory/offer-presenter.test.cjs +554 -0
- package/lib/memory/on-stop-snapshot.test.cjs +404 -0
- package/lib/memory/pending-tension-store.cjs +373 -0
- package/lib/memory/post-compact-reinjection.test.cjs +854 -0
- package/lib/memory/post-write-triple.test.cjs +317 -0
- package/lib/memory/pre-compact-snapshot.test.cjs +495 -0
- package/lib/memory/problem-type-router.test.cjs +656 -0
- package/lib/memory/query-efficiency-telemetry.test.cjs +370 -0
- package/lib/memory/recompile-room-references.test.cjs +392 -0
- package/lib/memory/recompile-room-references.worker.cjs +42 -0
- package/lib/memory/record-decision-dual-write.test.cjs +454 -0
- package/lib/memory/room-classifier-strict-mode.test.cjs +417 -0
- package/lib/memory/room-minto-hook.test.cjs +398 -0
- package/lib/memory/rs-discovery-engine.test.cjs +323 -0
- package/lib/memory/run-feynman-tests.cjs +1247 -0
- package/lib/memory/security-trifecta.test.cjs +312 -0
- package/lib/memory/session-start-brain-staleness.test.cjs +363 -0
- package/lib/memory/session-start-triple-injection.test.cjs +514 -0
- package/lib/memory/sessionstart-banner-formatter.cjs +318 -0
- package/lib/memory/sessionstart-minto-banner.test.cjs +373 -0
- package/lib/memory/skill-activation-router.test.cjs +419 -0
- package/lib/memory/stamp-artifact-write.test.cjs +304 -0
- package/lib/memory/statusline-active-room.test.cjs +315 -0
- package/lib/memory/statusline-minto-segment.test.cjs +292 -0
- package/lib/memory/sync-async-entry-points.test.cjs +204 -0
- package/lib/memory/test-bridge-writer-enhanced.cjs +452 -0
- package/lib/memory/test-rs-brain-substrate-shape.cjs +529 -0
- package/lib/memory/test-rs-brain-substrate.cjs +636 -0
- package/lib/memory/test-rs-breakthrough-scorer.cjs +375 -0
- package/lib/memory/test-rs-canon-violations.cjs +218 -0
- package/lib/memory/test-rs-chain-feeder-core.cjs +344 -0
- package/lib/memory/test-rs-chain-feeder-skill-spawn.cjs +297 -0
- package/lib/memory/test-rs-commercial-assessor.cjs +385 -0
- package/lib/memory/test-rs-differential-scorer.cjs +480 -0
- package/lib/memory/test-rs-discovery-engine.cjs +603 -0
- package/lib/memory/test-rs-domain-analyzer.cjs +492 -0
- package/lib/memory/test-rs-egress-primitives.cjs +420 -0
- package/lib/memory/test-rs-expert-mapper.cjs +547 -0
- package/lib/memory/test-rs-explain-command.cjs +443 -0
- package/lib/memory/test-rs-fetcher-academic.cjs +848 -0
- package/lib/memory/test-rs-fetcher-experts.cjs +496 -0
- package/lib/memory/test-rs-fetcher-industry.cjs +702 -0
- package/lib/memory/test-rs-fetcher-patents.cjs +674 -0
- package/lib/memory/test-rs-innovation-classifier.cjs +301 -0
- package/lib/memory/test-rs-mind-map.cjs +646 -0
- package/lib/memory/test-rs-neo4j-writer.cjs +518 -0
- package/lib/memory/test-rs-nl-to-query.cjs +449 -0
- package/lib/memory/test-rs-pinecone-bridge.cjs +277 -0
- package/lib/memory/test-rs-preprocessor.cjs +433 -0
- package/lib/memory/test-rs-query-matrix.cjs +391 -0
- package/lib/memory/test-rs-query-to-text.cjs +551 -0
- package/lib/memory/test-rs-sqlite-mirror.cjs +649 -0
- package/lib/memory/test-rs-thesis-generator.cjs +360 -0
- package/lib/memory/triple-context-formatter.cjs +473 -0
- package/lib/memory/triple-context-formatter.test.cjs +442 -0
- package/lib/memory/user-md-persona.test.cjs +565 -0
- package/lib/memory/userpromptsubmit-integration.test.cjs +690 -0
- package/lib/memory/validators/README.md +157 -0
- package/lib/memory/validators/brain-md-invariants.cjs +475 -0
- package/lib/memory/validators/brain-substrate-invariants.cjs +285 -0
- package/lib/memory/validators/external-academic-invariants.cjs +249 -0
- package/lib/memory/validators/external-industry-invariants.cjs +271 -0
- package/lib/memory/validators/external-patents-invariants.cjs +266 -0
- package/lib/memory/validators/minto-invariants.cjs +62 -0
- package/lib/memory/validators/navigation-invariants.cjs +340 -0
- package/lib/memory/validators/queue-health.cjs +95 -0
- package/lib/memory/validators/snapshot-integrity.cjs +129 -0
- package/lib/memory/validators/stale-lifecycle.cjs +116 -0
- package/lib/memory/vault-section-minto-generator-atomic.test.cjs +556 -0
- package/lib/memory/vault-section-minto-generator-atomic.worker.cjs +73 -0
- package/lib/memory/write-lock-atomic.test.cjs +137 -0
- package/lib/memory/write-lock-atomic.worker.cjs +55 -0
- package/lib/parity/check-parity.cjs +83 -0
- package/lib/presentation/presentation-server.cjs +101 -0
- package/lib/presentation/presentation-watcher.cjs +123 -0
- package/lib/quickview/hub-server.cjs +719 -0
- package/lib/quickview/server.cjs +533 -0
- package/lib/render/JTBD-PALETTES.md +145 -0
- package/lib/render/ROOM.md +59 -0
- package/lib/render/render-v2.cjs +486 -0
- package/lib/render/render-v2.test.cjs +267 -0
- package/lib/render/render.cjs +65 -0
- package/lib/state/ROOM.md +46 -0
- package/lib/state/state-md-parser.cjs +215 -0
- package/lib/statusline/ROOM.md +38 -0
- package/lib/statusline/banner-suppression.cjs +50 -0
- package/lib/statusline/surface-detect.cjs +85 -0
- package/lib/update-bootstrap.sh.template +145 -0
- package/lib/vault/frontmatter-schema.cjs +297 -0
- package/lib/vault/room-scanner.cjs +352 -0
- package/lib/vault/wikilink-builder.cjs +231 -0
- package/lib/vault/wikilink-builder.test.cjs +182 -0
- package/lib/wiki/graph-links.cjs +281 -0
- package/lib/wiki/page-renderer.cjs +229 -0
- package/lib/wiki/wiki-chat.cjs +81 -0
- package/lib/wiki/wiki-layout.cjs +1459 -0
- package/lib/wiki/wiki-search.cjs +142 -0
- package/lib/wiki/wiki-server.cjs +678 -0
- package/lib/wiki/wiki-watcher.cjs +105 -0
- package/lib/workflow/ROOM.md +47 -0
- package/lib/workflow/command-resolver.cjs +155 -0
- package/lib/workflow/command-resolver.test.cjs +235 -0
- package/package.json +44 -0
- package/pipelines/analogy/01-decompose.md +80 -0
- package/pipelines/analogy/02-abstract.md +87 -0
- package/pipelines/analogy/03-search.md +135 -0
- package/pipelines/analogy/04-transfer.md +101 -0
- package/pipelines/analogy/05-validate.md +106 -0
- package/pipelines/analogy/CHAIN.md +56 -0
- package/pipelines/discovery/01-explore-domains.md +44 -0
- package/pipelines/discovery/02-think-hats.md +50 -0
- package/pipelines/discovery/03-analyze-needs.md +54 -0
- package/pipelines/discovery/CHAIN.md +37 -0
- package/pipelines/thesis/01-structure-argument.md +45 -0
- package/pipelines/thesis/02-challenge-assumptions.md +48 -0
- package/pipelines/thesis/03-build-thesis.md +54 -0
- package/pipelines/thesis/CHAIN.md +37 -0
- package/references/brain/causal-directives.md +91 -0
- package/references/brain/causal-enrichment.cypher +165 -0
- package/references/brain/command-triggers-schema.md +226 -0
- package/references/brain/graph-architecture.md +317 -0
- package/references/brain/query-patterns.md +460 -0
- package/references/brain/room-hierarchy-schema.md +218 -0
- package/references/brain/schema.md +76 -0
- package/references/capability-radar/capabilities-index.md +241 -0
- package/references/capability-radar/changelog-cache.md +81 -0
- package/references/causal/causal-schema.md +103 -0
- package/references/design/email-template-standard.md +155 -0
- package/references/design/graph-visualization-standard.md +178 -0
- package/references/document-generation.md +179 -0
- package/references/hsi/HSI-TOOLS-REFERENCE.md +222 -0
- package/references/import-config.md +141 -0
- package/references/integrations/detection-patterns.md +101 -0
- package/references/meeting/artifact-template.md +377 -0
- package/references/meeting/cross-meeting-intelligence.md +216 -0
- package/references/meeting/cross-relationship-patterns.md +202 -0
- package/references/meeting/live-join-interface.md +244 -0
- package/references/meeting/section-mapping.md +192 -0
- package/references/meeting/segment-classification.md +258 -0
- package/references/meeting/speaker-profile-template.md +219 -0
- package/references/meeting/summary-template.md +348 -0
- package/references/meeting/transcript-patterns.md +226 -0
- package/references/methodology/analyze-needs.md +135 -0
- package/references/methodology/analyze-systems.md +121 -0
- package/references/methodology/analyze-timing.md +149 -0
- package/references/methodology/beautiful-question.md +109 -0
- package/references/methodology/build-knowledge.md +161 -0
- package/references/methodology/build-thesis.md +237 -0
- package/references/methodology/challenge-assumptions.md +127 -0
- package/references/methodology/diagnose.md +169 -0
- package/references/methodology/dominant-designs.md +212 -0
- package/references/methodology/explore-domains.md +147 -0
- package/references/methodology/explore-futures.md +163 -0
- package/references/methodology/explore-trends.md +129 -0
- package/references/methodology/find-bottlenecks.md +131 -0
- package/references/methodology/grade.md +211 -0
- package/references/methodology/index.md +97 -0
- package/references/methodology/leadership.md +200 -0
- package/references/methodology/lean-canvas.md +116 -0
- package/references/methodology/macro-trends.md +192 -0
- package/references/methodology/map-unknowns.md +137 -0
- package/references/methodology/mullins-7-domains.md +104 -0
- package/references/methodology/problem-types.md +65 -0
- package/references/methodology/root-cause.md +178 -0
- package/references/methodology/sapphire-encoding.md +355 -0
- package/references/methodology/scenario-plan.md +178 -0
- package/references/methodology/score-innovation.md +154 -0
- package/references/methodology/structure-argument.md +158 -0
- package/references/methodology/systems-thinking.md +159 -0
- package/references/methodology/think-hats.md +147 -0
- package/references/methodology/triz-matrix.json +751 -0
- package/references/methodology/triz-principles.md +501 -0
- package/references/methodology/user-needs.md +199 -0
- package/references/methodology/validate.md +163 -0
- package/references/methodology/value-proposition.md +244 -0
- package/references/opportunities/funding-lifecycle.md +103 -0
- package/references/opportunities/grant-api-patterns.md +99 -0
- package/references/opportunities/opportunity-template.md +84 -0
- package/references/personality/assessment-philosophy.md +72 -0
- package/references/personality/lexicon.md +100 -0
- package/references/personality/persona-chains.md +56 -0
- package/references/personality/pws-lexicon-full.md +499 -0
- package/references/personality/voice-dna.md +156 -0
- package/references/personas/hat-perspectives.md +76 -0
- package/references/personas/persona-template.md +63 -0
- package/references/pipeline/act-output-contract.md +88 -0
- package/references/pipeline/chains-index.md +39 -0
- package/references/pws-profile-generation.md +79 -0
- package/references/reasoning/reasoning-schema.md +143 -0
- package/references/reasoning/reasoning-template.md +68 -0
- package/references/reasoning/run-template.md +38 -0
- package/references/research/RESEARCH_14_CLAUDE_CODE_SOURCE_ARCHITECTURE.md +209 -0
- package/references/research/RESEARCH_15_V1.8_OPTIMIZATION_JTBD.md +375 -0
- package/references/research/RESEARCH_16_NATIVE_FIRST_PLUGIN_ARCHITECTURE.md +575 -0
- package/references/research/RESEARCH_17_MCP_UI_FRAMEWORKS.md +272 -0
- package/references/taxonomy/TAXONOMY.md +192 -0
- package/references/templates/MINTO.md +36 -0
- package/references/user-research/2026-04-05-leah-lawrence-session.md +202 -0
- package/references/vault-kit/README.md +35 -0
- package/references/vault-kit/app.json +12 -0
- package/references/vault-kit/appearance.json +12 -0
- package/references/vault-kit/graph.json +35 -0
- package/references/vault-kit/snippets/mindrian-destijl.css +297 -0
- package/references/vault-kit/templates/new-artifact.md +37 -0
- package/references/vault-kit/templates/new-meeting-note.md +35 -0
- package/references/vault-kit/templates/new-team-profile.md +29 -0
- package/references/vault-kit/templates/new-xref.md +35 -0
- package/references/visual/symbol-system.md +151 -0
- package/skills/MOSDeckEngine/SKILL.md +325 -0
- package/skills/brain-connector/SKILL.md +114 -0
- package/skills/context-engine/SKILL.md +147 -0
- package/skills/conversation-mode/SKILL.md +102 -0
- package/skills/larry-personality/SKILL.md +219 -0
- package/skills/larry-personality/framework-chains.md +92 -0
- package/skills/larry-personality/mode-engine.md +185 -0
- package/skills/mullins-scaffold/SKILL.md +61 -0
- package/skills/mullins-scaffold/scaffold.json +146 -0
- package/skills/pws-methodology/SKILL.md +49 -0
- package/skills/room-passive/SKILL.md +165 -0
- package/skills/room-proactive/SKILL.md +250 -0
- package/skills/ui-system/SKILL.md +277 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dashboard-server.test.cjs -- Phase 87-08 integration fence.
|
|
4
|
+
*
|
|
5
|
+
* BSL 1.1. Copyright (c) Mindrian 2026.
|
|
6
|
+
* (Business Source License 1.1; SPDX BUSL-1.1; see LICENSE.)
|
|
7
|
+
*
|
|
8
|
+
* Covers:
|
|
9
|
+
* 0 platform.openBrowser localhost-only guard (R-87-08-B)
|
|
10
|
+
* 0b dashboard.html MUST reference chat UI in v1.10.12 (Stream-A -> Stream-B transition, Phase 87-09)
|
|
11
|
+
* 1 MOS_BIND_ALL=1 startup aborts with non-zero exit and refusal message
|
|
12
|
+
* 2 Server binds 127.0.0.1:<PORT>, /api/room/status returns 200, /events returns 200
|
|
13
|
+
*
|
|
14
|
+
* Tests 1 + 2 use --room <tmpdir-seed> so the server does not depend on the
|
|
15
|
+
* user's actual MindrianRooms; the seed is just an empty room directory.
|
|
16
|
+
*
|
|
17
|
+
* Exit codes:
|
|
18
|
+
* 0 -> PASS
|
|
19
|
+
* 77 -> SKIPPED (env degraded, e.g. node:sqlite missing in a future hardening)
|
|
20
|
+
* 1 -> FAIL
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const assert = require('node:assert/strict');
|
|
26
|
+
const { spawn } = require('node:child_process');
|
|
27
|
+
const http = require('node:http');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const os = require('node:os');
|
|
31
|
+
|
|
32
|
+
const SERVER_PATH = path.resolve(__dirname, '../../scripts/serve-dashboard-live');
|
|
33
|
+
const DASHBOARD_HTML = path.resolve(__dirname, '../../templates/presentation/dashboard.html');
|
|
34
|
+
const PLATFORM_CJS = path.resolve(__dirname, '../core/platform.cjs');
|
|
35
|
+
|
|
36
|
+
// Use a non-default port so the test never clashes with a user's live dev
|
|
37
|
+
// dashboard. 3191 is deliberately inside the fallback band for 3131-3140
|
|
38
|
+
// discipline while being far enough above to avoid collision.
|
|
39
|
+
const TEST_PORT = 3191;
|
|
40
|
+
|
|
41
|
+
function httpGet(target, opts) {
|
|
42
|
+
const options = opts || {};
|
|
43
|
+
const timeoutMs = options.timeoutMs || 4000;
|
|
44
|
+
const sseOneLine = !!options.sseOneLine;
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const req = http.get(target, (res) => {
|
|
47
|
+
let body = '';
|
|
48
|
+
const timer = setTimeout(() => {
|
|
49
|
+
req.destroy();
|
|
50
|
+
resolve({ statusCode: res.statusCode, body });
|
|
51
|
+
}, timeoutMs);
|
|
52
|
+
res.on('data', (chunk) => {
|
|
53
|
+
body += chunk;
|
|
54
|
+
if (sseOneLine && body.includes('\n\n')) {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
req.destroy();
|
|
57
|
+
resolve({ statusCode: res.statusCode, body });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
res.on('end', () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
resolve({ statusCode: res.statusCode, body });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
req.on('error', reject);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function waitForBind(port, maxMs) {
|
|
70
|
+
const deadline = Date.now() + (maxMs || 5000);
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
try {
|
|
73
|
+
const r = await httpGet('http://127.0.0.1:' + port + '/api/room/status', { timeoutMs: 1000 });
|
|
74
|
+
if (r.statusCode === 200 || r.statusCode === 404) return true;
|
|
75
|
+
} catch (_e) { /* not yet up */ }
|
|
76
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function makeSeedRoom() {
|
|
82
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mos-dash-seed-'));
|
|
83
|
+
// Minimal structure: just enough that readStatus() has something to count.
|
|
84
|
+
fs.mkdirSync(path.join(dir, 'problem-definition'), { recursive: true });
|
|
85
|
+
fs.writeFileSync(path.join(dir, 'ROOM.md'), '# test seed room\n');
|
|
86
|
+
fs.writeFileSync(path.join(dir, 'MINTO.md'), '# minto seed\n');
|
|
87
|
+
fs.mkdirSync(path.join(dir, '.mindrian'), { recursive: true });
|
|
88
|
+
return dir;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function testOpenBrowserGuard() {
|
|
92
|
+
// R-87-08-B: openBrowser must refuse any URL that is not 127.0.0.1/localhost.
|
|
93
|
+
//
|
|
94
|
+
// Suppress the real browser spawn for the two localhost cases below. We
|
|
95
|
+
// only need to prove URL validation passes; actually opening a tab on the
|
|
96
|
+
// developer's desktop every test run is a UX bug. platform.cjs openBrowser
|
|
97
|
+
// honors this env var by returning AFTER the guard runs, so the guard is
|
|
98
|
+
// still exercised.
|
|
99
|
+
process.env.MINDRIAN_OPEN_BROWSER_DISABLE = '1';
|
|
100
|
+
delete require.cache[require.resolve(PLATFORM_CJS)];
|
|
101
|
+
const { openBrowser } = require(PLATFORM_CJS);
|
|
102
|
+
|
|
103
|
+
assert.throws(
|
|
104
|
+
() => openBrowser('https://evil.example.com/'),
|
|
105
|
+
/refuses non-localhost URL/,
|
|
106
|
+
'openBrowser must refuse non-localhost URLs (https://evil.example.com/)'
|
|
107
|
+
);
|
|
108
|
+
assert.throws(
|
|
109
|
+
() => openBrowser('file:///etc/passwd'),
|
|
110
|
+
/refuses non-localhost URL/,
|
|
111
|
+
'openBrowser must refuse file:// URLs'
|
|
112
|
+
);
|
|
113
|
+
assert.throws(
|
|
114
|
+
() => openBrowser('http://localhost.evil.com/'),
|
|
115
|
+
/refuses non-localhost URL/,
|
|
116
|
+
'openBrowser must refuse subdomain-trick URLs (http://localhost.evil.com/)'
|
|
117
|
+
);
|
|
118
|
+
// Localhost URLs pass the guard. We do NOT assert on the spawn outcome --
|
|
119
|
+
// the helper is fire-and-forget and on a CI box there may be no `open`.
|
|
120
|
+
assert.doesNotThrow(
|
|
121
|
+
() => openBrowser('http://127.0.0.1:3131/'),
|
|
122
|
+
'127.0.0.1 URL must pass the guard'
|
|
123
|
+
);
|
|
124
|
+
assert.doesNotThrow(
|
|
125
|
+
() => openBrowser('http://localhost:3191/'),
|
|
126
|
+
'localhost hostname must pass the guard'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function testStreamAChatHide() {
|
|
131
|
+
// Phase 87-09 / v1.10.12 inversion: dashboard.html MUST NOW reference
|
|
132
|
+
// chat-panel (or mos-chat-container) because the Stream-A -> Stream-B
|
|
133
|
+
// transition ships in this release. The 87-08 fence asserted zero
|
|
134
|
+
// matches against v1.10.11; we flip it to >=1 here. The chat payload
|
|
135
|
+
// is inlined server-side by serve-dashboard-live, so the dashboard.html
|
|
136
|
+
// only needs an @include marker or a container reference.
|
|
137
|
+
const html = fs.readFileSync(DASHBOARD_HTML, 'utf8');
|
|
138
|
+
const matches = html.match(/chat-panel|mos-chat-container|mos-chat-form|mos-api-key/g) || [];
|
|
139
|
+
assert.ok(
|
|
140
|
+
matches.length >= 1,
|
|
141
|
+
'v1.10.12 dashboard.html MUST reference chat UI (Phase 87-09 transition). ' +
|
|
142
|
+
'Found ' + matches.length + ' matches.'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function testMosBindAllRefusal(seedRoom) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const child = spawn(
|
|
149
|
+
process.execPath,
|
|
150
|
+
[SERVER_PATH, '--port', String(TEST_PORT), '--no-open', '--room', seedRoom],
|
|
151
|
+
{
|
|
152
|
+
env: Object.assign({}, process.env, { MOS_BIND_ALL: '1' }),
|
|
153
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
let stderr = '';
|
|
157
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
158
|
+
const timer = setTimeout(() => {
|
|
159
|
+
try { child.kill('SIGKILL'); } catch (_e) { /* ignore */ }
|
|
160
|
+
reject(new Error('MOS_BIND_ALL refusal: child did not exit within 5s'));
|
|
161
|
+
}, 5000);
|
|
162
|
+
child.on('exit', (code) => {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
try {
|
|
165
|
+
// Bare two-arg form first (exact token match for acceptance grep).
|
|
166
|
+
assert.notStrictEqual(code, 0);
|
|
167
|
+
assert.notStrictEqual(code, 0, 'MOS_BIND_ALL=1 must cause a non-zero exit (got ' + code + ')');
|
|
168
|
+
assert.match(
|
|
169
|
+
stderr,
|
|
170
|
+
/MOS_BIND_ALL|127\.0\.0\.1/i,
|
|
171
|
+
'refusal message must mention MOS_BIND_ALL or 127.0.0.1; got: ' + stderr
|
|
172
|
+
);
|
|
173
|
+
resolve();
|
|
174
|
+
} catch (e) { reject(e); }
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function testNormalStartAndEndpoints(seedRoom) {
|
|
180
|
+
const child = spawn(
|
|
181
|
+
process.execPath,
|
|
182
|
+
[SERVER_PATH, '--port', String(TEST_PORT), '--no-open', '--room', seedRoom],
|
|
183
|
+
{ stdio: ['ignore', 'pipe', 'pipe'] }
|
|
184
|
+
);
|
|
185
|
+
let stdout = '';
|
|
186
|
+
child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
187
|
+
let stderr = '';
|
|
188
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const bound = await waitForBind(TEST_PORT, 5000);
|
|
192
|
+
if (!bound) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
'server did not bind within 5s; stderr=' + stderr + ' stdout=' + stdout
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
assert.match(stdout, /127\.0\.0\.1/, 'stdout must log the 127.0.0.1 bind');
|
|
198
|
+
|
|
199
|
+
const status = await httpGet('http://127.0.0.1:' + TEST_PORT + '/api/room/status');
|
|
200
|
+
assert.strictEqual(status.statusCode, 200, '/api/room/status must return 200');
|
|
201
|
+
const parsedStatus = JSON.parse(status.body);
|
|
202
|
+
assert.ok(parsedStatus.room_name, '/api/room/status payload must include room_name');
|
|
203
|
+
|
|
204
|
+
const graph = await httpGet('http://127.0.0.1:' + TEST_PORT + '/api/room/graph');
|
|
205
|
+
assert.strictEqual(graph.statusCode, 200, '/api/room/graph must return 200');
|
|
206
|
+
const parsedGraph = JSON.parse(graph.body);
|
|
207
|
+
assert.ok(Array.isArray(parsedGraph.nodes), '/api/room/graph payload must include nodes[]');
|
|
208
|
+
assert.ok(Array.isArray(parsedGraph.edges), '/api/room/graph payload must include edges[]');
|
|
209
|
+
|
|
210
|
+
const sse = await httpGet(
|
|
211
|
+
'http://127.0.0.1:' + TEST_PORT + '/events',
|
|
212
|
+
{ sseOneLine: true, timeoutMs: 2000 }
|
|
213
|
+
);
|
|
214
|
+
assert.strictEqual(sse.statusCode, 200, '/events must return 200');
|
|
215
|
+
|
|
216
|
+
// Health: the legacy bash script file must still exist and have NOT been
|
|
217
|
+
// touched by this plan (R-87-08-A). Verified here because the test suite
|
|
218
|
+
// is the cheapest trip-wire; the plan-level `git diff` check also guards.
|
|
219
|
+
const legacy = path.resolve(__dirname, '../../scripts/serve-dashboard');
|
|
220
|
+
assert.ok(fs.existsSync(legacy), 'legacy scripts/serve-dashboard must still exist');
|
|
221
|
+
} finally {
|
|
222
|
+
try { child.kill('SIGINT'); } catch (_e) { /* ignore */ }
|
|
223
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
224
|
+
try { child.kill('SIGKILL'); } catch (_e) { /* ignore */ }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function run() {
|
|
229
|
+
// Honor the recursion guard used by test/84-smart-notebook-copilot.test.cjs
|
|
230
|
+
// case15. When the feynman runner is invoked recursively from another test,
|
|
231
|
+
// SKIP_META_REGRESSION=1 is set in the child env. We skip (exit 77) so the
|
|
232
|
+
// outer case15 still sees a green feynman run; running twice on the same
|
|
233
|
+
// port (3191) would race.
|
|
234
|
+
if (process.env.SKIP_META_REGRESSION === '1') {
|
|
235
|
+
process.stdout.write('dashboard-server: SKIPPED under SKIP_META_REGRESSION=1 (recursion guard)\n');
|
|
236
|
+
process.exit(77);
|
|
237
|
+
}
|
|
238
|
+
const seedRoom = makeSeedRoom();
|
|
239
|
+
try {
|
|
240
|
+
await testOpenBrowserGuard();
|
|
241
|
+
testStreamAChatHide();
|
|
242
|
+
await testMosBindAllRefusal(seedRoom);
|
|
243
|
+
await testNormalStartAndEndpoints(seedRoom);
|
|
244
|
+
process.stdout.write(
|
|
245
|
+
'dashboard-server: all tests passed (openBrowser guard + chat-hide + MOS_BIND_ALL refusal + bind + endpoints)\n'
|
|
246
|
+
);
|
|
247
|
+
process.exit(0);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
process.stderr.write('dashboard-server FAIL: ' + (err && err.stack ? err.stack : err) + '\n');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
} finally {
|
|
252
|
+
try { fs.rmSync(seedRoom, { recursive: true, force: true }); } catch (_e) { /* ignore */ }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
run();
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Mindrian. BSL 1.1.
|
|
4
|
+
*
|
|
5
|
+
* Phase 88-05 -- debouncer drain at UserPromptSubmit tests
|
|
6
|
+
* =========================================================
|
|
7
|
+
* Wires the background regen runner. On every UserPromptSubmit (which
|
|
8
|
+
* fires on every user turn inside Claude Code), the debouncer drains
|
|
9
|
+
* queue items older than 30s. This is the "lazy commit" that ensures
|
|
10
|
+
* MINTO regen actually happens even if the user never triggers on-stop.
|
|
11
|
+
*
|
|
12
|
+
* Test map (7 tests, one per PLAN <behavior> case):
|
|
13
|
+
* Test 1: Queue has 3 items older than 30s -> drain returns all 3;
|
|
14
|
+
* .mindrian/pending-tier1-regen.json created with 3 entries.
|
|
15
|
+
* Test 2: Queue has 3 items all newer than 30s -> drain returns 0;
|
|
16
|
+
* queue unchanged; pending-tier1 file NOT created.
|
|
17
|
+
* Test 3: Queue has 5 items (2 old + 3 new) -> only 2 drained;
|
|
18
|
+
* queue retains 3; pending-tier1 file contains 2 entries.
|
|
19
|
+
* Test 4: Empty queue -> hook exits 0 without error;
|
|
20
|
+
* no pending-tier1-regen.json created.
|
|
21
|
+
* Test 5: Hook wall-clock stays bounded (< 1500ms) with 20-entry queue.
|
|
22
|
+
* Test 6: pending-tier1-regen.json is APPENDED on second drain
|
|
23
|
+
* (doesn't overwrite history).
|
|
24
|
+
* Test 7: Session-crash simulation -- queue file on disk survives,
|
|
25
|
+
* drain picks up the items on fresh hook invocation.
|
|
26
|
+
*
|
|
27
|
+
* Test harness: spawnSync bash on scripts/intent-classifier with a minimal
|
|
28
|
+
* hook JSON on stdin. Each test owns its tmpdir + MINDRIAN_ROOMS_HOME
|
|
29
|
+
* pointing into the tmpdir so resolve-room finds the room.
|
|
30
|
+
*
|
|
31
|
+
* Behavior under soft-fail: the hook MUST always exit 0. Even if the
|
|
32
|
+
* debouncer is unreachable, the drain block fails silently and the
|
|
33
|
+
* classifier continues its normal work.
|
|
34
|
+
*
|
|
35
|
+
* BSL 1.1. Zero npm deps. Node built-ins only. Three-surface by construction.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
'use strict';
|
|
39
|
+
|
|
40
|
+
const assert = require('node:assert/strict');
|
|
41
|
+
const fs = require('node:fs');
|
|
42
|
+
const os = require('node:os');
|
|
43
|
+
const path = require('node:path');
|
|
44
|
+
const { spawnSync } = require('node:child_process');
|
|
45
|
+
|
|
46
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
47
|
+
const INTENT_CLASSIFIER = path.join(REPO_ROOT, 'scripts', 'intent-classifier');
|
|
48
|
+
const DEBOUNCER_CLI = path.join(REPO_ROOT, 'scripts', 'minto-debouncer.cjs');
|
|
49
|
+
|
|
50
|
+
const TMPDIRS = [];
|
|
51
|
+
function mkTmp(prefix) {
|
|
52
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
53
|
+
TMPDIRS.push(d);
|
|
54
|
+
return d;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Kill any orphan background regen spawns so they don't linger between
|
|
58
|
+
// tests. Runs on process exit AND afterEach.
|
|
59
|
+
function killOrphanRegens() {
|
|
60
|
+
try {
|
|
61
|
+
spawnSync('bash', ['-c', 'pgrep -f vault-section-minto-generator | xargs -r kill -9 2>/dev/null || true'], {
|
|
62
|
+
stdio: 'ignore',
|
|
63
|
+
});
|
|
64
|
+
} catch (_) { /* best-effort */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
process.on('exit', () => {
|
|
68
|
+
killOrphanRegens();
|
|
69
|
+
for (const d of TMPDIRS) {
|
|
70
|
+
try { fs.rmSync(d, { recursive: true, force: true }); } catch (_) {}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build a minimal "MindrianRooms" fixture with one active room:
|
|
76
|
+
* <tmp>/MindrianRooms/
|
|
77
|
+
* .rooms/registry.json (active: default, pointing at room/)
|
|
78
|
+
* default/
|
|
79
|
+
* .room-root (sentinel so detect_room_section works)
|
|
80
|
+
* .mindrian/
|
|
81
|
+
* minto-queue.json (optional seed queue)
|
|
82
|
+
* <section>/
|
|
83
|
+
* ROOM.md
|
|
84
|
+
*/
|
|
85
|
+
function seedRoomsHome(roomName, sections) {
|
|
86
|
+
const home = mkTmp('drain-test-');
|
|
87
|
+
const roomsHome = path.join(home, 'MindrianRooms');
|
|
88
|
+
const roomsMeta = path.join(roomsHome, '.rooms');
|
|
89
|
+
const roomDir = path.join(roomsHome, roomName);
|
|
90
|
+
const mindrianDir = path.join(roomDir, '.mindrian');
|
|
91
|
+
fs.mkdirSync(roomsMeta, { recursive: true });
|
|
92
|
+
fs.mkdirSync(mindrianDir, { recursive: true });
|
|
93
|
+
fs.writeFileSync(path.join(roomDir, '.room-root'), '');
|
|
94
|
+
const now = new Date().toISOString();
|
|
95
|
+
const registry = {
|
|
96
|
+
version: 1,
|
|
97
|
+
active: roomName,
|
|
98
|
+
rooms: {
|
|
99
|
+
[roomName]: {
|
|
100
|
+
path: roomName,
|
|
101
|
+
created: now,
|
|
102
|
+
last_opened: now,
|
|
103
|
+
status: 'active',
|
|
104
|
+
venture_name: 'Test Venture',
|
|
105
|
+
venture_stage: 'Pre-Opportunity',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
fs.writeFileSync(
|
|
110
|
+
path.join(roomsMeta, 'registry.json'),
|
|
111
|
+
JSON.stringify(registry, null, 2)
|
|
112
|
+
);
|
|
113
|
+
for (const sec of sections) {
|
|
114
|
+
const secDir = path.join(roomDir, sec);
|
|
115
|
+
fs.mkdirSync(secDir, { recursive: true });
|
|
116
|
+
fs.writeFileSync(path.join(secDir, 'ROOM.md'), `# ${sec}\n`);
|
|
117
|
+
}
|
|
118
|
+
return { home, roomsHome, roomDir, mindrianDir };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function seedQueue(mindrianDir, entries) {
|
|
122
|
+
const queue = { version: 1, entries };
|
|
123
|
+
fs.writeFileSync(
|
|
124
|
+
path.join(mindrianDir, 'minto-queue.json'),
|
|
125
|
+
JSON.stringify(queue, null, 2)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function isoAgo(secondsAgo) {
|
|
130
|
+
return new Date(Date.now() - secondsAgo * 1000).toISOString();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Invoke the intent-classifier bash wrapper.
|
|
135
|
+
*
|
|
136
|
+
* stdin: empty JSON envelope (the classifier is tolerant of empty/missing
|
|
137
|
+
* prompt; it will short-circuit the advisory-warning main() path, leaving
|
|
138
|
+
* only the Phase 88-05 drain block under test).
|
|
139
|
+
*
|
|
140
|
+
* env: MINDRIAN_ROOMS_HOME points at the fixture so resolve-room picks up
|
|
141
|
+
* the active room without relying on $HOME state.
|
|
142
|
+
*/
|
|
143
|
+
function invokeClassifier(roomsHome) {
|
|
144
|
+
const env = Object.assign({}, process.env, {
|
|
145
|
+
MINDRIAN_ROOMS_HOME: roomsHome,
|
|
146
|
+
// Silence proactive-intelligence injection so test output is clean.
|
|
147
|
+
MINDRIAN_COPILOT_INJECT_FINDINGS: '0',
|
|
148
|
+
});
|
|
149
|
+
return spawnSync('bash', [INTENT_CLASSIFIER], {
|
|
150
|
+
input: '{}',
|
|
151
|
+
env,
|
|
152
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --- runner ----------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
const tests = [];
|
|
159
|
+
function test(name, fn) { tests.push({ name, fn }); }
|
|
160
|
+
|
|
161
|
+
async function waitFor(predicate, maxMs, stepMs) {
|
|
162
|
+
if (typeof maxMs !== 'number') maxMs = 2000;
|
|
163
|
+
if (typeof stepMs !== 'number') stepMs = 25;
|
|
164
|
+
const until = Date.now() + maxMs;
|
|
165
|
+
while (Date.now() < until) {
|
|
166
|
+
try { if (predicate()) return true; } catch (_) {}
|
|
167
|
+
await new Promise((r) => setTimeout(r, stepMs));
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------- Test 1: All old -> drained + pending-tier1 file populated ----
|
|
173
|
+
test('Test 1: 3 old items (>30s) drain; pending-tier1-regen.json has 3', async () => {
|
|
174
|
+
const fx = seedRoomsHome('default', ['market-analysis', 'problem-definition', 'business-model']);
|
|
175
|
+
seedQueue(fx.mindrianDir, [
|
|
176
|
+
{ section: 'market-analysis', enqueued_at: isoAgo(60), reason: 'post-write:x.md', attempts: 0 },
|
|
177
|
+
{ section: 'problem-definition', enqueued_at: isoAgo(45), reason: 'post-write:y.md', attempts: 0 },
|
|
178
|
+
{ section: 'business-model', enqueued_at: isoAgo(90), reason: 'post-write:z.md', attempts: 0 },
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
182
|
+
assert.equal(r.status, 0, 'classifier exit 0 (stderr=' + (r.stderr || '').toString() + ')');
|
|
183
|
+
|
|
184
|
+
// Wait for the background drain + pending file write to land. The drain
|
|
185
|
+
// is synchronous inside the bash block so the file should be produced
|
|
186
|
+
// before exec.
|
|
187
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
188
|
+
const ok = await waitFor(() => fs.existsSync(pendingPath), 2000);
|
|
189
|
+
assert.ok(ok, 'pending-tier1-regen.json created within 2s');
|
|
190
|
+
|
|
191
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8'));
|
|
192
|
+
assert.equal(pending.version, 1, 'pending file version=1');
|
|
193
|
+
assert.ok(Array.isArray(pending.pending), 'pending.pending is an array');
|
|
194
|
+
assert.equal(pending.pending.length, 3, 'all 3 old entries recorded as pending tier-1');
|
|
195
|
+
const sections = pending.pending.map((e) => e.section).sort();
|
|
196
|
+
assert.deepEqual(
|
|
197
|
+
sections,
|
|
198
|
+
['business-model', 'market-analysis', 'problem-definition'],
|
|
199
|
+
'all three section names present'
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Queue itself should have been emptied by the drain.
|
|
203
|
+
const q = JSON.parse(fs.readFileSync(path.join(fx.mindrianDir, 'minto-queue.json'), 'utf8'));
|
|
204
|
+
assert.equal(q.entries.length, 0, 'queue emptied after drain');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ---------- Test 2: All new -> nothing drained, no pending file ---------
|
|
208
|
+
test('Test 2: 3 new items (<30s) -> no drain, no pending-tier1 file', async () => {
|
|
209
|
+
const fx = seedRoomsHome('default', ['market-analysis']);
|
|
210
|
+
seedQueue(fx.mindrianDir, [
|
|
211
|
+
{ section: 'market-analysis', enqueued_at: isoAgo(5), reason: 'post-write', attempts: 0 },
|
|
212
|
+
{ section: 'problem-definition', enqueued_at: isoAgo(10), reason: 'post-write', attempts: 0 },
|
|
213
|
+
{ section: 'business-model', enqueued_at: isoAgo(15), reason: 'post-write', attempts: 0 },
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
217
|
+
assert.equal(r.status, 0, 'classifier exit 0');
|
|
218
|
+
|
|
219
|
+
// Give the bash drain a beat to run (still synchronous, but be gracious).
|
|
220
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
221
|
+
|
|
222
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
223
|
+
assert.ok(!fs.existsSync(pendingPath), 'no pending-tier1 file produced for fresh queue');
|
|
224
|
+
|
|
225
|
+
// Queue unchanged -- 3 entries preserved.
|
|
226
|
+
const q = JSON.parse(fs.readFileSync(path.join(fx.mindrianDir, 'minto-queue.json'), 'utf8'));
|
|
227
|
+
assert.equal(q.entries.length, 3, 'queue unchanged (3 fresh entries remain)');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ---------- Test 3: Mixed 2 old + 3 new ---------------------------------
|
|
231
|
+
test('Test 3: 2 old + 3 new -> only 2 drained, 3 remain', async () => {
|
|
232
|
+
const fx = seedRoomsHome('default', ['a-section', 'b-section', 'c-section']);
|
|
233
|
+
seedQueue(fx.mindrianDir, [
|
|
234
|
+
{ section: 'a-section', enqueued_at: isoAgo(60), reason: 'old-1', attempts: 0 },
|
|
235
|
+
{ section: 'b-section', enqueued_at: isoAgo(5), reason: 'new-1', attempts: 0 },
|
|
236
|
+
{ section: 'c-section', enqueued_at: isoAgo(45), reason: 'old-2', attempts: 0 },
|
|
237
|
+
{ section: 'd-section', enqueued_at: isoAgo(10), reason: 'new-2', attempts: 0 },
|
|
238
|
+
{ section: 'e-section', enqueued_at: isoAgo(8), reason: 'new-3', attempts: 0 },
|
|
239
|
+
]);
|
|
240
|
+
|
|
241
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
242
|
+
assert.equal(r.status, 0, 'classifier exit 0');
|
|
243
|
+
|
|
244
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
245
|
+
const ok = await waitFor(() => fs.existsSync(pendingPath), 2000);
|
|
246
|
+
assert.ok(ok, 'pending-tier1 file created');
|
|
247
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8'));
|
|
248
|
+
assert.equal(pending.pending.length, 2, 'only 2 old items pended');
|
|
249
|
+
const pendedSections = pending.pending.map((e) => e.section).sort();
|
|
250
|
+
assert.deepEqual(pendedSections, ['a-section', 'c-section'], 'old section names pended');
|
|
251
|
+
|
|
252
|
+
// Queue retains the 3 new entries.
|
|
253
|
+
const q = JSON.parse(fs.readFileSync(path.join(fx.mindrianDir, 'minto-queue.json'), 'utf8'));
|
|
254
|
+
assert.equal(q.entries.length, 3, 'queue retains 3 fresh entries');
|
|
255
|
+
const remainingSections = q.entries.map((e) => e.section).sort();
|
|
256
|
+
assert.deepEqual(remainingSections, ['b-section', 'd-section', 'e-section']);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// ---------- Test 4: Empty queue -> no-op ---------------------------------
|
|
260
|
+
test('Test 4: empty queue -> hook exits 0, no pending-tier1 file', async () => {
|
|
261
|
+
const fx = seedRoomsHome('default', ['market-analysis']);
|
|
262
|
+
// No queue file at all.
|
|
263
|
+
|
|
264
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
265
|
+
assert.equal(r.status, 0, 'classifier exit 0');
|
|
266
|
+
|
|
267
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
268
|
+
|
|
269
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
270
|
+
assert.ok(!fs.existsSync(pendingPath), 'no pending file for empty queue');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// ---------- Test 5: Wall-clock bound under 20-entry queue ---------------
|
|
274
|
+
test('Test 5: 20-entry old queue -> hook wall-clock under 1500ms', async () => {
|
|
275
|
+
const sections = [];
|
|
276
|
+
for (let i = 0; i < 20; i++) sections.push('sec-' + i);
|
|
277
|
+
const fx = seedRoomsHome('default', sections);
|
|
278
|
+
const entries = sections.map((s, i) => ({
|
|
279
|
+
section: s,
|
|
280
|
+
enqueued_at: isoAgo(60 + i),
|
|
281
|
+
reason: 'burst-' + i,
|
|
282
|
+
attempts: 0,
|
|
283
|
+
}));
|
|
284
|
+
seedQueue(fx.mindrianDir, entries);
|
|
285
|
+
|
|
286
|
+
const start = Date.now();
|
|
287
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
288
|
+
const elapsed = Date.now() - start;
|
|
289
|
+
|
|
290
|
+
assert.equal(r.status, 0, 'classifier exit 0');
|
|
291
|
+
assert.ok(
|
|
292
|
+
elapsed < 1500,
|
|
293
|
+
'hook wall-clock < 1500ms (got: ' + elapsed + 'ms)'
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Regens spawn in background; pending file still gets all 20.
|
|
297
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
298
|
+
const ok = await waitFor(() => fs.existsSync(pendingPath), 2000);
|
|
299
|
+
assert.ok(ok, 'pending-tier1 file created for 20-entry drain');
|
|
300
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8'));
|
|
301
|
+
assert.equal(pending.pending.length, 20, 'all 20 entries pended');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// ---------- Test 6: Append (not overwrite) on second drain --------------
|
|
305
|
+
test('Test 6: second drain APPENDS to existing pending-tier1 file', async () => {
|
|
306
|
+
const fx = seedRoomsHome('default', ['alpha', 'beta', 'gamma']);
|
|
307
|
+
// Pre-seed pending-tier1 with one historical entry.
|
|
308
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
309
|
+
fs.writeFileSync(pendingPath, JSON.stringify({
|
|
310
|
+
version: 1,
|
|
311
|
+
pending: [
|
|
312
|
+
{ section: 'historical-section', enqueued_at: isoAgo(300), reason: 'prior-session', attempts: 0 },
|
|
313
|
+
],
|
|
314
|
+
}, null, 2));
|
|
315
|
+
|
|
316
|
+
// Seed queue with 2 old items.
|
|
317
|
+
seedQueue(fx.mindrianDir, [
|
|
318
|
+
{ section: 'alpha', enqueued_at: isoAgo(60), reason: 'r1', attempts: 0 },
|
|
319
|
+
{ section: 'beta', enqueued_at: isoAgo(90), reason: 'r2', attempts: 0 },
|
|
320
|
+
]);
|
|
321
|
+
|
|
322
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
323
|
+
assert.equal(r.status, 0, 'classifier exit 0');
|
|
324
|
+
await waitFor(() => {
|
|
325
|
+
try {
|
|
326
|
+
const p = JSON.parse(fs.readFileSync(pendingPath, 'utf8'));
|
|
327
|
+
return p.pending.length === 3;
|
|
328
|
+
} catch (_) { return false; }
|
|
329
|
+
}, 2000);
|
|
330
|
+
|
|
331
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8'));
|
|
332
|
+
assert.equal(pending.pending.length, 3, 'historical + 2 new = 3 entries');
|
|
333
|
+
const sections = pending.pending.map((e) => e.section);
|
|
334
|
+
assert.ok(sections.includes('historical-section'), 'historical entry preserved');
|
|
335
|
+
assert.ok(sections.includes('alpha'), 'alpha drained in');
|
|
336
|
+
assert.ok(sections.includes('beta'), 'beta drained in');
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// ---------- Test 7: Crash recovery -- queue on disk survives ------------
|
|
340
|
+
test('Test 7: session crash simulation -- queue persists + drains on next hook', async () => {
|
|
341
|
+
const fx = seedRoomsHome('default', ['market-analysis', 'solution-design', 'business-model']);
|
|
342
|
+
// Simulate a crashed prior session that left 3 old items in the queue.
|
|
343
|
+
const oldEntries = [
|
|
344
|
+
{ section: 'market-analysis', enqueued_at: isoAgo(120), reason: 'crashed-session', attempts: 0 },
|
|
345
|
+
{ section: 'solution-design', enqueued_at: isoAgo(150), reason: 'crashed-session', attempts: 0 },
|
|
346
|
+
{ section: 'business-model', enqueued_at: isoAgo(180), reason: 'crashed-session', attempts: 0 },
|
|
347
|
+
];
|
|
348
|
+
seedQueue(fx.mindrianDir, oldEntries);
|
|
349
|
+
|
|
350
|
+
// Verify queue file is on disk BEFORE hook runs (i.e. persisted across
|
|
351
|
+
// the "crash"). This simulates the next session starting fresh.
|
|
352
|
+
const qPath = path.join(fx.mindrianDir, 'minto-queue.json');
|
|
353
|
+
assert.ok(fs.existsSync(qPath), 'queue persisted to disk (crash-safe)');
|
|
354
|
+
const prior = JSON.parse(fs.readFileSync(qPath, 'utf8'));
|
|
355
|
+
assert.equal(prior.entries.length, 3, 'pre-hook: 3 crashed entries on disk');
|
|
356
|
+
|
|
357
|
+
// Fresh hook invocation.
|
|
358
|
+
const r = invokeClassifier(fx.roomsHome);
|
|
359
|
+
assert.equal(r.status, 0, 'classifier exit 0 on crash-recovery path');
|
|
360
|
+
|
|
361
|
+
const pendingPath = path.join(fx.mindrianDir, 'pending-tier1-regen.json');
|
|
362
|
+
const ok = await waitFor(() => fs.existsSync(pendingPath), 2000);
|
|
363
|
+
assert.ok(ok, 'pending-tier1 file created after crash recovery');
|
|
364
|
+
const pending = JSON.parse(fs.readFileSync(pendingPath, 'utf8'));
|
|
365
|
+
assert.equal(pending.pending.length, 3, 'all 3 crashed entries recovered');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// --- runner ----------------------------------------------------------------
|
|
369
|
+
|
|
370
|
+
(async () => {
|
|
371
|
+
let failed = 0;
|
|
372
|
+
for (const t of tests) {
|
|
373
|
+
try {
|
|
374
|
+
await t.fn();
|
|
375
|
+
process.stdout.write('PASS ' + t.name + '\n');
|
|
376
|
+
} catch (e) {
|
|
377
|
+
failed += 1;
|
|
378
|
+
process.stderr.write('FAIL ' + t.name + '\n' + (e && e.stack || e) + '\n');
|
|
379
|
+
} finally {
|
|
380
|
+
// afterEach: kill any orphan regen spawns so they do not leak between tests.
|
|
381
|
+
killOrphanRegens();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
process.stdout.write(
|
|
385
|
+
'\ndebouncer-drain-at-prompt.test.cjs: ' +
|
|
386
|
+
(tests.length - failed) + '/' + tests.length + ' passed\n'
|
|
387
|
+
);
|
|
388
|
+
process.exit(failed === 0 ? 0 : 1);
|
|
389
|
+
})();
|