@monoes/monomindcli 1.7.0 → 1.9.0
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/agents/design/design-monodesign.md +121 -0
- package/.claude/agents/github/issue-tracker.md +12 -12
- package/.claude/agents/github/pr-manager.md +10 -10
- package/.claude/agents/github/release-manager.md +49 -105
- package/.claude/agents/github/repo-architect.md +73 -92
- package/.claude/agents/github/sync-coordinator.md +55 -123
- package/.claude/agents/marketing/marketing-competitive-content.md +155 -0
- package/.claude/agents/marketing/marketing-content-creator.md +13 -0
- package/.claude/agents/marketing/marketing-cro-specialist.md +147 -0
- package/.claude/agents/marketing/marketing-email-specialist.md +90 -0
- package/.claude/agents/marketing/marketing-launch-strategist.md +129 -0
- package/.claude/agents/marketing/marketing-pricing-strategist.md +127 -0
- package/.claude/agents/specialists/integration-architect.md +94 -0
- package/.claude/commands/agents/README.md +4 -0
- package/.claude/commands/agents/agent-capabilities.md +6 -2
- package/.claude/commands/agents/agent-coordination.md +4 -0
- package/.claude/commands/agents/agent-spawning.md +4 -0
- package/.claude/commands/agents/agent-types.md +6 -2
- package/.claude/commands/analysis/README.md +14 -5
- package/.claude/commands/analysis/bottleneck-detect.md +30 -123
- package/.claude/commands/analysis/performance-bottlenecks.md +14 -14
- package/.claude/commands/analysis/performance-report.md +38 -11
- package/.claude/commands/analysis/token-efficiency.md +13 -16
- package/.claude/commands/analysis/token-usage.md +34 -12
- package/.claude/commands/automation/README.md +15 -5
- package/.claude/commands/automation/auto-agent.md +49 -85
- package/.claude/commands/automation/self-healing.md +20 -18
- package/.claude/commands/automation/session-memory.md +28 -29
- package/.claude/commands/automation/smart-agents.md +17 -9
- package/.claude/commands/automation/smart-spawn.md +52 -11
- package/.claude/commands/automation/workflow-select.md +46 -11
- package/.claude/commands/browse.md +5 -0
- package/.claude/commands/coordination/README.md +9 -5
- package/.claude/commands/coordination/agent-spawn.md +53 -9
- package/.claude/commands/coordination/swarm-init.md +39 -42
- package/.claude/commands/coordination/task-orchestrate.md +65 -11
- package/.claude/commands/github/README.md +21 -8
- package/.claude/commands/github/github-modes.md +9 -5
- package/.claude/commands/github/issue-tracker.md +34 -33
- package/.claude/commands/github/pr-manager.md +20 -17
- package/.claude/commands/github/release-manager.md +37 -49
- package/.claude/commands/github/repo-architect.md +39 -41
- package/.claude/commands/github/sync-coordinator.md +45 -49
- package/.claude/commands/hive-mind/README.md +42 -17
- package/.claude/commands/hive-mind/hive-mind-consensus.md +68 -4
- package/.claude/commands/hive-mind/hive-mind-init.md +55 -5
- package/.claude/commands/hive-mind/hive-mind-memory.md +69 -4
- package/.claude/commands/hive-mind/hive-mind-spawn.md +71 -10
- package/.claude/commands/hive-mind/hive-mind-status.md +52 -4
- package/.claude/commands/hive-mind/hive-mind-stop.md +51 -4
- package/.claude/commands/hive-mind/hive-mind.md +74 -14
- package/.claude/commands/hooks/README.md +62 -7
- package/.claude/commands/hooks/overview.md +94 -35
- package/.claude/commands/hooks/post-edit.md +48 -87
- package/.claude/commands/hooks/post-task.md +37 -87
- package/.claude/commands/hooks/pre-edit.md +52 -84
- package/.claude/commands/hooks/pre-task.md +46 -81
- package/.claude/commands/hooks/session-end.md +49 -85
- package/.claude/commands/hooks/setup.md +87 -58
- package/.claude/commands/mastermind/_repeat.md +308 -0
- package/.claude/commands/mastermind/architect.md +49 -0
- package/.claude/commands/mastermind/brain.md +98 -0
- package/.claude/commands/mastermind/build.md +22 -0
- package/.claude/commands/mastermind/content.md +22 -0
- package/.claude/commands/mastermind/createorg.md +94 -0
- package/.claude/commands/mastermind/finance.md +22 -0
- package/.claude/commands/mastermind/idea.md +22 -0
- package/.claude/commands/mastermind/marketing.md +22 -0
- package/.claude/commands/mastermind/master.md +379 -0
- package/.claude/commands/mastermind/ops.md +22 -0
- package/.claude/commands/mastermind/release.md +22 -0
- package/.claude/commands/mastermind/research.md +22 -0
- package/.claude/commands/mastermind/review.md +22 -0
- package/.claude/commands/mastermind/runorg.md +106 -0
- package/.claude/commands/mastermind/sales.md +22 -0
- package/.claude/commands/mastermind/techport.md +17 -0
- package/.claude/commands/memory/README.md +75 -5
- package/.claude/commands/memory/memory-search.md +63 -11
- package/.claude/commands/monitoring/README.md +64 -4
- package/.claude/commands/monitoring/agent-metrics.md +50 -10
- package/.claude/commands/monitoring/agents.md +59 -32
- package/.claude/commands/monitoring/status.md +96 -34
- package/.claude/commands/monograph/README.md +102 -0
- package/.claude/commands/monograph/monograph-build.md +79 -0
- package/.claude/commands/monograph/monograph-search.md +96 -0
- package/.claude/commands/monograph/monograph-stats.md +53 -0
- package/.claude/commands/monograph/monograph-watch.md +63 -0
- package/.claude/commands/monograph/monograph-wiki.md +91 -0
- package/.claude/commands/monomind/createtask.md +277 -0
- package/.claude/commands/{monomind-do.md → monomind/do.md} +22 -9
- package/.claude/commands/monomind/help.md +118 -0
- package/.claude/commands/{monomind-idea.md → monomind/idea.md} +23 -29
- package/.claude/commands/{monomind-improve.md → monomind/improve.md} +24 -30
- package/.claude/commands/monomind/memory.md +230 -0
- package/.claude/commands/monomind/repeat.md +201 -0
- package/.claude/commands/monomind/review.md +313 -0
- package/.claude/commands/monomind/specialagents.md +125 -0
- package/.claude/commands/monomind/swarm.md +161 -0
- package/.claude/commands/monomind/understand.md +148 -0
- package/.claude/commands/optimization/README.md +69 -5
- package/.claude/commands/optimization/auto-topology.md +66 -43
- package/.claude/commands/optimization/parallel-execution.md +65 -39
- package/.claude/commands/optimization/performance-optimize.md +79 -0
- package/.claude/commands/pair/README.md +48 -230
- package/.claude/commands/pair/examples.md +85 -441
- package/.claude/commands/pair/modes.md +77 -303
- package/.claude/commands/pair/session.md +76 -359
- package/.claude/commands/sparc/analyzer.md +9 -26
- package/.claude/commands/sparc/architect.md +8 -25
- package/.claude/commands/sparc/ask.md +27 -68
- package/.claude/commands/sparc/batch-executor.md +8 -25
- package/.claude/commands/sparc/code.md +12 -53
- package/.claude/commands/sparc/coder.md +8 -25
- package/.claude/commands/sparc/debug.md +12 -53
- package/.claude/commands/sparc/debugger.md +8 -25
- package/.claude/commands/sparc/designer.md +8 -25
- package/.claude/commands/sparc/devops.md +16 -57
- package/.claude/commands/sparc/docs-writer.md +12 -53
- package/.claude/commands/sparc/documenter.md +8 -25
- package/.claude/commands/sparc/innovator.md +8 -25
- package/.claude/commands/sparc/integration.md +12 -53
- package/.claude/commands/sparc/mcp.md +12 -53
- package/.claude/commands/sparc/memory-manager.md +28 -25
- package/.claude/commands/sparc/optimizer.md +8 -25
- package/.claude/commands/sparc/orchestrator.md +35 -97
- package/.claude/commands/sparc/post-deployment-monitoring-mode.md +13 -54
- package/.claude/commands/sparc/refinement-optimization-mode.md +13 -54
- package/.claude/commands/sparc/researcher.md +8 -25
- package/.claude/commands/sparc/reviewer.md +8 -25
- package/.claude/commands/sparc/security-review.md +13 -54
- package/.claude/commands/sparc/sparc-modes.md +97 -151
- package/.claude/commands/sparc/sparc.md +16 -56
- package/.claude/commands/sparc/spec-pseudocode.md +13 -54
- package/.claude/commands/sparc/supabase-admin.md +19 -66
- package/.claude/commands/sparc/swarm-coordinator.md +21 -25
- package/.claude/commands/sparc/tdd.md +8 -25
- package/.claude/commands/sparc/tester.md +8 -25
- package/.claude/commands/sparc/tutorial.md +12 -53
- package/.claude/commands/sparc/workflow-manager.md +8 -25
- package/.claude/commands/sparc.md +76 -130
- package/.claude/commands/stream-chain/pipeline.md +72 -77
- package/.claude/commands/stream-chain/run.md +133 -47
- package/.claude/commands/swarm/README.md +37 -12
- package/.claude/commands/swarm/analysis.md +47 -69
- package/.claude/commands/swarm/development.md +45 -69
- package/.claude/commands/swarm/examples.md +77 -142
- package/.claude/commands/swarm/maintenance.md +47 -74
- package/.claude/commands/swarm/optimization.md +54 -87
- package/.claude/commands/swarm/research.md +47 -107
- package/.claude/commands/swarm/swarm-analysis.md +58 -4
- package/.claude/commands/swarm/swarm-background.md +61 -4
- package/.claude/commands/swarm/swarm-modes.md +63 -4
- package/.claude/commands/swarm/swarm-monitor.md +50 -4
- package/.claude/commands/swarm/swarm-status.md +40 -4
- package/.claude/commands/swarm/swarm-strategies.md +73 -5
- package/.claude/commands/swarm/swarm.md +70 -18
- package/.claude/commands/swarm/testing.md +51 -102
- package/.claude/commands/tokens.md +6 -1
- package/.claude/commands/training/README.md +36 -6
- package/.claude/commands/training/model-update.md +68 -15
- package/.claude/commands/training/neural-patterns.md +54 -55
- package/.claude/commands/training/neural-train.md +70 -16
- package/.claude/commands/training/pattern-learn.md +60 -16
- package/.claude/commands/training/specialization.md +78 -49
- package/.claude/commands/truth/start.md +87 -109
- package/.claude/commands/ts.md +7 -2
- package/.claude/commands/verify/check.md +90 -34
- package/.claude/commands/verify/start.md +71 -94
- package/.claude/commands/workflows/README.md +62 -6
- package/.claude/commands/workflows/development.md +69 -61
- package/.claude/commands/workflows/research.md +73 -47
- package/.claude/commands/workflows/workflow-create.md +75 -16
- package/.claude/commands/workflows/workflow-execute.md +94 -16
- package/.claude/commands/workflows/workflow-export.md +81 -16
- package/.claude/helpers/control-start.cjs +91 -0
- package/.claude/helpers/extras-registry.json +4104 -1991
- package/.claude/helpers/graphify-freshen.cjs +44 -13
- package/.claude/helpers/hook-handler.cjs +256 -1
- package/.claude/helpers/loop-tracker.cjs +107 -0
- package/.claude/helpers/router.cjs +48 -68
- package/.claude/helpers/skill-registry.json +89 -104
- package/.claude/helpers/statusline.cjs +33 -2
- package/.claude/skills/.monomind/data/ranked-context.json +5 -0
- package/.claude/skills/.monomind/sessions/current.json +13 -0
- package/.claude/skills/.monomind/sessions/session-1777829336455.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777831614725.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777832095857.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777839814183.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777841847131.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777843309463.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777880867159.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777881884593.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777884090471.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777884808221.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777885672155.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777886852818.json +15 -0
- package/.claude/skills/.monomind/sessions/session-1777896532690.json +15 -0
- package/.claude/skills/agentdb-advanced/SKILL.md +11 -12
- package/.claude/skills/agentdb-learning/SKILL.md +20 -21
- package/.claude/skills/agentdb-memory-patterns/SKILL.md +28 -30
- package/.claude/skills/agentdb-optimization/SKILL.md +11 -12
- package/.claude/skills/agentdb-vector-search/SKILL.md +37 -41
- package/.claude/skills/{v3-integration-deep → agentic-integration}/SKILL.md +20 -13
- package/.claude/skills/agentic-jujutsu/SKILL.md +22 -22
- package/.claude/skills/{v3-cli-modernization → cli-modernization}/SKILL.md +17 -8
- package/.claude/skills/{v3-core-implementation → core-implementation}/SKILL.md +33 -8
- package/.claude/skills/{v3-ddd-architecture → ddd-architecture}/SKILL.md +18 -25
- package/.claude/skills/github-code-review/SKILL.md +82 -83
- package/.claude/skills/github-multi-repo/SKILL.md +42 -46
- package/.claude/skills/github-project-management/SKILL.md +83 -88
- package/.claude/skills/github-release-management/SKILL.md +12 -18
- package/.claude/skills/github-workflow-automation/SKILL.md +70 -74
- package/.claude/skills/hooks-automation/SKILL.md +9 -13
- package/.claude/skills/mastermind/_intake.md +83 -0
- package/.claude/skills/mastermind/_protocol.md +275 -0
- package/.claude/skills/mastermind/architect.md +847 -0
- package/.claude/skills/mastermind/build.md +158 -0
- package/.claude/skills/mastermind/content.md +185 -0
- package/.claude/skills/mastermind/createorg.md +318 -0
- package/.claude/skills/mastermind/finance.md +154 -0
- package/.claude/skills/mastermind/idea.md +158 -0
- package/.claude/skills/mastermind/marketing.md +216 -0
- package/.claude/skills/mastermind/monotask.md +350 -0
- package/.claude/skills/mastermind/ops.md +156 -0
- package/.claude/skills/mastermind/references/copywriting-frameworks.md +181 -0
- package/.claude/skills/mastermind/references/persuasion-psychology.md +158 -0
- package/.claude/skills/mastermind/release.md +156 -0
- package/.claude/skills/mastermind/research.md +156 -0
- package/.claude/skills/mastermind/review.md +157 -0
- package/.claude/skills/mastermind/runorg.md +308 -0
- package/.claude/skills/mastermind/sales.md +158 -0
- package/.claude/skills/mastermind/techport.md +743 -0
- package/.claude/skills/{v3-mcp-optimization → mcp-optimization}/SKILL.md +35 -14
- package/.claude/skills/{v3-memory-unification → memory-unification}/SKILL.md +20 -4
- package/.claude/skills/monodesign/SKILL.md +302 -0
- package/.claude/skills/monodesign/reference/adapt.md +190 -0
- package/.claude/skills/monodesign/reference/animate.md +175 -0
- package/.claude/skills/monodesign/reference/antipatterns-catalog.md +187 -0
- package/.claude/skills/monodesign/reference/audit.md +133 -0
- package/.claude/skills/monodesign/reference/bolder.md +113 -0
- package/.claude/skills/monodesign/reference/brand-workflow.md +180 -0
- package/.claude/skills/monodesign/reference/brand.md +114 -0
- package/.claude/skills/monodesign/reference/clarify.md +174 -0
- package/.claude/skills/monodesign/reference/cognitive-load.md +106 -0
- package/.claude/skills/monodesign/reference/color-and-contrast.md +105 -0
- package/.claude/skills/monodesign/reference/colorize.md +154 -0
- package/.claude/skills/monodesign/reference/component-specs.md +260 -0
- package/.claude/skills/monodesign/reference/component-states.md +274 -0
- package/.claude/skills/monodesign/reference/component-system.md +358 -0
- package/.claude/skills/monodesign/reference/copy-formulas.md +160 -0
- package/.claude/skills/monodesign/reference/craft.md +193 -0
- package/.claude/skills/monodesign/reference/critique.md +213 -0
- package/.claude/skills/monodesign/reference/delight.md +302 -0
- package/.claude/skills/monodesign/reference/design-principles.md +246 -0
- package/.claude/skills/monodesign/reference/distill.md +111 -0
- package/.claude/skills/monodesign/reference/document.md +427 -0
- package/.claude/skills/monodesign/reference/extract.md +69 -0
- package/.claude/skills/monodesign/reference/harden.md +347 -0
- package/.claude/skills/monodesign/reference/heuristics-scoring.md +234 -0
- package/.claude/skills/monodesign/reference/image-prompts.md +118 -0
- package/.claude/skills/monodesign/reference/interaction-design.md +195 -0
- package/.claude/skills/monodesign/reference/layout.md +141 -0
- package/.claude/skills/monodesign/reference/live.md +622 -0
- package/.claude/skills/monodesign/reference/motion-design.md +109 -0
- package/.claude/skills/monodesign/reference/onboard.md +234 -0
- package/.claude/skills/monodesign/reference/optimize.md +258 -0
- package/.claude/skills/monodesign/reference/overdrive.md +130 -0
- package/.claude/skills/monodesign/reference/personas.md +179 -0
- package/.claude/skills/monodesign/reference/polish.md +233 -0
- package/.claude/skills/monodesign/reference/pre-delivery-checklist.md +108 -0
- package/.claude/skills/monodesign/reference/product.md +62 -0
- package/.claude/skills/monodesign/reference/quieter.md +99 -0
- package/.claude/skills/monodesign/reference/responsive-design.md +114 -0
- package/.claude/skills/monodesign/reference/shape.md +151 -0
- package/.claude/skills/monodesign/reference/spatial-design.md +100 -0
- package/.claude/skills/monodesign/reference/teach.md +156 -0
- package/.claude/skills/monodesign/reference/token-architecture.md +222 -0
- package/.claude/skills/monodesign/reference/typeset.md +124 -0
- package/.claude/skills/monodesign/reference/typography.md +159 -0
- package/.claude/skills/monodesign/reference/ux-research.md +143 -0
- package/.claude/skills/monodesign/reference/ux-rules.md +211 -0
- package/.claude/skills/monodesign/reference/ux-writing.md +107 -0
- package/.claude/skills/monomotion/SKILL.md +145 -0
- package/.claude/skills/monomotion/rules/api-control.md +139 -0
- package/.claude/skills/monomotion/rules/effects.md +109 -0
- package/.claude/skills/monomotion/rules/integration.md +140 -0
- package/.claude/skills/monomotion/rules/scroll.md +131 -0
- package/.claude/skills/monomotion/rules/sequencing.md +105 -0
- package/.claude/skills/monomotion/rules/svg.md +101 -0
- package/.claude/skills/monomotion/rules/text.md +119 -0
- package/.claude/skills/pair-programming/SKILL.md +1 -1
- package/.claude/skills/performance-analysis/SKILL.md +3 -3
- package/.claude/skills/{v3-performance-optimization → performance-optimization}/SKILL.md +16 -8
- package/.claude/skills/reasoningbank-agentdb/SKILL.md +17 -19
- package/.claude/skills/reasoningbank-intelligence/SKILL.md +4 -6
- package/.claude/skills/{v3-security-overhaul → security-hardening}/SKILL.md +13 -3
- package/.claude/skills/skill-builder/SKILL.md +19 -19
- package/.claude/skills/sparc-methodology/SKILL.md +55 -211
- package/.claude/skills/stop-slop/SKILL.md +67 -0
- package/.claude/skills/stop-slop/references/examples.md +61 -0
- package/.claude/skills/stop-slop/references/phrases.md +130 -0
- package/.claude/skills/stop-slop/references/structures.md +136 -0
- package/.claude/skills/swarm-advanced/SKILL.md +13 -43
- package/.claude/skills/{v3-swarm-coordination → swarm-coordination}/SKILL.md +39 -21
- package/.claude/skills/swarm-orchestration/SKILL.md +12 -12
- package/.claude/skills/verification-quality/SKILL.md +5 -5
- package/README.md +5 -5
- package/bin/cli.js +78 -13
- package/dist/src/agents/halt-signal.d.ts.map +1 -1
- package/dist/src/agents/halt-signal.js +33 -7
- package/dist/src/agents/halt-signal.js.map +1 -1
- package/dist/src/agents/managed-agent.d.ts.map +1 -1
- package/dist/src/agents/managed-agent.js +5 -2
- package/dist/src/agents/managed-agent.js.map +1 -1
- package/dist/src/agents/prompt-experiment.d.ts +3 -2
- package/dist/src/agents/prompt-experiment.d.ts.map +1 -1
- package/dist/src/agents/prompt-experiment.js +1 -1
- package/dist/src/agents/prompt-experiment.js.map +1 -1
- package/dist/src/agents/prompt-version-manager.d.ts +5 -2
- package/dist/src/agents/prompt-version-manager.d.ts.map +1 -1
- package/dist/src/agents/prompt-version-manager.js +26 -4
- package/dist/src/agents/prompt-version-manager.js.map +1 -1
- package/dist/src/agents/specialization-scorer.d.ts.map +1 -1
- package/dist/src/agents/specialization-scorer.js +17 -9
- package/dist/src/agents/specialization-scorer.js.map +1 -1
- package/dist/src/agents/trigger-scanner.d.ts +5 -3
- package/dist/src/agents/trigger-scanner.d.ts.map +1 -1
- package/dist/src/agents/trigger-scanner.js +58 -10
- package/dist/src/agents/trigger-scanner.js.map +1 -1
- package/dist/src/agents/version-store.d.ts +0 -1
- package/dist/src/agents/version-store.d.ts.map +1 -1
- package/dist/src/agents/version-store.js +44 -21
- package/dist/src/agents/version-store.js.map +1 -1
- package/dist/src/autopilot-state.d.ts.map +1 -1
- package/dist/src/autopilot-state.js +79 -28
- package/dist/src/autopilot-state.js.map +1 -1
- package/dist/src/benchmarks/benchmark-runner.d.ts +7 -2
- package/dist/src/benchmarks/benchmark-runner.d.ts.map +1 -1
- package/dist/src/benchmarks/benchmark-runner.js +20 -8
- package/dist/src/benchmarks/benchmark-runner.js.map +1 -1
- package/dist/src/benchmarks/metric-evaluators.d.ts +2 -1
- package/dist/src/benchmarks/metric-evaluators.d.ts.map +1 -1
- package/dist/src/benchmarks/metric-evaluators.js +25 -2
- package/dist/src/benchmarks/metric-evaluators.js.map +1 -1
- package/dist/src/commands/agent.d.ts.map +1 -1
- package/dist/src/commands/agent.js +6 -4
- package/dist/src/commands/agent.js.map +1 -1
- package/dist/src/commands/appliance-advanced.d.ts.map +1 -1
- package/dist/src/commands/appliance-advanced.js +23 -0
- package/dist/src/commands/appliance-advanced.js.map +1 -1
- package/dist/src/commands/autopilot.d.ts.map +1 -1
- package/dist/src/commands/autopilot.js +3 -3
- package/dist/src/commands/autopilot.js.map +1 -1
- package/dist/src/commands/benchmark.d.ts.map +1 -1
- package/dist/src/commands/benchmark.js +119 -8
- package/dist/src/commands/benchmark.js.map +1 -1
- package/dist/src/commands/claims.d.ts.map +1 -1
- package/dist/src/commands/claims.js +22 -14
- package/dist/src/commands/claims.js.map +1 -1
- package/dist/src/commands/config.d.ts.map +1 -1
- package/dist/src/commands/config.js +32 -0
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +13 -11
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/commands/deployment.d.ts.map +1 -1
- package/dist/src/commands/deployment.js +21 -2
- package/dist/src/commands/deployment.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +28 -62
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/embeddings.d.ts.map +1 -1
- package/dist/src/commands/embeddings.js +124 -48
- package/dist/src/commands/embeddings.js.map +1 -1
- package/dist/src/commands/hive-mind.d.ts.map +1 -1
- package/dist/src/commands/hive-mind.js +15 -14
- package/dist/src/commands/hive-mind.js.map +1 -1
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +45 -41
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/index.d.ts +2 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +20 -7
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +53 -19
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +31 -44
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/memory.d.ts.map +1 -1
- package/dist/src/commands/memory.js +47 -15
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +156 -108
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/monograph.d.ts +8 -0
- package/dist/src/commands/monograph.d.ts.map +1 -0
- package/dist/src/commands/monograph.js +526 -0
- package/dist/src/commands/monograph.js.map +1 -0
- package/dist/src/commands/neural.d.ts.map +1 -1
- package/dist/src/commands/neural.js +96 -56
- package/dist/src/commands/neural.js.map +1 -1
- package/dist/src/commands/performance.d.ts.map +1 -1
- package/dist/src/commands/performance.js +30 -8
- package/dist/src/commands/performance.js.map +1 -1
- package/dist/src/commands/plugins.d.ts.map +1 -1
- package/dist/src/commands/plugins.js +13 -37
- package/dist/src/commands/plugins.js.map +1 -1
- package/dist/src/commands/process.d.ts.map +1 -1
- package/dist/src/commands/process.js +25 -2
- package/dist/src/commands/process.js.map +1 -1
- package/dist/src/commands/providers.d.ts.map +1 -1
- package/dist/src/commands/providers.js +37 -5
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/commands/replay.js +4 -4
- package/dist/src/commands/replay.js.map +1 -1
- package/dist/src/commands/route.d.ts.map +1 -1
- package/dist/src/commands/route.js +37 -5
- package/dist/src/commands/route.js.map +1 -1
- package/dist/src/commands/ruvector/import.d.ts.map +1 -1
- package/dist/src/commands/ruvector/import.js +12 -2
- package/dist/src/commands/ruvector/import.js.map +1 -1
- package/dist/src/commands/ruvector/init.d.ts.map +1 -1
- package/dist/src/commands/ruvector/init.js +15 -0
- package/dist/src/commands/ruvector/init.js.map +1 -1
- package/dist/src/commands/ruvector/status.d.ts.map +1 -1
- package/dist/src/commands/ruvector/status.js +16 -3
- package/dist/src/commands/ruvector/status.js.map +1 -1
- package/dist/src/commands/security.d.ts.map +1 -1
- package/dist/src/commands/security.js +342 -193
- package/dist/src/commands/security.js.map +1 -1
- package/dist/src/commands/session.d.ts.map +1 -1
- package/dist/src/commands/session.js +51 -8
- package/dist/src/commands/session.js.map +1 -1
- package/dist/src/commands/start.d.ts.map +1 -1
- package/dist/src/commands/start.js +18 -4
- package/dist/src/commands/start.js.map +1 -1
- package/dist/src/commands/swarm.d.ts.map +1 -1
- package/dist/src/commands/swarm.js +47 -36
- package/dist/src/commands/swarm.js.map +1 -1
- package/dist/src/commands/tokens.js +11 -11
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/transfer-store.js +1 -1
- package/dist/src/commands/transfer-store.js.map +1 -1
- package/dist/src/commands/workflow.d.ts.map +1 -1
- package/dist/src/commands/workflow.js +31 -4
- package/dist/src/commands/workflow.js.map +1 -1
- package/dist/src/config-adapter.d.ts +2 -1
- package/dist/src/config-adapter.d.ts.map +1 -1
- package/dist/src/config-adapter.js.map +1 -1
- package/dist/src/consensus/audit-writer.d.ts.map +1 -1
- package/dist/src/consensus/audit-writer.js +46 -13
- package/dist/src/consensus/audit-writer.js.map +1 -1
- package/dist/src/consensus/vote-signer.d.ts +0 -3
- package/dist/src/consensus/vote-signer.d.ts.map +1 -1
- package/dist/src/consensus/vote-signer.js +9 -1
- package/dist/src/consensus/vote-signer.js.map +1 -1
- package/dist/src/dlq/dlq-reader.d.ts +4 -2
- package/dist/src/dlq/dlq-reader.d.ts.map +1 -1
- package/dist/src/dlq/dlq-reader.js +25 -8
- package/dist/src/dlq/dlq-reader.js.map +1 -1
- package/dist/src/dlq/dlq-replayer.d.ts +10 -3
- package/dist/src/dlq/dlq-replayer.d.ts.map +1 -1
- package/dist/src/dlq/dlq-replayer.js +50 -16
- package/dist/src/dlq/dlq-replayer.js.map +1 -1
- package/dist/src/dlq/dlq-writer.d.ts.map +1 -1
- package/dist/src/dlq/dlq-writer.js +27 -5
- package/dist/src/dlq/dlq-writer.js.map +1 -1
- package/dist/src/eval/dataset-manager.d.ts +2 -2
- package/dist/src/eval/dataset-manager.d.ts.map +1 -1
- package/dist/src/eval/dataset-manager.js +26 -16
- package/dist/src/eval/dataset-manager.js.map +1 -1
- package/dist/src/eval/trace-collector.d.ts.map +1 -1
- package/dist/src/eval/trace-collector.js +23 -3
- package/dist/src/eval/trace-collector.js.map +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/init/claudemd-generator.js +8 -8
- package/dist/src/init/claudemd-generator.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +163 -137
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/helpers-generator.d.ts.map +1 -1
- package/dist/src/init/helpers-generator.js +49 -36
- package/dist/src/init/helpers-generator.js.map +1 -1
- package/dist/src/init/mcp-generator.js +3 -3
- package/dist/src/init/mcp-generator.js.map +1 -1
- package/dist/src/init/settings-generator.d.ts.map +1 -1
- package/dist/src/init/settings-generator.js +10 -3
- package/dist/src/init/settings-generator.js.map +1 -1
- package/dist/src/init/shared-instructions-generator.d.ts.map +1 -1
- package/dist/src/init/shared-instructions-generator.js +18 -3
- package/dist/src/init/shared-instructions-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +3 -1
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/init/types.d.ts +35 -11
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js +5 -9
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/interactive/interrupt.d.ts.map +1 -1
- package/dist/src/interactive/interrupt.js +8 -3
- package/dist/src/interactive/interrupt.js.map +1 -1
- package/dist/src/mcp/tool-registry.d.ts.map +1 -1
- package/dist/src/mcp/tool-registry.js +38 -4
- package/dist/src/mcp/tool-registry.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +15 -6
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-server.d.ts +9 -2
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +182 -35
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-tools.js +66 -34
- package/dist/src/mcp-tools/agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.js +34 -7
- package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/analyze-tools.js +25 -16
- package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
- package/dist/src/mcp-tools/auto-install.d.ts.map +1 -1
- package/dist/src/mcp-tools/auto-install.js +4 -6
- package/dist/src/mcp-tools/auto-install.js.map +1 -1
- package/dist/src/mcp-tools/autopilot-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/autopilot-tools.js +12 -2
- package/dist/src/mcp-tools/autopilot-tools.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +199 -20
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/claims-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/claims-tools.js +68 -18
- package/dist/src/mcp-tools/claims-tools.js.map +1 -1
- package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/config-tools.js +33 -5
- package/dist/src/mcp-tools/config-tools.js.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/coordination-tools.js +59 -4
- package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
- package/dist/src/mcp-tools/daa-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/daa-tools.js +46 -10
- package/dist/src/mcp-tools/daa-tools.js.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/embeddings-tools.js +46 -5
- package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
- package/dist/src/mcp-tools/github-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/github-tools.js +29 -16
- package/dist/src/mcp-tools/github-tools.js.map +1 -1
- package/dist/src/mcp-tools/graphify-tools.d.ts +4 -67
- package/dist/src/mcp-tools/graphify-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/graphify-tools.js +40 -1250
- package/dist/src/mcp-tools/graphify-tools.js.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.js +38 -10
- package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +96 -33
- package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +70 -37
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +1 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/memory-tools.js +29 -13
- package/dist/src/mcp-tools/memory-tools.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts +9 -0
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/monograph-tools.js +6306 -0
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -0
- package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +121 -37
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/mcp-tools/performance-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/performance-tools.js +21 -8
- package/dist/src/mcp-tools/performance-tools.js.map +1 -1
- package/dist/src/mcp-tools/progress-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/progress-tools.js +10 -8
- package/dist/src/mcp-tools/progress-tools.js.map +1 -1
- package/dist/src/mcp-tools/request-tracker.d.ts.map +1 -1
- package/dist/src/mcp-tools/request-tracker.js +4 -1
- package/dist/src/mcp-tools/request-tracker.js.map +1 -1
- package/dist/src/mcp-tools/ruvllm-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/ruvllm-tools.js +19 -8
- package/dist/src/mcp-tools/ruvllm-tools.js.map +1 -1
- package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/session-tools.js +57 -17
- package/dist/src/mcp-tools/session-tools.js.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/swarm-tools.js +35 -17
- package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +4 -3
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/task-tools.js +53 -13
- package/dist/src/mcp-tools/task-tools.js.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +63 -14
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/transfer-tools.js +21 -16
- package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.js +92 -23
- package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
- package/dist/src/memory/ewc-consolidation.d.ts.map +1 -1
- package/dist/src/memory/ewc-consolidation.js +41 -10
- package/dist/src/memory/ewc-consolidation.js.map +1 -1
- package/dist/src/memory/intelligence.d.ts +2 -2
- package/dist/src/memory/intelligence.d.ts.map +1 -1
- package/dist/src/memory/intelligence.js +39 -13
- package/dist/src/memory/intelligence.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts +1 -0
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +149 -56
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +107 -45
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/memory/sona-optimizer.d.ts +8 -1
- package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
- package/dist/src/memory/sona-optimizer.js +25 -8
- package/dist/src/memory/sona-optimizer.js.map +1 -1
- package/dist/src/observability/replay-reader.d.ts +40 -0
- package/dist/src/observability/replay-reader.d.ts.map +1 -0
- package/dist/src/observability/replay-reader.js +138 -0
- package/dist/src/observability/replay-reader.js.map +1 -0
- package/dist/src/orchestration/routing-modes.d.ts.map +1 -1
- package/dist/src/orchestration/routing-modes.js +35 -5
- package/dist/src/orchestration/routing-modes.js.map +1 -1
- package/dist/src/parser.d.ts +8 -0
- package/dist/src/parser.d.ts.map +1 -1
- package/dist/src/parser.js +48 -14
- package/dist/src/parser.js.map +1 -1
- package/dist/src/plugins/manager.d.ts.map +1 -1
- package/dist/src/plugins/manager.js +112 -19
- package/dist/src/plugins/manager.js.map +1 -1
- package/dist/src/plugins/store/discovery.d.ts +1 -1
- package/dist/src/plugins/store/discovery.d.ts.map +1 -1
- package/dist/src/plugins/store/discovery.js +80 -62
- package/dist/src/plugins/store/discovery.js.map +1 -1
- package/dist/src/production/circuit-breaker.d.ts.map +1 -1
- package/dist/src/production/circuit-breaker.js +8 -1
- package/dist/src/production/circuit-breaker.js.map +1 -1
- package/dist/src/production/error-handler.d.ts +4 -2
- package/dist/src/production/error-handler.d.ts.map +1 -1
- package/dist/src/production/error-handler.js +27 -5
- package/dist/src/production/error-handler.js.map +1 -1
- package/dist/src/production/monitoring.d.ts.map +1 -1
- package/dist/src/production/monitoring.js +8 -4
- package/dist/src/production/monitoring.js.map +1 -1
- package/dist/src/production/rate-limiter.d.ts.map +1 -1
- package/dist/src/production/rate-limiter.js +30 -22
- package/dist/src/production/rate-limiter.js.map +1 -1
- package/dist/src/ruvector/agent-wasm.js +2 -2
- package/dist/src/ruvector/agent-wasm.js.map +1 -1
- package/dist/src/ruvector/coverage-router.d.ts.map +1 -1
- package/dist/src/ruvector/coverage-router.js +19 -9
- package/dist/src/ruvector/coverage-router.js.map +1 -1
- package/dist/src/ruvector/diff-classifier.d.ts +1 -0
- package/dist/src/ruvector/diff-classifier.d.ts.map +1 -1
- package/dist/src/ruvector/diff-classifier.js +26 -6
- package/dist/src/ruvector/diff-classifier.js.map +1 -1
- package/dist/src/ruvector/enhanced-model-router.d.ts.map +1 -1
- package/dist/src/ruvector/enhanced-model-router.js +24 -2
- package/dist/src/ruvector/enhanced-model-router.js.map +1 -1
- package/dist/src/ruvector/index.d.ts +1 -2
- package/dist/src/ruvector/index.d.ts.map +1 -1
- package/dist/src/ruvector/index.js +2 -2
- package/dist/src/ruvector/index.js.map +1 -1
- package/dist/src/ruvector/model-router.d.ts +4 -2
- package/dist/src/ruvector/model-router.d.ts.map +1 -1
- package/dist/src/ruvector/model-router.js +30 -6
- package/dist/src/ruvector/model-router.js.map +1 -1
- package/dist/src/ruvector/moe-router.d.ts +7 -0
- package/dist/src/ruvector/moe-router.d.ts.map +1 -1
- package/dist/src/ruvector/moe-router.js +35 -12
- package/dist/src/ruvector/moe-router.js.map +1 -1
- package/dist/src/ruvector/q-learning-router.d.ts +7 -1
- package/dist/src/ruvector/q-learning-router.d.ts.map +1 -1
- package/dist/src/ruvector/q-learning-router.js +40 -9
- package/dist/src/ruvector/q-learning-router.js.map +1 -1
- package/dist/src/services/claim-service.d.ts +3 -1
- package/dist/src/services/claim-service.d.ts.map +1 -1
- package/dist/src/services/claim-service.js +33 -2
- package/dist/src/services/claim-service.js.map +1 -1
- package/dist/src/services/config-file-manager.d.ts +16 -2
- package/dist/src/services/config-file-manager.d.ts.map +1 -1
- package/dist/src/services/config-file-manager.js +105 -17
- package/dist/src/services/config-file-manager.js.map +1 -1
- package/dist/src/services/container-worker-pool.d.ts.map +1 -1
- package/dist/src/services/container-worker-pool.js +51 -11
- package/dist/src/services/container-worker-pool.js.map +1 -1
- package/dist/src/services/headless-worker-executor.d.ts +7 -0
- package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
- package/dist/src/services/headless-worker-executor.js +188 -45
- package/dist/src/services/headless-worker-executor.js.map +1 -1
- package/dist/src/services/registry-api.d.ts.map +1 -1
- package/dist/src/services/registry-api.js +62 -9
- package/dist/src/services/registry-api.js.map +1 -1
- package/dist/src/services/ruvector-training.d.ts.map +1 -1
- package/dist/src/services/ruvector-training.js +8 -0
- package/dist/src/services/ruvector-training.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts +4 -1
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +112 -28
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/src/services/worker-queue.d.ts +9 -2
- package/dist/src/services/worker-queue.d.ts.map +1 -1
- package/dist/src/services/worker-queue.js +86 -5
- package/dist/src/services/worker-queue.js.map +1 -1
- package/dist/src/suggest.d.ts.map +1 -1
- package/dist/src/suggest.js +9 -0
- package/dist/src/suggest.js.map +1 -1
- package/dist/src/swarm/flow-enforcer.d.ts +5 -3
- package/dist/src/swarm/flow-enforcer.d.ts.map +1 -1
- package/dist/src/swarm/flow-enforcer.js +17 -5
- package/dist/src/swarm/flow-enforcer.js.map +1 -1
- package/dist/src/swarm/flow-visualizer.d.ts +3 -0
- package/dist/src/swarm/flow-visualizer.d.ts.map +1 -1
- package/dist/src/swarm/flow-visualizer.js +30 -6
- package/dist/src/swarm/flow-visualizer.js.map +1 -1
- package/dist/src/transfer/anonymization/index.d.ts.map +1 -1
- package/dist/src/transfer/anonymization/index.js +5 -3
- package/dist/src/transfer/anonymization/index.js.map +1 -1
- package/dist/src/transfer/export.d.ts.map +1 -1
- package/dist/src/transfer/export.js +5 -3
- package/dist/src/transfer/export.js.map +1 -1
- package/dist/src/transfer/ipfs/client.d.ts.map +1 -1
- package/dist/src/transfer/ipfs/client.js +84 -7
- package/dist/src/transfer/ipfs/client.js.map +1 -1
- package/dist/src/transfer/ipfs/upload.d.ts.map +1 -1
- package/dist/src/transfer/ipfs/upload.js +13 -4
- package/dist/src/transfer/ipfs/upload.js.map +1 -1
- package/dist/src/transfer/storage/gcs.d.ts.map +1 -1
- package/dist/src/transfer/storage/gcs.js +19 -10
- package/dist/src/transfer/storage/gcs.js.map +1 -1
- package/dist/src/transfer/store/discovery.d.ts +9 -2
- package/dist/src/transfer/store/discovery.d.ts.map +1 -1
- package/dist/src/transfer/store/discovery.js +68 -13
- package/dist/src/transfer/store/discovery.js.map +1 -1
- package/dist/src/transfer/store/download.d.ts +15 -6
- package/dist/src/transfer/store/download.d.ts.map +1 -1
- package/dist/src/transfer/store/download.js +113 -24
- package/dist/src/transfer/store/download.js.map +1 -1
- package/dist/src/transfer/store/publish.d.ts +1 -1
- package/dist/src/transfer/store/publish.d.ts.map +1 -1
- package/dist/src/transfer/store/publish.js +13 -14
- package/dist/src/transfer/store/publish.js.map +1 -1
- package/dist/src/transfer/store/registry.d.ts +3 -3
- package/dist/src/transfer/store/registry.d.ts.map +1 -1
- package/dist/src/transfer/store/registry.js +32 -16
- package/dist/src/transfer/store/registry.js.map +1 -1
- package/dist/src/ui/.monomind/sessions/current.json +2 -2
- package/dist/src/ui/dashboard.html +2507 -187
- package/dist/src/ui/server.mjs +1343 -153
- package/dist/src/update/checker.d.ts.map +1 -1
- package/dist/src/update/checker.js +17 -4
- package/dist/src/update/checker.js.map +1 -1
- package/dist/src/update/executor.d.ts.map +1 -1
- package/dist/src/update/executor.js +25 -20
- package/dist/src/update/executor.js.map +1 -1
- package/dist/src/update/rate-limiter.d.ts +11 -0
- package/dist/src/update/rate-limiter.d.ts.map +1 -1
- package/dist/src/update/rate-limiter.js +23 -3
- package/dist/src/update/rate-limiter.js.map +1 -1
- package/dist/src/utils/parse-jsonl.d.ts +6 -0
- package/dist/src/utils/parse-jsonl.d.ts.map +1 -0
- package/dist/src/utils/parse-jsonl.js +22 -0
- package/dist/src/utils/parse-jsonl.js.map +1 -0
- package/dist/src/workflow/condition-evaluator.d.ts.map +1 -1
- package/dist/src/workflow/condition-evaluator.js +37 -3
- package/dist/src/workflow/condition-evaluator.js.map +1 -1
- package/dist/src/workflow/dag-builder.d.ts.map +1 -1
- package/dist/src/workflow/dag-builder.js +27 -11
- package/dist/src/workflow/dag-builder.js.map +1 -1
- package/dist/src/workflow/dag-executor.d.ts.map +1 -1
- package/dist/src/workflow/dag-executor.js +51 -13
- package/dist/src/workflow/dag-executor.js.map +1 -1
- package/dist/src/workflow/dsl-schema.d.ts +3 -0
- package/dist/src/workflow/dsl-schema.d.ts.map +1 -1
- package/dist/src/workflow/dsl-schema.js +6 -2
- package/dist/src/workflow/dsl-schema.js.map +1 -1
- package/dist/src/workflow/template-engine.js +7 -0
- package/dist/src/workflow/template-engine.js.map +1 -1
- package/dist/src/workflow/workflow-executor.d.ts.map +1 -1
- package/dist/src/workflow/workflow-executor.js +95 -14
- package/dist/src/workflow/workflow-executor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -2
- package/.claude/agents/design/design-brand-guardian.md +0 -323
- package/.claude/agents/design/design-image-prompt-engineer.md +0 -237
- package/.claude/agents/design/design-inclusive-visuals-specialist.md +0 -72
- package/.claude/agents/design/design-ui-designer.md +0 -384
- package/.claude/agents/design/design-ux-architect.md +0 -470
- package/.claude/agents/design/design-ux-researcher.md +0 -330
- package/.claude/agents/design/design-visual-storyteller.md +0 -150
- package/.claude/agents/design/design-whimsy-injector.md +0 -439
- package/.claude/agents/v3/integration-architect.md +0 -338
- package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
- package/.claude/commands/coordination/init.md +0 -44
- package/.claude/commands/coordination/orchestrate.md +0 -43
- package/.claude/commands/coordination/spawn.md +0 -45
- package/.claude/commands/github/code-review-swarm.md +0 -550
- package/.claude/commands/github/code-review.md +0 -25
- package/.claude/commands/github/github-swarm.md +0 -121
- package/.claude/commands/github/issue-triage.md +0 -25
- package/.claude/commands/github/multi-repo-swarm.md +0 -519
- package/.claude/commands/github/pr-enhance.md +0 -26
- package/.claude/commands/github/project-board-sync.md +0 -471
- package/.claude/commands/github/release-swarm.md +0 -590
- package/.claude/commands/github/repo-analyze.md +0 -25
- package/.claude/commands/github/swarm-issue.md +0 -482
- package/.claude/commands/github/swarm-pr.md +0 -310
- package/.claude/commands/github/workflow-automation.md +0 -468
- package/.claude/commands/hive-mind/hive-mind-metrics.md +0 -8
- package/.claude/commands/hive-mind/hive-mind-resume.md +0 -8
- package/.claude/commands/hive-mind/hive-mind-sessions.md +0 -8
- package/.claude/commands/hive-mind/hive-mind-wizard.md +0 -8
- package/.claude/commands/list-agents.md +0 -17
- package/.claude/commands/memory/memory-persist.md +0 -25
- package/.claude/commands/memory/memory-usage.md +0 -25
- package/.claude/commands/memory/neural.md +0 -47
- package/.claude/commands/metrics.md +0 -11
- package/.claude/commands/monitoring/real-time-view.md +0 -25
- package/.claude/commands/monitoring/swarm-monitor.md +0 -25
- package/.claude/commands/monomind-createtask.md +0 -302
- package/.claude/commands/monomind-help.md +0 -103
- package/.claude/commands/monomind-memory.md +0 -107
- package/.claude/commands/monomind-repeat.md +0 -149
- package/.claude/commands/monomind-swarm.md +0 -205
- package/.claude/commands/optimization/cache-manage.md +0 -25
- package/.claude/commands/optimization/topology-optimize.md +0 -25
- package/.claude/commands/pair/commands.md +0 -546
- package/.claude/commands/pair/config.md +0 -510
- package/.claude/commands/pair/start.md +0 -209
- package/.claude/commands/use-agent.md +0 -67
- package/.claude/skills/monomind-createtask/SKILL.md +0 -269
- package/.claude/skills/monomind-task-engine/SKILL.md +0 -358
- /package/.claude/agents/{v3 → specialists}/memory-specialist.md +0 -0
- /package/.claude/agents/{v3 → specialists}/performance-engineer.md +0 -0
- /package/.claude/agents/{v3 → specialists}/queen-coordinator.md +0 -0
- /package/.claude/agents/{v3 → specialists}/security-architect.md +0 -0
package/dist/src/ui/server.mjs
CHANGED
|
@@ -3,11 +3,29 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { createRequire } from 'module';
|
|
6
7
|
import { collectAll, getWatchPaths, collectProject, collectSessions, collectSwarm, collectSwarmHistory, appendSwarmHistory, collectSwarmEvents, getSwarmDataSize, cleanSwarmData, collectAgents, collectTokens, collectHooks, collectKnowledge, collectMetrics, collectMemory, collectMemoryFiles, collectSystem } from './collector.mjs';
|
|
7
8
|
|
|
8
9
|
const JSONL_SIZE_CAP = 10 * 1024 * 1024; // 10 MB — skip files larger than this in /api/graph
|
|
10
|
+
const buildDocsState = new Map(); // key: resolved dir → { status, sections, files, error, startedAt, completedAt }
|
|
9
11
|
|
|
10
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const MASTERMIND_DIAGRAM_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>MASTERMIND — Live Dashboard</title>\n<style>\n* { box-sizing: border-box; margin: 0; padding: 0; }\nhtml, body {\n width: 100%; height: 100%; overflow: hidden;\n background: #07071a;\n font-family: 'Azeret Mono', 'Space Mono', 'Courier New', monospace;\n color: #e0e0ff;\n user-select: none;\n}\n\n/* ── Main layout ── */\n#app { display: flex; height: 100vh; }\n#sidebar {\n width: 220px; flex-shrink: 0;\n background: oklch(9% 0.012 186);\n border-right: 1px solid oklch(62% 0.2 186 / 0.18);\n display: flex; flex-direction: column;\n overflow: hidden; z-index: 10;\n}\n#stage-wrap { flex: 1; position: relative; overflow: hidden; }\n#detail-panel {\n width: 0; flex-shrink: 0; overflow: hidden;\n background: oklch(9% 0.012 186);\n border-left: 1px solid oklch(62% 0.2 186 / 0.18);\n transition: width 0.3s ease;\n display: flex; flex-direction: column;\n z-index: 10;\n}\n#detail-panel.open { width: 280px; }\n#stage { position: absolute; inset: 0; width: 100%; height: 100%; }\n\n/* ── Sidebar ── */\n#sb-header {\n padding: 14px 14px 10px;\n border-bottom: 1px solid oklch(62% 0.2 186 / 0.18);\n flex-shrink: 0;\n}\n#sb-title {\n font-size: 8px; letter-spacing: 4px; color: oklch(52% 0.1 186); margin-bottom: 4px;\n}\n.live-row { display: flex; align-items: center; gap: 6px; }\n.l-dot {\n width: 6px; height: 6px; border-radius: 50%;\n background: #252560; flex-shrink: 0;\n transition: background 0.5s;\n}\n.l-dot.on { background: #28c068; }\n@media (prefers-reduced-motion: no-preference) { .l-dot.on { animation: ldp 2s ease-in-out infinite; } }\n@keyframes ldp { 0%,100%{opacity:1} 50%{opacity:0.4} }\n#l-status { font-size: 9px; letter-spacing: 2px; color: oklch(44% 0.08 186); }\n#l-agents { font-size: 8px; color: oklch(40% 0.07 186); margin-left: auto; }\n#sb-sessions {\n flex: 1; overflow-y: auto; padding: 8px 0;\n scrollbar-width: thin; scrollbar-color: oklch(62% 0.2 186 / 0.3) transparent;\n}\n#sb-sessions::-webkit-scrollbar { width: 4px; }\n#sb-sessions::-webkit-scrollbar-thumb { background: oklch(62% 0.2 186 / 0.3); border-radius: 2px; }\n.sess-item {\n padding: 8px 14px; cursor: pointer;\n border-left: 2px solid transparent;\n transition: background 0.15s, border-color 0.15s;\n}\n.sess-item:hover { background: oklch(62% 0.2 186 / 0.09); }\n.sess-item.active { border-left-color: transparent; background: oklch(62% 0.2 186 / 0.14); box-shadow: inset 0 0 0 1px oklch(62% 0.2 186 / 0.32); }\n.sess-item.running { border-left-color: #28c068; }\n.sess-ts { font-size: 10px; color: oklch(42% 0.05 186); margin-bottom: 3px; }\n.sess-prompt {\n font-size: 12px; color: oklch(70% 0.05 186); line-height: 1.4;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 188px;\n}\n.sess-badges { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 4px; }\n.sess-badge {\n font-size: 8px; padding: 2px 6px; border-radius: 3px;\n border: 1px solid oklch(62% 0.2 186 / 0.25); color: oklch(62% 0.09 186);\n background: oklch(62% 0.2 186 / 0.08);\n}\n.sess-badge.running-badge { border-color: rgba(40,192,104,0.4); color: #28c068; background: rgba(40,192,104,0.08); }\n#git-user-row {\n display: flex; align-items: center; gap: 5px;\n margin-top: 7px; padding-top: 6px;\n border-top: 1px solid oklch(62% 0.2 186 / 0.12);\n}\n#git-user-icon { font-size: 9px; color: #3a3a70; }\n#git-user-name {\n font-size: 9px; letter-spacing: 0.5px; color: #4a4a90;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n#git-cwd-row {\n display: flex; align-items: center; gap: 5px; margin-top: 4px;\n}\n#git-cwd-icon { font-size: 9px; color: #2a2a58; }\n#git-cwd-name {\n font-size: 9px; letter-spacing: 0.3px; color: #38386a;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n direction: rtl; text-align: left;\n}\n.sess-trace-link {\n font-size: 7px; color: #3a3a70; text-decoration: none; letter-spacing: 0.5px;\n padding: 1px 5px; border: 1px solid oklch(62% 0.2 186 / 0.2); border-radius: 3px;\n margin-left: auto; flex-shrink: 0;\n}\n.sess-trace-link:hover { color: oklch(66% 0.11 186); border-color: oklch(62% 0.2 186 / 0.5); }\n.dp-export-btn {\n font-size: 9px; font-family: inherit; color: oklch(58% 0.09 186); text-decoration: none;\n padding: 4px 8px; border: 1px solid oklch(62% 0.2 186 / 0.25); border-radius: 4px;\n background: oklch(62% 0.2 186 / 0.07); cursor: pointer; letter-spacing: 0.3px;\n}\n.dp-export-btn:hover { color: oklch(72% 0.12 186); border-color: oklch(62% 0.2 186 / 0.5); background: oklch(62% 0.2 186 / 0.15); }\n#sb-no-sessions {\n padding: 20px 14px; font-size: 9px; color: oklch(42% 0.06 186); line-height: 1.7;\n text-align: center;\n}\n#sb-movie-btn {\n margin: 10px 14px;\n background: oklch(62% 0.2 186 / 0.12);\n border: 1px solid oklch(62% 0.2 186 / 0.35);\n color: oklch(56% 0.16 186); font-size: 9px; letter-spacing: 2px;\n border-radius: 6px; padding: 7px; cursor: pointer; width: calc(100% - 28px);\n transition: background 0.15s, color 0.15s;\n font-family: 'Azeret Mono', 'Space Mono', 'Courier New', monospace;\n}\n#sb-movie-btn:hover { background: oklch(62% 0.2 186 / 0.25); color: #d0b0ff; }\n#sb-movie-btn.active { background: oklch(62% 0.2 186 / 0.25); color: #d0b0ff; border-color: oklch(62% 0.2 186 / 0.6); }\n\n/* ── SVG title overlay ── */\n#title-wrap {\n position: absolute; top: 16px; left: 50%; transform: translateX(-50%);\n text-align: center; pointer-events: none; z-index: 5;\n}\n#title-h1 {\n font-size: 22px; font-weight: 900; letter-spacing: 0.38em;\n color: oklch(84% 0.14 186);\n}\n#title-sub { font-size: 9px; color: oklch(38% 0.06 186); letter-spacing: 3px; margin-top: 6px; }\n\n/* ── Prompt box ── */\n#prompt-box {\n position: absolute; bottom: 76px; left: 50%; transform: translateX(-50%);\n min-width: 340px; max-width: 500px;\n background: rgba(6,4,22,0.96);\n border: 1px solid rgba(130,80,255,0.5);\n border-radius: 12px; padding: 10px 18px;\n z-index: 50; opacity: 0;\n box-shadow: 0 4px 28px rgba(100,50,255,0.16);\n backdrop-filter: blur(18px);\n}\n#p-tag { font-size: 8px; letter-spacing: 3px; color: #48489a; margin-bottom: 4px; }\n#p-line { font-size: 12.5px; color: #90c8ff; display: flex; align-items: center; gap: 2px; min-height: 19px; }\n#p-cursor {\n display: inline-block; width: 2px; height: 14px;\n background: #90c8ff; flex-shrink: 0;\n animation: blink 0.8s step-end infinite;\n}\n@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }\n\n/* ── Activity log ── */\n#activity-log {\n position: absolute; left: 10px; bottom: 76px;\n width: 240px;\n background: rgba(5,3,18,0.93);\n border: 1px solid rgba(70,45,165,0.35);\n border-radius: 10px; padding: 9px 12px;\n z-index: 50; opacity: 0;\n}\n#log-title { font-size: 7.5px; letter-spacing: 3px; color: #282870; margin-bottom: 6px;\n padding-bottom: 5px; border-bottom: 1px solid rgba(70,45,165,0.18); }\n#log-entries { font-size: 9px; line-height: 1.95; max-height: 160px; overflow: hidden; }\n.log-row { display: flex; gap: 5px; opacity: 0; }\n.log-tag { font-weight: bold; min-width: 58px; flex-shrink: 0; }\n.log-msg { color: #525298; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 150px; }\n\n/* ── Mode banner ── */\n#mode-banner {\n position: absolute; top: 14px; right: 10px;\n font-size: 8px; letter-spacing: 3px; color: #303070;\n z-index: 5; pointer-events: none;\n}\n#mode-banner.live-mode { color: #28c068; }\n\n/* ── Control bar ── */\n#ctrl {\n position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%);\n display: flex; align-items: center; gap: 7px;\n background: rgba(8,6,26,0.95);\n border: 1px solid rgba(100,60,220,0.35);\n border-radius: 26px; padding: 6px 16px;\n z-index: 100; backdrop-filter: blur(18px);\n opacity: 0;\n}\n.c-btn {\n background: none; border: 1px solid rgba(100,60,220,0.4);\n color: #7858d0; width: 26px; height: 26px; border-radius: 50%;\n cursor: pointer; font-size: 10px;\n display: flex; align-items: center; justify-content: center;\n transition: background 0.12s, color 0.12s; flex-shrink: 0; line-height: 1;\n}\n.c-btn:hover { background: rgba(100,60,220,0.2); color: #d0b0ff; }\n.c-btn.disabled { opacity: 0.3; pointer-events: none; }\n#scrubber {\n width: 180px; height: 3px; cursor: pointer;\n -webkit-appearance: none; appearance: none;\n background: rgba(100,60,220,0.2); border-radius: 2px; outline: none;\n}\n#scrubber::-webkit-slider-thumb {\n -webkit-appearance: none; width: 11px; height: 11px;\n border-radius: 50%; background: #7858d0; cursor: pointer; border: none;\n}\n#t-disp { font-size: 9px; color: #484888; min-width: 36px; text-align: right; font-variant-numeric: tabular-nums; }\n#spd {\n background: rgba(8,6,26,0.85); border: 1px solid rgba(100,60,220,0.3);\n color: oklch(55% 0.12 186); font-size: 9px; font-family: 'Azeret Mono', 'Space Mono', monospace;\n border-radius: 4px; padding: 2px 4px; cursor: pointer; outline: none;\n}\n#spd option { background: #0d0a20; }\n\n/* ── Detail panel ── */\n#dp-header {\n padding: 14px 16px 10px;\n border-bottom: 1px solid oklch(62% 0.2 186 / 0.18); flex-shrink: 0;\n}\n#dp-close {\n float: right; background: none; border: none; color: #404070;\n cursor: pointer; font-size: 13px; padding: 0; line-height: 1;\n}\n#dp-close:hover { color: #a090e0; }\n#dp-title { font-size: 9px; letter-spacing: 3px; color: #5050a0; margin-top: 2px; }\n#dp-emoji { font-size: 22px; display: block; margin-bottom: 4px; }\n#dp-body { flex: 1; overflow-y: auto; padding: 12px 16px; scrollbar-width: thin; scrollbar-color: oklch(62% 0.2 186 / 0.3) transparent; }\n#dp-body::-webkit-scrollbar { width: 4px; }\n#dp-body::-webkit-scrollbar-thumb { background: oklch(62% 0.2 186 / 0.3); border-radius: 2px; }\n.dp-section { margin-bottom: 14px; }\n.dp-section-title { font-size: 7.5px; letter-spacing: 3px; color: oklch(38% 0.07 186); margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid oklch(62% 0.2 186 / 0.15); }\n.dp-event { font-size: 9px; line-height: 1.6; color: #5060a0; margin-bottom: 4px; }\n.dp-event .ev-ts { color: #282855; }\n.dp-event .ev-type { color: inherit; font-weight: bold; }\n.dp-artifact { font-size: 9px; color: #6070a0; padding: 3px 6px; background: oklch(62% 0.2 186 / 0.08); border-radius: 3px; margin-bottom: 3px; }\n.dp-agent { display: inline-block; font-size: 8px; padding: 2px 7px; border-radius: 10px; margin: 2px 3px 2px 0; border: 1px solid oklch(62% 0.2 186 / 0.3); color: oklch(55% 0.09 186); }\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <!-- ── Left sidebar: session history ── -->\n <div id=\"sidebar\">\n <div id=\"sb-header\">\n <div id=\"sb-title\">SESSIONS</div>\n <div class=\"live-row\">\n <div class=\"l-dot\" id=\"l-dot\"></div>\n <span id=\"l-status\">OFFLINE</span>\n <span id=\"l-agents\">0 agents</span>\n </div>\n <div id=\"git-user-row\">\n <span id=\"git-user-icon\">⬡</span>\n <span id=\"git-user-name\">—</span>\n </div>\n <div id=\"git-cwd-row\">\n <span id=\"git-cwd-icon\">◎</span>\n <span id=\"git-cwd-name\">—</span>\n </div>\n </div>\n <div id=\"sb-sessions\">\n <div id=\"sb-no-sessions\">No sessions yet.<br><br>Describe a goal and<br>Mastermind routes it<br>across specialist agents.<br><br><span style=\"color:oklch(56% 0.16 186);letter-spacing:1px\">/mastermind</span></div>\n </div>\n <button id=\"sb-movie-btn\" onclick=\"toggleMovieMode()\">▶ MOVIE MODE</button>\n </div>\n\n <!-- ── Stage ── -->\n <div id=\"stage-wrap\">\n <!-- SVG -->\n <svg id=\"stage\" viewBox=\"0 0 960 720\" preserveAspectRatio=\"xMidYMid meet\">\n <defs>\n <filter id=\"glow\" x=\"-55%\" y=\"-55%\" width=\"210%\" height=\"210%\">\n <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"5\" result=\"b\"/>\n <feMerge><feMergeNode in=\"b\"/><feMergeNode in=\"SourceGraphic\"/></feMerge>\n </filter>\n <filter id=\"bloom\" x=\"-100%\" y=\"-100%\" width=\"300%\" height=\"300%\">\n <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"15\" result=\"b\"/>\n <feMerge><feMergeNode in=\"b\"/><feMergeNode in=\"SourceGraphic\"/></feMerge>\n </filter>\n <radialGradient id=\"tbl-g\" cx=\"50%\" cy=\"50%\" r=\"50%\">\n <stop offset=\"0%\" stop-color=\"#180840\" stop-opacity=\"0.7\"/>\n <stop offset=\"100%\" stop-color=\"#07071a\" stop-opacity=\"0\"/>\n </radialGradient>\n <radialGradient id=\"brain-g\" cx=\"40%\" cy=\"35%\" r=\"60%\">\n <stop offset=\"0%\" stop-color=\"#2c1aaa\"/>\n <stop offset=\"100%\" stop-color=\"#12083a\"/>\n </radialGradient>\n </defs>\n <g id=\"stars\"></g>\n <circle cx=\"480\" cy=\"360\" r=\"260\" fill=\"url(#tbl-g)\" id=\"tbl-bg\"/>\n <circle cx=\"480\" cy=\"360\" r=\"260\" fill=\"none\" stroke=\"#1c0d46\" stroke-width=\"1.5\" id=\"tbl-ring\"/>\n <circle cx=\"480\" cy=\"360\" r=\"205\" fill=\"none\" stroke=\"#110830\" stroke-width=\"1\" stroke-dasharray=\"5 10\" id=\"orb-ring\"/>\n <g id=\"spokes\"></g>\n <g id=\"domains\"></g>\n <g id=\"packets\"></g>\n <g id=\"brain\">\n <circle cx=\"480\" cy=\"360\" r=\"75\" fill=\"#170d4c\" opacity=\"0.35\" filter=\"url(#bloom)\" id=\"brain-glow\"/>\n <circle cx=\"480\" cy=\"360\" r=\"46\" fill=\"url(#brain-g)\" id=\"brain-body\"/>\n <circle cx=\"480\" cy=\"360\" r=\"46\" fill=\"none\" stroke=\"#007d75\" stroke-width=\"2.5\" id=\"brain-ring\"/>\n <circle cx=\"480\" cy=\"360\" r=\"55\" fill=\"none\" stroke=\"#007d75\" stroke-width=\"0.8\" opacity=\"0.2\" id=\"pulse-ring\"/>\n <g id=\"brain-emoji\">\n <polygon points=\"480,340 497.3,350 497.3,370 480,380 462.7,370 462.7,350\" fill=\"none\" stroke=\"#008f85\" stroke-width=\"1.8\" opacity=\"0.55\"/>\n <polygon points=\"480,347 491.3,353.5 491.3,366.5 480,373 468.7,366.5 468.7,353.5\" fill=\"none\" stroke=\"#00a89a\" stroke-width=\"1.2\" opacity=\"0.78\"/>\n <polygon points=\"480,353 486.1,356.5 486.1,363.5 480,367 473.9,363.5 473.9,356.5\" fill=\"rgba(0,168,154,0.26)\" stroke=\"#00c4b8\" stroke-width=\"1\"/>\n <line x1=\"480\" y1=\"353\" x2=\"480\" y2=\"340\" stroke=\"#008f85\" stroke-width=\"0.7\" opacity=\"0.42\"/>\n <line x1=\"486.1\" y1=\"356.5\" x2=\"497.3\" y2=\"350\" stroke=\"#008f85\" stroke-width=\"0.7\" opacity=\"0.42\"/>\n <line x1=\"486.1\" y1=\"363.5\" x2=\"497.3\" y2=\"370\" stroke=\"#008f85\" stroke-width=\"0.7\" opacity=\"0.42\"/>\n <line x1=\"480\" y1=\"367\" x2=\"480\" y2=\"380\" stroke=\"#008f85\" stroke-width=\"0.7\" opacity=\"0.42\"/>\n <line x1=\"473.9\" y1=\"363.5\" x2=\"462.7\" y2=\"370\" stroke=\"#008f85\" stroke-width=\"0.7\" opacity=\"0.42\"/>\n <line x1=\"473.9\" y1=\"356.5\" x2=\"462.7\" y2=\"350\" stroke=\"#008f85\" stroke-width=\"0.7\" opacity=\"0.42\"/>\n </g>\n <text x=\"480\" y=\"394\" text-anchor=\"middle\" font-size=\"7\" fill=\"#009e94\" letter-spacing=\"2.5\"\n font-family=\"'Azeret Mono','Space Mono',monospace\" id=\"brain-lbl\">MASTERMIND</text>\n </g>\n </svg>\n\n <!-- Overlays -->\n <div id=\"title-wrap\">\n <div id=\"title-h1\">MASTERMIND</div>\n <div id=\"title-sub\">AUTONOMOUS EXECUTION · 12 DOMAINS · PERSISTENT ORGS</div>\n </div>\n\n <div id=\"mode-banner\">LIVE</div>\n\n <div id=\"prompt-box\">\n <div id=\"p-tag\">USER PROMPT</div>\n <div id=\"p-line\"><span id=\"p-text\"></span><span id=\"p-cursor\"></span></div>\n </div>\n\n <div id=\"activity-log\">\n <div id=\"log-title\">ACTIVITY LOG</div>\n <div id=\"log-entries\"></div>\n </div>\n\n <div id=\"ctrl\">\n <button class=\"c-btn disabled\" id=\"btn-restart\" title=\"Restart\">↺</button>\n <button class=\"c-btn disabled\" id=\"btn-play\" title=\"Play\">▶</button>\n <button class=\"c-btn disabled\" id=\"btn-pause\" title=\"Pause\">⏸</button>\n <input type=\"range\" id=\"scrubber\" min=\"0\" max=\"100\" value=\"0\" step=\"0.1\" disabled/>\n <span id=\"t-disp\">—</span>\n <select id=\"spd\">\n <option value=\"0.5\">0.5×</option>\n <option value=\"1\" selected>1×</option>\n <option value=\"2\">2×</option>\n <option value=\"3\">3×</option>\n </select>\n </div>\n </div>\n\n <!-- ── Right panel: session/domain detail ── -->\n <div id=\"detail-panel\">\n <div id=\"dp-header\">\n <button id=\"dp-close\" onclick=\"closeDetail()\">✕</button>\n <span id=\"dp-emoji\"></span>\n <div id=\"dp-title\">SELECT A DOMAIN OR SESSION</div>\n </div>\n <div id=\"dp-body\"></div>\n </div>\n</div>\n\n<script src=\"https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js\"></script>\n<script>\n'use strict';\n\n// ── Layout constants (SVG viewBox 0 0 960 720) ────────────────────────────────\nconst CX = 480, CY = 360, R = 205;\nconst PERIM = 2 * Math.PI * 34;\nconst SCRAMBLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%&*!';\n\n// ── Domain definitions ─────────────────────────────────────────────────────────\nconst DOMAINS = [\n { id:'build', emoji:'⚙️', label:'BUILD', color:'#60a5fa', tag:'[BUILD]', msg:'Spawning architect + coder + tester', cmd:'/mastermind:build' },\n { id:'idea', emoji:'💡', label:'IDEA', color:'#fbbf24', tag:'[IDEA]', msg:'Generating product concepts', cmd:'/mastermind:idea' },\n { id:'marketing',emoji:'📣', label:'MARKETING', color:'#f472b6', tag:'[MARKET]', msg:'Crafting launch campaign', cmd:'/mastermind:marketing' },\n { id:'review', emoji:'🔍', label:'REVIEW', color:'#34d399', tag:'[REVIEW]', msg:'Auditing code quality', cmd:'/mastermind:review' },\n { id:'research', emoji:'🔬', label:'RESEARCH', color:'#a78bfa', tag:'[RESEARCH]', msg:'Scraping competitor sites', cmd:'/mastermind:research' },\n { id:'content', emoji:'✍️', label:'CONTENT', color:'#fb923c', tag:'[CONTENT]', msg:'Writing blog + social posts', cmd:'/mastermind:content' },\n { id:'release', emoji:'🚀', label:'RELEASE', color:'#22d3ee', tag:'[RELEASE]', msg:'Preparing deployment pipeline', cmd:'/mastermind:release' },\n { id:'sales', emoji:'💼', label:'SALES', color:'#f87171', tag:'[SALES]', msg:'Building outreach sequences', cmd:'/mastermind:sales' },\n { id:'ops', emoji:'⚡', label:'OPS', color:'#4ade80', tag:'[OPS]', msg:'Automating workflows', cmd:'/mastermind:ops' },\n { id:'finance', emoji:'💰', label:'FINANCE', color:'#fde68a', tag:'[FINANCE]', msg:'Forecasting revenue model', cmd:'/mastermind:finance' },\n];\nDOMAINS.forEach((d, i) => {\n const a = -Math.PI / 2 + (i / DOMAINS.length) * 2 * Math.PI;\n d.x = Math.round(CX + R * Math.cos(a));\n d.y = Math.round(CY + R * Math.sin(a));\n d.events = []; // live event history per domain\n});\n\nconst IC = [\n [0,3,'code→review'], [3,6,'LGTM→ship'], [1,2,'concepts→copy'],\n [2,5,'brief→write'], [4,1,'data→ideate'], [7,8,'leads→ops'], [8,9,'metrics→model'],\n];\n\n// ── SVG helpers ────────────────────────────────────────────────────────────────\nconst NS = 'http://www.w3.org/2000/svg';\nconst mk = (tag, a={}) => {\n const el = document.createElementNS(NS, tag);\n for (const [k,v] of Object.entries(a)) el.setAttribute(k, v);\n return el;\n};\n\n// ── Build star field ───────────────────────────────────────────────────────────\nconst starsG = document.getElementById('stars');\nfor (let i = 0; i < 170; i++) {\n starsG.appendChild(mk('circle', {\n cx: (Math.random()*960).toFixed(1), cy: (Math.random()*720).toFixed(1),\n r: (Math.random() < 0.1 ? Math.random()*1.5+0.8 : Math.random()*0.8+0.15).toFixed(1),\n fill: `rgba(160,150,255,${(Math.random()*0.35+0.08).toFixed(2)})`\n }));\n}\n\n// ── Build spokes ───────────────────────────────────────────────────────────────\nconst spokesG = document.getElementById('spokes');\nconst spokeEls = DOMAINS.map(d => {\n const len = Math.hypot(d.x-CX, d.y-CY);\n const el = mk('line', { x1:CX, y1:CY, x2:d.x, y2:d.y,\n stroke:d.color, 'stroke-width':'1', opacity:'0.35',\n 'stroke-dasharray':len.toFixed(1), 'stroke-dashoffset':len.toFixed(1),\n 'stroke-linecap':'round' });\n spokesG.appendChild(el);\n return { el, len };\n});\n\n// ── Build domain nodes ─────────────────────────────────────────────────────────\nconst domainsG = document.getElementById('domains');\nconst domEls = DOMAINS.map(d => {\n const g = mk('g', { id:`dn-${d.id}`, transform:`translate(${d.x},${d.y})`, style:'cursor:pointer' });\n g.appendChild(mk('circle', { r:'44', fill:'none', stroke:d.color, 'stroke-width':'1', opacity:'0', id:`gr-${d.id}` }));\n g.appendChild(mk('circle', { r:'34', fill:'none', stroke:d.color, 'stroke-width':'2.8',\n 'stroke-dasharray':PERIM.toFixed(1), 'stroke-dashoffset':PERIM.toFixed(1),\n 'stroke-linecap':'round', transform:'rotate(-90)', id:`pr-${d.id}` }));\n g.appendChild(mk('circle', { r:'28', fill:'#0b0920', stroke:d.color, 'stroke-width':'1.8' }));\n const emj = mk('text', { x:'0', y:'9', 'text-anchor':'middle', 'font-size':'20' });\n emj.textContent = d.emoji;\n g.appendChild(emj);\n const lbl = mk('text', { x:'0', y:'46', 'text-anchor':'middle', 'font-size':'7.5',\n fill:d.color, 'letter-spacing':'1.5', 'font-family':\"'Azeret Mono','Space Mono',monospace\" });\n lbl.textContent = d.label;\n g.appendChild(lbl);\n g.appendChild(mk('circle', { r:'10', cx:'22', cy:'-22', fill:'#0b0920',\n stroke:d.color, 'stroke-width':'1.5', opacity:'0', id:`cb-${d.id}` }));\n const chk = mk('text', { x:'22', y:'-18', 'text-anchor':'middle', 'font-size':'10',\n fill:d.color, opacity:'0', id:`ct-${d.id}` });\n chk.textContent = '✓';\n g.appendChild(chk);\n // Invisible hit target (wider than visual)\n const hit = mk('circle', { r:'50', fill:'transparent' });\n hit.addEventListener('click', (e) => { e.stopPropagation(); openDomainDetail(d); });\n g.appendChild(hit);\n domainsG.appendChild(g);\n return g;\n});\n\n// ── Initial GSAP states ────────────────────────────────────────────────────────\ngsap.set([...starsG.children], { opacity: 0 });\ngsap.set(['#tbl-bg','#tbl-ring','#orb-ring','#brain'], { opacity: 0 });\ngsap.set(domEls, { scale: 0, opacity: 0, transformOrigin: 'center center' });\ngsap.set(['#title-h1','#title-sub'], { opacity: 0 });\ngsap.set('#ctrl', { opacity: 0 });\ngsap.set('#activity-log', { opacity: 0 });\ngsap.set('#prompt-box', { opacity: 0 });\n\n// ── Ambient star twinkle ───────────────────────────────────────────────────────\nif (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
|
14
|
+
gsap.to([...starsG.children], {\n opacity: 'random(0.06, 0.6)',\n duration: 'random(2, 5)',\n stagger: { amount: 16, from: 'random', repeat: -1, yoyo: true, ease: 'sine.inOut' },\n delay: 1,\n});
|
|
15
|
+
}\n\n// ── Static table appears (always shown) ───────────────────────────────────────\nfunction buildStage() {\n const tl = gsap.timeline();\n tl.to([...starsG.children], { opacity: 1, stagger: { amount: 1.2, from: 'random' } }, 0)\n .set('#title-h1', { opacity: 1 }, 0.5)\n .to({}, {\n duration: 1.4,\n onUpdate() {\n const p = this.progress();\n const T = 'MASTERMIND';\n document.getElementById('title-h1').textContent = T.split('').map((c,i) =>\n i < Math.floor(p * T.length) ? c : SCRAMBLE[Math.floor(Math.random()*SCRAMBLE.length)]\n ).join('');\n },\n onComplete() { document.getElementById('title-h1').textContent = 'MASTERMIND'; }\n }, 0.5)\n .to('#title-sub', { opacity: 1, duration: 0.7 }, 1.4)\n .to(['#tbl-bg','#tbl-ring'], { opacity: 1, duration: 1.0 }, 2.2)\n .to('#orb-ring', { opacity: 1, duration: 0.8 }, 2.6)\n .fromTo('#brain', { opacity:0, scale:0, transformOrigin:'480px 360px' },\n { opacity:1, scale:1, duration:0.9, ease:'power4.out' }, 3.2)\n .fromTo('#pulse-ring', { attr:{r:48}, opacity:0.6 }, { attr:{r:70}, opacity:0, duration:1.0 }, 3.3);\n DOMAINS.forEach((d,i) => {\n tl.fromTo(domEls[i], { scale:0, opacity:0 },\n { scale:1, opacity:1, duration:0.55, ease:'power4.out', transformOrigin:'center center' },\n 3.8 + i*0.28);\n tl.to(spokeEls[i].el, { strokeDashoffset:0, duration:0.5, ease:'power2.in' }, 3.88 + i*0.28);\n });\n tl.to('#activity-log', { opacity:1, duration:0.5 }, 7.2);\n return tl;\n}\n\n// ── Movie mode GSAP timeline ───────────────────────────────────────────────────\nlet movieTl = null;\nlet isMovieMode = false;\n\nfunction buildMovieTl() {\n const USER_PROMPT = '/mastermind \"launch v2.0 — research, build, market, ship\" --auto';\n const tl = gsap.timeline({ paused: true, defaults: { ease:'power2.out' } });\n\n tl.to('#prompt-box', { opacity:1, duration:0.4 }, 0);\n tl.to({}, {\n duration: USER_PROMPT.length * 0.033,\n onStart() { document.getElementById('p-tag').textContent='USER PROMPT'; document.getElementById('p-text').textContent=''; },\n onUpdate() { document.getElementById('p-text').textContent = USER_PROMPT.slice(0, Math.ceil(this.progress()*USER_PROMPT.length)); },\n onComplete() { document.getElementById('p-text').textContent = USER_PROMPT; }\n }, 0.3);\n\n const fireAt = 0.3 + USER_PROMPT.length * 0.033 + 0.5;\n tl.to('#prompt-box', { opacity:0, y:-10, duration:0.35 }, fireAt);\n tl.set('#prompt-box', { y:0 }, fireAt+0.4);\n tl.add(() => spawnPacket(480, 700, CX, CY, '#8855ee', 'PROMPT'), fireAt+0.2);\n tl.to('#brain-ring', { attr:{stroke:'#cc70ff','stroke-width':5}, duration:0.3 }, fireAt+0.85);\n tl.to('#brain-ring', { attr:{stroke:'#007d75','stroke-width':2.5}, duration:0.8 }, fireAt+1.15);\n tl.to('#brain-glow', { attr:{r:100}, opacity:0.6, duration:0.4 }, fireAt+0.85);\n tl.to('#brain-glow', { attr:{r:75}, opacity:0.35, duration:0.9 }, fireAt+1.25);\n tl.add(() => { document.getElementById('brain-lbl').textContent='DISPATCHING'; addLog('[🧠]','Brain loaded. Decomposing...','#9070ff'); }, fireAt+0.9);\n\n tl.to('#prompt-box', { opacity:1, duration:0.3 }, fireAt+1.8);\n const activateAt = fireAt+2.2;\n\n DOMAINS.forEach((d,i) => {\n const t = activateAt + i*0.85;\n tl.to({}, {\n duration: d.cmd.length*0.025,\n onStart() { document.getElementById('p-tag').textContent=`→ ${d.label}`; document.getElementById('p-text').textContent=''; },\n onUpdate() { document.getElementById('p-text').textContent = d.cmd.slice(0, Math.ceil(this.progress()*d.cmd.length)); },\n onComplete() { document.getElementById('p-text').textContent = d.cmd; }\n }, t);\n tl.add(() => spawnPacket(CX, CY, d.x, d.y, d.color), t + d.cmd.length*0.025 + 0.06);\n tl.to(`#gr-${d.id}`, { opacity:0.85, attr:{r:52}, duration:0.3 }, t + d.cmd.length*0.025 + 0.6);\n tl.to(`#gr-${d.id}`, { opacity:0.2, attr:{r:44}, duration:0.9 }, t + d.cmd.length*0.025 + 0.9);\n tl.to(`#pr-${d.id}`, { strokeDashoffset:0, duration:2.2, ease:'power1.inOut' }, t + d.cmd.length*0.025 + 0.6);\n tl.add(() => addLog(d.tag, d.msg, d.color), t + d.cmd.length*0.025 + 0.65);\n });\n\n const icAt = activateAt + DOMAINS.length*0.85 + 2.5;\n tl.to('#prompt-box', { opacity:0, duration:0.3 }, icAt);\n IC.forEach(([fi,ti,lbl],j) => {\n const t = icAt + j*0.6;\n tl.add(() => spawnPacket(DOMAINS[fi].x, DOMAINS[fi].y, DOMAINS[ti].x, DOMAINS[ti].y, DOMAINS[fi].color, lbl), t);\n tl.to(`#gr-${DOMAINS[ti].id}`, { opacity:0.6, attr:{r:50}, duration:0.28 }, t+0.72);\n tl.to(`#gr-${DOMAINS[ti].id}`, { opacity:0.2, attr:{r:44}, duration:0.8 }, t+1.0);\n tl.add(() => addLog('[IC]', lbl, '#7080d8'), t+0.5);\n });\n\n const resAt = icAt + IC.length*0.6 + 0.9;\n DOMAINS.forEach((d,i) => {\n const t = resAt + i*0.26;\n tl.add(() => spawnPacket(d.x, d.y, CX, CY, d.color), t);\n tl.to(`#cb-${d.id}`, { opacity:1, duration:0.28 }, t+0.72);\n tl.to(`#ct-${d.id}`, { opacity:1, duration:0.28 }, t+0.72);\n });\n\n const doneAt = resAt + DOMAINS.length*0.26 + 1.2;\n tl.to('#brain-glow', { attr:{r:125}, opacity:0.9, duration:0.45 }, doneAt);\n tl.to('#brain-glow', { attr:{r:78}, opacity:0.35, duration:1.5 }, doneAt+0.45);\n tl.to('#brain-ring', { attr:{stroke:'#ff88ff','stroke-width':6}, duration:0.4 }, doneAt);\n tl.to('#brain-ring', { attr:{stroke:'#007d75','stroke-width':2.5}, duration:1.5 }, doneAt+0.45);\n tl.to('#brain-emoji', { scale:1.5, transformOrigin:'480px 360px', duration:0.45, ease:'power4.out' }, doneAt);\n tl.to('#brain-emoji', { scale:1.0, duration:0.9 }, doneAt+0.45);\n tl.add(() => { document.getElementById('brain-lbl').textContent='MASTERMIND'; addLog('[✓]','Run complete — 10 domains','#40e880'); }, doneAt+0.1);\n const FINAL = 'RUN COMPLETE · 10 DOMAINS · ALL AGENTS DONE ✓';\n tl.to('#prompt-box', { opacity:1, duration:0.4 }, doneAt+0.6);\n tl.to({}, {\n duration: FINAL.length*0.028,\n onStart() { document.getElementById('p-tag').textContent='MASTERMIND'; document.getElementById('p-text').textContent=''; },\n onUpdate() { document.getElementById('p-text').textContent = FINAL.slice(0, Math.ceil(this.progress()*FINAL.length)); },\n onComplete() { document.getElementById('p-text').textContent = FINAL; }\n }, doneAt+0.75);\n\n return tl;\n}\n\nfunction toggleMovieMode() {\n isMovieMode = !isMovieMode;\n const btn = document.getElementById('sb-movie-btn');\n const banner = document.getElementById('mode-banner');\n const scrubEl = document.getElementById('scrubber');\n const tDisp = document.getElementById('t-disp');\n\n if (isMovieMode) {\n btn.classList.add('active');\n btn.textContent = '■ EXIT MOVIE';\n banner.textContent = 'MOVIE';\n banner.classList.remove('live-mode');\n // Enable scrubber/play/pause\n ['btn-restart','btn-play','btn-pause'].forEach(id => document.getElementById(id).classList.remove('disabled'));\n scrubEl.disabled = false;\n // Reset log & dynamic state\n document.getElementById('log-entries').innerHTML = '';\n document.getElementById('p-text').textContent = '';\n document.getElementById('brain-lbl').textContent = 'MASTERMIND';\n // Reset progress rings and badges\n DOMAINS.forEach(d => {\n gsap.set(`#pr-${d.id}`, { strokeDashoffset: PERIM });\n gsap.set(`#cb-${d.id}`, { opacity: 0 });\n gsap.set(`#ct-${d.id}`, { opacity: 0 });\n gsap.set(`#gr-${d.id}`, { opacity: 0, attr: { r: 44 } });\n });\n // Build and play\n if (movieTl) { movieTl.kill(); }\n movieTl = buildMovieTl();\n // Wire controls\n document.getElementById('btn-play').onclick = () => movieTl.resume();\n document.getElementById('btn-pause').onclick = () => movieTl.pause();\n document.getElementById('btn-restart').onclick = () => {\n document.getElementById('packets').innerHTML = '';\n document.getElementById('log-entries').innerHTML = '';\n document.getElementById('p-text').textContent = '';\n document.getElementById('brain-lbl').textContent = 'MASTERMIND';\n DOMAINS.forEach(d => {\n gsap.set(`#pr-${d.id}`, { strokeDashoffset: PERIM });\n gsap.set(`#cb-${d.id}`, { opacity: 0 });\n gsap.set(`#ct-${d.id}`, { opacity: 0 });\n gsap.set(`#gr-${d.id}`, { opacity: 0, attr: { r: 44 } });\n });\n movieTl.restart();\n };\n document.getElementById('spd').onchange = e => movieTl && movieTl.timeScale(Number(e.target.value));\n let scrubbing = false;\n scrubEl.addEventListener('mousedown', () => { scrubbing=true; movieTl&&movieTl.pause(); });\n scrubEl.addEventListener('mouseup', () => { scrubbing=false; });\n scrubEl.addEventListener('input', () => { if(movieTl) movieTl.progress(Number(scrubEl.value)/100); tDisp.textContent = (movieTl?movieTl.time():0).toFixed(1)+'s'; });\n gsap.ticker.add(() => {\n if(!scrubbing && movieTl && movieTl.totalDuration()>0) {\n scrubEl.value = movieTl.progress()*100;\n tDisp.textContent = movieTl.time().toFixed(1)+'s';\n }\n });\n movieTl.play();\n gsap.to('#ctrl', { opacity:1, duration:0.35, ease:'power2.out' });\n } else {\n btn.classList.remove('active');\n btn.textContent = '▶ MOVIE MODE';\n banner.textContent = 'LIVE';\n banner.classList.add('live-mode');\n ['btn-restart','btn-play','btn-pause'].forEach(id => document.getElementById(id).classList.add('disabled'));\n scrubEl.disabled = true;\n tDisp.textContent = '—';\n document.getElementById('prompt-box').style.opacity = '0';\n if (movieTl) { movieTl.kill(); movieTl = null; }\n gsap.to('#ctrl', { opacity:0, duration:0.25 });\n }\n}\n\n// ── Packet animation utility ───────────────────────────────────────────────────\nfunction spawnPacket(fx, fy, tx, ty, color, lbl) {\n const g = mk('g', {});\n g.appendChild(mk('circle', { r:'7', fill:color, filter:'url(#glow)', opacity:'0.55' }));\n g.appendChild(mk('circle', { r:'3.5', fill:'#fff' }));\n if (lbl) {\n const t = mk('text', { x:'10', y:'4', 'font-size':'7', fill:color, 'font-family':\"'Azeret Mono','Space Mono',monospace\" });\n t.textContent = lbl;\n g.appendChild(t);\n }\n document.getElementById('packets').appendChild(g);\n gsap.set(g, { x:fx, y:fy, opacity:0 });\n gsap.timeline({ onComplete: ()=>g.remove() })\n .to(g, { opacity:1, duration:0.12 })\n .to(g, { x:tx, y:ty, duration:0.88, ease:'power2.inOut' }, '<')\n .to(g, { opacity:0, scale:1.5, transformOrigin:'0 0', duration:0.2 });\n}\n\n// ── Activity log ───────────────────────────────────────────────────────────────\nfunction addLog(tag, msg, color) {\n const wrap = document.getElementById('log-entries');\n const row = document.createElement('div');\n row.className = 'log-row';\n row.innerHTML = `<span class=\"log-tag\" style=\"color:${color}\">${tag}</span><span class=\"log-msg\">${msg}</span>`;\n wrap.appendChild(row);\n gsap.fromTo(row, { opacity:0 }, { opacity:1, duration:0.3 });\n const rows = wrap.querySelectorAll('.log-row');\n if (rows.length > 10) {\n gsap.to(rows[0], { opacity:0, height:0, duration:0.22, onComplete:()=>rows[0].remove() });\n }\n}\n\n// ── Live event handler ─────────────────────────────────────────────────────────\nfunction handleLiveEvent(ev) {\n if (isMovieMode) return; // live events suppressed in movie mode\n\n if (ev.type === 'session:start') {\n gsap.to('#brain-ring', { attr:{stroke:'#cc70ff','stroke-width':5}, duration:0.3 });\n gsap.to('#brain-ring', { attr:{stroke:'#007d75','stroke-width':2.5}, duration:0.8, delay:0.3 });\n gsap.to('#brain-glow', { attr:{r:100}, opacity:0.65, duration:0.4 });\n gsap.to('#brain-glow', { attr:{r:75}, opacity:0.35, duration:0.9, delay:0.4 });\n gsap.to('#brain-emoji', { scale:1.25, transformOrigin:'480px 360px', duration:0.32, ease:'power4.out' });\n gsap.to('#brain-emoji', { scale:1.0, duration:0.5, delay:0.32 });\n document.getElementById('brain-lbl').textContent = 'ANALYZING...';\n addLog('[SESSION]', ev.prompt ? ev.prompt.slice(0,28)+'…' : 'started', '#9070ff');\n // Show prompt\n if (ev.prompt) {\n const box = document.getElementById('prompt-box');\n document.getElementById('p-tag').textContent = 'RUNNING';\n document.getElementById('p-text').textContent = ev.prompt;\n gsap.to(box, { opacity:1, duration:0.4 });\n }\n refreshSessions();\n }\n\n else if (ev.type === 'domain:dispatch') {\n const d = DOMAINS.find(x => x.id === ev.domain);\n if (!d) return;\n spawnPacket(CX, CY, d.x, d.y, d.color);\n gsap.to(`#gr-${d.id}`, { opacity:0.85, attr:{r:52}, duration:0.32 });\n gsap.to(`#gr-${d.id}`, { opacity:0.25, attr:{r:44}, duration:1.0, delay:0.32 });\n document.getElementById('brain-lbl').textContent = 'DISPATCHING';\n addLog(d.tag, ev.cmd || d.cmd, d.color);\n d.events.push(ev);\n }\n\n else if (ev.type === 'agent:spawn') {\n const d = DOMAINS.find(x => x.id === ev.domain);\n if (d) {\n addLog(d.tag, `agents: ${(ev.agents||[]).join(', ')}`, d.color);\n d.events.push(ev);\n }\n }\n\n else if (ev.type === 'domain:complete') {\n const d = DOMAINS.find(x => x.id === ev.domain);\n if (!d) return;\n gsap.to(`#pr-${d.id}`, { strokeDashoffset:0, duration:1.8, ease:'power1.inOut' });\n gsap.to(`#cb-${d.id}`, { opacity:1, duration:0.3 });\n gsap.to(`#ct-${d.id}`, { opacity:1, duration:0.3 });\n spawnPacket(d.x, d.y, CX, CY, d.color);\n addLog(d.tag, 'complete ✓', d.color);\n d.events.push(ev);\n refreshSessions();\n }\n\n else if (ev.type === 'intercom') {\n const fi = DOMAINS.findIndex(x => x.id === ev.from);\n const ti = DOMAINS.findIndex(x => x.id === ev.to);\n if (fi >= 0 && ti >= 0) {\n spawnPacket(DOMAINS[fi].x, DOMAINS[fi].y, DOMAINS[ti].x, DOMAINS[ti].y, DOMAINS[fi].color, ev.msg||'');\n gsap.to(`#gr-${DOMAINS[ti].id}`, { opacity:0.6, attr:{r:50}, duration:0.28 });\n gsap.to(`#gr-${DOMAINS[ti].id}`, { opacity:0.2, attr:{r:44}, duration:0.8, delay:0.28 });\n addLog('[IC]', `${ev.from}→${ev.to}: ${ev.msg||''}`, '#7080d8');\n }\n }\n\n else if (ev.type === 'session:complete') {\n document.getElementById('brain-lbl').textContent = 'MASTERMIND';\n gsap.to('#brain-glow', { attr:{r:120}, opacity:0.9, duration:0.45 });\n gsap.to('#brain-glow', { attr:{r:75}, opacity:0.35, duration:1.5, delay:0.45 });\n gsap.to('#brain-ring', { attr:{stroke:'#ff88ff','stroke-width':6}, duration:0.4 });\n gsap.to('#brain-ring', { attr:{stroke:'#007d75','stroke-width':2.5}, duration:1.5, delay:0.4 });\n addLog('[✓]', `run complete — ${ev.domains||'?'} domains`, '#40e880');\n gsap.to('#prompt-box', { opacity:0, duration:0.5 });\n refreshSessions();\n }\n}\n\n// ── SSE event stream ───────────────────────────────────────────────────────────\nlet evtSource = null;\nfunction connectSSE() {\n if (evtSource) evtSource.close();\n evtSource = new EventSource('/api/mastermind-stream');\n evtSource.onmessage = (e) => {\n try {\n const ev = JSON.parse(e.data);\n handleLiveEvent(ev);\n } catch (_) {}\n };\n evtSource.onerror = () => {\n const dot = document.getElementById('l-dot');\n if (dot) dot.classList.remove('on');\n const st = document.getElementById('l-status');\n if (st) st.textContent = 'RECONNECTING';\n showStatusBanner('SSE disconnected — reconnecting in 4s');\n setTimeout(connectSSE, 4000);\n };\n}\n\n// ── Session sidebar ────────────────────────────────────────────────────────────\nlet currentSessionId = null;\n\nasync function refreshSessions() {\n try {\n const res = await fetch('/api/mastermind/sessions');\n const sessions = await res.json();\n renderSessions(sessions);\n } catch (_) {}\n}\n\nfunction renderSessions(sessions) {\n const wrap = document.getElementById('sb-sessions');\n const noSess = document.getElementById('sb-no-sessions');\n if (!sessions || !sessions.length) {\n if (noSess) noSess.style.display = 'block';\n const items = wrap.querySelectorAll('.sess-item');\n items.forEach(i => i.remove());\n return;\n }\n if (noSess) noSess.style.display = 'none';\n // Remove old items\n wrap.querySelectorAll('.sess-item').forEach(el => el.remove());\n sessions.forEach(s => {\n const item = document.createElement('div');\n item.className = 'sess-item' + (s.status === 'running' ? ' running' : '') + (s.id === currentSessionId ? ' active' : '');\n const ts = new Date(s.ts).toLocaleTimeString([], {hour:'2-digit',minute:'2-digit',second:'2-digit'});\n const date = new Date(s.ts).toLocaleDateString([], {month:'short',day:'numeric'});\n const elapsed = s.endTs ? ((s.endTs - s.ts)/1000).toFixed(0)+'s' : (s.status==='running'?'RUNNING…':'?');\n item.innerHTML = `\n <div class=\"sess-ts\">${date} ${ts} · ${elapsed}</div>\n <div class=\"sess-prompt\">${s.prompt||'(no prompt)'}</div>\n <div class=\"sess-badges\">\n <span class=\"sess-badge ${s.status==='running'?'running-badge':''}\">${s.status||'?'}</span>\n ${(s.domains||[]).slice(0,4).map(d=>`<span class=\"sess-badge\">${d}</span>`).join('')}\n ${(s.domains||[]).length>4?`<span class=\"sess-badge\">+${s.domains.length-4}</span>`:''}\n <a class=\"sess-trace-link\" href=\"/api/mastermind/session/${s.id}/trace\" target=\"_blank\" title=\"View raw trace\" onclick=\"event.stopPropagation()\">trace↗</a>\n </div>`;\n item.addEventListener('click', () => {\n wrap.querySelectorAll('.sess-item').forEach(x=>x.classList.remove('active'));\n item.classList.add('active');\n currentSessionId = s.id;\n openSessionDetail(s);\n });\n wrap.appendChild(item);\n });\n}\n\n// ── Detail panel ───────────────────────────────────────────────────────────────\nfunction openDomainDetail(d) {\n const panel = document.getElementById('detail-panel');\n document.getElementById('dp-emoji').textContent = d.emoji;\n document.getElementById('dp-title').textContent = `DOMAIN · ${d.label}`;\n const body = document.getElementById('dp-body');\n // Count total events for this domain across all sessions\n const evts = d.events;\n body.innerHTML = `\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">DOMAIN INFO</div>\n <div class=\"dp-event\"><span class=\"ev-type\" style=\"color:${d.color}\">${d.emoji} ${d.label}</span></div>\n <div class=\"dp-event\">Command: <span style=\"color:#7080c0\">${d.cmd}</span></div>\n <div class=\"dp-event\">Events this session: <span style=\"color:${d.color}\">${evts.length}</span></div>\n </div>\n ${evts.length > 0 ? `\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">RECENT EVENTS</div>\n ${evts.slice(-8).map(e => {\n const ts = new Date(e.ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'});\n return `<div class=\"dp-event\"><span class=\"ev-ts\">${ts}</span> <span class=\"ev-type\" style=\"color:${d.color}\">${e.type}</span>${e.cmd?' '+e.cmd:''}</div>`;\n }).join('')}\n </div>` : ''}\n ${evts.some(e=>e.type==='agent:spawn') ? `\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">AGENTS SPAWNED</div>\n <div>${evts.filter(e=>e.type==='agent:spawn').flatMap(e=>e.agents||[]).map(a=>`<span class=\"dp-agent\">${a}</span>`).join('')}</div>\n </div>` : ''}\n ${evts.some(e=>e.artifacts) ? `\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">ARTIFACTS</div>\n ${evts.flatMap(e=>e.artifacts||[]).map(a=>`<div class=\"dp-artifact\">📄 ${a}</div>`).join('')}\n </div>` : ''}\n `;\n panel.classList.add('open');\n}\n\nasync function openSessionDetail(s) {\n const panel = document.getElementById('detail-panel');\n document.getElementById('dp-emoji').textContent = '📋';\n document.getElementById('dp-title').textContent = 'SESSION DETAIL';\n const body = document.getElementById('dp-body');\n body.innerHTML = '<div style=\"color:#303060;font-size:9px;padding:8px\">Loading…</div>';\n panel.classList.add('open');\n try {\n const res = await fetch(`/api/mastermind/session/${s.id}`);\n const full = await res.json();\n if (!full) { body.innerHTML = '<div style=\"color:#303060;font-size:9px\">Session not found.</div>'; return; }\n const ts = new Date(full.ts).toLocaleString();\n const elapsed = full.endTs ? ((full.endTs - full.ts)/1000).toFixed(1)+'s' : 'running';\n const evts = full.events || [];\n const domainSet = full.domains || [];\n body.innerHTML = `\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">OVERVIEW</div>\n <div class=\"dp-event\">Started: <span style=\"color:#6060a0\">${ts}</span></div>\n <div class=\"dp-event\">Duration: <span style=\"color:#6060a0\">${elapsed}</span></div>\n <div class=\"dp-event\">Status: <span style=\"color:${full.status==='complete'?'#40e880':full.status==='running'?'#28c068':'#f87171'}\">${full.status||'?'}</span></div>\n <div class=\"dp-event\">Domains: <span style=\"color:#8080c0\">${domainSet.join(', ')||'—'}</span></div>\n </div>\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">PROMPT</div>\n <div class=\"dp-event\" style=\"color:oklch(58% 0.09 186);word-break:break-all;white-space:normal;line-height:1.6\">${full.prompt||'—'}</div>\n </div>\n ${domainSet.length ? `\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">ACTIVE DOMAINS</div>\n ${domainSet.map(did => {\n const d = DOMAINS.find(x=>x.id===did);\n return d ? `<div class=\"dp-event\"><span style=\"color:${d.color}\">${d.emoji} ${d.label}</span></div>` : '';\n }).join('')}\n </div>` : ''}\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">EVENT TIMELINE (${evts.length})</div>\n ${evts.map(e => {\n const et = new Date(e.ts).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'});\n const d = e.domain ? DOMAINS.find(x=>x.id===e.domain) : null;\n const color = d ? d.color : '#6060a0';\n let detail = '';\n if (e.type === 'session:start') detail = `<span style=\"color:#5050a0;font-size:8px;word-break:break-all\">${e.prompt||''}</span>`;\n else if (e.type === 'domain:dispatch') detail = `<span style=\"color:#5060a0;font-size:8px\">${e.cmd||''}</span>`;\n else if (e.type === 'agent:spawn') detail = `<span style=\"color:#507090;font-size:8px\">agent: <b>${e.agent||''}</b> — ${(e.task||'').slice(0,50)}</span>`;\n else if (e.type === 'intercom') detail = `<span style=\"color:#506070;font-size:8px\">${e.from||'?'} → ${e.to||'?'}: ${e.msg||''}</span>`;\n else if (e.type === 'domain:complete') {\n const arts = (e.artifacts||[]).map(a=>`<span style=\"color:#407050;font-size:7px\">📄 ${a}</span>`).join(' ');\n detail = `<span style=\"color:#406050;font-size:8px\">status: ${e.status||'?'}</span>${arts?' '+arts:''}`;\n }\n else if (e.type === 'session:complete') detail = `<span style=\"color:#405080;font-size:8px\">domains: ${(e.domains||[]).join(', ')}</span>`;\n return `<div class=\"dp-event\" style=\"flex-direction:column;align-items:flex-start;gap:1px\"><div><span class=\"ev-ts\">${et}</span> <span class=\"ev-type\" style=\"color:${color}\">${e.type}</span>${e.domain?' <span style=\"color:#404060;font-size:8px\">['+e.domain+']</span>':''}</div>${detail?'<div style=\"padding-left:4px\">'+detail+'</div>':''}</div>`;\n }).join('')}\n </div>\n <div class=\"dp-section\">\n <div class=\"dp-section-title\">EXPORT</div>\n <div style=\"display:flex;gap:6px;flex-wrap:wrap\">\n <a class=\"dp-export-btn\" href=\"/api/mastermind/session/${full.id}/trace\" target=\"_blank\">📄 View Trace</a>\n <button class=\"dp-export-btn\" onclick=\"downloadSession('${full.id}')\">⬇ Download JSON</button>\n </div>\n </div>\n `;\n } catch(err) {\n body.innerHTML = `<div style=\"color:#a03030;font-size:9px\">${err.message}</div>`;\n }\n}\n\nfunction closeDetail() {\n document.getElementById('detail-panel').classList.remove('open');\n currentSessionId = null;\n document.querySelectorAll('.sess-item').forEach(x=>x.classList.remove('active'));\n}\n\nasync function downloadSession(id) {\n const res = await fetch(`/api/mastermind/session/${id}`);\n const data = await res.json();\n const blob = new Blob([JSON.stringify(data, null, 2)], {type:'application/json'});\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = `${id}.json`;\n a.click();\n URL.revokeObjectURL(a.href);\n}\n\n// ── Live data polling for status bar ──────────────────────────────────────────\nasync function pollStatus() {\n try {\n const res = await fetch('/api/data');\n if (!res.ok) return;\n const data = await res.json();\n const active = !!data?.swarm?.activity?.swarm?.active;\n const dot = document.getElementById('l-dot');\n dot.classList.toggle('on', active);\n document.getElementById('l-status').textContent = active ? 'LIVE' : 'IDLE';\n const n = data?.swarm?.state?.agentPlan?.length || 0;\n document.getElementById('l-agents').textContent = n + ' agent' + (n!==1?'s':'');\n // Highlight last routed domain\n const route = data?.hooks?.lastRoute || '';\n if (route && !isMovieMode) {\n const hit = DOMAINS.find(d => route.toLowerCase().includes(d.id));\n if (hit) {\n gsap.to(`#gr-${hit.id}`, { opacity:0.85, attr:{r:52}, duration:0.35 });\n gsap.to(`#gr-${hit.id}`, { opacity:0.2, attr:{r:44}, duration:1.8, delay:0.35 });\n }\n }\n } catch (_) {}\n}\n\n
|
|
16
|
+
function showStatusBanner(msg) {
|
|
17
|
+
let b = document.getElementById('status-banner');
|
|
18
|
+
if (!b) {
|
|
19
|
+
b = document.createElement('div'); b.id = 'status-banner';
|
|
20
|
+
b.style.cssText = 'position:fixed;top:0;left:0;right:0;padding:5px 14px;background:oklch(24% 0.05 186);border-bottom:1px solid oklch(68% 0.18 186 / 0.35);color:oklch(70% 0.05 186);font-size:9px;letter-spacing:1.5px;text-align:center;z-index:9999;transition:opacity 0.5s;pointer-events:none;';
|
|
21
|
+
document.body.appendChild(b);
|
|
22
|
+
}
|
|
23
|
+
b.textContent = msg; b.style.opacity = '1';
|
|
24
|
+
clearTimeout(b._t); b._t = setTimeout(() => { b.style.opacity = '0'; }, 5000);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── Bootstrap ──────────────────────────────────────────────────────────────────\nbuildStage();\nconnectSSE();\nrefreshSessions();\npollStatus();\nfetch('/api/git-user').then(r=>r.json()).then(u=>{\n if (u.name) document.getElementById('git-user-name').textContent = u.name;\n if (u.cwd) {\n const parts = u.cwd.replace(/\\\\/g, '/').split('/');\n document.getElementById('git-cwd-name').textContent = parts.slice(-2).join('/');\n document.getElementById('git-cwd-name').title = u.cwd;\n }\n}).catch(()=>{});\nsetInterval(pollStatus, 4000);\nsetInterval(refreshSessions, 8000);\n\n// Set initial live mode banner\ndocument.getElementById('mode-banner').classList.add('live-mode');\n</script>\n</body>\n</html>\n";
|
|
28
|
+
|
|
11
29
|
|
|
12
30
|
// ─── Session JSONL parser ────────────────────────────────────────────────────
|
|
13
31
|
function categorizeTool(name) {
|
|
@@ -140,6 +158,8 @@ function pathToSections(filename) {
|
|
|
140
158
|
|
|
141
159
|
// SSE client registry
|
|
142
160
|
const sseClients = new Set();
|
|
161
|
+
// Mastermind real-time event stream clients
|
|
162
|
+
const mmSseClients = new Set();
|
|
143
163
|
|
|
144
164
|
// Server state
|
|
145
165
|
let running = false;
|
|
@@ -230,11 +250,28 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
230
250
|
return;
|
|
231
251
|
}
|
|
232
252
|
|
|
253
|
+
// --------------------------------------------------------- GET /api/git-user
|
|
254
|
+
if (req.method === 'GET' && url === '/api/git-user') {
|
|
255
|
+
try {
|
|
256
|
+
const { execSync: gitExec } = await import('child_process');
|
|
257
|
+
const cwd = projectDir || process.cwd();
|
|
258
|
+
const name = gitExec('git config user.name', { cwd, encoding: 'utf8' }).trim();
|
|
259
|
+
const email = gitExec('git config user.email', { cwd, encoding: 'utf8' }).trim();
|
|
260
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
261
|
+
res.end(JSON.stringify({ name, email, cwd }));
|
|
262
|
+
} catch (_) {
|
|
263
|
+
const cwd2 = projectDir || process.cwd();
|
|
264
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
265
|
+
res.end(JSON.stringify({ name: '', email: '', cwd: cwd2 }));
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
233
270
|
// --------------------------------------------------------- GET /api/data
|
|
234
271
|
if (req.method === 'GET' && url === '/api/data') {
|
|
235
272
|
try {
|
|
236
273
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
237
|
-
const dir = qs.get('dir') || projectDir;
|
|
274
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
238
275
|
const snapshot = await collectAll(dir);
|
|
239
276
|
res.writeHead(200, {
|
|
240
277
|
'Content-Type': 'application/json',
|
|
@@ -281,7 +318,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
281
318
|
if (req.method === 'GET' && url === '/api/session-journal') {
|
|
282
319
|
try {
|
|
283
320
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
284
|
-
const dir = qs.get('dir') || projectDir;
|
|
321
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
285
322
|
const d = path.resolve(dir || process.cwd());
|
|
286
323
|
const slug = d.replace(/\//g, '-');
|
|
287
324
|
const projectClaudeDir = path.join(os.homedir(), '.claude', 'projects', slug);
|
|
@@ -340,7 +377,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
340
377
|
if (req.method === 'GET' && url === '/api/palace') {
|
|
341
378
|
try {
|
|
342
379
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
343
|
-
const dir = qs.get('dir') || projectDir;
|
|
380
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
344
381
|
const d = path.resolve(dir || process.cwd());
|
|
345
382
|
const palaceDir = path.join(d, '.monomind', 'palace');
|
|
346
383
|
|
|
@@ -369,7 +406,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
369
406
|
if (req.method === 'GET' && url === '/api/memory-files') {
|
|
370
407
|
try {
|
|
371
408
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
372
|
-
const dir = qs.get('dir') || projectDir;
|
|
409
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
373
410
|
const d = path.resolve(dir || process.cwd());
|
|
374
411
|
const homeDir = os.homedir();
|
|
375
412
|
const slug = d.replace(/\//g, '-');
|
|
@@ -478,11 +515,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
478
515
|
// ---------------------------------------------------------- GET /api/loops
|
|
479
516
|
if (req.method === 'GET' && url === '/api/loops') {
|
|
480
517
|
try {
|
|
481
|
-
const
|
|
518
|
+
const cwd = projectDir || process.cwd();
|
|
519
|
+
const loopsDir = path.join(cwd, '.monomind', 'loops');
|
|
482
520
|
let loops = [];
|
|
521
|
+
let stopFiles = new Set();
|
|
483
522
|
try {
|
|
484
523
|
const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('.json'));
|
|
485
|
-
|
|
524
|
+
stopFiles = new Set(fs.readdirSync(loopsDir).filter(f => f.endsWith('.stop')).map(f => f.replace('.stop', '')));
|
|
486
525
|
for (const file of files) {
|
|
487
526
|
try {
|
|
488
527
|
const data = JSON.parse(fs.readFileSync(path.join(loopsDir, file), 'utf-8'));
|
|
@@ -491,6 +530,98 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
491
530
|
} catch {}
|
|
492
531
|
}
|
|
493
532
|
} catch (e) { if (e.code !== 'ENOENT') throw e; }
|
|
533
|
+
|
|
534
|
+
// Also read .claude/scheduled_tasks.lock — active Claude Code /loop sessions
|
|
535
|
+
// that haven't had their ScheduleWakeup hook fire yet (or running on older version)
|
|
536
|
+
try {
|
|
537
|
+
const lockPath = path.join(cwd, '.claude', 'scheduled_tasks.lock');
|
|
538
|
+
if (fs.existsSync(lockPath)) {
|
|
539
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, 'utf-8'));
|
|
540
|
+
const sessionId = lock.sessionId;
|
|
541
|
+
const pid = lock.pid;
|
|
542
|
+
// Verify PID is alive
|
|
543
|
+
let alive = false;
|
|
544
|
+
try { process.kill(pid, 0); alive = true; } catch {}
|
|
545
|
+
const alreadyTracked = loops.some(l => l.id === sessionId || l.sessionId === sessionId);
|
|
546
|
+
if (alive && sessionId && !alreadyTracked && !stopFiles.has(sessionId)) {
|
|
547
|
+
// Try to extract ScheduleWakeup context from session JSONL
|
|
548
|
+
let loopEntry = null;
|
|
549
|
+
try {
|
|
550
|
+
const escaped = cwd.replace(/\//g, '-');
|
|
551
|
+
const sessionFile = path.join(os.homedir(), '.claude', 'projects', escaped, `${sessionId}.jsonl`);
|
|
552
|
+
if (fs.existsSync(sessionFile)) {
|
|
553
|
+
const stat = fs.statSync(sessionFile);
|
|
554
|
+
const readStart = Math.max(0, stat.size - 100000);
|
|
555
|
+
const buf = Buffer.alloc(stat.size - readStart);
|
|
556
|
+
const fd = fs.openSync(sessionFile, 'r');
|
|
557
|
+
fs.readSync(fd, buf, 0, buf.length, readStart);
|
|
558
|
+
fs.closeSync(fd);
|
|
559
|
+
const lines = buf.toString('utf-8').split('\n').filter(Boolean);
|
|
560
|
+
let lastWakeup = null;
|
|
561
|
+
for (const line of lines) {
|
|
562
|
+
try {
|
|
563
|
+
const entry = JSON.parse(line);
|
|
564
|
+
const content = entry?.message?.content;
|
|
565
|
+
if (Array.isArray(content)) {
|
|
566
|
+
for (const block of content) {
|
|
567
|
+
if (block?.type === 'tool_use' && block?.name === 'ScheduleWakeup') {
|
|
568
|
+
lastWakeup = block.input;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
} catch {}
|
|
573
|
+
}
|
|
574
|
+
if (lastWakeup) {
|
|
575
|
+
const prompt = lastWakeup.prompt || '';
|
|
576
|
+
const reason = lastWakeup.reason || '';
|
|
577
|
+
const delaySeconds = lastWakeup.delaySeconds || 60;
|
|
578
|
+
// Parse rep info from reason e.g. "repeat run 2/10"
|
|
579
|
+
const repM = (reason || prompt).match(/(\d+)\s*\/\s*(\d+)/);
|
|
580
|
+
const currentRep = repM ? parseInt(repM[1]) : 1;
|
|
581
|
+
const maxReps = repM ? parseInt(repM[2]) : 0;
|
|
582
|
+
const repFlag = (prompt).match(/--rep\s+(\d+)/);
|
|
583
|
+
const timesFlag = (prompt).match(/--times\s+(\d+)/);
|
|
584
|
+
const finalRep = repFlag ? parseInt(repFlag[1]) : currentRep;
|
|
585
|
+
const finalMax = timesFlag ? parseInt(timesFlag[1]) : maxReps;
|
|
586
|
+
const type = (finalMax > 0 || /repeat|loop/i.test(prompt)) ? 'repeat' : 'do';
|
|
587
|
+
loopEntry = {
|
|
588
|
+
id: sessionId,
|
|
589
|
+
sessionId,
|
|
590
|
+
type,
|
|
591
|
+
status: 'waiting',
|
|
592
|
+
prompt: prompt.slice(0, 300),
|
|
593
|
+
reason,
|
|
594
|
+
startedAt: lock.acquiredAt || Date.now(),
|
|
595
|
+
lastRunAt: Date.now(),
|
|
596
|
+
nextRunAt: Date.now() + delaySeconds * 1000,
|
|
597
|
+
currentRep: finalRep,
|
|
598
|
+
maxReps: finalMax,
|
|
599
|
+
interval: Math.round(delaySeconds / 60),
|
|
600
|
+
source: 'scheduled_tasks_lock',
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
} catch {}
|
|
605
|
+
// Fallback: minimal entry from lock file alone
|
|
606
|
+
if (!loopEntry) {
|
|
607
|
+
loopEntry = {
|
|
608
|
+
id: sessionId,
|
|
609
|
+
sessionId,
|
|
610
|
+
type: 'do',
|
|
611
|
+
status: 'running',
|
|
612
|
+
prompt: '(active session)',
|
|
613
|
+
reason: '',
|
|
614
|
+
startedAt: lock.acquiredAt || Date.now(),
|
|
615
|
+
lastRunAt: lock.acquiredAt || Date.now(),
|
|
616
|
+
nextRunAt: null,
|
|
617
|
+
source: 'scheduled_tasks_lock',
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
loops.push(loopEntry);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} catch {}
|
|
624
|
+
|
|
494
625
|
loops.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
|
|
495
626
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
496
627
|
res.end(JSON.stringify({ loops }));
|
|
@@ -594,62 +725,103 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
594
725
|
return;
|
|
595
726
|
}
|
|
596
727
|
|
|
597
|
-
// ------------------------------------------------------- GET /api/
|
|
598
|
-
if (req.method === 'GET' && url === '/api/
|
|
728
|
+
// ------------------------------------------------------- GET /api/monograph-html
|
|
729
|
+
if (req.method === 'GET' && url === '/api/monograph-html') {
|
|
599
730
|
try {
|
|
600
731
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
601
|
-
const dir = qs.get('dir') || projectDir;
|
|
732
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
602
733
|
const d = path.resolve(dir || process.cwd());
|
|
734
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
735
|
+
|
|
736
|
+
// Generate HTML on-the-fly from SQLite DB using the improved toHtml export
|
|
737
|
+
if (fs.existsSync(dbPath)) {
|
|
738
|
+
const { openDb, closeDb, toHtml } = await import('@monoes/monograph');
|
|
739
|
+
const db = openDb(dbPath);
|
|
740
|
+
let html;
|
|
741
|
+
try {
|
|
742
|
+
const rawNodes = db.prepare('SELECT * FROM nodes LIMIT 5000').all();
|
|
743
|
+
const rawEdges = db.prepare('SELECT * FROM edges').all();
|
|
744
|
+
// Remap snake_case DB columns to camelCase MonographNode/MonographEdge interfaces
|
|
745
|
+
const parsedNodes = rawNodes.map(n => ({
|
|
746
|
+
id: n.id,
|
|
747
|
+
label: n.label,
|
|
748
|
+
name: n.name,
|
|
749
|
+
normLabel: n.norm_label,
|
|
750
|
+
filePath: n.file_path,
|
|
751
|
+
startLine: n.start_line,
|
|
752
|
+
endLine: n.end_line,
|
|
753
|
+
communityId: n.community_id,
|
|
754
|
+
isExported: !!n.is_exported,
|
|
755
|
+
language: n.language,
|
|
756
|
+
properties: n.properties ? JSON.parse(n.properties) : {},
|
|
757
|
+
}));
|
|
758
|
+
const parsedEdges = rawEdges.map(e => ({
|
|
759
|
+
id: e.id,
|
|
760
|
+
sourceId: e.source_id,
|
|
761
|
+
targetId: e.target_id,
|
|
762
|
+
relation: e.relation,
|
|
763
|
+
confidence: e.confidence,
|
|
764
|
+
confidenceScore: e.confidence_score,
|
|
765
|
+
weight: e.weight,
|
|
766
|
+
}));
|
|
767
|
+
html = toHtml(parsedNodes, parsedEdges);
|
|
768
|
+
} finally {
|
|
769
|
+
closeDb(db);
|
|
770
|
+
}
|
|
771
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
772
|
+
res.end(html);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Fallback: try legacy graph.html on disk
|
|
603
777
|
const htmlPath = path.join(d, '.monomind', 'graph', 'graph.html');
|
|
604
778
|
const html = fs.readFileSync(htmlPath, 'utf-8');
|
|
605
779
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
606
780
|
res.end(html);
|
|
607
781
|
} catch (err) {
|
|
608
782
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
609
|
-
res.end('<html><body style="background:#0f0f1a;color:#888;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;"><div style="text-align:center;"><h3 style="color:#4E79A7;">No Graph Built Yet</h3><p>Run <code style="color:#00E5C8;">
|
|
783
|
+
res.end('<html><body style="background:#0f0f1a;color:#888;font-family:monospace;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;"><div style="text-align:center;"><h3 style="color:#4E79A7;">No Graph Built Yet</h3><p>Run <code style="color:#00E5C8;">mcp__monomind__monograph_build</code> or click BUILD in the sidebar.</p></div></body></html>');
|
|
610
784
|
}
|
|
611
785
|
return;
|
|
612
786
|
}
|
|
613
787
|
|
|
614
|
-
// ------------------------------------------------------- GET /api/
|
|
615
|
-
if (req.method === 'GET' && url === '/api/
|
|
788
|
+
// ------------------------------------------------------- GET /api/monograph-report
|
|
789
|
+
if (req.method === 'GET' && url === '/api/monograph-report') {
|
|
616
790
|
try {
|
|
617
791
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
618
|
-
const dir = qs.get('dir') || projectDir;
|
|
792
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
619
793
|
const d = path.resolve(dir || process.cwd());
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const
|
|
629
|
-
const
|
|
630
|
-
const
|
|
631
|
-
const
|
|
632
|
-
stats = { nodes, edges, size:
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
} catch {}
|
|
650
|
-
|
|
794
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
795
|
+
let report = null, exists = false, stats = null;
|
|
796
|
+
if (fs.existsSync(dbPath)) {
|
|
797
|
+
exists = true;
|
|
798
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
799
|
+
const db = openDb(dbPath);
|
|
800
|
+
try {
|
|
801
|
+
const nodeCount = db.prepare('SELECT COUNT(*) AS c FROM nodes').get().c;
|
|
802
|
+
const edgeCount = db.prepare('SELECT COUNT(*) AS c FROM edges').get().c;
|
|
803
|
+
const topNodes = db.prepare(`SELECT n.id, n.name, n.label, (SELECT COUNT(*) FROM edges e WHERE e.source_id=n.id OR e.target_id=n.id) AS deg FROM nodes n ORDER BY deg DESC LIMIT 20`).all();
|
|
804
|
+
const labelDist = db.prepare('SELECT label, COUNT(*) AS cnt FROM nodes GROUP BY label ORDER BY cnt DESC LIMIT 10').all();
|
|
805
|
+
const dbStat = fs.statSync(dbPath);
|
|
806
|
+
stats = { nodes: nodeCount, edges: edgeCount, size: dbStat.size, mtime: dbStat.mtimeMs };
|
|
807
|
+
report = [
|
|
808
|
+
'# Monograph Knowledge Graph',
|
|
809
|
+
'',
|
|
810
|
+
`## Overview`,
|
|
811
|
+
`- **Nodes**: ${nodeCount.toLocaleString()}`,
|
|
812
|
+
`- **Edges**: ${edgeCount.toLocaleString()}`,
|
|
813
|
+
`- **Last built**: ${new Date(dbStat.mtimeMs).toLocaleString()}`,
|
|
814
|
+
'',
|
|
815
|
+
'## Top 20 Nodes by Degree',
|
|
816
|
+
...topNodes.map((n, i) => `${String(i+1).padStart(3,' ')}. **${n.name || n.id}** \`${n.label}\` — ${n.deg} connections`),
|
|
817
|
+
'',
|
|
818
|
+
'## Node Type Distribution',
|
|
819
|
+
...labelDist.map(r => `- **${r.label}**: ${r.cnt}`),
|
|
820
|
+
].join('\n');
|
|
821
|
+
} finally { closeDb(db); }
|
|
822
|
+
}
|
|
651
823
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
652
|
-
res.end(JSON.stringify({ exists,
|
|
824
|
+
res.end(JSON.stringify({ exists, report, stats }));
|
|
653
825
|
} catch (err) {
|
|
654
826
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
655
827
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -657,68 +829,108 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
657
829
|
return;
|
|
658
830
|
}
|
|
659
831
|
|
|
660
|
-
// -------------------------------------------------- GET /api/
|
|
661
|
-
if (req.method === 'GET' && url === '/api/
|
|
832
|
+
// -------------------------------------------------- GET /api/monograph-graph
|
|
833
|
+
if (req.method === 'GET' && url === '/api/monograph-graph') {
|
|
662
834
|
try {
|
|
663
835
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
664
|
-
const dir = qs.get('dir') || projectDir;
|
|
836
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
665
837
|
const d = path.resolve(dir || process.cwd());
|
|
666
|
-
const
|
|
667
|
-
let nodes = [], edges = []
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
838
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
839
|
+
let nodes = [], edges = [];
|
|
840
|
+
if (fs.existsSync(dbPath)) {
|
|
841
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
842
|
+
const db = openDb(dbPath);
|
|
843
|
+
try {
|
|
844
|
+
const nodeLimit = Math.min(parseInt(qs.get('limit') || '500', 10), 5000);
|
|
845
|
+
// ?labels=Section,Concept → fetch only those label types (no degree cutoff)
|
|
846
|
+
const labelFilter = qs.get('labels') ? new Set(qs.get('labels').split(',').map(s => s.trim())) : null;
|
|
847
|
+
const rawNodes = labelFilter
|
|
848
|
+
? db.prepare(`SELECT id, name, label, file_path, community_id FROM nodes WHERE label IN (${[...labelFilter].map(() => '?').join(',')}) LIMIT 5000`).all(...labelFilter)
|
|
849
|
+
: db.prepare('SELECT id, name, label, file_path, community_id FROM nodes LIMIT 5000').all();
|
|
850
|
+
const rawEdges = db.prepare('SELECT source_id, target_id, relation FROM edges').all();
|
|
851
|
+
// Compute degree
|
|
678
852
|
const degree = new Map();
|
|
679
|
-
for (const n of
|
|
853
|
+
for (const n of rawNodes) degree.set(n.id, 0);
|
|
680
854
|
for (const e of rawEdges) {
|
|
681
|
-
if (
|
|
682
|
-
if (
|
|
855
|
+
if (degree.has(e.source_id)) degree.set(e.source_id, (degree.get(e.source_id) || 0) + 1);
|
|
856
|
+
if (degree.has(e.target_id)) degree.set(e.target_id, (degree.get(e.target_id) || 0) + 1);
|
|
683
857
|
}
|
|
684
|
-
//
|
|
685
|
-
const topNodes =
|
|
686
|
-
|
|
687
|
-
.slice(0,
|
|
858
|
+
// When filtering by labels, return all matching nodes (skip degree sort+slice)
|
|
859
|
+
const topNodes = labelFilter
|
|
860
|
+
? rawNodes
|
|
861
|
+
: [...rawNodes].sort((a, b) => (degree.get(b.id) || 0) - (degree.get(a.id) || 0)).slice(0, nodeLimit);
|
|
688
862
|
const topIds = new Set(topNodes.map(n => n.id));
|
|
689
|
-
nodes = topNodes.map(n => ({ id: n.id, label: n.
|
|
690
|
-
edges = rawEdges
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
.slice(0, 500)
|
|
694
|
-
.map(e => ({ source: e.source, target: e.target, relation: e.relation || e.type }));
|
|
695
|
-
} else {
|
|
696
|
-
tooLarge = true;
|
|
697
|
-
}
|
|
698
|
-
} catch (e) { if (e.code !== 'ENOENT') throw e; }
|
|
863
|
+
nodes = topNodes.map(n => ({ id: n.id, label: n.name || n.id, type: n.label || 'unknown', degree: degree.get(n.id) || 0 }));
|
|
864
|
+
edges = rawEdges.filter(e => topIds.has(e.source_id) && topIds.has(e.target_id)).slice(0, 2000).map(e => ({ source: e.source_id, target: e.target_id, relation: e.relation || 'REF' }));
|
|
865
|
+
} finally { closeDb(db); }
|
|
866
|
+
}
|
|
699
867
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
700
|
-
res.end(JSON.stringify({ nodes, edges
|
|
868
|
+
res.end(JSON.stringify({ nodes, edges }));
|
|
701
869
|
} catch (err) {
|
|
702
870
|
res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
|
|
703
871
|
}
|
|
704
872
|
return;
|
|
705
873
|
}
|
|
706
874
|
|
|
707
|
-
// -------------------------------------------------- POST /api/
|
|
708
|
-
|
|
875
|
+
// -------------------------------------------------- POST /api/ua-enrich
|
|
876
|
+
// Trigger semantic enrichment on an existing monograph DB.
|
|
877
|
+
// Imports understand graph.json if present; falls back to structural-only pass.
|
|
878
|
+
if (req.method === 'POST' && url === '/api/ua-enrich') {
|
|
709
879
|
try {
|
|
710
880
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
711
|
-
const dir = qs.get('dir') || projectDir;
|
|
881
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
882
|
+
const d = path.resolve(dir || process.cwd());
|
|
883
|
+
const dbFilePath = path.join(d, '.monomind', 'monograph.db');
|
|
884
|
+
|
|
885
|
+
// Check for UA graph.json first
|
|
886
|
+
const uaGraphCandidates = [
|
|
887
|
+
path.join(d, '.understand-anything', 'knowledge-graph.json'),
|
|
888
|
+
path.join(d, '.understand-anything', 'graph.json'),
|
|
889
|
+
path.join(d, '.ua', 'knowledge-graph.json'),
|
|
890
|
+
path.join(d, '.ua', 'graph.json'),
|
|
891
|
+
];
|
|
892
|
+
const uaGraph = uaGraphCandidates.find(p => fs.existsSync(p));
|
|
893
|
+
const importScript = path.join(process.cwd(), 'scripts', 'ua-import.mjs');
|
|
894
|
+
const enrichScript = path.join(process.cwd(), 'scripts', 'ua-enrich.mjs');
|
|
895
|
+
|
|
896
|
+
res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
897
|
+
|
|
898
|
+
if (uaGraph && fs.existsSync(importScript)) {
|
|
899
|
+
res.end(JSON.stringify({ status: 'importing', source: uaGraph }));
|
|
900
|
+
const { spawn: sp } = await import('child_process');
|
|
901
|
+
const child = sp(process.execPath, [importScript, uaGraph, dbFilePath], { stdio: 'ignore', detached: true, cwd: d });
|
|
902
|
+
child.unref();
|
|
903
|
+
} else if (fs.existsSync(enrichScript)) {
|
|
904
|
+
res.end(JSON.stringify({ status: 'enriching', mode: 'structural-only' }));
|
|
905
|
+
const { spawn: sp } = await import('child_process');
|
|
906
|
+
const child = sp(process.execPath, [enrichScript, '--dir', d, '--db', dbFilePath, '--full'], { stdio: 'ignore', detached: true, cwd: d });
|
|
907
|
+
child.unref();
|
|
908
|
+
} else {
|
|
909
|
+
res.end(JSON.stringify({ status: 'skipped', reason: 'No understand graph.json found. Run /monomind:understand in Claude Code first.' }));
|
|
910
|
+
}
|
|
911
|
+
} catch (err) {
|
|
912
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
913
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// -------------------------------------------------- POST /api/monograph-build
|
|
919
|
+
if (req.method === 'POST' && url === '/api/monograph-build') {
|
|
920
|
+
try {
|
|
921
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
922
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
712
923
|
const d = path.resolve(dir || process.cwd());
|
|
713
924
|
|
|
714
925
|
res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
715
926
|
res.end(JSON.stringify({ status: 'building', dir: d }));
|
|
716
927
|
|
|
717
|
-
// Build via
|
|
928
|
+
// Build via monograph in background
|
|
718
929
|
const { spawn: sp } = await import('child_process');
|
|
719
|
-
const
|
|
930
|
+
const script = `import { buildAsync } from '@monoes/monograph'; await buildAsync(${JSON.stringify(d)});`;
|
|
931
|
+
const child = sp(process.execPath, ['--input-type=module', '--eval', script], { stdio: 'ignore', detached: true, cwd: d });
|
|
720
932
|
child.unref();
|
|
721
|
-
console.log(`[graph] build started for ${d} via
|
|
933
|
+
console.log(`[graph] build started for ${d} via monograph`);
|
|
722
934
|
} catch (err) {
|
|
723
935
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
724
936
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -726,32 +938,189 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
726
938
|
return;
|
|
727
939
|
}
|
|
728
940
|
|
|
729
|
-
// -------------------------------------------------- GET /api/
|
|
730
|
-
if (req.method === 'GET' && url === '/api/
|
|
941
|
+
// -------------------------------------------------- GET /api/monograph-build-docs-status
|
|
942
|
+
if (req.method === 'GET' && url === '/api/monograph-build-docs-status') {
|
|
943
|
+
const qs2 = new URL(req.url, 'http://localhost').searchParams;
|
|
944
|
+
const d2 = path.resolve(qs2.get('dir') || projectDir || process.cwd());
|
|
945
|
+
const state = buildDocsState.get(d2) || { status: 'idle' };
|
|
946
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
947
|
+
res.end(JSON.stringify(state));
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// -------------------------------------------------- POST /api/monograph-build-docs
|
|
952
|
+
if (req.method === 'POST' && url === '/api/monograph-build-docs') {
|
|
731
953
|
try {
|
|
732
954
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
733
|
-
const dir = qs.get('dir') || projectDir;
|
|
734
|
-
const q = qs.get('q') || '';
|
|
735
|
-
const mode = qs.get('mode') === 'dfs' ? '--dfs' : '';
|
|
736
|
-
const budget = qs.get('budget') || '2000';
|
|
955
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
737
956
|
const d = path.resolve(dir || process.cwd());
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
957
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
958
|
+
if (!fs.existsSync(dbPath)) {
|
|
959
|
+
res.writeHead(400, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
960
|
+
res.end(JSON.stringify({ error: 'monograph.db not found — run BUILD GRAPH first' }));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
741
963
|
|
|
742
|
-
if
|
|
743
|
-
|
|
744
|
-
|
|
964
|
+
// Reject if already running
|
|
965
|
+
const existing = buildDocsState.get(d);
|
|
966
|
+
if (existing && existing.status === 'pending') {
|
|
967
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
968
|
+
res.end(JSON.stringify({ status: 'pending', message: 'Build already in progress' }));
|
|
745
969
|
return;
|
|
746
970
|
}
|
|
747
971
|
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
972
|
+
const startedAt = Date.now();
|
|
973
|
+
buildDocsState.set(d, { status: 'pending', sections: 0, files: 0, error: null, startedAt });
|
|
974
|
+
res.writeHead(202, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
975
|
+
res.end(JSON.stringify({ status: 'pending', dir: d }));
|
|
976
|
+
|
|
977
|
+
// Run doc parsing in background
|
|
978
|
+
(async () => {
|
|
979
|
+
const { openDb, closeDb, isFileCached, updateFileCache, hashFileContent } = await import('@monoes/monograph');
|
|
980
|
+
const { readFileSync, readdirSync, statSync } = fs;
|
|
981
|
+
|
|
982
|
+
const docExts = new Set(['.md', '.mdx', '.txt', '.rst']);
|
|
983
|
+
const ignoreDirs = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '.monomind', '__pycache__', 'vendor']);
|
|
984
|
+
const docFiles = [];
|
|
985
|
+
function walk(dir2, depth = 0) {
|
|
986
|
+
if (depth > 12) return;
|
|
987
|
+
let entries;
|
|
988
|
+
try { entries = readdirSync(dir2); } catch { return; }
|
|
989
|
+
for (const e of entries) {
|
|
990
|
+
if (ignoreDirs.has(e) || e.startsWith('.')) continue;
|
|
991
|
+
const full = path.join(dir2, e);
|
|
992
|
+
let st;
|
|
993
|
+
try { st = statSync(full); } catch { continue; }
|
|
994
|
+
if (st.isDirectory()) { walk(full, depth + 1); }
|
|
995
|
+
else if (docExts.has(path.extname(e).toLowerCase()) && st.size < 600000) docFiles.push(full);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
walk(d);
|
|
999
|
+
|
|
1000
|
+
const db = openDb(dbPath);
|
|
1001
|
+
try {
|
|
1002
|
+
const insertNode = db.prepare(`INSERT OR REPLACE INTO nodes (id, label, name, norm_label, file_path, start_line, end_line, language, is_exported) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)`);
|
|
1003
|
+
const insertEdge = db.prepare(`INSERT OR IGNORE INTO edges (id, source_id, target_id, relation, confidence, confidence_score, weight) VALUES (?, ?, ?, ?, 'EXTRACTED', 1.0, 1.0)`);
|
|
1004
|
+
|
|
1005
|
+
const insertAll = db.transaction((nodes, edges) => {
|
|
1006
|
+
for (const n of nodes) {
|
|
1007
|
+
try { insertNode.run(n.id, n.label, n.name, n.norm_label, n.file_path, n.start_line, n.end_line, n.language); } catch {}
|
|
1008
|
+
}
|
|
1009
|
+
for (const e of edges) { try { insertEdge.run(e.id, e.src, e.dst, e.rel); } catch {} }
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
const normTitle = t => t.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
1013
|
+
|
|
1014
|
+
let totalSections = 0;
|
|
1015
|
+
let skipped = 0;
|
|
1016
|
+
for (const filePath of docFiles) {
|
|
1017
|
+
let content;
|
|
1018
|
+
try { content = readFileSync(filePath, 'utf-8'); } catch { continue; }
|
|
1019
|
+
|
|
1020
|
+
// Skip unchanged files using file cache
|
|
1021
|
+
let isCached = false;
|
|
1022
|
+
let contentHash = '';
|
|
1023
|
+
try {
|
|
1024
|
+
contentHash = hashFileContent(content);
|
|
1025
|
+
isCached = isFileCached(db, filePath, contentHash);
|
|
1026
|
+
} catch {}
|
|
1027
|
+
if (isCached) { skipped++; continue; }
|
|
1028
|
+
const relPath = path.relative(d, filePath);
|
|
1029
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
1030
|
+
const fileId = 'doc:' + relPath;
|
|
1031
|
+
const lineCount = content.split('\n').length;
|
|
1032
|
+
|
|
1033
|
+
const nodes = [{ id: fileId, label: 'File', name: relPath, norm_label: normTitle(relPath), file_path: relPath, start_line: 1, end_line: lineCount, language: ext }];
|
|
1034
|
+
const edges = [];
|
|
1035
|
+
const lines = content.split('\n');
|
|
1036
|
+
const sectionStack = [{ id: fileId, depth: 0 }];
|
|
1037
|
+
let inCodeBlock = false;
|
|
1038
|
+
let codeBlockLang = null;
|
|
1039
|
+
|
|
1040
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1041
|
+
const line = lines[i];
|
|
1042
|
+
|
|
1043
|
+
// Track fenced code blocks — don't parse headings inside them
|
|
1044
|
+
const fenceMatch = line.match(/^```([a-zA-Z0-9_+-]*)$/);
|
|
1045
|
+
if (fenceMatch) {
|
|
1046
|
+
if (!inCodeBlock) {
|
|
1047
|
+
inCodeBlock = true;
|
|
1048
|
+
codeBlockLang = fenceMatch[1].trim() || null;
|
|
1049
|
+
if (codeBlockLang) {
|
|
1050
|
+
const cId = 'concept:lang:' + codeBlockLang.toLowerCase();
|
|
1051
|
+
if (!nodes.find(n => n.id === cId)) {
|
|
1052
|
+
nodes.push({ id: cId, label: 'Concept', name: codeBlockLang, norm_label: normTitle(codeBlockLang), file_path: null, start_line: 0, end_line: 0, language: null });
|
|
1053
|
+
}
|
|
1054
|
+
const curSec = sectionStack[sectionStack.length - 1].id;
|
|
1055
|
+
edges.push({ id: 'e:' + curSec + ':' + cId + ':code', src: curSec, dst: cId, rel: 'TAGGED_AS' });
|
|
1056
|
+
}
|
|
1057
|
+
} else { inCodeBlock = false; codeBlockLang = null; }
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (inCodeBlock) continue;
|
|
1061
|
+
|
|
1062
|
+
// ATX headings: # Title
|
|
1063
|
+
const hMatch = line.match(/^(#{1,6})\s+(.+)/);
|
|
1064
|
+
if (hMatch) {
|
|
1065
|
+
const depth = hMatch[1].length;
|
|
1066
|
+
const title = hMatch[2].trim().replace(/\s+#+\s*$/, '').trim();
|
|
1067
|
+
const secId = 'sec:' + relPath + ':' + (i + 1);
|
|
1068
|
+
nodes.push({ id: secId, label: 'Section', name: title, norm_label: normTitle(title), file_path: relPath, start_line: i + 1, end_line: i + 1, language: ext });
|
|
1069
|
+
totalSections++;
|
|
1070
|
+
while (sectionStack.length > 1 && sectionStack[sectionStack.length - 1].depth >= depth) sectionStack.pop();
|
|
1071
|
+
const parentId = sectionStack[sectionStack.length - 1].id;
|
|
1072
|
+
edges.push({ id: 'e:' + secId + ':' + parentId + ':parent', src: parentId, dst: secId, rel: 'DEFINES' });
|
|
1073
|
+
sectionStack.push({ id: secId, depth });
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
// RST-style headings: line followed by ===, ---, ~~~, ^^^, etc.
|
|
1078
|
+
if (i + 1 < lines.length && lines[i + 1].match(/^[=\-~^"'`#*+!]{3,}\s*$/) && line.trim().length > 0 && line.trim().length <= lines[i + 1].trim().length + 2) {
|
|
1079
|
+
const underlineChar = lines[i + 1].trim()[0];
|
|
1080
|
+
const rstDepth = '=-~^"\'`#*+!'.indexOf(underlineChar) + 1 || 3;
|
|
1081
|
+
const title = line.trim();
|
|
1082
|
+
const secId = 'sec:' + relPath + ':' + (i + 1);
|
|
1083
|
+
nodes.push({ id: secId, label: 'Section', name: title, norm_label: normTitle(title), file_path: relPath, start_line: i + 1, end_line: i + 1, language: ext });
|
|
1084
|
+
totalSections++;
|
|
1085
|
+
const depth = Math.min(6, Math.ceil(rstDepth / 2));
|
|
1086
|
+
while (sectionStack.length > 1 && sectionStack[sectionStack.length - 1].depth >= depth) sectionStack.pop();
|
|
1087
|
+
const parentId = sectionStack[sectionStack.length - 1].id;
|
|
1088
|
+
edges.push({ id: 'e:' + secId + ':' + parentId + ':parent', src: parentId, dst: secId, rel: 'DEFINES' });
|
|
1089
|
+
sectionStack.push({ id: secId, depth });
|
|
1090
|
+
i++; // skip underline line
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// #hashtag concepts (skip markdown headings already matched)
|
|
1095
|
+
const tags = line.match(/#([a-zA-Z][a-zA-Z0-9_-]{2,})/g);
|
|
1096
|
+
if (tags) {
|
|
1097
|
+
for (const tag of tags) {
|
|
1098
|
+
const concept = tag.slice(1);
|
|
1099
|
+
const cId = 'concept:tag:' + concept.toLowerCase();
|
|
1100
|
+
if (!nodes.find(n => n.id === cId)) {
|
|
1101
|
+
nodes.push({ id: cId, label: 'Concept', name: concept, norm_label: normTitle(concept), file_path: null, start_line: 0, end_line: 0, language: null });
|
|
1102
|
+
}
|
|
1103
|
+
const curSec = sectionStack[sectionStack.length - 1].id;
|
|
1104
|
+
edges.push({ id: 'e:' + curSec + ':' + cId + ':tag', src: curSec, dst: cId, rel: 'TAGGED_AS' });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
try {
|
|
1110
|
+
insertAll(nodes, edges);
|
|
1111
|
+
// Update file cache so we skip unchanged files next run
|
|
1112
|
+
try {
|
|
1113
|
+
updateFileCache(db, { filePath, contentHash, lastParsed: Date.now(), nodeCount: nodes.length, edgeCount: edges.length });
|
|
1114
|
+
} catch {}
|
|
1115
|
+
} catch (e) { console.error('[docs-build] error inserting', relPath, e.message); }
|
|
1116
|
+
}
|
|
1117
|
+
console.log(`[docs-build] indexed ${docFiles.length - skipped} docs (${skipped} cached), ${totalSections} sections → ${dbPath}`);
|
|
1118
|
+
buildDocsState.set(d, { status: 'done', sections: totalSections, files: docFiles.length - skipped, cached: skipped, error: null, startedAt, completedAt: Date.now() });
|
|
1119
|
+
} finally { closeDb(db); }
|
|
1120
|
+
})().catch(e => {
|
|
1121
|
+
console.error('[docs-build] fatal:', e.message);
|
|
1122
|
+
buildDocsState.set(d, { status: 'error', sections: 0, files: 0, error: e.message, startedAt, completedAt: Date.now() });
|
|
1123
|
+
});
|
|
755
1124
|
} catch (err) {
|
|
756
1125
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
757
1126
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -759,29 +1128,218 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
759
1128
|
return;
|
|
760
1129
|
}
|
|
761
1130
|
|
|
762
|
-
// -------------------------------------------------- GET /api/
|
|
763
|
-
|
|
1131
|
+
// -------------------------------------------------- GET /api/monograph-content
|
|
1132
|
+
// Returns actual file content for a node (properties.content or file slice)
|
|
1133
|
+
if (req.method === 'GET' && url === '/api/monograph-content') {
|
|
764
1134
|
try {
|
|
765
1135
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
766
|
-
const dir = qs.get('dir') || projectDir;
|
|
767
|
-
const
|
|
1136
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1137
|
+
const id = qs.get('id') || '';
|
|
768
1138
|
const d = path.resolve(dir || process.cwd());
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
1139
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1140
|
+
if (!id) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?id=' })); return; }
|
|
1141
|
+
if (!fs.existsSync(dbPath)) { res.writeHead(404); res.end(JSON.stringify({ error: 'Graph not built' })); return; }
|
|
1142
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
1143
|
+
const db = openDb(dbPath);
|
|
1144
|
+
let content = '', filePath = '', startLine = 0, endLine = 0, language = '', name = '', type = '';
|
|
1145
|
+
try {
|
|
1146
|
+
const node = db.prepare('SELECT * FROM nodes WHERE id=?').get(id);
|
|
1147
|
+
if (!node) { res.writeHead(404); res.end(JSON.stringify({ error: 'Node not found' })); return; }
|
|
1148
|
+
name = node.name || id;
|
|
1149
|
+
type = node.label || 'Unknown';
|
|
1150
|
+
filePath = node.file_path || '';
|
|
1151
|
+
startLine = node.start_line || 0;
|
|
1152
|
+
endLine = node.end_line || 0;
|
|
1153
|
+
language = node.language || '';
|
|
1154
|
+
// Try properties.content first (from official monograph pipeline)
|
|
1155
|
+
if (node.properties) {
|
|
1156
|
+
try {
|
|
1157
|
+
const props = JSON.parse(node.properties);
|
|
1158
|
+
if (props.content && props.content.trim()) { content = props.content; }
|
|
1159
|
+
} catch {}
|
|
1160
|
+
}
|
|
1161
|
+
// Fallback: read from actual file
|
|
1162
|
+
if (!content && filePath) {
|
|
1163
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.join(d, filePath);
|
|
1164
|
+
try {
|
|
1165
|
+
const lines = fs.readFileSync(absPath, 'utf-8').split('\n');
|
|
1166
|
+
const sl = Math.max(0, (startLine || 1) - 1);
|
|
1167
|
+
const el = Math.min(lines.length, (endLine || startLine || lines.length) + 5);
|
|
1168
|
+
content = lines.slice(sl, Math.min(el, sl + 120)).join('\n');
|
|
1169
|
+
} catch {}
|
|
1170
|
+
}
|
|
1171
|
+
} finally { closeDb(db); }
|
|
1172
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1173
|
+
res.end(JSON.stringify({ content, filePath, startLine, endLine, language, name, type }));
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
772
1179
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1180
|
+
// -------------------------------------------------- GET /api/monograph-fts
|
|
1181
|
+
// Full-text search with content snippets — powers the wiki search box
|
|
1182
|
+
if (req.method === 'GET' && url === '/api/monograph-fts') {
|
|
1183
|
+
try {
|
|
1184
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1185
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1186
|
+
const q = (qs.get('q') || '').trim();
|
|
1187
|
+
const limit = Math.min(100, parseInt(qs.get('limit') || '50', 10));
|
|
1188
|
+
const d = path.resolve(dir || process.cwd());
|
|
1189
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1190
|
+
if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q=' })); return; }
|
|
1191
|
+
if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ nodes: [] })); return; }
|
|
1192
|
+
const { openDb, closeDb, ftsSearch } = await import('@monoes/monograph');
|
|
1193
|
+
const db = openDb(dbPath);
|
|
1194
|
+
let nodes = [];
|
|
1195
|
+
try {
|
|
1196
|
+
const hits = ftsSearch(db, q, limit);
|
|
1197
|
+
nodes = hits.map(h => {
|
|
1198
|
+
let snippet = '';
|
|
1199
|
+
if (h.properties) { try { const p = JSON.parse(h.properties); snippet = (p.content || '').slice(0, 200); } catch {} }
|
|
1200
|
+
return { id: h.id, label: h.name, type: h.label, degree: 0, filePath: h.filePath || h.file_path, startLine: h.startLine || h.start_line, snippet };
|
|
1201
|
+
});
|
|
1202
|
+
} finally { closeDb(db); }
|
|
1203
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1204
|
+
res.end(JSON.stringify({ nodes }));
|
|
1205
|
+
} catch (err) {
|
|
1206
|
+
res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
|
|
1207
|
+
}
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
778
1210
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1211
|
+
// -------------------------------------------------- GET /api/monograph-related
|
|
1212
|
+
// BFS from a node — returns node IDs sorted by graph distance (for re-ranking)
|
|
1213
|
+
if (req.method === 'GET' && url === '/api/monograph-related') {
|
|
1214
|
+
try {
|
|
1215
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1216
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1217
|
+
const id = qs.get('id') || '';
|
|
1218
|
+
const limit = Math.min(200, parseInt(qs.get('limit') || '60', 10));
|
|
1219
|
+
const maxDepth = Math.min(4, parseInt(qs.get('depth') || '3', 10));
|
|
1220
|
+
const d = path.resolve(dir || process.cwd());
|
|
1221
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1222
|
+
if (!id || !fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ related: [] })); return; }
|
|
1223
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
1224
|
+
const db = openDb(dbPath);
|
|
1225
|
+
const related = [];
|
|
1226
|
+
try {
|
|
1227
|
+
const visited = new Set([id]);
|
|
1228
|
+
let frontier = [id];
|
|
1229
|
+
for (let depth = 1; depth <= maxDepth && frontier.length > 0 && related.length < limit; depth++) {
|
|
1230
|
+
const next = [];
|
|
1231
|
+
for (const nodeId of frontier) {
|
|
1232
|
+
const rows = db.prepare(`SELECT DISTINCT target_id as nid FROM edges WHERE source_id=? UNION SELECT DISTINCT source_id as nid FROM edges WHERE target_id=? LIMIT 30`).all(nodeId, nodeId);
|
|
1233
|
+
for (const r of rows) {
|
|
1234
|
+
if (!visited.has(r.nid)) {
|
|
1235
|
+
visited.add(r.nid);
|
|
1236
|
+
next.push(r.nid);
|
|
1237
|
+
related.push({ id: r.nid, distance: depth });
|
|
1238
|
+
if (related.length >= limit) break;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (related.length >= limit) break;
|
|
1242
|
+
}
|
|
1243
|
+
frontier = next;
|
|
1244
|
+
}
|
|
1245
|
+
} finally { closeDb(db); }
|
|
1246
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1247
|
+
res.end(JSON.stringify({ related }));
|
|
1248
|
+
} catch (err) {
|
|
1249
|
+
res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
|
|
1250
|
+
}
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// -------------------------------------------------- GET /api/monograph-ai-context
|
|
1255
|
+
// Builds a rich AI context bundle for a node: content + 1-hop neighbors
|
|
1256
|
+
if (req.method === 'GET' && url === '/api/monograph-ai-context') {
|
|
1257
|
+
try {
|
|
1258
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1259
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1260
|
+
const id = qs.get('id') || '';
|
|
1261
|
+
const d = path.resolve(dir || process.cwd());
|
|
1262
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1263
|
+
if (!id || !fs.existsSync(dbPath)) { res.writeHead(404); res.end(JSON.stringify({ error: 'Not found' })); return; }
|
|
1264
|
+
const { openDb, closeDb } = await import('@monoes/monograph');
|
|
1265
|
+
const db = openDb(dbPath);
|
|
1266
|
+
let result = { node: null, content: '', neighbors: [], markdown: '' };
|
|
1267
|
+
try {
|
|
1268
|
+
const node = db.prepare('SELECT * FROM nodes WHERE id=?').get(id);
|
|
1269
|
+
if (!node) { res.writeHead(404); res.end(JSON.stringify({ error: 'Node not found' })); return; }
|
|
1270
|
+
result.node = { id: node.id, name: node.name, type: node.label, filePath: node.file_path, startLine: node.start_line, endLine: node.end_line };
|
|
1271
|
+
// Get content
|
|
1272
|
+
let content = '';
|
|
1273
|
+
if (node.properties) { try { const p = JSON.parse(node.properties); content = p.content || ''; } catch {} }
|
|
1274
|
+
if (!content && node.file_path) {
|
|
1275
|
+
const absPath = path.isAbsolute(node.file_path) ? node.file_path : path.join(d, node.file_path);
|
|
1276
|
+
try {
|
|
1277
|
+
const lines = fs.readFileSync(absPath, 'utf-8').split('\n');
|
|
1278
|
+
const sl = Math.max(0, (node.start_line || 1) - 1);
|
|
1279
|
+
const el = Math.min(lines.length, (node.end_line || node.start_line || lines.length) + 5);
|
|
1280
|
+
content = lines.slice(sl, Math.min(el, sl + 80)).join('\n');
|
|
1281
|
+
} catch {}
|
|
1282
|
+
}
|
|
1283
|
+
result.content = content;
|
|
1284
|
+
// Get 1-hop neighbors
|
|
1285
|
+
const outEdges = db.prepare('SELECT e.relation, n.id, n.name, n.label, n.file_path FROM edges e JOIN nodes n ON n.id=e.target_id WHERE e.source_id=? LIMIT 20').all(id);
|
|
1286
|
+
const inEdges = db.prepare('SELECT e.relation, n.id, n.name, n.label, n.file_path FROM edges e JOIN nodes n ON n.id=e.source_id WHERE e.target_id=? LIMIT 20').all(id);
|
|
1287
|
+
result.neighbors = [
|
|
1288
|
+
...outEdges.map(e => ({ direction: 'out', relation: e.relation, id: e.id, name: e.name, type: e.label, filePath: e.file_path })),
|
|
1289
|
+
...inEdges.map(e => ({ direction: 'in', relation: e.relation, id: e.id, name: e.name, type: e.label, filePath: e.file_path })),
|
|
1290
|
+
];
|
|
1291
|
+
// Build markdown for clipboard/AI
|
|
1292
|
+
const lines2 = [];
|
|
1293
|
+
lines2.push(`# ${node.name} [${node.label}]`);
|
|
1294
|
+
if (node.file_path) lines2.push(`**File:** \`${node.file_path}\`${node.start_line ? ` (line ${node.start_line})` : ''}`);
|
|
1295
|
+
if (content) lines2.push(`\n\`\`\`${node.language || ''}\n${content.slice(0, 3000)}\n\`\`\``);
|
|
1296
|
+
if (outEdges.length) {
|
|
1297
|
+
lines2.push(`\n**Depends on (${outEdges.length}):**`);
|
|
1298
|
+
outEdges.forEach(e => lines2.push(`- ${e.relation} → ${e.name} [${e.label}]${e.file_path ? ' `' + e.file_path + '`' : ''}`));
|
|
1299
|
+
}
|
|
1300
|
+
if (inEdges.length) {
|
|
1301
|
+
lines2.push(`\n**Used by (${inEdges.length}):**`);
|
|
1302
|
+
inEdges.forEach(e => lines2.push(`- ${e.relation} ← ${e.name} [${e.label}]${e.file_path ? ' `' + e.file_path + '`' : ''}`));
|
|
1303
|
+
}
|
|
1304
|
+
result.markdown = lines2.join('\n');
|
|
1305
|
+
} finally { closeDb(db); }
|
|
783
1306
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
784
|
-
res.end(JSON.stringify(
|
|
1307
|
+
res.end(JSON.stringify(result));
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
res.writeHead(500); res.end(JSON.stringify({ error: err.message }));
|
|
1310
|
+
}
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// -------------------------------------------------- GET /api/monograph-query
|
|
1315
|
+
if (req.method === 'GET' && url === '/api/monograph-query') {
|
|
1316
|
+
try {
|
|
1317
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1318
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1319
|
+
const q = qs.get('q') || '';
|
|
1320
|
+
const d = path.resolve(dir || process.cwd());
|
|
1321
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1322
|
+
if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q= parameter' })); return; }
|
|
1323
|
+
if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ success: false, result: 'Graph not built yet. Run: monomind monograph build' })); return; }
|
|
1324
|
+
const { openDb, closeDb, ftsSearch } = await import('@monoes/monograph');
|
|
1325
|
+
const db = openDb(dbPath);
|
|
1326
|
+
let result = '';
|
|
1327
|
+
try {
|
|
1328
|
+
const hits = ftsSearch(db, q, 20);
|
|
1329
|
+
if (!hits.length) {
|
|
1330
|
+
result = `No matches found for: "${q}"`;
|
|
1331
|
+
} else {
|
|
1332
|
+
result = hits.map((h, i) => `${String(i+1).padStart(3,' ')}. ${h.name} [${h.normLabel}]${h.filePath ? '\n ' + h.filePath : ''}`).join('\n');
|
|
1333
|
+
// Show outgoing edges for top hit
|
|
1334
|
+
const topHit = hits[0];
|
|
1335
|
+
const neighbors = db.prepare('SELECT target_id, relation FROM edges WHERE source_id=? LIMIT 10').all(topHit.id);
|
|
1336
|
+
if (neighbors.length) {
|
|
1337
|
+
result += `\n\n── ${topHit.name} references:\n` + neighbors.map(n => ` ${n.relation} → ${n.target_id.split('/').pop() || n.target_id}`).join('\n');
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
} finally { closeDb(db); }
|
|
1341
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1342
|
+
res.end(JSON.stringify({ success: true, query: q, result }));
|
|
785
1343
|
} catch (err) {
|
|
786
1344
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
787
1345
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -789,30 +1347,80 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
789
1347
|
return;
|
|
790
1348
|
}
|
|
791
1349
|
|
|
792
|
-
// -------------------------------------------------- GET /api/
|
|
793
|
-
if (req.method === 'GET' && url === '/api/
|
|
1350
|
+
// -------------------------------------------------- GET /api/monograph-explain
|
|
1351
|
+
if (req.method === 'GET' && url === '/api/monograph-explain') {
|
|
794
1352
|
try {
|
|
795
1353
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
796
|
-
const dir = qs.get('dir') || projectDir;
|
|
1354
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1355
|
+
const nodeQ = qs.get('node') || '';
|
|
1356
|
+
const d = path.resolve(dir || process.cwd());
|
|
1357
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1358
|
+
if (!nodeQ) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?node= parameter' })); return; }
|
|
1359
|
+
if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ success: false, explanation: 'Graph not built yet. Run: monomind monograph build' })); return; }
|
|
1360
|
+
const { openDb, closeDb, ftsSearch } = await import('@monoes/monograph');
|
|
1361
|
+
const db = openDb(dbPath);
|
|
1362
|
+
let explanation = '';
|
|
1363
|
+
try {
|
|
1364
|
+
let nd = db.prepare('SELECT * FROM nodes WHERE id=?').get(nodeQ) || db.prepare('SELECT * FROM nodes WHERE name=?').get(nodeQ);
|
|
1365
|
+
if (!nd) { const hits = ftsSearch(db, nodeQ, 1); if (hits[0]) nd = db.prepare('SELECT * FROM nodes WHERE id=?').get(hits[0].id); }
|
|
1366
|
+
if (!nd) {
|
|
1367
|
+
explanation = `No node found matching: "${nodeQ}"`;
|
|
1368
|
+
} else {
|
|
1369
|
+
const outEdges = db.prepare('SELECT target_id, relation FROM edges WHERE source_id=? LIMIT 20').all(nd.id);
|
|
1370
|
+
const inEdges = db.prepare('SELECT source_id, relation FROM edges WHERE target_id=? LIMIT 20').all(nd.id);
|
|
1371
|
+
explanation = [
|
|
1372
|
+
`## ${nd.name} [${nd.label}]`,
|
|
1373
|
+
nd.file_path ? `File: ${nd.file_path}${nd.start_line ? ':' + nd.start_line : ''}` : '',
|
|
1374
|
+
nd.language ? `Language: ${nd.language}` : '',
|
|
1375
|
+
nd.is_exported ? 'Exported: yes' : 'Exported: no',
|
|
1376
|
+
'',
|
|
1377
|
+
outEdges.length ? `References (${outEdges.length}):\n` + outEdges.map(e => ` ${e.relation} → ${e.target_id.split('/').pop() || e.target_id}`).join('\n') : 'No outgoing references.',
|
|
1378
|
+
inEdges.length ? `\nReferenced by (${inEdges.length}):\n` + inEdges.map(e => ` ${e.source_id.split('/').pop() || e.source_id} [${e.relation}]`).join('\n') : '',
|
|
1379
|
+
].filter(Boolean).join('\n');
|
|
1380
|
+
}
|
|
1381
|
+
} finally { closeDb(db); }
|
|
1382
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
1383
|
+
res.end(JSON.stringify({ success: true, node: nodeQ, explanation }));
|
|
1384
|
+
} catch (err) {
|
|
1385
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1386
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1387
|
+
}
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// -------------------------------------------------- GET /api/monograph-path
|
|
1392
|
+
if (req.method === 'GET' && url === '/api/monograph-path') {
|
|
1393
|
+
try {
|
|
1394
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1395
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
797
1396
|
const from = qs.get('from') || '';
|
|
798
1397
|
const to = qs.get('to') || '';
|
|
799
1398
|
const d = path.resolve(dir || process.cwd());
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1399
|
+
const dbPath = path.join(d, '.monomind', 'monograph.db');
|
|
1400
|
+
if (!from || !to) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?from= and ?to= parameters' })); return; }
|
|
1401
|
+
if (!fs.existsSync(dbPath)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end(JSON.stringify({ success: false, path: 'Graph not built yet.' })); return; }
|
|
1402
|
+
const { openDb, closeDb, getShortestPath, ftsSearch } = await import('@monoes/monograph');
|
|
1403
|
+
const db = openDb(dbPath);
|
|
1404
|
+
let pathResult = '';
|
|
1405
|
+
try {
|
|
1406
|
+
const resolveId = (q) => {
|
|
1407
|
+
const direct = db.prepare('SELECT id FROM nodes WHERE id=? OR name=?').get(q, q);
|
|
1408
|
+
if (direct) return direct.id;
|
|
1409
|
+
const hits = ftsSearch(db, q, 1);
|
|
1410
|
+
return hits[0]?.id || q;
|
|
1411
|
+
};
|
|
1412
|
+
const fromId = resolveId(from);
|
|
1413
|
+
const toId = resolveId(to);
|
|
1414
|
+
const p = getShortestPath(db, fromId, toId);
|
|
1415
|
+
if (!p || !p.length) {
|
|
1416
|
+
pathResult = `No path found between "${from}" and "${to}"`;
|
|
1417
|
+
} else {
|
|
1418
|
+
const names = p.map(id => { const n = db.prepare('SELECT name FROM nodes WHERE id=?').get(id); return n ? n.name : id.split('/').pop() || id; });
|
|
1419
|
+
pathResult = names.join(' → ') + ` (${p.length - 1} hop${p.length !== 2 ? 's' : ''})`;
|
|
1420
|
+
}
|
|
1421
|
+
} finally { closeDb(db); }
|
|
814
1422
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
815
|
-
res.end(JSON.stringify({ success: true, from, to, path:
|
|
1423
|
+
res.end(JSON.stringify({ success: true, from, to, path: pathResult }));
|
|
816
1424
|
} catch (err) {
|
|
817
1425
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
818
1426
|
res.end(JSON.stringify({ error: err.message }));
|
|
@@ -820,13 +1428,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
820
1428
|
return;
|
|
821
1429
|
}
|
|
822
1430
|
|
|
823
|
-
// -------------------------------------------------- GET /api/
|
|
824
|
-
if (req.method === 'GET' && url === '/api/
|
|
1431
|
+
// -------------------------------------------------- GET /api/monograph-watch-status
|
|
1432
|
+
if (req.method === 'GET' && url === '/api/monograph-watch-status') {
|
|
825
1433
|
try {
|
|
826
1434
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
827
|
-
const dir = qs.get('dir') || projectDir;
|
|
1435
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
828
1436
|
const d = path.resolve(dir || process.cwd());
|
|
829
|
-
const pidPath = path.join(d, '.monomind', '
|
|
1437
|
+
const pidPath = path.join(d, '.monomind', 'monograph.watch.pid');
|
|
830
1438
|
let running = false, pid = null;
|
|
831
1439
|
try {
|
|
832
1440
|
pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
@@ -842,13 +1450,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
842
1450
|
return;
|
|
843
1451
|
}
|
|
844
1452
|
|
|
845
|
-
// -------------------------------------------------- POST /api/
|
|
846
|
-
if (req.method === 'POST' && url === '/api/
|
|
1453
|
+
// -------------------------------------------------- POST /api/monograph-watch-toggle
|
|
1454
|
+
if (req.method === 'POST' && url === '/api/monograph-watch-toggle') {
|
|
847
1455
|
try {
|
|
848
1456
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
849
|
-
const dir = qs.get('dir') || projectDir;
|
|
1457
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
850
1458
|
const d = path.resolve(dir || process.cwd());
|
|
851
|
-
const pidPath = path.join(d, '.monomind', '
|
|
1459
|
+
const pidPath = path.join(d, '.monomind', 'monograph.watch.pid');
|
|
852
1460
|
let wasRunning = false;
|
|
853
1461
|
try {
|
|
854
1462
|
const pid = parseInt(fs.readFileSync(pidPath, 'utf-8').trim(), 10);
|
|
@@ -863,9 +1471,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
863
1471
|
res.end(JSON.stringify({ running: false, action: 'stopped' }));
|
|
864
1472
|
} else {
|
|
865
1473
|
const { spawn: sp } = await import('child_process');
|
|
866
|
-
const child = sp('
|
|
1474
|
+
const child = sp(process.execPath, [process.argv[1], 'monograph', 'watch'], { stdio: 'ignore', detached: true, cwd: d, env: process.env });
|
|
867
1475
|
child.unref();
|
|
868
|
-
try { fs.mkdirSync(path.join(d, '.monomind'
|
|
1476
|
+
try { fs.mkdirSync(path.join(d, '.monomind'), { recursive: true }); } catch {}
|
|
869
1477
|
try { fs.writeFileSync(pidPath, String(child.pid)); } catch {}
|
|
870
1478
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
871
1479
|
res.end(JSON.stringify({ running: true, pid: child.pid, action: 'started' }));
|
|
@@ -877,11 +1485,286 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
877
1485
|
return;
|
|
878
1486
|
}
|
|
879
1487
|
|
|
880
|
-
// --------------------------------------------------
|
|
881
|
-
if (req.method === '
|
|
1488
|
+
// -------------------------------------------------- POST /api/mcp/call
|
|
1489
|
+
if (req.method === 'POST' && url === '/api/mcp/call') {
|
|
1490
|
+
let body = '';
|
|
1491
|
+
req.on('data', c => body += c);
|
|
1492
|
+
req.on('end', async () => {
|
|
1493
|
+
const json = res => { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); };
|
|
1494
|
+
const ok = (data) => { json(res); res.end(JSON.stringify({ content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }] })); };
|
|
1495
|
+
const err = (msg) => { json(res); res.end(JSON.stringify({ error: msg })); };
|
|
1496
|
+
try {
|
|
1497
|
+
const { tool, input = {} } = JSON.parse(body);
|
|
1498
|
+
const qs2 = new URL(req.url, 'http://localhost').searchParams;
|
|
1499
|
+
const dir2 = qs2.get('dir') || projectDir;
|
|
1500
|
+
const d2 = path.resolve(dir2 || process.cwd());
|
|
1501
|
+
const dbPath2 = path.join(d2, '.monomind', 'monograph.db');
|
|
1502
|
+
if (!fs.existsSync(dbPath2)) { err('monograph.db not found — run monograph build first'); return; }
|
|
1503
|
+
const { openDb, closeDb, ftsSearch, getShortestPath, countNodes, countEdges } = await import('@monoes/monograph');
|
|
1504
|
+
const db2 = openDb(dbPath2);
|
|
1505
|
+
try {
|
|
1506
|
+
if (tool === 'monograph_stats') {
|
|
1507
|
+
const n = countNodes(db2), e = countEdges(db2);
|
|
1508
|
+
ok(`nodes: ${n}\nedges: ${e}`);
|
|
1509
|
+
} else if (tool === 'monograph_cypher') {
|
|
1510
|
+
// Translate basic MATCH (n:Label) queries to SQL
|
|
1511
|
+
const q = (input.query || '').trim();
|
|
1512
|
+
const labelMatch = q.match(/MATCH\s+\(n:(\w+)\)/i);
|
|
1513
|
+
if (labelMatch) {
|
|
1514
|
+
const label = labelMatch[1];
|
|
1515
|
+
const rows = db2.prepare('SELECT name FROM nodes WHERE label = ? LIMIT 5000').all(label);
|
|
1516
|
+
ok(rows.map(r => r.name).join('\n'));
|
|
1517
|
+
} else {
|
|
1518
|
+
ok('Cypher: unsupported query pattern');
|
|
1519
|
+
}
|
|
1520
|
+
} else if (tool === 'monograph_cohesion') {
|
|
1521
|
+
const limit = input.limit || 30;
|
|
1522
|
+
// Check if community_id is populated
|
|
1523
|
+
const hasCommunities = db2.prepare('SELECT COUNT(*) as c FROM nodes WHERE community_id IS NOT NULL').get().c > 0;
|
|
1524
|
+
if (hasCommunities) {
|
|
1525
|
+
const rows = db2.prepare('SELECT community_id, COUNT(*) as size FROM nodes GROUP BY community_id ORDER BY size DESC LIMIT ?').all(limit);
|
|
1526
|
+
ok(rows.map(r => `community ${r.community_id}: ${r.size} nodes`).join('\n'));
|
|
1527
|
+
} else {
|
|
1528
|
+
// Fallback: group by type (label)
|
|
1529
|
+
const rows = db2.prepare('SELECT label, COUNT(*) as cnt FROM nodes GROUP BY label ORDER BY cnt DESC LIMIT ?').all(limit);
|
|
1530
|
+
const total = db2.prepare('SELECT COUNT(*) as c FROM nodes').get().c;
|
|
1531
|
+
const lines = rows.map(r => {
|
|
1532
|
+
const pct = ((r.cnt / total) * 100).toFixed(1);
|
|
1533
|
+
const bar = '█'.repeat(Math.round(pct / 3));
|
|
1534
|
+
return `${(r.label || 'unknown').padEnd(12)} ${r.cnt.toString().padStart(6)} nodes (${pct}%) ${bar}`;
|
|
1535
|
+
});
|
|
1536
|
+
ok(`Type Distribution (community clustering not yet run)\n${'─'.repeat(50)}\n${lines.join('\n')}`);
|
|
1537
|
+
}
|
|
1538
|
+
} else if (tool === 'monograph_bridge') {
|
|
1539
|
+
const limit = input.limit || 20;
|
|
1540
|
+
// Find hub nodes that connect many different directories (cross-module connectors)
|
|
1541
|
+
const rows = db2.prepare(`
|
|
1542
|
+
SELECT n.name, n.label, n.file_path,
|
|
1543
|
+
COUNT(DISTINCT CASE WHEN e.source_id = n.id THEN n2.file_path ELSE NULL END) +
|
|
1544
|
+
COUNT(DISTINCT CASE WHEN e.target_id = n.id THEN n2.file_path ELSE NULL END) as cross_file_count,
|
|
1545
|
+
(SELECT COUNT(*) FROM edges WHERE source_id = n.id OR target_id = n.id) as total_degree
|
|
1546
|
+
FROM nodes n
|
|
1547
|
+
JOIN edges e ON e.source_id = n.id OR e.target_id = n.id
|
|
1548
|
+
JOIN nodes n2 ON (e.source_id = n2.id OR e.target_id = n2.id) AND n2.id != n.id
|
|
1549
|
+
GROUP BY n.id
|
|
1550
|
+
HAVING cross_file_count > 2
|
|
1551
|
+
ORDER BY cross_file_count DESC, total_degree DESC
|
|
1552
|
+
LIMIT ?`).all(limit);
|
|
1553
|
+
if (!rows.length) {
|
|
1554
|
+
ok('No cross-module bridge nodes found in top results. Try running monograph build to index more files.');
|
|
1555
|
+
} else {
|
|
1556
|
+
const lines = rows.map(r =>
|
|
1557
|
+
`${r.name} (${r.label})\n → connects ${r.cross_file_count} files, degree ${r.total_degree}\n ${r.file_path || '?'}`
|
|
1558
|
+
);
|
|
1559
|
+
ok(`Cross-Module Bridge Nodes (${rows.length})\n${'─'.repeat(50)}\n${lines.join('\n\n')}`);
|
|
1560
|
+
}
|
|
1561
|
+
} else if (tool === 'monograph_detect_changes') {
|
|
1562
|
+
const { execSync } = await import('child_process');
|
|
1563
|
+
let changed = '';
|
|
1564
|
+
try { changed = execSync('git diff --name-only HEAD', { cwd: d2, encoding: 'utf-8' }); } catch { changed = '(git not available)'; }
|
|
1565
|
+
ok(changed.trim() || 'No changed files detected');
|
|
1566
|
+
} else if (tool === 'monograph_diff') {
|
|
1567
|
+
ok('Graph diff: compare two snapshots using monograph snapshot + monograph diff commands');
|
|
1568
|
+
} else if (tool === 'monograph_rename') {
|
|
1569
|
+
const sym = input.symbolName || '';
|
|
1570
|
+
if (!sym) { ok('Provide symbolName to rename'); return; }
|
|
1571
|
+
const hits = ftsSearch(db2, sym, 20);
|
|
1572
|
+
ok(`Found ${hits.length} occurrences of "${sym}":\n` + hits.map(h => ` ${h.filePath || '?'}:${h.startLine || '?'} — ${h.name}`).join('\n'));
|
|
1573
|
+
} else if (tool === 'monograph_impact') {
|
|
1574
|
+
const target = input.target || '';
|
|
1575
|
+
const dir3 = input.direction || 'both';
|
|
1576
|
+
const depth = input.maxDepth || 4;
|
|
1577
|
+
const hits = ftsSearch(db2, target, 5);
|
|
1578
|
+
if (!hits.length) { ok(`Node not found: ${target}`); return; }
|
|
1579
|
+
const nodeId = hits[0].id;
|
|
1580
|
+
const visited = new Set([nodeId]);
|
|
1581
|
+
const frontier = [nodeId];
|
|
1582
|
+
const results = [];
|
|
1583
|
+
for (let d3 = 0; d3 < depth && frontier.length; d3++) {
|
|
1584
|
+
const next = [];
|
|
1585
|
+
for (const id of frontier) {
|
|
1586
|
+
const outgoing = dir3 !== 'upstream' ? db2.prepare('SELECT target_id, relation FROM edges WHERE source_id = ?').all(id) : [];
|
|
1587
|
+
const incoming = dir3 !== 'downstream' ? db2.prepare('SELECT source_id as target_id, relation FROM edges WHERE target_id = ?').all(id) : [];
|
|
1588
|
+
for (const e of [...outgoing, ...incoming]) {
|
|
1589
|
+
if (!visited.has(e.target_id)) {
|
|
1590
|
+
visited.add(e.target_id);
|
|
1591
|
+
next.push(e.target_id);
|
|
1592
|
+
const n3 = db2.prepare('SELECT name, label FROM nodes WHERE id = ?').get(e.target_id);
|
|
1593
|
+
if (n3) results.push(` [hop ${d3+1}] ${n3.name} (${n3.label}) via ${e.relation}`);
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
frontier.length = 0; frontier.push(...next);
|
|
1598
|
+
}
|
|
1599
|
+
ok(`Impact of "${hits[0].name}" (${dir3}, depth=${depth}):\n` + (results.join('\n') || ' (no dependencies found)'));
|
|
1600
|
+
} else if (tool === 'monograph_context') {
|
|
1601
|
+
const id = input.id || '';
|
|
1602
|
+
const hits = ftsSearch(db2, id, 5);
|
|
1603
|
+
if (!hits.length) { ok(`Node not found: ${id}`); return; }
|
|
1604
|
+
const node = hits[0];
|
|
1605
|
+
const outEdges = db2.prepare('SELECT e.relation, n.name FROM edges e JOIN nodes n ON n.id = e.target_id WHERE e.source_id = ? LIMIT 20').all(node.id);
|
|
1606
|
+
const inEdges = db2.prepare('SELECT e.relation, n.name FROM edges e JOIN nodes n ON n.id = e.source_id WHERE e.target_id = ? LIMIT 20').all(node.id);
|
|
1607
|
+
ok(`# ${node.name} (${node.label})\nFile: ${node.filePath || '?'}\n\n**Imports / depends on (${outEdges.length}):**\n${outEdges.map(e => ` → ${e.name} [${e.relation}]`).join('\n') || ' (none)'}\n\n**Used by / depended on by (${inEdges.length}):**\n${inEdges.map(e => ` ← ${e.name} [${e.relation}]`).join('\n') || ' (none)'}`);
|
|
1608
|
+
} else if (tool === 'monograph_query' || tool === 'monograph_suggest') {
|
|
1609
|
+
const q2 = input.query || input.task || '';
|
|
1610
|
+
const hits2 = ftsSearch(db2, q2, 20);
|
|
1611
|
+
ok(hits2.map(h => `${h.name} (${h.label}) — ${h.filePath || '?'}:${h.startLine || '?'}`).join('\n') || 'No results');
|
|
1612
|
+
|
|
1613
|
+
} else if (tool === 'monograph_unlinked_refs') {
|
|
1614
|
+
const limit = input.limit || 50;
|
|
1615
|
+
const rows = db2.prepare(`SELECT n.name, n.label, n.file_path FROM nodes n LEFT JOIN edges e ON e.target_id = n.id WHERE e.target_id IS NULL AND n.label IN ('Function','Class','Variable','Interface','Method','Module') ORDER BY n.name LIMIT ?`).all(limit);
|
|
1616
|
+
if (!rows.length) { ok('No unlinked symbols found — all exports appear to be referenced.'); }
|
|
1617
|
+
else { ok(`Unlinked Symbols (${rows.length}) — potentially unused exports:\n${'─'.repeat(50)}\n${rows.map(r => ` ${r.name} (${r.label})\n ${r.file_path || '?'}`).join('\n\n')}`); }
|
|
1618
|
+
|
|
1619
|
+
} else if (tool === 'monograph_reachability') {
|
|
1620
|
+
const limit = input.limit || 30;
|
|
1621
|
+
const unreachable = db2.prepare(`SELECT n.name, n.file_path, (SELECT COUNT(*) FROM edges WHERE source_id = n.id) as out_deg FROM nodes n LEFT JOIN edges e ON e.target_id = n.id WHERE e.target_id IS NULL AND n.label = 'File' ORDER BY out_deg DESC LIMIT ?`).all(limit);
|
|
1622
|
+
const total = db2.prepare("SELECT COUNT(*) as c FROM nodes WHERE label = 'File'").get().c;
|
|
1623
|
+
if (!unreachable.length) { ok(`All ${total} files are reachable from at least one other file.`); }
|
|
1624
|
+
else { ok(`Unreachable Files (${unreachable.length} of ${total} total):\n${'─'.repeat(50)}\n${unreachable.map(r => ` ${r.name}${r.out_deg ? ` (imports ${r.out_deg} others)` : ''}\n ${r.file_path || '?'}`).join('\n\n')}`); }
|
|
1625
|
+
|
|
1626
|
+
} else if (tool === 'monograph_boundary_check') {
|
|
1627
|
+
const limit = input.limit || 40;
|
|
1628
|
+
const rows = db2.prepare(`SELECT n1.file_path as src, n2.file_path as dst, e.relation, COUNT(*) as cnt FROM edges e JOIN nodes n1 ON n1.id = e.source_id JOIN nodes n2 ON n2.id = e.target_id WHERE n1.file_path IS NOT NULL AND n2.file_path IS NOT NULL AND n1.file_path != n2.file_path GROUP BY n1.file_path, n2.file_path ORDER BY cnt DESC LIMIT ?`).all(limit);
|
|
1629
|
+
const suspicious = rows.filter(r => { const s = (r.src||'').toLowerCase(), t = (r.dst||'').toLowerCase(); return (s.includes('test') && !t.includes('test')) || (s.includes('spec') && !t.includes('spec')) || (s.includes('/ui/') && t.includes('/db/')) || (s.includes('/view') && t.includes('/model')); });
|
|
1630
|
+
if (!suspicious.length) { ok(`Boundary check: ${rows.length} cross-file edge groups — no obvious violations.\nTop connections:\n${rows.slice(0,10).map(r => ` ${r.src} → ${r.dst} [${r.cnt}x]`).join('\n')}`); }
|
|
1631
|
+
else { ok(`Boundary Violations (${suspicious.length} suspicious):\n${'─'.repeat(50)}\n${suspicious.map(r => ` ⚠ ${r.src}\n → ${r.dst} [${r.cnt} edges]`).join('\n\n')}`); }
|
|
1632
|
+
|
|
1633
|
+
} else if (tool === 'monograph_regression_check' || tool === 'monograph_baseline_compare') {
|
|
1634
|
+
const n = countNodes(db2), e = countEdges(db2);
|
|
1635
|
+
const bPath = path.join(d2, '.monomind', 'monograph-baseline.json');
|
|
1636
|
+
if (!fs.existsSync(bPath)) {
|
|
1637
|
+
fs.writeFileSync(bPath, JSON.stringify({ nodes: n, edges: e, savedAt: new Date().toISOString() }), 'utf-8');
|
|
1638
|
+
ok(`Baseline saved (${n} nodes, ${e} edges). Run again to compare.`);
|
|
1639
|
+
} else {
|
|
1640
|
+
const base = JSON.parse(fs.readFileSync(bPath, 'utf-8'));
|
|
1641
|
+
const dn = n - base.nodes, de = e - base.edges;
|
|
1642
|
+
const sign = v => v > 0 ? `+${v}` : String(v);
|
|
1643
|
+
ok(`Comparison vs baseline (${base.savedAt || 'unknown'}):\n${'─'.repeat(50)}\n Nodes: ${base.nodes} → ${n} (${sign(dn)})\n Edges: ${base.edges} → ${e} (${sign(de)})\n\n${dn === 0 && de === 0 ? '✓ No structural regressions detected.' : '⚠ Graph has changed since baseline.'}`);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
} else if (tool === 'monograph_clone_detect' || tool === 'monograph_similar_files') {
|
|
1647
|
+
const limit = input.limit || 20;
|
|
1648
|
+
const fileNodes = db2.prepare("SELECT id, name, file_path FROM nodes WHERE label = 'File' LIMIT 300").all();
|
|
1649
|
+
const deps = {};
|
|
1650
|
+
for (const f of fileNodes) { deps[f.id] = { name: f.name, set: new Set(db2.prepare('SELECT target_id FROM edges WHERE source_id = ?').all(f.id).map(r => r.target_id)) }; }
|
|
1651
|
+
const keys = Object.keys(deps), pairs = [];
|
|
1652
|
+
for (let i = 0; i < Math.min(keys.length, 150); i++) {
|
|
1653
|
+
for (let j = i + 1; j < Math.min(keys.length, 150); j++) {
|
|
1654
|
+
const a = deps[keys[i]], b = deps[keys[j]];
|
|
1655
|
+
if (!a.set.size && !b.set.size) continue;
|
|
1656
|
+
const inter = [...a.set].filter(x => b.set.has(x)).length;
|
|
1657
|
+
const union = new Set([...a.set, ...b.set]).size;
|
|
1658
|
+
const jac = union ? inter / union : 0;
|
|
1659
|
+
if (jac > 0.5) pairs.push({ a: a.name, b: b.name, jac });
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
pairs.sort((x, y) => y.jac - x.jac);
|
|
1663
|
+
const top = pairs.slice(0, limit);
|
|
1664
|
+
if (!top.length) { ok('No similar file pairs found (Jaccard threshold: 0.5).'); }
|
|
1665
|
+
else { ok(`Similar File Pairs (${top.length}, by import pattern):\n${'─'.repeat(50)}\n${top.map(p => ` ${(p.jac*100).toFixed(0)}% similar\n ${p.a}\n ${p.b}`).join('\n\n')}`); }
|
|
1666
|
+
|
|
1667
|
+
} else if (tool === 'monograph_mirrored_dirs') {
|
|
1668
|
+
const fileNodes = db2.prepare("SELECT file_path FROM nodes WHERE label = 'File' AND file_path IS NOT NULL").all();
|
|
1669
|
+
const dirFiles = {};
|
|
1670
|
+
for (const f of fileNodes) { const dir = path.dirname(f.file_path), base = path.basename(f.file_path); if (!dirFiles[dir]) dirFiles[dir] = new Set(); dirFiles[dir].add(base); }
|
|
1671
|
+
const dirs = Object.keys(dirFiles), pairs = [];
|
|
1672
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
1673
|
+
for (let j = i + 1; j < dirs.length; j++) {
|
|
1674
|
+
const a = dirFiles[dirs[i]], b = dirFiles[dirs[j]];
|
|
1675
|
+
const inter = [...a].filter(x => b.has(x)).length;
|
|
1676
|
+
const union = new Set([...a, ...b]).size;
|
|
1677
|
+
const jac = union ? inter / union : 0;
|
|
1678
|
+
if (jac >= 0.5 && inter >= 2) pairs.push({ a: dirs[i], b: dirs[j], overlap: inter, jac });
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
pairs.sort((x, y) => y.jac - x.jac);
|
|
1682
|
+
if (!pairs.length) { ok('No mirrored directory pairs detected (Jaccard ≥ 0.5, min 2 shared files).'); }
|
|
1683
|
+
else { ok(`Mirrored Directories (${pairs.length} pairs):\n${'─'.repeat(50)}\n${pairs.slice(0,20).map(p => ` ${(p.jac*100).toFixed(0)}% overlap (${p.overlap} shared files)\n ${p.a}\n ${p.b}`).join('\n\n')}`); }
|
|
1684
|
+
|
|
1685
|
+
} else if (tool === 'monograph_health_score' || tool === 'monograph_vital_signs_snapshot') {
|
|
1686
|
+
const n = countNodes(db2), e = countEdges(db2);
|
|
1687
|
+
const dead = db2.prepare("SELECT COUNT(*) as c FROM nodes n LEFT JOIN edges e ON e.target_id = n.id WHERE e.target_id IS NULL AND n.label IN ('Function','Class','Method')").get().c;
|
|
1688
|
+
const hubs = db2.prepare('SELECT COUNT(*) as c FROM (SELECT source_id FROM edges GROUP BY source_id HAVING COUNT(*) > 20)').get().c;
|
|
1689
|
+
const density = n > 1 ? (2 * e / (n * (n - 1))).toFixed(4) : '0';
|
|
1690
|
+
const deadRatio = n ? (dead / n * 100).toFixed(1) : '0';
|
|
1691
|
+
const score = Math.max(0, Math.min(100, 100 - Math.min(30, parseFloat(deadRatio) * 0.5) - Math.min(20, hubs * 2))).toFixed(0);
|
|
1692
|
+
const status = parseInt(score) >= 70 ? '✓ OK' : parseInt(score) >= 40 ? '⚠ WARNING' : '✗ CRITICAL';
|
|
1693
|
+
ok(`Vital Signs — ${new Date().toISOString()}\n${'─'.repeat(50)}\n Health Score: ${score}/100 ${status}\n Nodes: ${n}\n Edges: ${e}\n Density: ${density}\n Dead symbols: ${dead} (${deadRatio}%)\n Hub nodes: ${hubs} nodes with >20 edges`);
|
|
1694
|
+
|
|
1695
|
+
} else if (tool === 'monograph_health_trend') {
|
|
1696
|
+
const bPath = path.join(d2, '.monomind', 'monograph-baseline.json');
|
|
1697
|
+
if (!fs.existsSync(bPath)) { ok('No trend data yet. Run "Health Score" or "Regression Check" first to save a baseline.'); }
|
|
1698
|
+
else {
|
|
1699
|
+
const base = JSON.parse(fs.readFileSync(bPath, 'utf-8'));
|
|
1700
|
+
const n = countNodes(db2), e = countEdges(db2);
|
|
1701
|
+
const dn = n - base.nodes, de = e - base.edges;
|
|
1702
|
+
const sign = v => v > 0 ? `+${v}` : String(v);
|
|
1703
|
+
ok(`Health Trend (vs ${base.savedAt || 'unknown'}):\n${'─'.repeat(50)}\n Nodes: ${base.nodes} → ${n} (${sign(dn)})\n Edges: ${base.edges} → ${e} (${sign(de)})\n Trend: ${dn === 0 && de === 0 ? 'stable' : dn > 0 ? 'growing' : 'shrinking'}`);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
} else if (tool === 'monograph_hotspots') {
|
|
1707
|
+
const limit = input.limit || 20;
|
|
1708
|
+
const rows = db2.prepare(`SELECT n.name, n.file_path, (SELECT COUNT(*) FROM edges WHERE source_id = n.id OR target_id = n.id) as degree, (SELECT COUNT(*) FROM edges WHERE source_id = n.id) as fan_out, (SELECT COUNT(*) FROM edges WHERE target_id = n.id) as fan_in FROM nodes n WHERE n.label = 'File' ORDER BY degree DESC LIMIT ?`).all(limit);
|
|
1709
|
+
if (!rows.length) { ok('No file hotspots found.'); }
|
|
1710
|
+
else { ok(`Hotspot Files (top ${rows.length} by degree):\n${'─'.repeat(50)}\n${rows.map((r,i) => ` ${i+1}. ${r.name} [degree ${r.degree}: ↑${r.fan_in} in, ↓${r.fan_out} out]\n ${r.file_path || '?'}`).join('\n')}`); }
|
|
1711
|
+
|
|
1712
|
+
} else if (tool === 'monograph_maintainability') {
|
|
1713
|
+
const limit = input.limit || 25;
|
|
1714
|
+
const rows = db2.prepare(`SELECT n.name, n.file_path, (SELECT COUNT(*) FROM edges WHERE source_id = n.id) as fan_out, (SELECT COUNT(*) FROM edges WHERE target_id = n.id) as fan_in FROM nodes n WHERE n.label = 'File' ORDER BY fan_out DESC LIMIT ?`).all(limit);
|
|
1715
|
+
if (!rows.length) { ok('No file data for maintainability analysis.'); }
|
|
1716
|
+
else {
|
|
1717
|
+
const maxOut = Math.max(...rows.map(r => r.fan_out), 1);
|
|
1718
|
+
const lines = rows.map(r => { const mi = Math.max(0, 100 - (r.fan_out / maxOut) * 60 - (r.fan_in > 10 ? 20 : 0)).toFixed(0); return ` ${parseInt(mi) >= 70 ? '✓' : parseInt(mi) >= 40 ? '⚠' : '✗'} MI:${mi.padStart(3)} out:${String(r.fan_out).padStart(4)} in:${String(r.fan_in).padStart(4)} ${r.name}`; });
|
|
1719
|
+
ok(`Maintainability Index (estimated from fan-out/fan-in):\n${'─'.repeat(60)}\n${lines.join('\n')}`);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
} else if (tool === 'monograph_complexity' || tool === 'monograph_crap_score') {
|
|
1723
|
+
const limit = input.limit || 25;
|
|
1724
|
+
const rows = db2.prepare(`SELECT n.name, n.label, n.file_path, (SELECT COUNT(*) FROM edges WHERE source_id = n.id) as out_deg FROM nodes n WHERE n.label IN ('Function','Method','Class') ORDER BY out_deg DESC LIMIT ?`).all(limit);
|
|
1725
|
+
if (!rows.length) { ok('No function/method nodes found. Build the graph first.'); }
|
|
1726
|
+
else {
|
|
1727
|
+
const isCrap = tool === 'monograph_crap_score';
|
|
1728
|
+
const header = isCrap ? 'CRAP Score proxy (degree² — lower is better)' : 'Complexity by Out-Degree';
|
|
1729
|
+
ok(`${header}:\n${'─'.repeat(50)}\n${rows.map(r => ` ${r.name} (${r.label}) ${isCrap ? 'CRAP' : 'complexity'}: ${isCrap ? Math.pow(r.out_deg,2) : r.out_deg}\n ${r.file_path || '?'}`).join('\n')}`);
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
} else if (tool === 'monograph_risk_profile') {
|
|
1733
|
+
const n = countNodes(db2), e = countEdges(db2);
|
|
1734
|
+
const dead = db2.prepare("SELECT COUNT(*) as c FROM nodes n LEFT JOIN edges e ON e.target_id = n.id WHERE e.target_id IS NULL AND n.label IN ('Function','Class','Method')").get().c;
|
|
1735
|
+
const hubs = db2.prepare('SELECT COUNT(*) as c FROM (SELECT source_id FROM edges GROUP BY source_id HAVING COUNT(*) > 15)').get().c;
|
|
1736
|
+
const files = db2.prepare("SELECT COUNT(*) as c FROM nodes WHERE label = 'File'").get().c;
|
|
1737
|
+
const orphans = db2.prepare("SELECT COUNT(*) as c FROM nodes n LEFT JOIN edges e ON e.target_id = n.id WHERE e.target_id IS NULL AND n.label = 'File'").get().c;
|
|
1738
|
+
const risks = [];
|
|
1739
|
+
if (dead > 10) risks.push(` HIGH Dead symbols: ${dead} unreferenced nodes`);
|
|
1740
|
+
if (hubs > 3) risks.push(` MEDIUM Hub nodes: ${hubs} nodes with >15 dependencies`);
|
|
1741
|
+
if (orphans > files * 0.3) risks.push(` MEDIUM Orphan files: ${orphans} of ${files} files unreachable`);
|
|
1742
|
+
if (n > 0 && e / n < 0.5) risks.push(` LOW Sparse graph: avg degree ${(e/n).toFixed(2)}`);
|
|
1743
|
+
ok(`Risk Profile — ${new Date().toISOString().split('T')[0]}\n${'─'.repeat(50)}\n${risks.length ? risks.join('\n') : ' No significant risks detected.'}\n\nSummary: ${n} nodes · ${e} edges · ${files} files`);
|
|
1744
|
+
|
|
1745
|
+
} else if (tool === 'monograph_author_analytics') {
|
|
1746
|
+
const limit = input.limit || 20;
|
|
1747
|
+
const { execSync: execS } = await import('child_process');
|
|
1748
|
+
try {
|
|
1749
|
+
const log = execS(`git log --format="%ae" --no-merges -- . 2>/dev/null | sort | uniq -c | sort -rn | head -${limit}`, { cwd: d2, encoding: 'utf-8', timeout: 5000 });
|
|
1750
|
+
if (!log.trim()) { ok('No git history found for this project directory.'); }
|
|
1751
|
+
else { ok(`Author Analytics (by commit count):\n${'─'.repeat(50)}\n${log.trim().split('\n').map(l => { const m = l.trim().match(/^(\d+)\s+(.+)$/); return m ? ` ${m[2].padEnd(45)} ${m[1]} commits` : l; }).join('\n')}`); }
|
|
1752
|
+
} catch { ok('Author analytics requires git. Ensure this directory is a git repository.'); }
|
|
1753
|
+
|
|
1754
|
+
} else {
|
|
1755
|
+
ok(`Tool "${tool}" not implemented in control panel`);
|
|
1756
|
+
}
|
|
1757
|
+
} finally { closeDb(db2); }
|
|
1758
|
+
} catch(e2) { err(String(e2)); }
|
|
1759
|
+
});
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// -------------------------------------------------- GET /api/monograph-benchmark
|
|
1764
|
+
if (req.method === 'GET' && url === '/api/monograph-benchmark') {
|
|
882
1765
|
try {
|
|
883
1766
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
884
|
-
const dir = qs.get('dir') || projectDir;
|
|
1767
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
885
1768
|
const d = path.resolve(dir || process.cwd());
|
|
886
1769
|
const graphPath = path.join(d, '.monomind', 'graph', 'graph.json');
|
|
887
1770
|
const legacyPath = path.join(d, 'graphify-out', 'graph.json');
|
|
@@ -909,7 +1792,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
909
1792
|
if (req.method === 'GET' && url === '/api/graph') {
|
|
910
1793
|
try {
|
|
911
1794
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
912
|
-
const dir = qs.get('dir') || projectDir;
|
|
1795
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
913
1796
|
const d = path.resolve(dir || process.cwd());
|
|
914
1797
|
|
|
915
1798
|
// Find session files — sort by mtime descending before processing
|
|
@@ -1076,12 +1959,71 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1076
1959
|
return;
|
|
1077
1960
|
}
|
|
1078
1961
|
|
|
1962
|
+
// -------------------------------------------------- GET /api/token-usage
|
|
1963
|
+
if (req.method === 'GET' && url.startsWith('/api/token-usage')) {
|
|
1964
|
+
try {
|
|
1965
|
+
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1966
|
+
const period = ['today','week','30days','month'].includes(qs.get('period')) ? qs.get('period') : 'today';
|
|
1967
|
+
const dir = path.resolve(qs.get('dir') || projectDir || process.cwd());
|
|
1968
|
+
const trackerPath = path.join(dir, '.claude', 'helpers', 'token-tracker.cjs');
|
|
1969
|
+
const fallback = () => {
|
|
1970
|
+
const summary = (() => { try { return JSON.parse(fs.readFileSync(path.join(dir, '.monomind', 'metrics', 'token-summary.json'), 'utf8')); } catch { return {}; } })();
|
|
1971
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
1972
|
+
res.end(JSON.stringify({ totalCost: summary.todayCost || 0, totalCalls: summary.todayCalls || 0, totalIn: 0, totalOut: 0, totalCR: 0, totalCW: 0, projects: [], modelBreakdown: {}, categoryBreakdown: {}, toolBreakdown: {}, mcpBreakdown: {}, periodLabel: period }));
|
|
1973
|
+
};
|
|
1974
|
+
if (!fs.existsSync(trackerPath)) { fallback(); return; }
|
|
1975
|
+
try {
|
|
1976
|
+
const _req = createRequire(import.meta.url);
|
|
1977
|
+
const tracker = _req(trackerPath);
|
|
1978
|
+
const range = tracker.getDateRange(period);
|
|
1979
|
+
const projects = tracker.parseAllSessions(range.start, range.end);
|
|
1980
|
+
let totalCost = 0, totalIn = 0, totalOut = 0, totalCR = 0, totalCW = 0, totalCalls = 0;
|
|
1981
|
+
const modelBreakdown = {}, categoryBreakdown = {}, toolBreakdown = {}, mcpBreakdown = {};
|
|
1982
|
+
for (const p of projects) {
|
|
1983
|
+
totalCost += p.totalCost || 0;
|
|
1984
|
+
for (const s of (p.sessions || [])) {
|
|
1985
|
+
totalIn += s.totalInputTokens || 0;
|
|
1986
|
+
totalOut += s.totalOutputTokens || 0;
|
|
1987
|
+
totalCR += s.totalCacheRead || 0;
|
|
1988
|
+
totalCW += s.totalCacheWrite || 0;
|
|
1989
|
+
totalCalls += s.apiCalls || 0;
|
|
1990
|
+
for (const [mn, m] of Object.entries(s.modelBreakdown || {})) {
|
|
1991
|
+
if (!modelBreakdown[mn]) modelBreakdown[mn] = { calls: 0, cost: 0, tokens: 0 };
|
|
1992
|
+
modelBreakdown[mn].calls += m.calls || 0;
|
|
1993
|
+
modelBreakdown[mn].cost += m.cost || 0;
|
|
1994
|
+
modelBreakdown[mn].tokens += m.tokens || 0;
|
|
1995
|
+
}
|
|
1996
|
+
for (const [cat, c] of Object.entries(s.categoryBreakdown || {})) {
|
|
1997
|
+
if (!categoryBreakdown[cat]) categoryBreakdown[cat] = { turns: 0, cost: 0 };
|
|
1998
|
+
categoryBreakdown[cat].turns += c.turns || 0;
|
|
1999
|
+
categoryBreakdown[cat].cost += c.cost || 0;
|
|
2000
|
+
}
|
|
2001
|
+
for (const [tool, t] of Object.entries(s.toolBreakdown || {})) {
|
|
2002
|
+
if (!toolBreakdown[tool]) toolBreakdown[tool] = { calls: 0 };
|
|
2003
|
+
toolBreakdown[tool].calls += t.calls || 0;
|
|
2004
|
+
}
|
|
2005
|
+
for (const [srv, m] of Object.entries(s.mcpBreakdown || {})) {
|
|
2006
|
+
if (!mcpBreakdown[srv]) mcpBreakdown[srv] = { calls: 0 };
|
|
2007
|
+
mcpBreakdown[srv].calls += m.calls || 0;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
|
|
2012
|
+
res.end(JSON.stringify({ totalCost, totalCalls, totalIn, totalOut, totalCR, totalCW, projects, modelBreakdown, categoryBreakdown, toolBreakdown, mcpBreakdown, periodLabel: period }));
|
|
2013
|
+
} catch (e) { fallback(); }
|
|
2014
|
+
} catch (err) {
|
|
2015
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2016
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2017
|
+
}
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1079
2021
|
// ------------------------------------------------------- GET /api/section
|
|
1080
2022
|
if (req.method === 'GET' && url === '/api/section') {
|
|
1081
2023
|
try {
|
|
1082
2024
|
const qs = new URL(req.url, 'http://localhost').searchParams;
|
|
1083
2025
|
const name = qs.get('name') || '';
|
|
1084
|
-
const dir = qs.get('dir') || projectDir;
|
|
2026
|
+
const dir = qs.get('dir') || projectDir || process.cwd();
|
|
1085
2027
|
const full = qs.get('full') === '1';
|
|
1086
2028
|
let partial = buildSectionData(name, dir || process.cwd());
|
|
1087
2029
|
// For full knowledge request, include all chunks
|
|
@@ -1150,6 +2092,243 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
|
|
|
1150
2092
|
return;
|
|
1151
2093
|
}
|
|
1152
2094
|
|
|
2095
|
+
// ------------------------------------------------- Org management
|
|
2096
|
+
// GET /api/orgs — list all saved org configs
|
|
2097
|
+
if (req.method === 'GET' && url === '/api/orgs') {
|
|
2098
|
+
try {
|
|
2099
|
+
const orgsDir = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
|
|
2100
|
+
let orgs = [];
|
|
2101
|
+
if (fs.existsSync(orgsDir)) {
|
|
2102
|
+
const files = fs.readdirSync(orgsDir).filter(f => f.endsWith('.json'));
|
|
2103
|
+
// Read events file once, outside the per-org loop
|
|
2104
|
+
let recentLines = [];
|
|
2105
|
+
try {
|
|
2106
|
+
const evFile = path.join(projectDir || process.cwd(), 'data', 'mastermind-events.jsonl');
|
|
2107
|
+
if (fs.existsSync(evFile)) {
|
|
2108
|
+
// Read only the last 64 KB to bound cost on large files
|
|
2109
|
+
const stat = fs.statSync(evFile);
|
|
2110
|
+
const TAIL = 65536;
|
|
2111
|
+
const fd = fs.openSync(evFile, 'r');
|
|
2112
|
+
const buf = Buffer.alloc(Math.min(TAIL, stat.size));
|
|
2113
|
+
try {
|
|
2114
|
+
fs.readSync(fd, buf, 0, buf.length, Math.max(0, stat.size - buf.length));
|
|
2115
|
+
} finally {
|
|
2116
|
+
fs.closeSync(fd);
|
|
2117
|
+
}
|
|
2118
|
+
recentLines = buf.toString('utf8').split('\n').filter(Boolean).reverse();
|
|
2119
|
+
}
|
|
2120
|
+
} catch(_) {}
|
|
2121
|
+
for (const f of files) {
|
|
2122
|
+
try {
|
|
2123
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(orgsDir, f), 'utf8'));
|
|
2124
|
+
let running = false;
|
|
2125
|
+
const lastStart = recentLines.find(l => { try { const e = JSON.parse(l); return e.type === 'org:start' && e.org === cfg.name; } catch(_) { return false; } });
|
|
2126
|
+
const lastStop = recentLines.find(l => { try { const e = JSON.parse(l); return (e.type === 'org:stop' || e.type === 'org:complete') && e.org === cfg.name; } catch(_) { return false; } });
|
|
2127
|
+
if (lastStart) {
|
|
2128
|
+
const startTs = JSON.parse(lastStart).ts || 0;
|
|
2129
|
+
const stopTs = lastStop ? (JSON.parse(lastStop).ts || 0) : 0;
|
|
2130
|
+
running = startTs > stopTs;
|
|
2131
|
+
}
|
|
2132
|
+
orgs.push({ name: cfg.name, goal: cfg.goal, roles: cfg.roles?.length || 0, topology: cfg.topology, created_at: cfg.created_at, running });
|
|
2133
|
+
} catch(_) {}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2137
|
+
res.end(JSON.stringify(orgs));
|
|
2138
|
+
} catch(_) { res.writeHead(500); res.end('[]'); }
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// GET /api/orgs/:name — get specific org config (exact path: /api/orgs/<slug>)
|
|
2143
|
+
if (req.method === 'GET' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
|
|
2144
|
+
try {
|
|
2145
|
+
const orgName = decodeURIComponent(url.slice('/api/orgs/'.length));
|
|
2146
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2147
|
+
const f = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}.json`);
|
|
2148
|
+
if (!fs.existsSync(f)) { res.writeHead(404); res.end('{"error":"not found"}'); return; }
|
|
2149
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2150
|
+
res.end(fs.readFileSync(f, 'utf8'));
|
|
2151
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// POST /api/orgs/:name/stop — send stop signal to a running org
|
|
2156
|
+
if (req.method === 'POST' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/stop$/i)) {
|
|
2157
|
+
try {
|
|
2158
|
+
const orgName = decodeURIComponent(url.split('/')[3]);
|
|
2159
|
+
if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
|
|
2160
|
+
const stopEvent = { type: 'org:stop', org: orgName, ts: Date.now() };
|
|
2161
|
+
const dataDir = path.join(projectDir || process.cwd(), 'data');
|
|
2162
|
+
try { fs.mkdirSync(dataDir, { recursive: true }); } catch(_) {}
|
|
2163
|
+
try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(stopEvent) + '\n'); } catch(_) {}
|
|
2164
|
+
// Write stop marker file for boss agent to detect
|
|
2165
|
+
try {
|
|
2166
|
+
const stopDir = path.join(projectDir || process.cwd(), '.monomind', 'orgs', '.stops');
|
|
2167
|
+
fs.mkdirSync(stopDir, { recursive: true });
|
|
2168
|
+
fs.writeFileSync(path.join(stopDir, `${orgName}.stop`), String(Date.now()));
|
|
2169
|
+
} catch(_) {}
|
|
2170
|
+
const msg = `data: ${JSON.stringify(stopEvent)}\n\n`;
|
|
2171
|
+
for (const c of mmSseClients) { try { c.write(msg); } catch(_) { mmSseClients.delete(c); } }
|
|
2172
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2173
|
+
res.end('{"ok":true}');
|
|
2174
|
+
} catch(_) { res.writeHead(500); res.end('{}'); }
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// ------------------------------------------------- Mastermind event system
|
|
2179
|
+
// POST /api/mastermind/event — ingest event from mastermind skill
|
|
2180
|
+
if (req.method === 'POST' && url === '/api/mastermind/event') {
|
|
2181
|
+
let body = '';
|
|
2182
|
+
for await (const chunk of req) body += chunk;
|
|
2183
|
+
let event = {};
|
|
2184
|
+
try { event = JSON.parse(body); } catch (_) {}
|
|
2185
|
+
event.ts = event.ts || Date.now();
|
|
2186
|
+
const root = projectDir || process.cwd();
|
|
2187
|
+
const dataDir = path.join(root, 'data');
|
|
2188
|
+
try { fs.mkdirSync(dataDir, { recursive: true }); } catch (_) {}
|
|
2189
|
+
try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch (_) {}
|
|
2190
|
+
// Persist session
|
|
2191
|
+
try {
|
|
2192
|
+
const sessFile = path.join(dataDir, 'mastermind-sessions.json');
|
|
2193
|
+
let sessions = [];
|
|
2194
|
+
try { sessions = JSON.parse(fs.readFileSync(sessFile, 'utf8')); } catch (_) {}
|
|
2195
|
+
if (event.type === 'session:start') {
|
|
2196
|
+
sessions.unshift({ id: event.session, ts: event.ts, prompt: event.prompt || '',
|
|
2197
|
+
status: 'running', domains: [], events: [event] });
|
|
2198
|
+
} else {
|
|
2199
|
+
const s = sessions.find(s => s.id === event.session);
|
|
2200
|
+
if (s) {
|
|
2201
|
+
(s.events = s.events || []).push(event);
|
|
2202
|
+
if (event.type === 'domain:dispatch' && event.domain && !s.domains.includes(event.domain))
|
|
2203
|
+
s.domains.push(event.domain);
|
|
2204
|
+
if (event.type === 'session:complete') { s.status = event.status || 'complete'; s.endTs = event.ts; }
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
fs.writeFileSync(sessFile, JSON.stringify(sessions.slice(0, 50), null, 2));
|
|
2208
|
+
// Also write individual session file for direct traceability
|
|
2209
|
+
const sessionObj = sessions.find(s => s.id === event.session);
|
|
2210
|
+
if (sessionObj) {
|
|
2211
|
+
const sessDir = path.join(dataDir, 'sessions');
|
|
2212
|
+
try { fs.mkdirSync(sessDir, { recursive: true }); } catch (_) {}
|
|
2213
|
+
try { fs.writeFileSync(path.join(sessDir, `${event.session}.json`), JSON.stringify(sessionObj, null, 2)); } catch (_) {}
|
|
2214
|
+
}
|
|
2215
|
+
} catch (_) {}
|
|
2216
|
+
// For org:stop events, write a stop marker the boss agent can detect
|
|
2217
|
+
if (event.type === 'org:stop' && event.org) {
|
|
2218
|
+
try {
|
|
2219
|
+
const orgName = String(event.org).trim();
|
|
2220
|
+
// Validate before any filesystem use — reject rather than strip
|
|
2221
|
+
if (orgName.length > 0 && orgName.length <= 64 && /^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) {
|
|
2222
|
+
const stopDir = path.join(root, '.monomind', 'orgs', '.stops');
|
|
2223
|
+
fs.mkdirSync(stopDir, { recursive: true });
|
|
2224
|
+
fs.writeFileSync(path.join(stopDir, `${orgName}.stop`), String(Date.now()));
|
|
2225
|
+
}
|
|
2226
|
+
} catch (_) {}
|
|
2227
|
+
}
|
|
2228
|
+
// Broadcast to all mastermind SSE clients
|
|
2229
|
+
const msg = `data: ${JSON.stringify(event)}\n\n`;
|
|
2230
|
+
for (const c of mmSseClients) { try { c.write(msg); } catch (_) { mmSseClients.delete(c); } }
|
|
2231
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2232
|
+
res.end('{"ok":true}');
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// GET /api/mastermind-stream — SSE for real-time events
|
|
2237
|
+
if (req.method === 'GET' && url === '/api/mastermind-stream') {
|
|
2238
|
+
res.writeHead(200, {
|
|
2239
|
+
'Content-Type': 'text/event-stream',
|
|
2240
|
+
'Cache-Control': 'no-cache',
|
|
2241
|
+
'Connection': 'keep-alive',
|
|
2242
|
+
'Access-Control-Allow-Origin': '*',
|
|
2243
|
+
});
|
|
2244
|
+
res.write(': connected\n\n');
|
|
2245
|
+
mmSseClients.add(res);
|
|
2246
|
+
// Replay last 50 events from disk
|
|
2247
|
+
try {
|
|
2248
|
+
const root2 = projectDir || process.cwd();
|
|
2249
|
+
const evFile = path.join(root2, 'data', 'mastermind-events.jsonl');
|
|
2250
|
+
const lines = fs.readFileSync(evFile, 'utf8').trim().split('\n').filter(Boolean).slice(-50);
|
|
2251
|
+
for (const l of lines) res.write(`data: ${l}\n\n`);
|
|
2252
|
+
} catch (_) {}
|
|
2253
|
+
const ka = setInterval(() => { try { res.write(': ping\n\n'); } catch (_) { clearInterval(ka); mmSseClients.delete(res); } }, 20000);
|
|
2254
|
+
req.on('close', () => { mmSseClients.delete(res); clearInterval(ka); });
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// GET /api/mastermind/sessions
|
|
2259
|
+
if (req.method === 'GET' && url === '/api/mastermind/sessions') {
|
|
2260
|
+
try {
|
|
2261
|
+
const f = path.join(projectDir || process.cwd(), 'data', 'mastermind-sessions.json');
|
|
2262
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2263
|
+
res.end(fs.existsSync(f) ? fs.readFileSync(f, 'utf8') : '[]');
|
|
2264
|
+
} catch (_) { res.writeHead(200); res.end('[]'); }
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// GET /api/mastermind/session/:id/trace — human-readable markdown trace
|
|
2269
|
+
if (req.method === 'GET' && url.match(/^\/api\/mastermind\/session\/[^/]+\/trace$/)) {
|
|
2270
|
+
try {
|
|
2271
|
+
const sid = url.split('/')[4];
|
|
2272
|
+
const sessFile = path.join(projectDir || process.cwd(), 'data', 'sessions', `${sid}.json`);
|
|
2273
|
+
let s = null;
|
|
2274
|
+
if (fs.existsSync(sessFile)) {
|
|
2275
|
+
s = JSON.parse(fs.readFileSync(sessFile, 'utf8'));
|
|
2276
|
+
} else {
|
|
2277
|
+
const f = path.join(projectDir || process.cwd(), 'data', 'mastermind-sessions.json');
|
|
2278
|
+
const sessions = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
2279
|
+
s = sessions.find(x => x.id === sid);
|
|
2280
|
+
}
|
|
2281
|
+
if (!s) { res.writeHead(404); res.end('Session not found'); return; }
|
|
2282
|
+
const fmt = (ts) => new Date(ts).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';
|
|
2283
|
+
const lines = [`# Mastermind Session Trace: ${s.id}`, ``, `**Prompt:** ${s.prompt || '(none)'}`, `**Status:** ${s.status}`, `**Started:** ${fmt(s.ts)}`, s.endTs ? `**Ended:** ${fmt(s.endTs)}` : '', `**Domains:** ${(s.domains || []).join(', ') || '(none yet)'}`, ``];
|
|
2284
|
+
for (const ev of (s.events || [])) {
|
|
2285
|
+
const t = fmt(ev.ts);
|
|
2286
|
+
if (ev.type === 'session:start') lines.push(`\`${t}\` **SESSION START** — prompt: "${ev.prompt || ''}"`);
|
|
2287
|
+
else if (ev.type === 'domain:dispatch') lines.push(`\`${t}\` **DOMAIN DISPATCH** → \`${ev.domain}\` — ${ev.cmd || ''}`);
|
|
2288
|
+
else if (ev.type === 'agent:spawn') lines.push(`\`${t}\` **AGENT SPAWN** [\`${ev.domain}\`] → agent: \`${ev.agent}\` — ${ev.task || ''}`);
|
|
2289
|
+
else if (ev.type === 'intercom') lines.push(`\`${t}\` **INTERCOM** \`${ev.from}\` → \`${ev.to}\`: ${ev.msg || ''}`);
|
|
2290
|
+
else if (ev.type === 'domain:complete') lines.push(`\`${t}\` **DOMAIN COMPLETE** [\`${ev.domain}\`] status: ${ev.status}${ev.artifacts?.length ? ` — artifacts: ${ev.artifacts.join(', ')}` : ''}`);
|
|
2291
|
+
else if (ev.type === 'session:complete') lines.push(`\`${t}\` **SESSION COMPLETE** — status: ${ev.status}, domains: ${(ev.domains || []).join(', ')}`);
|
|
2292
|
+
else lines.push(`\`${t}\` ${ev.type} ${JSON.stringify(ev)}`);
|
|
2293
|
+
}
|
|
2294
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
|
|
2295
|
+
res.end(lines.join('\n'));
|
|
2296
|
+
} catch (_) { res.writeHead(500); res.end('Error'); }
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
// GET /api/mastermind/session/:id
|
|
2301
|
+
if (req.method === 'GET' && url.startsWith('/api/mastermind/session/')) {
|
|
2302
|
+
try {
|
|
2303
|
+
const sid = url.slice('/api/mastermind/session/'.length);
|
|
2304
|
+
// Check individual session file first
|
|
2305
|
+
const sessFile = path.join(projectDir || process.cwd(), 'data', 'sessions', `${sid}.json`);
|
|
2306
|
+
if (fs.existsSync(sessFile)) {
|
|
2307
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2308
|
+
res.end(fs.readFileSync(sessFile, 'utf8'));
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
const f = path.join(projectDir || process.cwd(), 'data', 'mastermind-sessions.json');
|
|
2312
|
+
const sessions = JSON.parse(fs.readFileSync(f, 'utf8'));
|
|
2313
|
+
const s = sessions.find(x => x.id === sid);
|
|
2314
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2315
|
+
res.end(JSON.stringify(s || null));
|
|
2316
|
+
} catch (_) { res.writeHead(200); res.end('null'); }
|
|
2317
|
+
return;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// -------------------------------------------------------- GET /mastermind
|
|
2321
|
+
if (req.method === 'GET' && url === '/mastermind') {
|
|
2322
|
+
// Serve local file if present (dev), otherwise fall back to bundled HTML
|
|
2323
|
+
const root = projectDir || process.cwd();
|
|
2324
|
+
const htmlPath = path.join(root, 'docs', 'mastermind-diagram.html');
|
|
2325
|
+
let html = MASTERMIND_DIAGRAM_HTML;
|
|
2326
|
+
try { html = fs.readFileSync(htmlPath, 'utf8'); } catch (_) {}
|
|
2327
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
2328
|
+
res.end(html);
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
1153
2332
|
// ------------------------------------------------------------------ 404
|
|
1154
2333
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
1155
2334
|
res.end('Not found');
|
|
@@ -1257,3 +2436,14 @@ export function getServerStatus() {
|
|
|
1257
2436
|
clientCount: sseClients.size,
|
|
1258
2437
|
};
|
|
1259
2438
|
}
|
|
2439
|
+
|
|
2440
|
+
// Auto-start when invoked directly: node server.mjs [port]
|
|
2441
|
+
const _isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
2442
|
+
if (_isMain) {
|
|
2443
|
+
const _port = parseInt(process.argv[2] || process.env.CONTROL_PORT || '4242', 10);
|
|
2444
|
+
const _dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
2445
|
+
startServer({ port: _port, openBrowser: false, projectDir: _dir }).catch(err => {
|
|
2446
|
+
process.stderr.write(`[server] failed to start: ${err.message}\n`);
|
|
2447
|
+
process.exit(1);
|
|
2448
|
+
});
|
|
2449
|
+
}
|