@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,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for lib/vault/wikilink-builder.cjs
|
|
4
|
+
*
|
|
5
|
+
* Zero-dependency test runner using Node's built-in `assert`.
|
|
6
|
+
* Run: node lib/vault/wikilink-builder.test.cjs
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const assert = require('assert');
|
|
12
|
+
const wb = require('./wikilink-builder.cjs');
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
|
|
17
|
+
function test(name, fn) {
|
|
18
|
+
try {
|
|
19
|
+
fn();
|
|
20
|
+
passed += 1;
|
|
21
|
+
process.stdout.write(` ok ${name}\n`);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
failed += 1;
|
|
24
|
+
process.stdout.write(` FAIL ${name}\n ${err.message}\n`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const profiles = [
|
|
29
|
+
{
|
|
30
|
+
name: 'avital-leibovich',
|
|
31
|
+
displayName: 'Avital Leibovich',
|
|
32
|
+
path: '/room/team/advisors/avital-leibovich/PROFILE.md',
|
|
33
|
+
category: 'advisors',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'avital-only',
|
|
37
|
+
displayName: 'Avital',
|
|
38
|
+
path: '/room/team/advisors/avital-only/PROFILE.md',
|
|
39
|
+
category: 'advisors',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'lawrence-aronhime',
|
|
43
|
+
displayName: 'Lawrence Aronhime',
|
|
44
|
+
path: '/room/team/mentors/lawrence-aronhime/PROFILE.md',
|
|
45
|
+
category: 'mentors',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Test 1: first occurrence only
|
|
50
|
+
test('buildTeamLinks replaces first occurrence only', () => {
|
|
51
|
+
const input = 'Lawrence Aronhime said X. Later Lawrence Aronhime said Y.';
|
|
52
|
+
const out = wb.buildTeamLinks(input, profiles, {});
|
|
53
|
+
const link = '[[team/mentors/lawrence-aronhime/PROFILE.md|Lawrence Aronhime]]';
|
|
54
|
+
assert.ok(out.includes(link), 'expected link inserted');
|
|
55
|
+
// Only one link
|
|
56
|
+
const count = out.split(link).length - 1;
|
|
57
|
+
assert.strictEqual(count, 1, `expected exactly 1 link, got ${count}`);
|
|
58
|
+
// Second occurrence is untouched plain text
|
|
59
|
+
assert.ok(out.endsWith('Later Lawrence Aronhime said Y.'), 'second occurrence preserved');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Test 2: longest-name preference
|
|
63
|
+
test('buildTeamLinks prefers longer name over prefix (Avital Leibovich > Avital)', () => {
|
|
64
|
+
const input = 'Meeting with Avital Leibovich today.';
|
|
65
|
+
const out = wb.buildTeamLinks(input, profiles, {});
|
|
66
|
+
assert.ok(
|
|
67
|
+
out.includes('[[team/advisors/avital-leibovich/PROFILE.md|Avital Leibovich]]'),
|
|
68
|
+
'longer name should win'
|
|
69
|
+
);
|
|
70
|
+
assert.ok(
|
|
71
|
+
!out.includes('[[team/advisors/avital-only/PROFILE.md|Avital]]'),
|
|
72
|
+
'shorter prefix should not link'
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Test 3: self-link skipped
|
|
77
|
+
test('buildTeamLinks skips self-link when ownProfilePath matches', () => {
|
|
78
|
+
const input = '# Lawrence Aronhime\n\nLawrence Aronhime is a mentor.';
|
|
79
|
+
const out = wb.buildTeamLinks(input, profiles, {
|
|
80
|
+
ownProfilePath: '/room/team/mentors/lawrence-aronhime/PROFILE.md',
|
|
81
|
+
});
|
|
82
|
+
assert.strictEqual(out, input, 'content should be unchanged when processing own profile');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Test 4: frontmatter skipped
|
|
86
|
+
test('buildTeamLinks skips frontmatter region', () => {
|
|
87
|
+
const input =
|
|
88
|
+
'---\nauthor: Lawrence Aronhime\n---\nBody text by Lawrence Aronhime here.';
|
|
89
|
+
const out = wb.buildTeamLinks(input, profiles, {});
|
|
90
|
+
// Frontmatter author line unchanged
|
|
91
|
+
assert.ok(out.includes('author: Lawrence Aronhime'), 'frontmatter untouched');
|
|
92
|
+
// Body got linked
|
|
93
|
+
assert.ok(
|
|
94
|
+
out.includes('Body text by [[team/mentors/lawrence-aronhime/PROFILE.md|Lawrence Aronhime]] here.'),
|
|
95
|
+
'body should be linked'
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Test 5: idempotence
|
|
100
|
+
test('buildTeamLinks is idempotent (running twice = same output)', () => {
|
|
101
|
+
const input = 'Lawrence Aronhime opened the meeting.';
|
|
102
|
+
const once = wb.buildTeamLinks(input, profiles, {});
|
|
103
|
+
const twice = wb.buildTeamLinks(once, profiles, {});
|
|
104
|
+
assert.strictEqual(once, twice, 'second pass should not change output');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Test 6: buildSectionLink
|
|
108
|
+
test('buildSectionLink returns exact format and honors display override', () => {
|
|
109
|
+
assert.strictEqual(
|
|
110
|
+
wb.buildSectionLink('business-model'),
|
|
111
|
+
'[[business-model/ROOM.md|business-model]]'
|
|
112
|
+
);
|
|
113
|
+
assert.strictEqual(
|
|
114
|
+
wb.buildSectionLink('business-model', { display: 'Business Model' }),
|
|
115
|
+
'[[business-model/ROOM.md|Business Model]]'
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Test 7: buildMeetingLink
|
|
120
|
+
test('buildMeetingLink strips date prefix and title-cases', () => {
|
|
121
|
+
const out = wb.buildMeetingLink('2026-04-09-align-strategy-session');
|
|
122
|
+
assert.strictEqual(
|
|
123
|
+
out,
|
|
124
|
+
'[[meetings/2026-04-09-align-strategy-session/summary.md|Align Strategy Session]]'
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Test 8: buildFiledToFooter
|
|
129
|
+
test('buildFiledToFooter returns both lines / one line appropriately', () => {
|
|
130
|
+
const both = wb.buildFiledToFooter({
|
|
131
|
+
targetPath: 'market-analysis/2026-04-09-tam.md',
|
|
132
|
+
meetingSlug: '2026-04-09-align-strategy-session',
|
|
133
|
+
});
|
|
134
|
+
assert.ok(both.includes('-> Full artifact: [[market-analysis/2026-04-09-tam.md]]'));
|
|
135
|
+
assert.ok(
|
|
136
|
+
both.includes('<- Source meeting: [[meetings/2026-04-09-align-strategy-session/summary.md|Align Strategy Session]]')
|
|
137
|
+
);
|
|
138
|
+
assert.strictEqual(both.split('\n').length, 2, 'should be 2 lines');
|
|
139
|
+
|
|
140
|
+
const one = wb.buildFiledToFooter({
|
|
141
|
+
targetPath: null,
|
|
142
|
+
meetingSlug: '2026-04-09-align-strategy-session',
|
|
143
|
+
});
|
|
144
|
+
assert.strictEqual(one.split('\n').length, 1, 'should be 1 line');
|
|
145
|
+
assert.ok(one.startsWith('<- Source meeting:'));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Test 9: injectFiledToFooter idempotent + placement
|
|
149
|
+
test('injectFiledToFooter inserts after frontmatter and is idempotent', () => {
|
|
150
|
+
const input = '---\nmeeting_id: 2026-04-09-align-strategy-session\n---\nBody.';
|
|
151
|
+
const once = wb.injectFiledToFooter(input, {
|
|
152
|
+
targetPath: 'market-analysis/2026-04-09-tam.md',
|
|
153
|
+
meetingSlug: '2026-04-09-align-strategy-session',
|
|
154
|
+
});
|
|
155
|
+
assert.ok(once.includes('-> Full artifact: [[market-analysis/2026-04-09-tam.md]]'));
|
|
156
|
+
assert.ok(once.includes('<- Source meeting: [[meetings/2026-04-09-align-strategy-session/summary.md|Align Strategy Session]]'));
|
|
157
|
+
// Lines sit after frontmatter close
|
|
158
|
+
const fmIdx = once.indexOf('\n---');
|
|
159
|
+
const arrowIdx = once.indexOf('-> Full artifact');
|
|
160
|
+
assert.ok(arrowIdx > fmIdx, 'footer should be after frontmatter close');
|
|
161
|
+
|
|
162
|
+
const twice = wb.injectFiledToFooter(once, {
|
|
163
|
+
targetPath: 'market-analysis/2026-04-09-tam.md',
|
|
164
|
+
meetingSlug: '2026-04-09-align-strategy-session',
|
|
165
|
+
});
|
|
166
|
+
assert.strictEqual(once, twice, 'second injection should be a no-op');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Test 10: graceful zero-profile behavior
|
|
170
|
+
test('buildTeamLinks with zero team profiles returns content unchanged', () => {
|
|
171
|
+
const input = 'Some body text mentioning Lawrence Aronhime.';
|
|
172
|
+
assert.strictEqual(wb.buildTeamLinks(input, [], {}), input);
|
|
173
|
+
assert.strictEqual(wb.buildTeamLinks(input, null, {}), input);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ---------- Summary ----------
|
|
177
|
+
process.stdout.write(`\n${passed} passed, ${failed} failed\n`);
|
|
178
|
+
if (failed > 0) {
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
process.stdout.write('PASS\n');
|
|
182
|
+
process.exit(0);
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* graph-links.cjs -- SQLite integration for the wiki
|
|
4
|
+
*
|
|
5
|
+
* Queries LazyGraph edges (SQLite via lazygraph-ops.cjs) and converts
|
|
6
|
+
* them into navigational hyperlinks, backlinks, "See also" sections,
|
|
7
|
+
* and full graph data for Cytoscape.js visualization.
|
|
8
|
+
*
|
|
9
|
+
* Exports: getPageLinks, getBacklinks, getSeeAlso, getGraphData
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
// Edge type display configuration
|
|
16
|
+
// BELONGS_TO and REASONING_INFORMS are structural -- skip for navigation
|
|
17
|
+
const EDGE_DISPLAY = {
|
|
18
|
+
INFORMS: { symbol: '\u2192', color: '#1E3A6E', cssClass: 'link-informs', label: 'Informs' },
|
|
19
|
+
CONTRADICTS: { symbol: '\u2297', color: '#A63D2F', cssClass: 'link-contradicts', label: 'Contradicts' },
|
|
20
|
+
CONVERGES: { symbol: '\u2295', color: '#C8A43C', cssClass: 'link-converges', label: 'Converges' },
|
|
21
|
+
ENABLES: { symbol: '\u25B6', color: '#2D6B4A', cssClass: 'link-enables', label: 'Enables' },
|
|
22
|
+
INVALIDATES: { symbol: '\u2298', color: '#B5602A', cssClass: 'link-invalidates', label: 'Invalidates' }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const NAV_EDGE_TYPES = Object.keys(EDGE_DISPLAY);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Safely require lazygraph-ops. Returns null if not available.
|
|
29
|
+
*/
|
|
30
|
+
function getLazyGraphOps() {
|
|
31
|
+
try {
|
|
32
|
+
return require('../core/lazygraph-ops.cjs');
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a LazyGraph SQLite database exists for the room.
|
|
40
|
+
* @param {string} roomDir
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function hasLazyGraph(roomDir) {
|
|
44
|
+
const dbPath = path.join(path.resolve(roomDir), '.mindrian', 'room.db');
|
|
45
|
+
return fs.existsSync(dbPath);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get all outgoing navigational edges from an artifact.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} roomDir - Path to room/ directory
|
|
52
|
+
* @param {string} artifactId - e.g. "problem-definition/market-trends"
|
|
53
|
+
* @returns {Promise<Array<{type, targetId, targetTitle, symbol, color, cssClass, label}>>}
|
|
54
|
+
*/
|
|
55
|
+
async function getPageLinks(roomDir, artifactId) {
|
|
56
|
+
if (!hasLazyGraph(roomDir)) return [];
|
|
57
|
+
|
|
58
|
+
const ops = getLazyGraphOps();
|
|
59
|
+
if (!ops) return [];
|
|
60
|
+
|
|
61
|
+
let db, conn;
|
|
62
|
+
try {
|
|
63
|
+
({ db, conn } = await ops.openGraph(roomDir));
|
|
64
|
+
|
|
65
|
+
// SQLite query: outgoing edges from this artifact to other artifacts
|
|
66
|
+
const rows = await ops.queryGraph(conn,
|
|
67
|
+
`SELECT e.type AS rel, e.target AS target, json_extract(n.properties, '$.title') AS title
|
|
68
|
+
FROM edges e
|
|
69
|
+
JOIN nodes n ON n.id = e.target AND n.type = 'Artifact'
|
|
70
|
+
WHERE e.source = '${esc(artifactId)}'`
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const links = [];
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
const edgeType = row.rel;
|
|
76
|
+
const display = EDGE_DISPLAY[edgeType];
|
|
77
|
+
if (!display) continue; // Skip BELONGS_TO, REASONING_INFORMS, etc.
|
|
78
|
+
|
|
79
|
+
links.push({
|
|
80
|
+
type: edgeType,
|
|
81
|
+
targetId: row.target,
|
|
82
|
+
targetTitle: row.title || row.target,
|
|
83
|
+
symbol: display.symbol,
|
|
84
|
+
color: display.color,
|
|
85
|
+
cssClass: display.cssClass,
|
|
86
|
+
label: display.label
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return links;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
process.stderr.write(`[wiki] graph-links getPageLinks error: ${e.message}\n`);
|
|
93
|
+
return [];
|
|
94
|
+
} finally {
|
|
95
|
+
if (db) try { await ops.closeGraph(db); } catch (_) { /* ignore */ }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all incoming edges TO an artifact (backlinks -- "What links here").
|
|
101
|
+
*
|
|
102
|
+
* @param {string} roomDir - Path to room/ directory
|
|
103
|
+
* @param {string} artifactId - e.g. "problem-definition/market-trends"
|
|
104
|
+
* @returns {Promise<Array<{type, sourceId, sourceTitle, symbol, color}>>}
|
|
105
|
+
*/
|
|
106
|
+
async function getBacklinks(roomDir, artifactId) {
|
|
107
|
+
if (!hasLazyGraph(roomDir)) return [];
|
|
108
|
+
|
|
109
|
+
const ops = getLazyGraphOps();
|
|
110
|
+
if (!ops) return [];
|
|
111
|
+
|
|
112
|
+
let db, conn;
|
|
113
|
+
try {
|
|
114
|
+
({ db, conn } = await ops.openGraph(roomDir));
|
|
115
|
+
|
|
116
|
+
// SQLite query: incoming edges to this artifact from other artifacts
|
|
117
|
+
const rows = await ops.queryGraph(conn,
|
|
118
|
+
`SELECT e.type AS rel, e.source AS source, json_extract(n.properties, '$.title') AS title
|
|
119
|
+
FROM edges e
|
|
120
|
+
JOIN nodes n ON n.id = e.source AND n.type = 'Artifact'
|
|
121
|
+
WHERE e.target = '${esc(artifactId)}'`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const backlinks = [];
|
|
125
|
+
for (const row of rows) {
|
|
126
|
+
const edgeType = row.rel;
|
|
127
|
+
const display = EDGE_DISPLAY[edgeType];
|
|
128
|
+
if (!display) continue;
|
|
129
|
+
|
|
130
|
+
backlinks.push({
|
|
131
|
+
type: edgeType,
|
|
132
|
+
sourceId: row.source,
|
|
133
|
+
sourceTitle: row.title || row.source,
|
|
134
|
+
symbol: display.symbol,
|
|
135
|
+
color: display.color
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return backlinks;
|
|
140
|
+
} catch (e) {
|
|
141
|
+
process.stderr.write(`[wiki] graph-links getBacklinks error: ${e.message}\n`);
|
|
142
|
+
return [];
|
|
143
|
+
} finally {
|
|
144
|
+
if (db) try { await ops.closeGraph(db); } catch (_) { /* ignore */ }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get "See also" links -- CONVERGES and ENABLES edges grouped by theme.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} roomDir - Path to room/ directory
|
|
152
|
+
* @param {string} artifactId - e.g. "problem-definition/market-trends"
|
|
153
|
+
* @returns {Promise<Array<{targetId, targetTitle, reason}>>}
|
|
154
|
+
*/
|
|
155
|
+
async function getSeeAlso(roomDir, artifactId) {
|
|
156
|
+
if (!hasLazyGraph(roomDir)) return [];
|
|
157
|
+
|
|
158
|
+
const ops = getLazyGraphOps();
|
|
159
|
+
if (!ops) return [];
|
|
160
|
+
|
|
161
|
+
let db, conn;
|
|
162
|
+
try {
|
|
163
|
+
({ db, conn } = await ops.openGraph(roomDir));
|
|
164
|
+
|
|
165
|
+
// CONVERGES edges (with optional theme from properties.term)
|
|
166
|
+
const convergesRows = await ops.queryGraph(conn,
|
|
167
|
+
`SELECT e.target AS target, json_extract(n.properties, '$.title') AS title, json_extract(e.properties, '$.term') AS theme
|
|
168
|
+
FROM edges e
|
|
169
|
+
JOIN nodes n ON n.id = e.target AND n.type = 'Artifact'
|
|
170
|
+
WHERE e.source = '${esc(artifactId)}' AND e.type = 'CONVERGES'`
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// ENABLES edges
|
|
174
|
+
const enablesRows = await ops.queryGraph(conn,
|
|
175
|
+
`SELECT e.target AS target, json_extract(n.properties, '$.title') AS title
|
|
176
|
+
FROM edges e
|
|
177
|
+
JOIN nodes n ON n.id = e.target AND n.type = 'Artifact'
|
|
178
|
+
WHERE e.source = '${esc(artifactId)}' AND e.type = 'ENABLES'`
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const seeAlso = [];
|
|
182
|
+
const seen = new Set();
|
|
183
|
+
|
|
184
|
+
for (const row of convergesRows) {
|
|
185
|
+
if (seen.has(row.target)) continue;
|
|
186
|
+
seen.add(row.target);
|
|
187
|
+
seeAlso.push({
|
|
188
|
+
targetId: row.target,
|
|
189
|
+
targetTitle: row.title || row.target,
|
|
190
|
+
reason: row.theme ? `Converges on: ${row.theme}` : 'Converging topic'
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const row of enablesRows) {
|
|
195
|
+
if (seen.has(row.target)) continue;
|
|
196
|
+
seen.add(row.target);
|
|
197
|
+
seeAlso.push({
|
|
198
|
+
targetId: row.target,
|
|
199
|
+
targetTitle: row.title || row.target,
|
|
200
|
+
reason: 'Enables'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return seeAlso;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
process.stderr.write(`[wiki] graph-links getSeeAlso error: ${e.message}\n`);
|
|
207
|
+
return [];
|
|
208
|
+
} finally {
|
|
209
|
+
if (db) try { await ops.closeGraph(db); } catch (_) { /* ignore */ }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get all artifacts and edges for Cytoscape.js graph visualization.
|
|
215
|
+
*
|
|
216
|
+
* @param {string} roomDir - Path to room/ directory
|
|
217
|
+
* @returns {Promise<{nodes: Array<{id, title, section}>, edges: Array<{source, target, type}>}>}
|
|
218
|
+
*/
|
|
219
|
+
async function getGraphData(roomDir) {
|
|
220
|
+
if (!hasLazyGraph(roomDir)) return { nodes: [], edges: [] };
|
|
221
|
+
|
|
222
|
+
const ops = getLazyGraphOps();
|
|
223
|
+
if (!ops) return { nodes: [], edges: [] };
|
|
224
|
+
|
|
225
|
+
let db, conn;
|
|
226
|
+
try {
|
|
227
|
+
({ db, conn } = await ops.openGraph(roomDir));
|
|
228
|
+
|
|
229
|
+
// All artifact nodes
|
|
230
|
+
const nodeRows = await ops.queryGraph(conn,
|
|
231
|
+
`SELECT id, json_extract(properties, '$.title') AS title, json_extract(properties, '$.section') AS section
|
|
232
|
+
FROM nodes WHERE type = 'Artifact'`
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const nodes = nodeRows.map(row => ({
|
|
236
|
+
id: row.id,
|
|
237
|
+
title: row.title || row.id,
|
|
238
|
+
section: row.section || ''
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
// All navigational edges between artifacts (skip BELONGS_TO, REASONING_INFORMS)
|
|
242
|
+
const typePlaceholders = NAV_EDGE_TYPES.map(t => `'${t}'`).join(',');
|
|
243
|
+
const edgeRows = await ops.queryGraph(conn,
|
|
244
|
+
`SELECT e.source AS source, e.target AS target, e.type AS type
|
|
245
|
+
FROM edges e
|
|
246
|
+
JOIN nodes n1 ON n1.id = e.source AND n1.type = 'Artifact'
|
|
247
|
+
JOIN nodes n2 ON n2.id = e.target AND n2.type = 'Artifact'
|
|
248
|
+
WHERE e.type IN (${typePlaceholders})`
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const edges = edgeRows.map(row => ({
|
|
252
|
+
source: row.source,
|
|
253
|
+
target: row.target,
|
|
254
|
+
type: row.type
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
return { nodes, edges };
|
|
258
|
+
} catch (e) {
|
|
259
|
+
process.stderr.write(`[wiki] graph-links getGraphData error: ${e.message}\n`);
|
|
260
|
+
return { nodes: [], edges: [] };
|
|
261
|
+
} finally {
|
|
262
|
+
if (db) try { await ops.closeGraph(db); } catch (_) { /* ignore */ }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Escape single quotes for SQL string literals.
|
|
268
|
+
*/
|
|
269
|
+
function esc(str) {
|
|
270
|
+
if (!str) return '';
|
|
271
|
+
return str.replace(/'/g, "''");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
getPageLinks,
|
|
276
|
+
getBacklinks,
|
|
277
|
+
getSeeAlso,
|
|
278
|
+
getGraphData,
|
|
279
|
+
EDGE_DISPLAY,
|
|
280
|
+
NAV_EDGE_TYPES
|
|
281
|
+
};
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* page-renderer.cjs — Markdown rendering pipeline for the wiki
|
|
4
|
+
*
|
|
5
|
+
* Scans room/ directory for .md files, parses frontmatter,
|
|
6
|
+
* renders markdown with wikilinks, and generates TOC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const markdownIt = require('markdown-it');
|
|
12
|
+
const matter = require('gray-matter');
|
|
13
|
+
|
|
14
|
+
// Directories to skip when scanning room/
|
|
15
|
+
const SKIP_DIRS = new Set(['.lazygraph', '.reasoning', 'meetings', 'node_modules', '.git']);
|
|
16
|
+
const SKIP_FILES = new Set(['STATE.md']);
|
|
17
|
+
|
|
18
|
+
// Section color mapping (matches build-graph)
|
|
19
|
+
const SECTION_COLORS = {
|
|
20
|
+
'problem-definition': '#A63D2F',
|
|
21
|
+
'market-analysis': '#C8A43C',
|
|
22
|
+
'solution-design': '#5C5A56',
|
|
23
|
+
'business-model': '#2D6B4A',
|
|
24
|
+
'competitive-analysis': '#B5602A',
|
|
25
|
+
'team-execution': '#1E3A6E',
|
|
26
|
+
'team': '#1E3A6E',
|
|
27
|
+
'legal-ip': '#6B4E8B',
|
|
28
|
+
'financial-model': '#2A6B5E',
|
|
29
|
+
'opportunity-bank': '#C8A43C',
|
|
30
|
+
'funding': '#2D6B4A',
|
|
31
|
+
'personas': '#6B4E8B'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Scan room/ directory recursively for .md files.
|
|
36
|
+
* Returns { pages: Map, sections: Map }
|
|
37
|
+
*/
|
|
38
|
+
function scanRoom(roomDir) {
|
|
39
|
+
const absRoom = path.resolve(roomDir);
|
|
40
|
+
const pages = new Map(); // id -> { path, section, title, frontmatter, content }
|
|
41
|
+
const sections = new Map(); // section-name -> { label, color, pages: [] }
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(absRoom)) {
|
|
44
|
+
return { pages, sections };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const entries = fs.readdirSync(absRoom, { withFileTypes: true });
|
|
48
|
+
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (!entry.isDirectory()) {
|
|
51
|
+
// Top-level .md files (e.g., README.md at room root)
|
|
52
|
+
if (entry.name.endsWith('.md') && !SKIP_FILES.has(entry.name)) {
|
|
53
|
+
const filePath = path.join(absRoom, entry.name);
|
|
54
|
+
const page = parsePage(filePath, '_root', absRoom);
|
|
55
|
+
pages.set(page.id, page);
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sectionName = entry.name;
|
|
61
|
+
if (SKIP_DIRS.has(sectionName)) continue;
|
|
62
|
+
|
|
63
|
+
const sectionDir = path.join(absRoom, sectionName);
|
|
64
|
+
const label = sectionName
|
|
65
|
+
.split('-')
|
|
66
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
67
|
+
.join(' ');
|
|
68
|
+
const color = SECTION_COLORS[sectionName] || '#5C5A56';
|
|
69
|
+
|
|
70
|
+
const sectionPages = [];
|
|
71
|
+
|
|
72
|
+
// Read .md files in section directory (one level deep)
|
|
73
|
+
const sectionEntries = fs.readdirSync(sectionDir, { withFileTypes: true });
|
|
74
|
+
for (const se of sectionEntries) {
|
|
75
|
+
if (!se.isFile() || !se.name.endsWith('.md')) continue;
|
|
76
|
+
if (SKIP_FILES.has(se.name)) continue;
|
|
77
|
+
|
|
78
|
+
const filePath = path.join(sectionDir, se.name);
|
|
79
|
+
const page = parsePage(filePath, sectionName, absRoom);
|
|
80
|
+
pages.set(page.id, page);
|
|
81
|
+
sectionPages.push(page.id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
sections.set(sectionName, { label, color, pages: sectionPages });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { pages, sections };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Parse a single .md file into a page object.
|
|
92
|
+
*/
|
|
93
|
+
function parsePage(filePath, section, roomDir) {
|
|
94
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
95
|
+
const { data: frontmatter, content } = matter(raw);
|
|
96
|
+
const basename = path.basename(filePath, '.md');
|
|
97
|
+
const id = section === '_root' ? basename : `${section}/${basename}`;
|
|
98
|
+
const title = frontmatter.title || basename
|
|
99
|
+
.split('-')
|
|
100
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
101
|
+
.join(' ');
|
|
102
|
+
|
|
103
|
+
return { id, path: filePath, section, title, frontmatter, content };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build lookup index for wikilink resolution.
|
|
108
|
+
* Maps lowercase title / id variants to wiki URLs.
|
|
109
|
+
*/
|
|
110
|
+
function buildPageIndex(pages) {
|
|
111
|
+
const index = new Map();
|
|
112
|
+
for (const [id, page] of pages) {
|
|
113
|
+
const url = `/wiki/${id}`;
|
|
114
|
+
index.set(id.toLowerCase(), url);
|
|
115
|
+
index.set(page.title.toLowerCase(), url);
|
|
116
|
+
// Also map just the basename
|
|
117
|
+
const base = id.includes('/') ? id.split('/').pop() : id;
|
|
118
|
+
if (!index.has(base.toLowerCase())) {
|
|
119
|
+
index.set(base.toLowerCase(), url);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return index;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create configured markdown-it instance with wikilinks.
|
|
127
|
+
*/
|
|
128
|
+
function createMarkdownRenderer(pageIndex) {
|
|
129
|
+
const md = markdownIt({
|
|
130
|
+
html: true,
|
|
131
|
+
linkify: true,
|
|
132
|
+
typographer: true
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Wikilinks plugin — resolve [[page-name]] to wiki URLs
|
|
136
|
+
try {
|
|
137
|
+
const wikilinks = require('@ig3/markdown-it-wikilinks');
|
|
138
|
+
md.use(wikilinks({
|
|
139
|
+
baseURL: '/wiki/',
|
|
140
|
+
uriSuffix: '',
|
|
141
|
+
makeAllLinksAbsolute: true,
|
|
142
|
+
postProcessPageName: (pageName) => {
|
|
143
|
+
// Try exact lookup first
|
|
144
|
+
const lower = pageName.toLowerCase().replace(/\s+/g, '-');
|
|
145
|
+
if (pageIndex && pageIndex.has(lower)) {
|
|
146
|
+
// Return just the path part after /wiki/
|
|
147
|
+
return pageIndex.get(lower).replace('/wiki/', '');
|
|
148
|
+
}
|
|
149
|
+
return lower;
|
|
150
|
+
}
|
|
151
|
+
}));
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Wikilinks plugin not installed — render as plain text
|
|
154
|
+
// This is non-fatal; links just won't resolve
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return md;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Render a page object to HTML with TOC and wikilink extraction.
|
|
162
|
+
* @param {object} pageData - Page object from scanRoom
|
|
163
|
+
* @param {Map} pageIndex - Wikilink resolution index
|
|
164
|
+
* @param {Set} [contradictsTargets] - Set of artifact IDs that have CONTRADICTS relationship from this page
|
|
165
|
+
* @returns {{html, toc, frontmatter, wikilinks}}
|
|
166
|
+
*/
|
|
167
|
+
function renderPage(pageData, pageIndex, contradictsTargets) {
|
|
168
|
+
const md = createMarkdownRenderer(pageIndex);
|
|
169
|
+
let html = md.render(pageData.content);
|
|
170
|
+
|
|
171
|
+
// Post-process: mark CONTRADICTS wikilinks with special class
|
|
172
|
+
if (contradictsTargets && contradictsTargets.size > 0) {
|
|
173
|
+
// Match rendered wikilinks (from markdown-it-wikilinks) and add contradicts class
|
|
174
|
+
html = html.replace(/<a href="\/wiki\/([^"]+)"([^>]*)class="wikilink"([^>]*)>/g, (match, href, before, after) => {
|
|
175
|
+
const normalizedHref = href.toLowerCase();
|
|
176
|
+
for (const target of contradictsTargets) {
|
|
177
|
+
// Check if the link resolves to a contradicts target (by section or full id)
|
|
178
|
+
if (normalizedHref === target.toLowerCase() ||
|
|
179
|
+
normalizedHref.startsWith(target.toLowerCase().split('/')[0])) {
|
|
180
|
+
return `<a href="/wiki/${href}"${before}class="wikilink wikilink-contradicts"${after}>`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return match;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Extract TOC from rendered HTML (h2/h3)
|
|
188
|
+
const toc = extractTOC(html);
|
|
189
|
+
|
|
190
|
+
// Extract wikilinks from source content
|
|
191
|
+
const wikilinks = [];
|
|
192
|
+
const wlRegex = /\[\[([^\]]+)\]\]/g;
|
|
193
|
+
let match;
|
|
194
|
+
while ((match = wlRegex.exec(pageData.content)) !== null) {
|
|
195
|
+
wikilinks.push(match[1]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
html,
|
|
200
|
+
toc,
|
|
201
|
+
frontmatter: pageData.frontmatter,
|
|
202
|
+
wikilinks
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Extract TOC entries from rendered HTML.
|
|
208
|
+
* Returns array of { level, text, id }.
|
|
209
|
+
*/
|
|
210
|
+
function extractTOC(html) {
|
|
211
|
+
const toc = [];
|
|
212
|
+
const headingRegex = /<h([23])[^>]*(?:id="([^"]*)")?[^>]*>(.*?)<\/h[23]>/gi;
|
|
213
|
+
let match;
|
|
214
|
+
while ((match = headingRegex.exec(html)) !== null) {
|
|
215
|
+
const level = parseInt(match[1], 10);
|
|
216
|
+
const text = match[3].replace(/<[^>]+>/g, '').trim();
|
|
217
|
+
const id = match[2] || text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
|
218
|
+
toc.push({ level, text, id });
|
|
219
|
+
}
|
|
220
|
+
return toc;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
scanRoom,
|
|
225
|
+
renderPage,
|
|
226
|
+
buildPageIndex,
|
|
227
|
+
extractTOC,
|
|
228
|
+
SECTION_COLORS
|
|
229
|
+
};
|