@claude-flow/cli 3.32.3 → 3.32.5
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/helpers/helpers.manifest.json +2 -2
- package/catalog-manifest.json +2 -2
- package/dist/src/agenticow/speculative-exploration.d.ts +148 -0
- package/dist/src/agenticow/speculative-exploration.js +218 -0
- package/dist/src/appliance/gguf-engine.d.ts +91 -0
- package/dist/src/appliance/gguf-engine.js +425 -0
- package/dist/src/appliance/ruvllm-bridge.d.ts +102 -0
- package/dist/src/appliance/ruvllm-bridge.js +292 -0
- package/dist/src/appliance/rvfa-builder.d.ts +44 -0
- package/dist/src/appliance/rvfa-builder.js +329 -0
- package/dist/src/appliance/rvfa-distribution.d.ts +97 -0
- package/dist/src/appliance/rvfa-distribution.js +370 -0
- package/dist/src/appliance/rvfa-format.d.ts +111 -0
- package/dist/src/appliance/rvfa-format.js +393 -0
- package/dist/src/appliance/rvfa-runner.d.ts +69 -0
- package/dist/src/appliance/rvfa-runner.js +237 -0
- package/dist/src/appliance/rvfa-signing.d.ts +123 -0
- package/dist/src/appliance/rvfa-signing.js +347 -0
- package/dist/src/auth/client.d.ts +89 -0
- package/dist/src/auth/client.js +242 -0
- package/dist/src/auth/constants.d.ts +7 -0
- package/dist/src/auth/constants.js +7 -0
- package/dist/src/auth/scopes.d.ts +14 -0
- package/dist/src/auth/scopes.js +21 -0
- package/dist/src/auth/security-bridge.d.ts +36 -0
- package/dist/src/auth/security-bridge.js +42 -0
- package/dist/src/auth/session.d.ts +20 -0
- package/dist/src/auth/session.js +32 -0
- package/dist/src/auth/state.d.ts +19 -0
- package/dist/src/auth/state.js +53 -0
- package/dist/src/auth/types.d.ts +27 -0
- package/dist/src/auth/types.js +11 -0
- package/dist/src/autopilot-state.d.ts +77 -0
- package/dist/src/autopilot-state.js +271 -0
- package/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
- package/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
- package/dist/src/benchmarks/gaia-agent.d.ts +198 -0
- package/dist/src/benchmarks/gaia-agent.js +651 -0
- package/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
- package/dist/src/benchmarks/gaia-causal-memory.js +281 -0
- package/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
- package/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
- package/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
- package/dist/src/benchmarks/gaia-convergence.js +260 -0
- package/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
- package/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
- package/dist/src/benchmarks/gaia-critic.d.ts +123 -0
- package/dist/src/benchmarks/gaia-critic.js +312 -0
- package/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
- package/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
- package/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
- package/dist/src/benchmarks/gaia-decomposer.js +350 -0
- package/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
- package/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
- package/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
- package/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
- package/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
- package/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
- package/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
- package/dist/src/benchmarks/gaia-hardness/features.js +170 -0
- package/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
- package/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
- package/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
- package/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
- package/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
- package/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
- package/dist/src/benchmarks/gaia-judge.d.ts +88 -0
- package/dist/src/benchmarks/gaia-judge.js +437 -0
- package/dist/src/benchmarks/gaia-loader.d.ts +87 -0
- package/dist/src/benchmarks/gaia-loader.js +326 -0
- package/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
- package/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
- package/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
- package/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
- package/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
- package/dist/src/benchmarks/gaia-tools/index.js +36 -0
- package/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
- package/dist/src/benchmarks/gaia-tools/types.js +12 -0
- package/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
- package/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
- package/dist/src/benchmarks/gaia-voting.d.ts +88 -0
- package/dist/src/benchmarks/gaia-voting.js +297 -0
- package/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
- package/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
- package/dist/src/benchmarks/pretrain/index.d.ts +58 -0
- package/dist/src/benchmarks/pretrain/index.js +404 -0
- package/dist/src/business-pods/bbs-budget-tracker.d.ts +139 -0
- package/dist/src/business-pods/bbs-budget-tracker.js +358 -0
- package/dist/src/business-pods/domain-affinity-policy.d.ts +47 -0
- package/dist/src/business-pods/domain-affinity-policy.js +65 -0
- package/dist/src/business-pods/pod-schema.d.ts +96 -0
- package/dist/src/business-pods/pod-schema.js +225 -0
- package/dist/src/commands/advisor.d.ts +15 -0
- package/dist/src/commands/advisor.js +94 -0
- package/dist/src/commands/agent-wasm.d.ts +14 -0
- package/dist/src/commands/agent-wasm.js +333 -0
- package/dist/src/commands/agent.d.ts +8 -0
- package/dist/src/commands/agent.js +927 -0
- package/dist/src/commands/analyze.d.ts +19 -0
- package/dist/src/commands/analyze.js +2047 -0
- package/dist/src/commands/announcements.d.ts +17 -0
- package/dist/src/commands/announcements.js +0 -0
- package/dist/src/commands/appliance-advanced.d.ts +9 -0
- package/dist/src/commands/appliance-advanced.js +215 -0
- package/dist/src/commands/appliance.d.ts +8 -0
- package/dist/src/commands/appliance.js +404 -0
- package/dist/src/commands/auth.d.ts +15 -0
- package/dist/src/commands/auth.js +244 -0
- package/dist/src/commands/autopilot.d.ts +15 -0
- package/dist/src/commands/autopilot.js +407 -0
- package/dist/src/commands/benchmark.d.ts +10 -0
- package/dist/src/commands/benchmark.js +460 -0
- package/dist/src/commands/claims.d.ts +10 -0
- package/dist/src/commands/claims.js +620 -0
- package/dist/src/commands/cleanup.d.ts +13 -0
- package/dist/src/commands/cleanup.js +250 -0
- package/dist/src/commands/completions.d.ts +10 -0
- package/dist/src/commands/completions.js +539 -0
- package/dist/src/commands/config.d.ts +8 -0
- package/dist/src/commands/config.js +428 -0
- package/dist/src/commands/daemon.d.ts +59 -0
- package/dist/src/commands/daemon.js +1733 -0
- package/dist/src/commands/deployment.d.ts +10 -0
- package/dist/src/commands/deployment.js +672 -0
- package/dist/src/commands/doctor.d.ts +10 -0
- package/dist/src/commands/doctor.js +1902 -0
- package/dist/src/commands/eject.d.ts +33 -0
- package/dist/src/commands/eject.js +195 -0
- package/dist/src/commands/embeddings.d.ts +18 -0
- package/dist/src/commands/embeddings.js +1623 -0
- package/dist/src/commands/funnel.d.ts +18 -0
- package/dist/src/commands/funnel.js +302 -0
- package/dist/src/commands/gaia-bench.d.ts +40 -0
- package/dist/src/commands/gaia-bench.js +597 -0
- package/dist/src/commands/guidance.d.ts +8 -0
- package/dist/src/commands/guidance.js +556 -0
- package/dist/src/commands/hive-mind.d.ts +11 -0
- package/dist/src/commands/hive-mind.js +1319 -0
- package/dist/src/commands/hooks.d.ts +8 -0
- package/dist/src/commands/hooks.js +4606 -0
- package/dist/src/commands/index.d.ts +118 -0
- package/dist/src/commands/index.js +366 -0
- package/dist/src/commands/init.d.ts +13 -0
- package/dist/src/commands/init.js +1297 -0
- package/dist/src/commands/issues.d.ts +21 -0
- package/dist/src/commands/issues.js +567 -0
- package/dist/src/commands/mcp.d.ts +11 -0
- package/dist/src/commands/mcp.js +732 -0
- package/dist/src/commands/memory-backup.d.ts +11 -0
- package/dist/src/commands/memory-backup.js +46 -0
- package/dist/src/commands/memory-distill.d.ts +27 -0
- package/dist/src/commands/memory-distill.js +374 -0
- package/dist/src/commands/memory.d.ts +8 -0
- package/dist/src/commands/memory.js +1596 -0
- package/dist/src/commands/metaharness.d.ts +39 -0
- package/dist/src/commands/metaharness.js +215 -0
- package/dist/src/commands/migrate.d.ts +8 -0
- package/dist/src/commands/migrate.js +742 -0
- package/dist/src/commands/neural.d.ts +10 -0
- package/dist/src/commands/neural.js +4331 -0
- package/dist/src/commands/performance.d.ts +10 -0
- package/dist/src/commands/performance.js +583 -0
- package/dist/src/commands/plugins.d.ts +11 -0
- package/dist/src/commands/plugins.js +826 -0
- package/dist/src/commands/process.d.ts +10 -0
- package/dist/src/commands/process.js +694 -0
- package/dist/src/commands/progress.d.ts +11 -0
- package/dist/src/commands/progress.js +259 -0
- package/dist/src/commands/providers.d.ts +10 -0
- package/dist/src/commands/providers.js +502 -0
- package/dist/src/commands/proxy-lifecycle.d.ts +20 -0
- package/dist/src/commands/proxy-lifecycle.js +277 -0
- package/dist/src/commands/proxy.d.ts +21 -0
- package/dist/src/commands/proxy.js +403 -0
- package/dist/src/commands/route.d.ts +16 -0
- package/dist/src/commands/route.js +822 -0
- package/dist/src/commands/ruvector/backup.d.ts +11 -0
- package/dist/src/commands/ruvector/backup.js +747 -0
- package/dist/src/commands/ruvector/benchmark.d.ts +11 -0
- package/dist/src/commands/ruvector/benchmark.js +490 -0
- package/dist/src/commands/ruvector/import.d.ts +18 -0
- package/dist/src/commands/ruvector/import.js +373 -0
- package/dist/src/commands/ruvector/index.d.ts +29 -0
- package/dist/src/commands/ruvector/index.js +129 -0
- package/dist/src/commands/ruvector/init.d.ts +11 -0
- package/dist/src/commands/ruvector/init.js +467 -0
- package/dist/src/commands/ruvector/migrate.d.ts +11 -0
- package/dist/src/commands/ruvector/migrate.js +498 -0
- package/dist/src/commands/ruvector/optimize.d.ts +11 -0
- package/dist/src/commands/ruvector/optimize.js +505 -0
- package/dist/src/commands/ruvector/pg-utils.d.ts +14 -0
- package/dist/src/commands/ruvector/pg-utils.js +41 -0
- package/dist/src/commands/ruvector/setup.d.ts +18 -0
- package/dist/src/commands/ruvector/setup.js +765 -0
- package/dist/src/commands/ruvector/status.d.ts +11 -0
- package/dist/src/commands/ruvector/status.js +479 -0
- package/dist/src/commands/security.d.ts +10 -0
- package/dist/src/commands/security.js +1012 -0
- package/dist/src/commands/session.d.ts +8 -0
- package/dist/src/commands/session.js +757 -0
- package/dist/src/commands/settings.d.ts +19 -0
- package/dist/src/commands/settings.js +180 -0
- package/dist/src/commands/spinner.d.ts +16 -0
- package/dist/src/commands/spinner.js +329 -0
- package/dist/src/commands/start.d.ts +8 -0
- package/dist/src/commands/start.js +418 -0
- package/dist/src/commands/status.d.ts +8 -0
- package/dist/src/commands/status.js +608 -0
- package/dist/src/commands/swarm.d.ts +8 -0
- package/dist/src/commands/swarm.js +891 -0
- package/dist/src/commands/task.d.ts +8 -0
- package/dist/src/commands/task.js +675 -0
- package/dist/src/commands/transfer-store.d.ts +13 -0
- package/dist/src/commands/transfer-store.js +428 -0
- package/dist/src/commands/update.d.ts +8 -0
- package/dist/src/commands/update.js +276 -0
- package/dist/src/commands/verify.d.ts +19 -0
- package/dist/src/commands/verify.js +261 -0
- package/dist/src/commands/version.d.ts +42 -0
- package/dist/src/commands/version.js +106 -0
- package/dist/src/commands/workflow.d.ts +8 -0
- package/dist/src/commands/workflow.js +617 -0
- package/dist/src/config/harness-feedback-applier.d.ts +50 -0
- package/dist/src/config/harness-feedback-applier.js +122 -0
- package/dist/src/config/proven-config-refresh.d.ts +39 -0
- package/dist/src/config/proven-config-refresh.js +154 -0
- package/dist/src/config/proven-config-rvfa.d.ts +23 -0
- package/dist/src/config/proven-config-rvfa.js +73 -0
- package/dist/src/config/proven-config.d.ts +86 -0
- package/dist/src/config/proven-config.js +176 -0
- package/dist/src/config-adapter.d.ts +15 -0
- package/dist/src/config-adapter.js +186 -0
- package/dist/src/encryption/vault.d.ts +94 -0
- package/dist/src/encryption/vault.js +172 -0
- package/dist/src/fs-secure.d.ts +86 -0
- package/dist/src/fs-secure.js +133 -0
- package/dist/src/funnel/advisor-tip.d.ts +58 -0
- package/dist/src/funnel/advisor-tip.js +92 -0
- package/dist/src/funnel/attribution.d.ts +37 -0
- package/dist/src/funnel/attribution.js +101 -0
- package/dist/src/funnel/consent.d.ts +22 -0
- package/dist/src/funnel/consent.js +58 -0
- package/dist/src/funnel/credit-errors.d.ts +31 -0
- package/dist/src/funnel/credit-errors.js +88 -0
- package/dist/src/funnel/credit-notifier.d.ts +44 -0
- package/dist/src/funnel/credit-notifier.js +74 -0
- package/dist/src/funnel/disclosure.d.ts +47 -0
- package/dist/src/funnel/disclosure.js +109 -0
- package/dist/src/funnel/enrollment.d.ts +36 -0
- package/dist/src/funnel/enrollment.js +64 -0
- package/dist/src/funnel/environment.d.ts +17 -0
- package/dist/src/funnel/environment.js +39 -0
- package/dist/src/funnel/event-transport.d.ts +51 -0
- package/dist/src/funnel/event-transport.js +199 -0
- package/dist/src/funnel/events.d.ts +42 -0
- package/dist/src/funnel/events.js +150 -0
- package/dist/src/funnel/index.d.ts +21 -0
- package/dist/src/funnel/index.js +21 -0
- package/dist/src/funnel/insights.d.ts +50 -0
- package/dist/src/funnel/insights.js +120 -0
- package/dist/src/funnel/local-signals.d.ts +20 -0
- package/dist/src/funnel/local-signals.js +77 -0
- package/dist/src/funnel/message-transport.d.ts +51 -0
- package/dist/src/funnel/message-transport.js +149 -0
- package/dist/src/funnel/messages.d.ts +55 -0
- package/dist/src/funnel/messages.js +160 -0
- package/dist/src/funnel/payout.d.ts +40 -0
- package/dist/src/funnel/payout.js +60 -0
- package/dist/src/funnel/power-saver-notifier.d.ts +44 -0
- package/dist/src/funnel/power-saver-notifier.js +92 -0
- package/dist/src/funnel/precedence.d.ts +16 -0
- package/dist/src/funnel/precedence.js +85 -0
- package/dist/src/funnel/promo.d.ts +41 -0
- package/dist/src/funnel/promo.js +144 -0
- package/dist/src/funnel/rate-limit-notifier.d.ts +55 -0
- package/dist/src/funnel/rate-limit-notifier.js +102 -0
- package/dist/src/funnel/rotation.d.ts +19 -0
- package/dist/src/funnel/rotation.js +70 -0
- package/dist/src/funnel/state.d.ts +13 -0
- package/dist/src/funnel/state.js +52 -0
- package/dist/src/funnel/toggle-cooldown.d.ts +17 -0
- package/dist/src/funnel/toggle-cooldown.js +32 -0
- package/dist/src/funnel/types.d.ts +98 -0
- package/dist/src/funnel/types.js +26 -0
- package/dist/src/index.d.ts +81 -0
- package/dist/src/index.js +609 -0
- package/dist/src/infrastructure/in-memory-repositories.d.ts +68 -0
- package/dist/src/infrastructure/in-memory-repositories.js +264 -0
- package/dist/src/init/claudemd-generator.d.ts +16 -0
- package/dist/src/init/claudemd-generator.js +368 -0
- package/dist/src/init/executor.d.ts +41 -0
- package/dist/src/init/executor.js +2142 -0
- package/dist/src/init/helper-refresh.d.ts +79 -0
- package/dist/src/init/helper-refresh.js +347 -0
- package/dist/src/init/helper-signing.d.ts +37 -0
- package/dist/src/init/helper-signing.js +67 -0
- package/dist/src/init/helpers-generator.d.ts +87 -0
- package/dist/src/init/helpers-generator.js +1456 -0
- package/dist/src/init/index.d.ts +13 -0
- package/dist/src/init/index.js +15 -0
- package/dist/src/init/mcp-generator.d.ts +26 -0
- package/dist/src/init/mcp-generator.js +126 -0
- package/dist/src/init/memory-package-resolver.d.ts +53 -0
- package/dist/src/init/memory-package-resolver.js +118 -0
- package/dist/src/init/settings-generator.d.ts +14 -0
- package/dist/src/init/settings-generator.js +462 -0
- package/dist/src/init/statusline-generator.d.ts +28 -0
- package/dist/src/init/statusline-generator.js +177 -0
- package/dist/src/init/types.d.ts +331 -0
- package/dist/src/init/types.js +276 -0
- package/dist/src/log-filters.d.ts +46 -0
- package/dist/src/log-filters.js +107 -0
- package/dist/src/mcp-client.d.ts +92 -0
- package/dist/src/mcp-client.js +397 -0
- package/dist/src/mcp-server.d.ts +163 -0
- package/dist/src/mcp-server.js +751 -0
- package/dist/src/mcp-tools/agent-execute-core.d.ts +115 -0
- package/dist/src/mcp-tools/agent-execute-core.js +587 -0
- package/dist/src/mcp-tools/agent-tools.d.ts +9 -0
- package/dist/src/mcp-tools/agent-tools.js +837 -0
- package/dist/src/mcp-tools/agentbbs-tools.d.ts +28 -0
- package/dist/src/mcp-tools/agentbbs-tools.js +394 -0
- package/dist/src/mcp-tools/agentdb-tools.d.ts +35 -0
- package/dist/src/mcp-tools/agentdb-tools.js +1473 -0
- package/dist/src/mcp-tools/agenticow-loader.d.ts +59 -0
- package/dist/src/mcp-tools/agenticow-loader.js +105 -0
- package/dist/src/mcp-tools/agenticow-speculate-tools.d.ts +24 -0
- package/dist/src/mcp-tools/agenticow-speculate-tools.js +209 -0
- package/dist/src/mcp-tools/agenticow-tools.d.ts +36 -0
- package/dist/src/mcp-tools/agenticow-tools.js +360 -0
- package/dist/src/mcp-tools/analyze-tools.d.ts +38 -0
- package/dist/src/mcp-tools/analyze-tools.js +346 -0
- package/dist/src/mcp-tools/auto-install.d.ts +83 -0
- package/dist/src/mcp-tools/auto-install.js +131 -0
- package/dist/src/mcp-tools/autopilot-tools.d.ts +12 -0
- package/dist/src/mcp-tools/autopilot-tools.js +231 -0
- package/dist/src/mcp-tools/browser-intent-tools.d.ts +162 -0
- package/dist/src/mcp-tools/browser-intent-tools.js +548 -0
- package/dist/src/mcp-tools/browser-session-tools.d.ts +27 -0
- package/dist/src/mcp-tools/browser-session-tools.js +398 -0
- package/dist/src/mcp-tools/browser-tools.d.ts +21 -0
- package/dist/src/mcp-tools/browser-tools.js +760 -0
- package/dist/src/mcp-tools/business-pod-tools.d.ts +20 -0
- package/dist/src/mcp-tools/business-pod-tools.js +169 -0
- package/dist/src/mcp-tools/claims-tools.d.ts +12 -0
- package/dist/src/mcp-tools/claims-tools.js +863 -0
- package/dist/src/mcp-tools/config-tools.d.ts +8 -0
- package/dist/src/mcp-tools/config-tools.js +411 -0
- package/dist/src/mcp-tools/coordination-tools.d.ts +13 -0
- package/dist/src/mcp-tools/coordination-tools.js +729 -0
- package/dist/src/mcp-tools/daa-tools.d.ts +13 -0
- package/dist/src/mcp-tools/daa-tools.js +534 -0
- package/dist/src/mcp-tools/embeddings-tools.d.ts +9 -0
- package/dist/src/mcp-tools/embeddings-tools.js +904 -0
- package/dist/src/mcp-tools/github-tools.d.ts +9 -0
- package/dist/src/mcp-tools/github-tools.js +659 -0
- package/dist/src/mcp-tools/guidance-tools.d.ts +15 -0
- package/dist/src/mcp-tools/guidance-tools.js +639 -0
- package/dist/src/mcp-tools/hive-mind-tools.d.ts +8 -0
- package/dist/src/mcp-tools/hive-mind-tools.js +953 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +65 -0
- package/dist/src/mcp-tools/hooks-tools.js +4836 -0
- package/dist/src/mcp-tools/http-fetch-tools.d.ts +55 -0
- package/dist/src/mcp-tools/http-fetch-tools.js +329 -0
- package/dist/src/mcp-tools/index.d.ts +34 -0
- package/dist/src/mcp-tools/index.js +40 -0
- package/dist/src/mcp-tools/managed-agent-tools.d.ts +22 -0
- package/dist/src/mcp-tools/managed-agent-tools.js +357 -0
- package/dist/src/mcp-tools/memory-tools.d.ts +14 -0
- package/dist/src/mcp-tools/memory-tools.js +1330 -0
- package/dist/src/mcp-tools/metaharness-tools.d.ts +51 -0
- package/dist/src/mcp-tools/metaharness-tools.js +684 -0
- package/dist/src/mcp-tools/neural-tools.d.ts +54 -0
- package/dist/src/mcp-tools/neural-tools.js +1168 -0
- package/dist/src/mcp-tools/performance-tools.d.ts +16 -0
- package/dist/src/mcp-tools/performance-tools.js +675 -0
- package/dist/src/mcp-tools/progress-tools.d.ts +14 -0
- package/dist/src/mcp-tools/progress-tools.js +348 -0
- package/dist/src/mcp-tools/request-tracker.d.ts +17 -0
- package/dist/src/mcp-tools/request-tracker.js +27 -0
- package/dist/src/mcp-tools/ruvllm-tools.d.ts +9 -0
- package/dist/src/mcp-tools/ruvllm-tools.js +355 -0
- package/dist/src/mcp-tools/security-tools.d.ts +18 -0
- package/dist/src/mcp-tools/security-tools.js +556 -0
- package/dist/src/mcp-tools/session-tools.d.ts +8 -0
- package/dist/src/mcp-tools/session-tools.js +517 -0
- package/dist/src/mcp-tools/swarm-tools.d.ts +37 -0
- package/dist/src/mcp-tools/swarm-tools.js +390 -0
- package/dist/src/mcp-tools/system-tools.d.ts +13 -0
- package/dist/src/mcp-tools/system-tools.js +688 -0
- package/dist/src/mcp-tools/task-tools.d.ts +8 -0
- package/dist/src/mcp-tools/task-tools.js +487 -0
- package/dist/src/mcp-tools/terminal-tools.d.ts +8 -0
- package/dist/src/mcp-tools/terminal-tools.js +306 -0
- package/dist/src/mcp-tools/testgen-tools.d.ts +26 -0
- package/dist/src/mcp-tools/testgen-tools.js +168 -0
- package/dist/src/mcp-tools/tool-loop-guardrail.d.ts +31 -0
- package/dist/src/mcp-tools/tool-loop-guardrail.js +71 -0
- package/dist/src/mcp-tools/transfer-tools.d.ts +14 -0
- package/dist/src/mcp-tools/transfer-tools.js +447 -0
- package/dist/src/mcp-tools/types.d.ts +8 -0
- package/dist/src/mcp-tools/types.js +8 -0
- package/dist/src/mcp-tools/validate-input.d.ts +9 -0
- package/dist/src/mcp-tools/validate-input.js +9 -0
- package/dist/src/mcp-tools/wasm-agent-tools.d.ts +13 -0
- package/dist/src/mcp-tools/wasm-agent-tools.js +840 -0
- package/dist/src/mcp-tools/workflow-tools.d.ts +8 -0
- package/dist/src/mcp-tools/workflow-tools.js +884 -0
- package/dist/src/memory/bge-embedder.d.ts +25 -0
- package/dist/src/memory/bge-embedder.js +121 -0
- package/dist/src/memory/cross-encoder-rerank.d.ts +33 -0
- package/dist/src/memory/cross-encoder-rerank.js +123 -0
- package/dist/src/memory/embedding-policy.d.ts +21 -0
- package/dist/src/memory/embedding-policy.js +30 -0
- package/dist/src/memory/embedding-quantization.d.ts +62 -0
- package/dist/src/memory/embedding-quantization.js +156 -0
- package/dist/src/memory/ewc-consolidation.d.ts +305 -0
- package/dist/src/memory/ewc-consolidation.js +611 -0
- package/dist/src/memory/graph-edge-writer.d.ts +95 -0
- package/dist/src/memory/graph-edge-writer.js +217 -0
- package/dist/src/memory/hybrid-retrieval.d.ts +77 -0
- package/dist/src/memory/hybrid-retrieval.js +192 -0
- package/dist/src/memory/intelligence.d.ts +405 -0
- package/dist/src/memory/intelligence.js +1316 -0
- package/dist/src/memory/lucene-bm25.d.ts +19 -0
- package/dist/src/memory/lucene-bm25.js +308 -0
- package/dist/src/memory/memory-bridge.d.ts +537 -0
- package/dist/src/memory/memory-bridge.js +2460 -0
- package/dist/src/memory/memory-initializer.d.ts +556 -0
- package/dist/src/memory/memory-initializer.js +3021 -0
- package/dist/src/memory/neural-package-bridge.d.ts +48 -0
- package/dist/src/memory/neural-package-bridge.js +87 -0
- package/dist/src/memory/rabitq-index.d.ts +60 -0
- package/dist/src/memory/rabitq-index.js +242 -0
- package/dist/src/memory/sona-optimizer.d.ts +267 -0
- package/dist/src/memory/sona-optimizer.js +779 -0
- package/dist/src/memory/structured-distill.d.ts +48 -0
- package/dist/src/memory/structured-distill.js +125 -0
- package/dist/src/output.d.ts +9 -0
- package/dist/src/output.js +9 -0
- package/dist/src/parser.d.ts +89 -0
- package/dist/src/parser.js +516 -0
- package/dist/src/plugins/manager.d.ts +133 -0
- package/dist/src/plugins/manager.js +415 -0
- package/dist/src/plugins/store/discovery.d.ts +99 -0
- package/dist/src/plugins/store/discovery.js +1224 -0
- package/dist/src/plugins/store/index.d.ts +76 -0
- package/dist/src/plugins/store/index.js +141 -0
- package/dist/src/plugins/store/search.d.ts +46 -0
- package/dist/src/plugins/store/search.js +230 -0
- package/dist/src/plugins/store/types.d.ts +279 -0
- package/dist/src/plugins/store/types.js +7 -0
- package/dist/src/plugins/tests/demo-plugin-store.d.ts +7 -0
- package/dist/src/plugins/tests/demo-plugin-store.js +126 -0
- package/dist/src/plugins/tests/standalone-test.d.ts +12 -0
- package/dist/src/plugins/tests/standalone-test.js +188 -0
- package/dist/src/plugins/tests/test-plugin-store.d.ts +7 -0
- package/dist/src/plugins/tests/test-plugin-store.js +206 -0
- package/dist/src/production/circuit-breaker.d.ts +101 -0
- package/dist/src/production/circuit-breaker.js +241 -0
- package/dist/src/production/error-handler.d.ts +92 -0
- package/dist/src/production/error-handler.js +299 -0
- package/dist/src/production/index.d.ts +23 -0
- package/dist/src/production/index.js +18 -0
- package/dist/src/production/monitoring.d.ts +161 -0
- package/dist/src/production/monitoring.js +356 -0
- package/dist/src/production/rate-limiter.d.ts +80 -0
- package/dist/src/production/rate-limiter.js +201 -0
- package/dist/src/production/retry.d.ts +48 -0
- package/dist/src/production/retry.js +179 -0
- package/dist/src/prompt.d.ts +44 -0
- package/dist/src/prompt.js +501 -0
- package/dist/src/proxy/install.d.ts +29 -0
- package/dist/src/proxy/install.js +135 -0
- package/dist/src/proxy/lifecycle.d.ts +61 -0
- package/dist/src/proxy/lifecycle.js +249 -0
- package/dist/src/proxy/paths.d.ts +34 -0
- package/dist/src/proxy/paths.js +70 -0
- package/dist/src/proxy/release.d.ts +47 -0
- package/dist/src/proxy/release.js +138 -0
- package/dist/src/proxy/token-bridge.d.ts +5 -0
- package/dist/src/proxy/token-bridge.js +61 -0
- package/dist/src/proxy/verify.d.ts +44 -0
- package/dist/src/proxy/verify.js +68 -0
- package/dist/src/runtime/headless.d.ts +60 -0
- package/dist/src/runtime/headless.js +284 -0
- package/dist/src/runtime/parent-death-watchdog.d.ts +42 -0
- package/dist/src/runtime/parent-death-watchdog.js +70 -0
- package/dist/src/ruvector/agent-wasm.d.ts +228 -0
- package/dist/src/ruvector/agent-wasm.js +463 -0
- package/dist/src/ruvector/ast-analyzer.d.ts +67 -0
- package/dist/src/ruvector/ast-analyzer.js +277 -0
- package/dist/src/ruvector/codemods/engine.d.ts +45 -0
- package/dist/src/ruvector/codemods/engine.js +291 -0
- package/dist/src/ruvector/codemods/scope-analysis.d.ts +29 -0
- package/dist/src/ruvector/codemods/scope-analysis.js +162 -0
- package/dist/src/ruvector/coverage-router.d.ts +160 -0
- package/dist/src/ruvector/coverage-router.js +531 -0
- package/dist/src/ruvector/coverage-tools.d.ts +33 -0
- package/dist/src/ruvector/coverage-tools.js +157 -0
- package/dist/src/ruvector/diff-classifier.d.ts +175 -0
- package/dist/src/ruvector/diff-classifier.js +699 -0
- package/dist/src/ruvector/diskann-backend.d.ts +78 -0
- package/dist/src/ruvector/diskann-backend.js +310 -0
- package/dist/src/ruvector/enhanced-model-router.d.ts +172 -0
- package/dist/src/ruvector/enhanced-model-router.js +577 -0
- package/dist/src/ruvector/graph-analyzer.d.ts +187 -0
- package/dist/src/ruvector/graph-analyzer.js +929 -0
- package/dist/src/ruvector/graph-backend.d.ts +79 -0
- package/dist/src/ruvector/graph-backend.js +220 -0
- package/dist/src/ruvector/index.d.ts +38 -0
- package/dist/src/ruvector/index.js +86 -0
- package/dist/src/ruvector/lora-adapter.d.ts +292 -0
- package/dist/src/ruvector/lora-adapter.js +710 -0
- package/dist/src/ruvector/model-prices.d.ts +50 -0
- package/dist/src/ruvector/model-prices.js +72 -0
- package/dist/src/ruvector/model-router.d.ts +408 -0
- package/dist/src/ruvector/model-router.js +1162 -0
- package/dist/src/ruvector/neural-router.d.ts +182 -0
- package/dist/src/ruvector/neural-router.js +825 -0
- package/dist/src/ruvector/output-verifier.d.ts +83 -0
- package/dist/src/ruvector/output-verifier.js +277 -0
- package/dist/src/ruvector/q-learning-router.d.ts +227 -0
- package/dist/src/ruvector/q-learning-router.js +721 -0
- package/dist/src/ruvector/router-calibrator.d.ts +65 -0
- package/dist/src/ruvector/router-calibrator.js +120 -0
- package/dist/src/ruvector/router-parallel-recorder.d.ts +127 -0
- package/dist/src/ruvector/router-parallel-recorder.js +183 -0
- package/dist/src/ruvector/router-trajectory.d.ts +194 -0
- package/dist/src/ruvector/router-trajectory.js +281 -0
- package/dist/src/ruvector/run-transcript-recorder.d.ts +154 -0
- package/dist/src/ruvector/run-transcript-recorder.js +209 -0
- package/dist/src/ruvector/ruvllm-wasm.d.ts +179 -0
- package/dist/src/ruvector/ruvllm-wasm.js +379 -0
- package/dist/src/ruvector/semantic-router.d.ts +77 -0
- package/dist/src/ruvector/semantic-router.js +178 -0
- package/dist/src/ruvector/task-embedder.d.ts +56 -0
- package/dist/src/ruvector/task-embedder.js +237 -0
- package/dist/src/ruvector/trajectory-tree.d.ts +113 -0
- package/dist/src/ruvector/trajectory-tree.js +237 -0
- package/dist/src/ruvector/vector-db.d.ts +73 -0
- package/dist/src/ruvector/vector-db.js +301 -0
- package/dist/src/ruvector/wasm-embedder.d.ts +13 -0
- package/dist/src/ruvector/wasm-embedder.js +143 -0
- package/dist/src/security/builtin-aidefence.d.ts +34 -0
- package/dist/src/security/builtin-aidefence.js +86 -0
- package/dist/src/services/agentic-flow-bridge.d.ts +50 -0
- package/dist/src/services/agentic-flow-bridge.js +95 -0
- package/dist/src/services/ai-job-dedup.d.ts +61 -0
- package/dist/src/services/ai-job-dedup.js +136 -0
- package/dist/src/services/checkpoint-gate.d.ts +140 -0
- package/dist/src/services/checkpoint-gate.js +223 -0
- package/dist/src/services/claim-service.d.ts +204 -0
- package/dist/src/services/claim-service.js +818 -0
- package/dist/src/services/config-file-manager.d.ts +37 -0
- package/dist/src/services/config-file-manager.js +224 -0
- package/dist/src/services/container-worker-pool.d.ts +204 -0
- package/dist/src/services/container-worker-pool.js +589 -0
- package/dist/src/services/daemon-autostart.d.ts +17 -0
- package/dist/src/services/daemon-autostart.js +102 -0
- package/dist/src/services/distill-oracle.d.ts +190 -0
- package/dist/src/services/distill-oracle.js +349 -0
- package/dist/src/services/distill-tuning.d.ts +111 -0
- package/dist/src/services/distill-tuning.js +510 -0
- package/dist/src/services/evolve-proof.d.ts +253 -0
- package/dist/src/services/evolve-proof.js +343 -0
- package/dist/src/services/fable-harness.d.ts +208 -0
- package/dist/src/services/fable-harness.js +388 -0
- package/dist/src/services/git-workspace-identity.d.ts +42 -0
- package/dist/src/services/git-workspace-identity.js +99 -0
- package/dist/src/services/global-ai-budget.d.ts +135 -0
- package/dist/src/services/global-ai-budget.js +415 -0
- package/dist/src/services/harness-benchmark.d.ts +68 -0
- package/dist/src/services/harness-benchmark.js +94 -0
- package/dist/src/services/harness-canary.d.ts +60 -0
- package/dist/src/services/harness-canary.js +69 -0
- package/dist/src/services/harness-corpus-harvester.d.ts +51 -0
- package/dist/src/services/harness-corpus-harvester.js +74 -0
- package/dist/src/services/harness-flywheel-generations.d.ts +115 -0
- package/dist/src/services/harness-flywheel-generations.js +360 -0
- package/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
- package/dist/src/services/harness-flywheel-runtime.js +92 -0
- package/dist/src/services/harness-flywheel.d.ts +48 -0
- package/dist/src/services/harness-flywheel.js +207 -0
- package/dist/src/services/harness-frozen-eval.d.ts +22 -0
- package/dist/src/services/harness-frozen-eval.js +66 -0
- package/dist/src/services/harness-hosts.d.ts +40 -0
- package/dist/src/services/harness-hosts.js +88 -0
- package/dist/src/services/harness-improvement-ledger.d.ts +63 -0
- package/dist/src/services/harness-improvement-ledger.js +101 -0
- package/dist/src/services/harness-loop.d.ts +53 -0
- package/dist/src/services/harness-loop.js +85 -0
- package/dist/src/services/harness-qualification.d.ts +66 -0
- package/dist/src/services/harness-qualification.js +143 -0
- package/dist/src/services/harness-replay.d.ts +37 -0
- package/dist/src/services/harness-replay.js +92 -0
- package/dist/src/services/harness-verify.d.ts +33 -0
- package/dist/src/services/harness-verify.js +26 -0
- package/dist/src/services/harness-worker.d.ts +23 -0
- package/dist/src/services/harness-worker.js +66 -0
- package/dist/src/services/headless-worker-executor.d.ts +358 -0
- package/dist/src/services/headless-worker-executor.js +1269 -0
- package/dist/src/services/index.d.ts +13 -0
- package/dist/src/services/index.js +11 -0
- package/dist/src/services/memory-backup.d.ts +53 -0
- package/dist/src/services/memory-backup.js +198 -0
- package/dist/src/services/memory-distillation.d.ts +41 -0
- package/dist/src/services/memory-distillation.js +277 -0
- package/dist/src/services/native-training.d.ts +68 -0
- package/dist/src/services/native-training.js +141 -0
- package/dist/src/services/registry-api.d.ts +58 -0
- package/dist/src/services/registry-api.js +146 -0
- package/dist/src/services/repo-supervisor.d.ts +70 -0
- package/dist/src/services/repo-supervisor.js +228 -0
- package/dist/src/services/ruvector-training.d.ts +222 -0
- package/dist/src/services/ruvector-training.js +693 -0
- package/dist/src/services/swarm-memory-branches.d.ts +135 -0
- package/dist/src/services/swarm-memory-branches.js +213 -0
- package/dist/src/services/weight-eft.d.ts +305 -0
- package/dist/src/services/weight-eft.js +296 -0
- package/dist/src/services/worker-daemon.d.ts +439 -0
- package/dist/src/services/worker-daemon.js +1865 -0
- package/dist/src/services/worker-queue.d.ts +194 -0
- package/dist/src/services/worker-queue.js +513 -0
- package/dist/src/services/workspace-lease.d.ts +55 -0
- package/dist/src/services/workspace-lease.js +191 -0
- package/dist/src/suggest.d.ts +53 -0
- package/dist/src/suggest.js +200 -0
- package/dist/src/transfer/anonymization/index.d.ts +25 -0
- package/dist/src/transfer/anonymization/index.js +175 -0
- package/dist/src/transfer/deploy-seraphine.d.ts +13 -0
- package/dist/src/transfer/deploy-seraphine.js +205 -0
- package/dist/src/transfer/export.d.ts +25 -0
- package/dist/src/transfer/export.js +113 -0
- package/dist/src/transfer/index.d.ts +12 -0
- package/dist/src/transfer/index.js +31 -0
- package/dist/src/transfer/ipfs/client.d.ts +109 -0
- package/dist/src/transfer/ipfs/client.js +307 -0
- package/dist/src/transfer/ipfs/upload.d.ts +95 -0
- package/dist/src/transfer/ipfs/upload.js +413 -0
- package/dist/src/transfer/models/seraphine.d.ts +72 -0
- package/dist/src/transfer/models/seraphine.js +373 -0
- package/dist/src/transfer/serialization/cfp.d.ts +49 -0
- package/dist/src/transfer/serialization/cfp.js +183 -0
- package/dist/src/transfer/storage/gcs.d.ts +82 -0
- package/dist/src/transfer/storage/gcs.js +272 -0
- package/dist/src/transfer/storage/index.d.ts +6 -0
- package/dist/src/transfer/storage/index.js +6 -0
- package/dist/src/transfer/store/discovery.d.ts +84 -0
- package/dist/src/transfer/store/discovery.js +382 -0
- package/dist/src/transfer/store/download.d.ts +70 -0
- package/dist/src/transfer/store/download.js +334 -0
- package/dist/src/transfer/store/index.d.ts +84 -0
- package/dist/src/transfer/store/index.js +153 -0
- package/dist/src/transfer/store/publish.d.ts +76 -0
- package/dist/src/transfer/store/publish.js +294 -0
- package/dist/src/transfer/store/registry.d.ts +58 -0
- package/dist/src/transfer/store/registry.js +285 -0
- package/dist/src/transfer/store/search.d.ts +54 -0
- package/dist/src/transfer/store/search.js +232 -0
- package/dist/src/transfer/store/tests/standalone-test.d.ts +12 -0
- package/dist/src/transfer/store/tests/standalone-test.js +190 -0
- package/dist/src/transfer/store/types.d.ts +193 -0
- package/dist/src/transfer/store/types.js +6 -0
- package/dist/src/transfer/test-seraphine.d.ts +6 -0
- package/dist/src/transfer/test-seraphine.js +105 -0
- package/dist/src/transfer/tests/test-store.d.ts +7 -0
- package/dist/src/transfer/tests/test-store.js +214 -0
- package/dist/src/transfer/types.d.ts +245 -0
- package/dist/src/transfer/types.js +6 -0
- package/dist/src/types.d.ts +13 -0
- package/dist/src/types.js +13 -0
- package/dist/src/update/checker.d.ts +34 -0
- package/dist/src/update/checker.js +191 -0
- package/dist/src/update/executor.d.ts +33 -0
- package/dist/src/update/executor.js +217 -0
- package/dist/src/update/index.d.ts +33 -0
- package/dist/src/update/index.js +64 -0
- package/dist/src/update/rate-limiter.d.ts +20 -0
- package/dist/src/update/rate-limiter.js +96 -0
- package/dist/src/update/validator.d.ts +17 -0
- package/dist/src/update/validator.js +123 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1902 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 CLI Doctor Command
|
|
3
|
+
* System diagnostics, dependency checks, config validation
|
|
4
|
+
*
|
|
5
|
+
* Created with ruv.io
|
|
6
|
+
*/
|
|
7
|
+
import { output } from '../output.js';
|
|
8
|
+
import { existsSync, readFileSync, statSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
import { execSync, exec } from 'child_process';
|
|
13
|
+
import { promisify } from 'util';
|
|
14
|
+
import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
|
|
15
|
+
import { isEncryptedBlob } from '../encryption/vault.js';
|
|
16
|
+
import { resolveMemoryPackageFromProject, readMemoryPackageVersion, recordMemoryPackagePath, } from '../init/memory-package-resolver.js';
|
|
17
|
+
// Promisified exec with proper shell and env inheritance for cross-platform support
|
|
18
|
+
const execAsync = promisify(exec);
|
|
19
|
+
/**
|
|
20
|
+
* Execute command asynchronously with proper environment inheritance
|
|
21
|
+
* Critical for Windows where PATH may not be inherited properly
|
|
22
|
+
*/
|
|
23
|
+
async function runCommand(command, timeoutMs = 5000) {
|
|
24
|
+
const { stdout } = await execAsync(command, {
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
timeout: timeoutMs,
|
|
27
|
+
shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/sh', // Use proper shell per platform
|
|
28
|
+
env: { ...process.env }, // Explicitly inherit full environment
|
|
29
|
+
windowsHide: true, // Hide window on Windows
|
|
30
|
+
});
|
|
31
|
+
return stdout.trim();
|
|
32
|
+
}
|
|
33
|
+
// Check Node.js version
|
|
34
|
+
async function checkNodeVersion() {
|
|
35
|
+
const requiredMajor = 20;
|
|
36
|
+
const version = process.version;
|
|
37
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
38
|
+
if (major >= requiredMajor) {
|
|
39
|
+
return { name: 'Node.js Version', status: 'pass', message: `${version} (>= ${requiredMajor} required)` };
|
|
40
|
+
}
|
|
41
|
+
else if (major >= 18) {
|
|
42
|
+
return { name: 'Node.js Version', status: 'warn', message: `${version} (>= ${requiredMajor} recommended)`, fix: 'nvm install 20 && nvm use 20' };
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
return { name: 'Node.js Version', status: 'fail', message: `${version} (>= ${requiredMajor} required)`, fix: 'nvm install 20 && nvm use 20' };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Check npm version (async with proper env inheritance)
|
|
49
|
+
async function checkNpmVersion() {
|
|
50
|
+
try {
|
|
51
|
+
const version = await runCommand('npm --version');
|
|
52
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
53
|
+
if (major >= 9) {
|
|
54
|
+
return { name: 'npm Version', status: 'pass', message: `v${version}` };
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return { name: 'npm Version', status: 'warn', message: `v${version} (>= 9 recommended)`, fix: 'npm install -g npm@latest' };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { name: 'npm Version', status: 'fail', message: 'npm not found', fix: 'Install Node.js from https://nodejs.org' };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Check config file
|
|
65
|
+
async function checkConfigFile() {
|
|
66
|
+
// JSON configs (parse-validated). The first three are LEGACY shapes from
|
|
67
|
+
// pre-v3 init flows; v3 init writes only `.claude-flow/config.yaml`.
|
|
68
|
+
const jsonPaths = [
|
|
69
|
+
'.claude-flow/config.json',
|
|
70
|
+
'claude-flow.config.json',
|
|
71
|
+
'.claude-flow.json'
|
|
72
|
+
];
|
|
73
|
+
// YAML configs (existence-checked only — no heavy yaml parser dependency).
|
|
74
|
+
const yamlPaths = [
|
|
75
|
+
'.claude-flow/config.yaml',
|
|
76
|
+
'.claude-flow/config.yml',
|
|
77
|
+
'claude-flow.config.yaml'
|
|
78
|
+
];
|
|
79
|
+
// #1798 — collect ALL configs that exist instead of returning at the first
|
|
80
|
+
// hit. The previous early-return masked silent collisions: if both a v2
|
|
81
|
+
// JSON and a v3 YAML existed, doctor reported only the JSON while the
|
|
82
|
+
// daemon was actually reading from the YAML. Surfacing both lets the user
|
|
83
|
+
// see and resolve the disagreement.
|
|
84
|
+
const foundJson = [];
|
|
85
|
+
const invalidJson = [];
|
|
86
|
+
for (const configPath of jsonPaths) {
|
|
87
|
+
if (!existsSync(configPath))
|
|
88
|
+
continue;
|
|
89
|
+
try {
|
|
90
|
+
JSON.parse(readFileSync(configPath, 'utf8'));
|
|
91
|
+
foundJson.push(configPath);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
invalidJson.push(configPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const foundYaml = yamlPaths.filter(p => existsSync(p));
|
|
98
|
+
// Hard failures first: malformed JSON wins.
|
|
99
|
+
if (invalidJson.length > 0) {
|
|
100
|
+
return { name: 'Config File', status: 'fail', message: `Invalid JSON: ${invalidJson.join(', ')}`, fix: 'Fix JSON syntax in config file' };
|
|
101
|
+
}
|
|
102
|
+
// #1798 — collision: legacy JSON + new YAML both present. Subsystems can
|
|
103
|
+
// disagree on which to read; surface this as a warn with the recommended
|
|
104
|
+
// resolution (keep the YAML, archive the JSON).
|
|
105
|
+
if (foundJson.length > 0 && foundYaml.length > 0) {
|
|
106
|
+
return {
|
|
107
|
+
name: 'Config File',
|
|
108
|
+
status: 'warn',
|
|
109
|
+
message: `Config collision: legacy ${foundJson.join(', ')} + ${foundYaml.join(', ')} — subsystems may disagree silently`,
|
|
110
|
+
fix: `Archive the legacy JSON (mv ${foundJson[0]} ${foundJson[0]}.bak) and keep ${foundYaml[0]} as the canonical config`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (foundYaml.length > 0) {
|
|
114
|
+
return { name: 'Config File', status: 'pass', message: `Found: ${foundYaml[0]}` };
|
|
115
|
+
}
|
|
116
|
+
if (foundJson.length > 0) {
|
|
117
|
+
return { name: 'Config File', status: 'pass', message: `Found: ${foundJson[0]}` };
|
|
118
|
+
}
|
|
119
|
+
return { name: 'Config File', status: 'warn', message: 'No config file (using defaults)', fix: 'claude-flow config init' };
|
|
120
|
+
}
|
|
121
|
+
// Check daemon status
|
|
122
|
+
/**
|
|
123
|
+
* #2448 — Detect the runaway `npx @claude-flow/cli@latest` statusLine / hook
|
|
124
|
+
* commands left over in `.claude/settings.json` from pre-#2337 installs.
|
|
125
|
+
*
|
|
126
|
+
* These fire on every Claude Code event (statusLine refires every few hundred
|
|
127
|
+
* ms, hooks fire per tool-use), each spawning a cold Node process + npm
|
|
128
|
+
* registry round-trip. On the reporter's 48 GB macOS box this produced
|
|
129
|
+
* load average 49, jetsam, and a kernel watchdog panic two minutes after
|
|
130
|
+
* boot. Severity is CRITICAL when present; users who installed before #2337
|
|
131
|
+
* and never re-ran `ruflo init` still have it.
|
|
132
|
+
*
|
|
133
|
+
* Detection only — does not modify settings. Fix path is `ruflo init` (the
|
|
134
|
+
* executor's migration logic, also patched in #2448, will now regenerate
|
|
135
|
+
* the broken commands).
|
|
136
|
+
*/
|
|
137
|
+
async function checkStaleSettingsNpx() {
|
|
138
|
+
// Same regex pattern the executor migration uses — kept in sync. Flag-list
|
|
139
|
+
// repetition bounded at 10 (CodeQL js/redos — unbounded `*` here is
|
|
140
|
+
// exponential-backtracking-prone on a crafted settings.json).
|
|
141
|
+
const BROKEN_RE = /npx\s+(?:--?\S+\s+){0,10}@?claude-flow\/cli@latest\s+hooks\s+(?:statusline|\S+)/;
|
|
142
|
+
// Look in both project-local and home-dir settings.
|
|
143
|
+
const candidates = [
|
|
144
|
+
join(process.cwd(), '.claude', 'settings.json'),
|
|
145
|
+
join(process.env.HOME ?? '', '.claude', 'settings.json'),
|
|
146
|
+
].filter((p, i, a) => p && a.indexOf(p) === i);
|
|
147
|
+
const offenders = [];
|
|
148
|
+
for (const settingsPath of candidates) {
|
|
149
|
+
if (!existsSync(settingsPath))
|
|
150
|
+
continue;
|
|
151
|
+
let settings;
|
|
152
|
+
try {
|
|
153
|
+
settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
continue; // checkConfigFile reports JSON errors separately
|
|
157
|
+
}
|
|
158
|
+
// statusLine.command
|
|
159
|
+
const sl = settings.statusLine;
|
|
160
|
+
if (sl?.command && BROKEN_RE.test(sl.command)) {
|
|
161
|
+
offenders.push({ path: settingsPath, where: 'statusLine' });
|
|
162
|
+
}
|
|
163
|
+
// hooks.<event>[].hooks[].command
|
|
164
|
+
const hooks = settings.hooks;
|
|
165
|
+
if (hooks) {
|
|
166
|
+
for (const [eventName, groups] of Object.entries(hooks)) {
|
|
167
|
+
if (!Array.isArray(groups))
|
|
168
|
+
continue;
|
|
169
|
+
for (const group of groups) {
|
|
170
|
+
if (!Array.isArray(group.hooks))
|
|
171
|
+
continue;
|
|
172
|
+
for (const h of group.hooks) {
|
|
173
|
+
if (typeof h?.command === 'string' && BROKEN_RE.test(h.command)) {
|
|
174
|
+
offenders.push({ path: settingsPath, where: `hooks.${eventName}` });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (offenders.length === 0) {
|
|
182
|
+
return { name: 'Stale npx@latest in settings (#2448)', status: 'pass', message: 'no runaway commands detected' };
|
|
183
|
+
}
|
|
184
|
+
// Group by file for readable output
|
|
185
|
+
const byFile = offenders.reduce((acc, o) => {
|
|
186
|
+
(acc[o.path] ??= []).push(o.where);
|
|
187
|
+
return acc;
|
|
188
|
+
}, {});
|
|
189
|
+
const summary = Object.entries(byFile)
|
|
190
|
+
.map(([p, wheres]) => `${p} [${[...new Set(wheres)].join(', ')}]`)
|
|
191
|
+
.join('; ');
|
|
192
|
+
return {
|
|
193
|
+
name: 'Stale npx@latest in settings (#2448)',
|
|
194
|
+
status: 'fail',
|
|
195
|
+
message: `CRITICAL — runaway \`npx @claude-flow/cli@latest\` commands detected: ${summary}`,
|
|
196
|
+
fix: 'Re-run `npx ruflo init` to migrate (the v3.13.3+ init migrator regenerates these to local-helper form). On macOS this prevents the process-storm / kernel-panic class reported in #2448.',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function checkDaemonStatus() {
|
|
200
|
+
try {
|
|
201
|
+
const pidFile = '.claude-flow/daemon.pid';
|
|
202
|
+
if (existsSync(pidFile)) {
|
|
203
|
+
const pid = readFileSync(pidFile, 'utf8').trim();
|
|
204
|
+
try {
|
|
205
|
+
process.kill(parseInt(pid, 10), 0); // Check if process exists
|
|
206
|
+
return { name: 'Daemon Status', status: 'pass', message: `Running (PID: ${pid})` };
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Stale PID file', fix: 'rm .claude-flow/daemon.pid && claude-flow daemon start' };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Not running', fix: 'claude-flow daemon start' };
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Unable to check', fix: 'claude-flow daemon status' };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Check memory database
|
|
219
|
+
async function checkMemoryDatabase() {
|
|
220
|
+
// Authoritative path comes from `getMemoryRoot()` (honors
|
|
221
|
+
// `CLAUDE_FLOW_MEMORY_PATH`, claude-flow.config.json's `memory.persistPath`,
|
|
222
|
+
// then defaults to `.swarm/`). #1946: the previous hard-coded list missed
|
|
223
|
+
// `data/memory/memory.db` (a common config) and ignored the env var
|
|
224
|
+
// entirely, so doctor reported "Not initialized" on perfectly-init'd DBs.
|
|
225
|
+
// Try the configured path first, then fall back to the historic candidates.
|
|
226
|
+
const candidates = [];
|
|
227
|
+
try {
|
|
228
|
+
const { getMemoryRoot } = await import('../memory/memory-initializer.js');
|
|
229
|
+
candidates.push(join(getMemoryRoot(), 'memory.db'));
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
/* memory-initializer not available — fall through to legacy candidates */
|
|
233
|
+
}
|
|
234
|
+
candidates.push('.swarm/memory.db', '.claude-flow/memory.db', 'data/memory/memory.db', // matches `CLAUDE_FLOW_MEMORY_PATH=data/memory`
|
|
235
|
+
'data/memory.db');
|
|
236
|
+
for (const dbPath of candidates) {
|
|
237
|
+
if (existsSync(dbPath)) {
|
|
238
|
+
try {
|
|
239
|
+
const stats = statSync(dbPath);
|
|
240
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
241
|
+
return { name: 'Memory Database', status: 'pass', message: `${dbPath} (${sizeMB} MB)` };
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return { name: 'Memory Database', status: 'warn', message: `${dbPath} (unable to stat)` };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { name: 'Memory Database', status: 'warn', message: 'Not initialized', fix: 'claude-flow memory configure --backend hybrid' };
|
|
249
|
+
}
|
|
250
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
251
|
+
// #2677 — memory doctor: functional checks (stuinfla)
|
|
252
|
+
//
|
|
253
|
+
// The existing `checkMemoryDatabase` above asserts existence + statability
|
|
254
|
+
// only, so it CANNOT distinguish a healthy DB from a 99.97%-empty or
|
|
255
|
+
// SQLite-malformed one. Stuinfla reported both cases live (81-store fleet).
|
|
256
|
+
// The three checks below layer functional assertions on top, ordered so
|
|
257
|
+
// the earliest chain-break is always the first red the user sees:
|
|
258
|
+
// 1. Integrity — can sql.js open it AND does PRAGMA integrity_check pass?
|
|
259
|
+
// 2. Content — do most memory_entries rows carry non-empty content?
|
|
260
|
+
// 3. Embedding coverage — do most rows have a vector? (unembedded rows are
|
|
261
|
+
// both unrecallable AND undistillable per ADR-174)
|
|
262
|
+
// Ordering matters: content ratio is meaningless on a DB that can't open;
|
|
263
|
+
// embedding coverage is meaningless on rows with no content. First red wins.
|
|
264
|
+
//
|
|
265
|
+
// Recall probe (stuinfla check 4) requires actual write+search+delete round
|
|
266
|
+
// trips through the CLI's own memory pipeline — deferred to a follow-up PR
|
|
267
|
+
// to keep this one purely additive and safe.
|
|
268
|
+
//
|
|
269
|
+
// Design rules (also from stuinfla's report):
|
|
270
|
+
// - "A check that cannot fail protects nothing" — every check has a
|
|
271
|
+
// demonstrable red state.
|
|
272
|
+
// - "UNKNOWN is never PASS" — if the check can't RUN, report warn/fail,
|
|
273
|
+
// never a reassuring pass. Encrypted-DB case gets warn with the caveat
|
|
274
|
+
// spelled out; corruption gets fail.
|
|
275
|
+
// - "Print the measurement, not a checkmark" — messages include the
|
|
276
|
+
// ratios so operators see the shape of the problem.
|
|
277
|
+
/** Resolve the same DB path checkMemoryDatabase above uses. Returns null
|
|
278
|
+
* if no candidate exists (in which case none of these checks should
|
|
279
|
+
* fire — checkMemoryDatabase will already have surfaced the missing DB). */
|
|
280
|
+
async function resolveMemoryDbPath() {
|
|
281
|
+
const candidates = [];
|
|
282
|
+
try {
|
|
283
|
+
const { getMemoryRoot } = await import('../memory/memory-initializer.js');
|
|
284
|
+
candidates.push(join(getMemoryRoot(), 'memory.db'));
|
|
285
|
+
}
|
|
286
|
+
catch { /* fall through to legacy candidates */ }
|
|
287
|
+
candidates.push('.swarm/memory.db', '.claude-flow/memory.db', 'data/memory/memory.db', 'data/memory.db');
|
|
288
|
+
for (const p of candidates)
|
|
289
|
+
if (existsSync(p))
|
|
290
|
+
return p;
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
/** Open a sql.js Database over an on-disk file, returning null when the
|
|
294
|
+
* file can't be opened as a SQLite database (encrypted / corrupted / not
|
|
295
|
+
* a database). Callers decide whether that's warn or fail. */
|
|
296
|
+
async function tryOpenSqlJs(dbPath) {
|
|
297
|
+
try {
|
|
298
|
+
const initSqlJs = (await import('sql.js')).default ?? (await import('sql.js'));
|
|
299
|
+
const SQL = await (typeof initSqlJs === 'function' ? initSqlJs() : initSqlJs.default());
|
|
300
|
+
const { readFileSync } = await import('fs');
|
|
301
|
+
const buf = readFileSync(dbPath);
|
|
302
|
+
return new SQL.Database(new Uint8Array(buf));
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Check 1 — sql.js can open it AND PRAGMA integrity_check returns 'ok'.
|
|
309
|
+
// Two fail modes handled distinctly per "UNKNOWN is never PASS":
|
|
310
|
+
// - Open fails: warn ("cannot open; encrypted DB or corrupt — doctor
|
|
311
|
+
// can't distinguish from this side")
|
|
312
|
+
// - Open succeeds but pragma != 'ok': fail (definite corruption)
|
|
313
|
+
async function checkMemoryIntegrity() {
|
|
314
|
+
const dbPath = await resolveMemoryDbPath();
|
|
315
|
+
if (!dbPath)
|
|
316
|
+
return { name: 'Memory Integrity', status: 'warn', message: 'no memory.db found (see Memory Database check above)' };
|
|
317
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
318
|
+
if (!db) {
|
|
319
|
+
return {
|
|
320
|
+
name: 'Memory Integrity',
|
|
321
|
+
status: 'warn',
|
|
322
|
+
message: `${dbPath} — sql.js can't open (encrypted DB or corrupt; doctor can't tell which from outside)`,
|
|
323
|
+
fix: 'if encrypted: expected. if not: back up + `claude-flow memory init --force` to rebuild',
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
const res = db.exec('PRAGMA integrity_check');
|
|
328
|
+
const rows = res[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
329
|
+
if (rows.length === 1 && rows[0] === 'ok') {
|
|
330
|
+
return { name: 'Memory Integrity', status: 'pass', message: `${dbPath} — PRAGMA integrity_check: ok` };
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
name: 'Memory Integrity',
|
|
334
|
+
status: 'fail',
|
|
335
|
+
message: `${dbPath} — PRAGMA integrity_check: ${rows.slice(0, 3).join('; ')}${rows.length > 3 ? ` (+${rows.length - 3} more)` : ''}`,
|
|
336
|
+
fix: 'back up .swarm/memory.db then `claude-flow memory init --force`',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (e) {
|
|
340
|
+
const msg = e.message || String(e);
|
|
341
|
+
const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
|
|
342
|
+
return {
|
|
343
|
+
name: 'Memory Integrity',
|
|
344
|
+
status: 'warn',
|
|
345
|
+
message: encryptedOrCorrupt
|
|
346
|
+
? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
|
|
347
|
+
: `${dbPath} — probe threw: ${msg}`,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
try {
|
|
352
|
+
db.close();
|
|
353
|
+
}
|
|
354
|
+
catch { /* best-effort */ }
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Check 2 — memory_entries rows should mostly carry non-empty content.
|
|
358
|
+
// Stuinfla's live case: 11,133 rows, 3 with content (0.03%). Threshold 95%
|
|
359
|
+
// is the number he proposed. Values below → fail with the exact ratio in
|
|
360
|
+
// the message ("Print the measurement, not a checkmark").
|
|
361
|
+
async function checkMemoryContent() {
|
|
362
|
+
const dbPath = await resolveMemoryDbPath();
|
|
363
|
+
if (!dbPath)
|
|
364
|
+
return { name: 'Memory Content', status: 'warn', message: 'no memory.db found' };
|
|
365
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
366
|
+
if (!db)
|
|
367
|
+
return { name: 'Memory Content', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
|
|
368
|
+
try {
|
|
369
|
+
const tables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='memory_entries'")[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
370
|
+
if (tables.length === 0)
|
|
371
|
+
return { name: 'Memory Content', status: 'warn', message: 'no memory_entries table in DB — schema mismatch or empty init' };
|
|
372
|
+
const r = db.exec("SELECT count(*), sum(CASE WHEN length(trim(coalesce(content,'')))>0 THEN 1 ELSE 0 END) FROM memory_entries");
|
|
373
|
+
const total = Number(r[0]?.values?.[0]?.[0] ?? 0);
|
|
374
|
+
const populated = Number(r[0]?.values?.[0]?.[1] ?? 0);
|
|
375
|
+
if (total === 0)
|
|
376
|
+
return { name: 'Memory Content', status: 'pass', message: `${dbPath} — 0 rows (fresh DB, expected)` };
|
|
377
|
+
const ratio = populated / total;
|
|
378
|
+
const pct = (ratio * 100).toFixed(2);
|
|
379
|
+
const detail = `content ${populated}/${total} (${pct}%)`;
|
|
380
|
+
if (ratio < 0.95) {
|
|
381
|
+
return {
|
|
382
|
+
name: 'Memory Content',
|
|
383
|
+
status: 'fail',
|
|
384
|
+
message: `${dbPath} — ${detail} below 95% floor`,
|
|
385
|
+
fix: 'schema drift likely (rename of value→content or similar). check migration state via `claude-flow migrate status`',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return { name: 'Memory Content', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
const msg = e.message || String(e);
|
|
392
|
+
const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
|
|
393
|
+
return {
|
|
394
|
+
name: 'Memory Content',
|
|
395
|
+
status: 'warn',
|
|
396
|
+
message: encryptedOrCorrupt
|
|
397
|
+
? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
|
|
398
|
+
: `${dbPath} — probe threw: ${msg}`,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
finally {
|
|
402
|
+
try {
|
|
403
|
+
db.close();
|
|
404
|
+
}
|
|
405
|
+
catch { /* best-effort */ }
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Check 3 — most memory_entries with content should also carry an embedding
|
|
409
|
+
// vector. Rows without a vector are BOTH unrecallable (no similarity search
|
|
410
|
+
// can find them) AND undistillable (ADR-174's distill skips rows with no
|
|
411
|
+
// parseable vector). Same 95% threshold + fail-with-ratio pattern as check 2.
|
|
412
|
+
//
|
|
413
|
+
// Embedding storage varies across ruflo installs (agentdb migrations, HNSW
|
|
414
|
+
// index vs inline vector column). Discovers the shape via schema probes,
|
|
415
|
+
// falls back to warn if the schema is unrecognized (better than a false
|
|
416
|
+
// pass, per "UNKNOWN is never PASS").
|
|
417
|
+
async function checkMemoryEmbeddingCoverage() {
|
|
418
|
+
const dbPath = await resolveMemoryDbPath();
|
|
419
|
+
if (!dbPath)
|
|
420
|
+
return { name: 'Memory Embedding Coverage', status: 'warn', message: 'no memory.db found' };
|
|
421
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
422
|
+
if (!db)
|
|
423
|
+
return { name: 'Memory Embedding Coverage', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
|
|
424
|
+
try {
|
|
425
|
+
// Schema-shape discovery. Three candidate columns / tables we've seen
|
|
426
|
+
// across the agentdb / ruvector history — first match wins.
|
|
427
|
+
const cols = db.exec("PRAGMA table_info(memory_entries)");
|
|
428
|
+
const colNames = new Set((cols[0]?.values ?? []).map((v) => String(v[1])));
|
|
429
|
+
let vectorPredicate = null;
|
|
430
|
+
if (colNames.has('embedding'))
|
|
431
|
+
vectorPredicate = "embedding IS NOT NULL AND length(embedding) > 0";
|
|
432
|
+
else if (colNames.has('vector'))
|
|
433
|
+
vectorPredicate = "vector IS NOT NULL AND length(vector) > 0";
|
|
434
|
+
if (!vectorPredicate) {
|
|
435
|
+
const otherTables = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('vector_indexes','embeddings','memory_embeddings')")[0]?.values?.map((v) => String(v[0])) ?? [];
|
|
436
|
+
if (otherTables.length === 0) {
|
|
437
|
+
return {
|
|
438
|
+
name: 'Memory Embedding Coverage',
|
|
439
|
+
status: 'warn',
|
|
440
|
+
message: 'no embedding column/table recognized (agentdb schema mismatch or older format) — doctor cannot measure',
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
const tbl = otherTables[0];
|
|
444
|
+
const r = db.exec(`SELECT count(*) FROM memory_entries m WHERE EXISTS (SELECT 1 FROM ${tbl} e WHERE e.memory_id = m.id OR e.entry_id = m.id OR e.id = m.id)`);
|
|
445
|
+
const withEmbedding = Number(r[0]?.values?.[0]?.[0] ?? 0);
|
|
446
|
+
const total = Number(db.exec("SELECT count(*) FROM memory_entries WHERE length(trim(coalesce(content,'')))>0")[0]?.values?.[0]?.[0] ?? 0);
|
|
447
|
+
if (total === 0)
|
|
448
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — 0 content rows (nothing to embed)` };
|
|
449
|
+
const ratio = withEmbedding / total;
|
|
450
|
+
const pct = (ratio * 100).toFixed(2);
|
|
451
|
+
const detail = `embedded ${withEmbedding}/${total} (${pct}%) via ${tbl}`;
|
|
452
|
+
if (ratio < 0.95)
|
|
453
|
+
return { name: 'Memory Embedding Coverage', status: 'fail', message: `${dbPath} — ${detail} below 95% floor`, fix: 'unembedded rows are unrecallable + undistillable — re-run `claude-flow memory embed --namespace <name>` for populated namespaces' };
|
|
454
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
455
|
+
}
|
|
456
|
+
// Inline embedding column
|
|
457
|
+
const r = db.exec(`SELECT count(*), sum(CASE WHEN ${vectorPredicate} THEN 1 ELSE 0 END) FROM memory_entries WHERE length(trim(coalesce(content,'')))>0`);
|
|
458
|
+
const total = Number(r[0]?.values?.[0]?.[0] ?? 0);
|
|
459
|
+
const withEmb = Number(r[0]?.values?.[0]?.[1] ?? 0);
|
|
460
|
+
if (total === 0)
|
|
461
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — 0 content rows (nothing to embed)` };
|
|
462
|
+
const ratio = withEmb / total;
|
|
463
|
+
const pct = (ratio * 100).toFixed(2);
|
|
464
|
+
const detail = `embedded ${withEmb}/${total} (${pct}%)`;
|
|
465
|
+
if (ratio < 0.95) {
|
|
466
|
+
return { name: 'Memory Embedding Coverage', status: 'fail', message: `${dbPath} — ${detail} below 95% floor`, fix: 'unembedded rows are unrecallable + undistillable — re-run `claude-flow memory embed --namespace <name>` for populated namespaces' };
|
|
467
|
+
}
|
|
468
|
+
return { name: 'Memory Embedding Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
469
|
+
}
|
|
470
|
+
catch (e) {
|
|
471
|
+
const msg = e.message || String(e);
|
|
472
|
+
const encryptedOrCorrupt = msg.includes('file is not a database') || msg.includes('malformed');
|
|
473
|
+
return {
|
|
474
|
+
name: 'Memory Embedding Coverage',
|
|
475
|
+
status: 'warn',
|
|
476
|
+
message: encryptedOrCorrupt
|
|
477
|
+
? `${dbPath} — DB refused query: ${msg} (encrypted DB or corruption; see Memory Integrity above)`
|
|
478
|
+
: `${dbPath} — probe threw: ${msg}`,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
try {
|
|
483
|
+
db.close();
|
|
484
|
+
}
|
|
485
|
+
catch { /* best-effort */ }
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// #2545: Check that the self-learning bridge can actually load @claude-flow/memory
|
|
489
|
+
// the SAME way the SessionStart auto-memory hook does. On the documented `npx ruflo`
|
|
490
|
+
// path the package lands in the npx cache — unreachable from the project — so the
|
|
491
|
+
// hook silently no-op'd with no signal anywhere. This surfaces it.
|
|
492
|
+
async function checkLearningBridge() {
|
|
493
|
+
const cwd = process.cwd();
|
|
494
|
+
const hookPath = join(cwd, '.claude', 'helpers', 'auto-memory-hook.mjs');
|
|
495
|
+
// Only relevant once init has deployed the hook; otherwise stay quiet.
|
|
496
|
+
if (!existsSync(hookPath)) {
|
|
497
|
+
return {
|
|
498
|
+
name: 'Learning Bridge',
|
|
499
|
+
status: 'pass',
|
|
500
|
+
message: 'auto-memory hook not installed (run: npx ruflo@latest init)',
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
const distPath = resolveMemoryPackageFromProject(cwd);
|
|
504
|
+
if (distPath) {
|
|
505
|
+
const version = readMemoryPackageVersion(distPath);
|
|
506
|
+
return {
|
|
507
|
+
name: 'Learning Bridge',
|
|
508
|
+
status: 'pass',
|
|
509
|
+
message: `@claude-flow/memory resolvable${version ? ` (v${version})` : ''}`,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
// #2599: Self-heal on plain `doctor` (not just --fix). The project-side resolver
|
|
513
|
+
// fails when the sidecar is stale or missing (e.g. npx cache generation rotated),
|
|
514
|
+
// but the CLI process itself can resolve @claude-flow/memory from its own module
|
|
515
|
+
// context. Write the sidecar automatically instead of hard-failing the check.
|
|
516
|
+
const record = recordMemoryPackagePath(cwd, 'doctor-auto');
|
|
517
|
+
if (record) {
|
|
518
|
+
const version = readMemoryPackageVersion(record.distPath);
|
|
519
|
+
return {
|
|
520
|
+
name: 'Learning Bridge',
|
|
521
|
+
status: 'pass',
|
|
522
|
+
message: `@claude-flow/memory resolvable via auto-recorded sidecar${version ? ` (v${version})` : ''}`,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
name: 'Learning Bridge',
|
|
527
|
+
status: 'fail',
|
|
528
|
+
message: '@claude-flow/memory NOT resolvable — SessionStart self-learning imports are a silent no-op',
|
|
529
|
+
fix: 'npm i -D @claude-flow/memory (optional dep appears absent — likely --omit=optional install)',
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
// Check API keys
|
|
533
|
+
async function checkApiKeys() {
|
|
534
|
+
const keys = ['ANTHROPIC_API_KEY', 'CLAUDE_API_KEY', 'OPENAI_API_KEY'];
|
|
535
|
+
const found = [];
|
|
536
|
+
for (const key of keys) {
|
|
537
|
+
if (process.env[key]) {
|
|
538
|
+
found.push(key);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Detect Claude Code environment — API keys are managed internally
|
|
542
|
+
const inClaudeCode = !!(process.env.CLAUDE_CODE || process.env.CLAUDE_PROJECT_DIR || process.env.MCP_SESSION_ID);
|
|
543
|
+
if (found.includes('ANTHROPIC_API_KEY') || found.includes('CLAUDE_API_KEY')) {
|
|
544
|
+
return { name: 'API Keys', status: 'pass', message: `Found: ${found.join(', ')}` };
|
|
545
|
+
}
|
|
546
|
+
else if (inClaudeCode) {
|
|
547
|
+
return { name: 'API Keys', status: 'pass', message: 'Claude Code (managed internally)' };
|
|
548
|
+
}
|
|
549
|
+
else if (found.length > 0) {
|
|
550
|
+
return { name: 'API Keys', status: 'warn', message: `Found: ${found.join(', ')} (no Claude key)`, fix: 'export ANTHROPIC_API_KEY=your_key' };
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
return { name: 'API Keys', status: 'warn', message: 'No API keys found', fix: 'export ANTHROPIC_API_KEY=your_key' };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
// Check git (async with proper env inheritance)
|
|
557
|
+
async function checkGit() {
|
|
558
|
+
try {
|
|
559
|
+
const version = await runCommand('git --version');
|
|
560
|
+
return { name: 'Git', status: 'pass', message: version.replace('git version ', 'v') };
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return { name: 'Git', status: 'warn', message: 'Not installed', fix: 'Install git from https://git-scm.com' };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// Check if in git repo (async with proper env inheritance)
|
|
567
|
+
//
|
|
568
|
+
// #1791.7 — `git rev-parse` was reported as failing on hosts where `.git`
|
|
569
|
+
// clearly exists in cwd (linux-arm64 daemon contexts). Treat the git binary
|
|
570
|
+
// as authoritative when it succeeds, but fall back to a `.git` walk-up so a
|
|
571
|
+
// present repository is recognized even when the git invocation fails for
|
|
572
|
+
// environment reasons (PATH, broken global config, EBADCWD, etc.).
|
|
573
|
+
async function checkGitRepo() {
|
|
574
|
+
try {
|
|
575
|
+
await runCommand('git rev-parse --is-inside-work-tree');
|
|
576
|
+
return { name: 'Git Repository', status: 'pass', message: 'In a git repository' };
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// Walk parents of cwd for a .git directory before reporting "not a repo"
|
|
580
|
+
let dir = process.cwd();
|
|
581
|
+
while (true) {
|
|
582
|
+
if (existsSync(join(dir, '.git'))) {
|
|
583
|
+
return {
|
|
584
|
+
name: 'Git Repository',
|
|
585
|
+
status: 'warn',
|
|
586
|
+
message: `Repo detected on disk (${join(dir, '.git')}) but \`git rev-parse\` failed — check git installation and PATH`,
|
|
587
|
+
fix: 'Verify git is on PATH (try `git --version`) and that the working tree is not corrupted',
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
const parent = dirname(dir);
|
|
591
|
+
if (parent === dir)
|
|
592
|
+
break;
|
|
593
|
+
dir = parent;
|
|
594
|
+
}
|
|
595
|
+
return { name: 'Git Repository', status: 'warn', message: 'Not a git repository', fix: 'git init' };
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
// Check AIDefence package availability (#1807)
|
|
599
|
+
//
|
|
600
|
+
// `aidefence_*` MCP tools (scan, analyze, has_pii, stats, learn) require
|
|
601
|
+
// `@claude-flow/aidefence` to be installed and loadable. The package is an
|
|
602
|
+
// optional dependency — present in some installs (project-local) but
|
|
603
|
+
// missing in others (npm-global of `claude-flow`). Without it, every
|
|
604
|
+
// aidefence MCP call fails at runtime with "Cannot find module".
|
|
605
|
+
//
|
|
606
|
+
// Surface that state in `doctor` so operators know BEFORE they rely on
|
|
607
|
+
// AI-defence scanning. The probe is the same dynamic `import()` the MCP
|
|
608
|
+
// tool's handler uses, so a `pass` here means the actual tools will work.
|
|
609
|
+
async function checkAIDefence() {
|
|
610
|
+
try {
|
|
611
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
612
|
+
await import('@claude-flow/aidefence');
|
|
613
|
+
return {
|
|
614
|
+
name: 'AIDefence',
|
|
615
|
+
status: 'pass',
|
|
616
|
+
message: '@claude-flow/aidefence loadable — aidefence_* MCP tools functional',
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
return {
|
|
621
|
+
name: 'AIDefence',
|
|
622
|
+
status: 'warn',
|
|
623
|
+
message: '@claude-flow/aidefence not loadable — aidefence_* MCP tools will fail (optional package)',
|
|
624
|
+
fix: 'npm install --save @claude-flow/aidefence (in your project), or run `claude-flow mcp start` from a directory that has it installed',
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* ADR-097 Phase 4: federation peer-state surface for doctor.
|
|
630
|
+
*
|
|
631
|
+
* Probes the federation plugin loadability + asserts the breaker entity
|
|
632
|
+
* layer is present in the installed version. Without the plugin
|
|
633
|
+
* installed this is a "not configured" pass — federation is opt-in.
|
|
634
|
+
*
|
|
635
|
+
* Live coordinator state (per-peer counts) requires a running MCP server
|
|
636
|
+
* with `federation_init` called; operators inspect that via the
|
|
637
|
+
* `federation_breaker_status` MCP tool, not the doctor (which is a
|
|
638
|
+
* one-shot CLI process with no coordinator session).
|
|
639
|
+
*/
|
|
640
|
+
async function checkFederationBreaker() {
|
|
641
|
+
try {
|
|
642
|
+
// Optional plugin — not a hard dep of @claude-flow/cli. Build the
|
|
643
|
+
// module specifier dynamically so TypeScript cannot statically
|
|
644
|
+
// resolve it (which would emit TS2307); at runtime the import
|
|
645
|
+
// either resolves (plugin installed) or throws (handled below).
|
|
646
|
+
const specifier = ['@claude-flow', 'plugin-agent-federation'].join('/');
|
|
647
|
+
const mod = await import(specifier);
|
|
648
|
+
if (!mod.FederationNodeState) {
|
|
649
|
+
return {
|
|
650
|
+
name: 'Federation Breaker',
|
|
651
|
+
status: 'warn',
|
|
652
|
+
message: '@claude-flow/plugin-agent-federation loaded but FederationNodeState export missing — version older than ADR-097 Phase 2',
|
|
653
|
+
fix: 'Upgrade: npm install @claude-flow/plugin-agent-federation@alpha',
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
return {
|
|
657
|
+
name: 'Federation Breaker',
|
|
658
|
+
status: 'pass',
|
|
659
|
+
message: 'ADR-097 breaker loadable — federation_breaker_status / federation_evict / federation_reactivate MCP tools available',
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
return {
|
|
664
|
+
name: 'Federation Breaker',
|
|
665
|
+
status: 'pass',
|
|
666
|
+
message: 'Federation plugin not installed (optional) — install only if you need cross-installation peering',
|
|
667
|
+
fix: 'npm install --save @claude-flow/plugin-agent-federation@alpha',
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
// Check MCP servers
|
|
672
|
+
async function checkMcpServers() {
|
|
673
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
674
|
+
// #1842: ~/.claude.json holds project-scoped registrations under
|
|
675
|
+
// parsed.projects[<projectPath>].mcpServers.ruflo, in addition to any
|
|
676
|
+
// top-level mcpServers. Check both shapes plus the legacy desktop and
|
|
677
|
+
// local .mcp.json paths.
|
|
678
|
+
const mcpConfigPaths = [
|
|
679
|
+
join(home, '.claude.json'),
|
|
680
|
+
join(home, '.claude/claude_desktop_config.json'),
|
|
681
|
+
join(home, '.config/claude/mcp.json'),
|
|
682
|
+
'.mcp.json',
|
|
683
|
+
];
|
|
684
|
+
const isRufloKey = (k) => k === 'ruflo' || k === 'ruflo_alpha' || k === 'claude-flow' || k === 'claude-flow_alpha';
|
|
685
|
+
// Canonical MCP-server key is `claude-flow` — matches the `mcp__claude-flow__*`
|
|
686
|
+
// prefix that ~166 plugin tool references depend on (#2206). A `ruflo`-keyed
|
|
687
|
+
// entry pointing at the same binary is the legacy duplicate created by
|
|
688
|
+
// pre-rename setup docs that #2612 tracks; doctor treats it as removable.
|
|
689
|
+
const isCurrentRufloKey = (k) => k === 'claude-flow' || k === 'claude-flow_alpha';
|
|
690
|
+
const isLegacyRufloKey = (k) => k === 'ruflo' || k === 'ruflo_alpha';
|
|
691
|
+
const isRufloServer = (server) => {
|
|
692
|
+
if (!server || typeof server !== 'object')
|
|
693
|
+
return false;
|
|
694
|
+
const entry = server;
|
|
695
|
+
const command = typeof entry.command === 'string' ? entry.command : '';
|
|
696
|
+
const args = Array.isArray(entry.args)
|
|
697
|
+
? entry.args.filter((arg) => typeof arg === 'string')
|
|
698
|
+
: [];
|
|
699
|
+
const haystack = [command, ...args].join(' ');
|
|
700
|
+
return /\b(?:ruflo|claude-flow|@claude-flow\/cli)(?:@[\w.-]+)?\b/.test(haystack);
|
|
701
|
+
};
|
|
702
|
+
let totalServersSeen = 0;
|
|
703
|
+
const rufloLocations = [];
|
|
704
|
+
const duplicateLocations = [];
|
|
705
|
+
const legacyLocations = [];
|
|
706
|
+
const currentLocations = [];
|
|
707
|
+
const inspectServers = (servers, location) => {
|
|
708
|
+
if (!servers || typeof servers !== 'object')
|
|
709
|
+
return { total: 0, hasRuflo: false };
|
|
710
|
+
const entries = Object.entries(servers);
|
|
711
|
+
const activeRufloEntries = entries.filter(([key, server]) => isRufloKey(key) && isRufloServer(server));
|
|
712
|
+
const hasLegacy = activeRufloEntries.some(([key]) => isLegacyRufloKey(key));
|
|
713
|
+
const hasCurrent = activeRufloEntries.some(([key]) => isCurrentRufloKey(key));
|
|
714
|
+
if (activeRufloEntries.length > 0) {
|
|
715
|
+
rufloLocations.push(`${location}: ${activeRufloEntries.map(([key]) => key).join(', ')}`);
|
|
716
|
+
}
|
|
717
|
+
if (hasLegacy && hasCurrent) {
|
|
718
|
+
duplicateLocations.push(`${location}: ${activeRufloEntries.map(([key]) => key).join(' + ')}`);
|
|
719
|
+
}
|
|
720
|
+
for (const [key] of activeRufloEntries) {
|
|
721
|
+
if (isLegacyRufloKey(key))
|
|
722
|
+
legacyLocations.push(`${location}: ${key}`);
|
|
723
|
+
if (isCurrentRufloKey(key))
|
|
724
|
+
currentLocations.push(`${location}: ${key}`);
|
|
725
|
+
}
|
|
726
|
+
return { total: entries.length, hasRuflo: activeRufloEntries.length > 0 };
|
|
727
|
+
};
|
|
728
|
+
for (const configPath of mcpConfigPaths) {
|
|
729
|
+
if (!existsSync(configPath))
|
|
730
|
+
continue;
|
|
731
|
+
try {
|
|
732
|
+
const content = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
733
|
+
// Top-level mcpServers (legacy / desktop form)
|
|
734
|
+
const topResult = inspectServers(content.mcpServers || content.servers || {}, `${configPath} top-level`);
|
|
735
|
+
totalServersSeen += topResult.total;
|
|
736
|
+
// Project-scoped (Claude Code shape): projects[*].mcpServers.ruflo
|
|
737
|
+
if (content.projects && typeof content.projects === 'object') {
|
|
738
|
+
for (const [projectKey, projectVal] of Object.entries(content.projects)) {
|
|
739
|
+
const pm = projectVal?.mcpServers;
|
|
740
|
+
if (pm && typeof pm === 'object') {
|
|
741
|
+
const projectResult = inspectServers(pm, `${configPath} projects[${projectKey}]`);
|
|
742
|
+
totalServersSeen += projectResult.total;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// continue to next path
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (duplicateLocations.length > 0 || (legacyLocations.length > 0 && currentLocations.length > 0)) {
|
|
752
|
+
const locations = duplicateLocations.length > 0
|
|
753
|
+
? duplicateLocations.join('; ')
|
|
754
|
+
: `legacy ${legacyLocations.join('; ')} + current ${currentLocations.join('; ')}`;
|
|
755
|
+
return {
|
|
756
|
+
name: 'MCP Servers',
|
|
757
|
+
status: 'warn',
|
|
758
|
+
message: `Duplicate Ruflo MCP registrations found (${locations}) — Claude Code will start both tool schemas`,
|
|
759
|
+
fix: 'Remove the legacy `ruflo`-keyed MCP registration (pre-rename duplicate) and keep the canonical `claude-flow` entry: `claude mcp add claude-flow -- npx -y ruflo@latest mcp start`. The canonical key stays `claude-flow` so the ~166 `mcp__claude-flow__*` plugin tool references keep resolving (#2206).',
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (rufloLocations.length > 0) {
|
|
763
|
+
return {
|
|
764
|
+
name: 'MCP Servers',
|
|
765
|
+
status: 'pass',
|
|
766
|
+
message: `${totalServersSeen} servers (ruflo configured: ${rufloLocations.join('; ')})`,
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
if (totalServersSeen > 0) {
|
|
770
|
+
return {
|
|
771
|
+
name: 'MCP Servers',
|
|
772
|
+
status: 'warn',
|
|
773
|
+
message: `${totalServersSeen} servers (ruflo not found)`,
|
|
774
|
+
fix: 'claude mcp add claude-flow -- npx -y ruflo@latest mcp start',
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
name: 'MCP Servers',
|
|
779
|
+
status: 'warn',
|
|
780
|
+
message: 'No MCP config found',
|
|
781
|
+
fix: 'claude mcp add ruflo -- npx -y ruflo@latest mcp start',
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
// Check disk space (async with proper env inheritance)
|
|
785
|
+
async function checkDiskSpace() {
|
|
786
|
+
try {
|
|
787
|
+
if (process.platform === 'win32') {
|
|
788
|
+
return { name: 'Disk Space', status: 'pass', message: 'Check skipped on Windows' };
|
|
789
|
+
}
|
|
790
|
+
// Use df -Ph for POSIX mode (guarantees single-line output even with long device names)
|
|
791
|
+
const output_str = await runCommand('df -Ph . | tail -1');
|
|
792
|
+
const parts = output_str.split(/\s+/);
|
|
793
|
+
// POSIX format: Filesystem Size Used Avail Capacity Mounted
|
|
794
|
+
const available = parts[3];
|
|
795
|
+
const usePercent = parseInt(parts[4]?.replace('%', '') || '0', 10);
|
|
796
|
+
if (isNaN(usePercent)) {
|
|
797
|
+
return { name: 'Disk Space', status: 'warn', message: `${available || 'unknown'} available (unable to parse usage)` };
|
|
798
|
+
}
|
|
799
|
+
if (usePercent > 90) {
|
|
800
|
+
return { name: 'Disk Space', status: 'fail', message: `${available} available (${usePercent}% used)`, fix: 'Free up disk space' };
|
|
801
|
+
}
|
|
802
|
+
else if (usePercent > 80) {
|
|
803
|
+
return { name: 'Disk Space', status: 'warn', message: `${available} available (${usePercent}% used)` };
|
|
804
|
+
}
|
|
805
|
+
return { name: 'Disk Space', status: 'pass', message: `${available} available` };
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
return { name: 'Disk Space', status: 'warn', message: 'Unable to check' };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// Check TypeScript/build (async with proper env inheritance)
|
|
812
|
+
async function checkBuildTools() {
|
|
813
|
+
try {
|
|
814
|
+
const tscVersion = await runCommand('npx tsc --version', 10000); // tsc can be slow
|
|
815
|
+
if (!tscVersion || tscVersion.includes('not found')) {
|
|
816
|
+
return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
|
|
817
|
+
}
|
|
818
|
+
return { name: 'TypeScript', status: 'pass', message: tscVersion.replace('Version ', 'v') };
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
return { name: 'TypeScript', status: 'warn', message: 'Not installed locally', fix: 'npm install -D typescript' };
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
// Check for stale npx cache (version freshness)
|
|
825
|
+
async function checkVersionFreshness() {
|
|
826
|
+
try {
|
|
827
|
+
// Get current CLI version from package.json
|
|
828
|
+
// Use import.meta.url to reliably locate our own package.json,
|
|
829
|
+
// regardless of how deep the compiled file sits (e.g. dist/src/commands/).
|
|
830
|
+
let currentVersion = '0.0.0';
|
|
831
|
+
try {
|
|
832
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
833
|
+
let dir = dirname(thisFile);
|
|
834
|
+
// Walk up from the current file's directory until we find the
|
|
835
|
+
// package.json that belongs to @claude-flow/cli (or claude-flow/cli).
|
|
836
|
+
// Walk until dirname(dir) === dir (filesystem root on any platform).
|
|
837
|
+
for (;;) {
|
|
838
|
+
const candidate = join(dir, 'package.json');
|
|
839
|
+
try {
|
|
840
|
+
if (existsSync(candidate)) {
|
|
841
|
+
const pkg = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
842
|
+
if (pkg.version &&
|
|
843
|
+
typeof pkg.name === 'string' &&
|
|
844
|
+
(pkg.name === '@claude-flow/cli' || pkg.name === 'claude-flow' || pkg.name === 'ruflo')) {
|
|
845
|
+
currentVersion = pkg.version;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// Unreadable/invalid JSON -- skip and keep walking up
|
|
852
|
+
}
|
|
853
|
+
const parent = dirname(dir);
|
|
854
|
+
if (parent === dir)
|
|
855
|
+
break; // reached root
|
|
856
|
+
dir = parent;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
catch {
|
|
860
|
+
// Fall back to a default
|
|
861
|
+
currentVersion = '0.0.0';
|
|
862
|
+
}
|
|
863
|
+
// Check if running via npx (look for _npx in process path or argv)
|
|
864
|
+
const isNpx = process.argv[1]?.includes('_npx') ||
|
|
865
|
+
process.env.npm_execpath?.includes('npx') ||
|
|
866
|
+
process.cwd().includes('_npx');
|
|
867
|
+
// Query npm for latest version (using alpha tag since that's what we publish to)
|
|
868
|
+
let latestVersion = currentVersion;
|
|
869
|
+
try {
|
|
870
|
+
const npmInfo = await runCommand('npm view @claude-flow/cli@alpha version', 5000);
|
|
871
|
+
latestVersion = npmInfo.trim();
|
|
872
|
+
}
|
|
873
|
+
catch {
|
|
874
|
+
// Can't reach npm registry - skip check
|
|
875
|
+
return {
|
|
876
|
+
name: 'Version Freshness',
|
|
877
|
+
status: 'warn',
|
|
878
|
+
message: `v${currentVersion} (cannot check registry)`
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
// Parse version numbers for comparison (handle prerelease like 3.0.0-alpha.84)
|
|
882
|
+
const parseVersion = (v) => {
|
|
883
|
+
const match = v.match(/^(\d+)\.(\d+)\.(\d+)(?:-[a-zA-Z]+\.(\d+))?/);
|
|
884
|
+
if (!match)
|
|
885
|
+
return { major: 0, minor: 0, patch: 0, prerelease: 0 };
|
|
886
|
+
return {
|
|
887
|
+
major: parseInt(match[1], 10) || 0,
|
|
888
|
+
minor: parseInt(match[2], 10) || 0,
|
|
889
|
+
patch: parseInt(match[3], 10) || 0,
|
|
890
|
+
prerelease: parseInt(match[4], 10) || 0
|
|
891
|
+
};
|
|
892
|
+
};
|
|
893
|
+
const current = parseVersion(currentVersion);
|
|
894
|
+
const latest = parseVersion(latestVersion);
|
|
895
|
+
// Compare versions (including prerelease number)
|
|
896
|
+
const isOutdated = (latest.major > current.major ||
|
|
897
|
+
(latest.major === current.major && latest.minor > current.minor) ||
|
|
898
|
+
(latest.major === current.major && latest.minor === current.minor && latest.patch > current.patch) ||
|
|
899
|
+
(latest.major === current.major && latest.minor === current.minor && latest.patch === current.patch && latest.prerelease > current.prerelease));
|
|
900
|
+
if (isOutdated) {
|
|
901
|
+
const fix = isNpx
|
|
902
|
+
? 'rm -rf ~/.npm/_npx/* && npx -y @claude-flow/cli@latest'
|
|
903
|
+
: 'npm update @claude-flow/cli';
|
|
904
|
+
return {
|
|
905
|
+
name: 'Version Freshness',
|
|
906
|
+
status: 'warn',
|
|
907
|
+
message: `v${currentVersion} (latest: v${latestVersion})${isNpx ? ' [npx cache stale]' : ''}`,
|
|
908
|
+
fix
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
name: 'Version Freshness',
|
|
913
|
+
status: 'pass',
|
|
914
|
+
message: `v${currentVersion} (up to date)`
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
return {
|
|
919
|
+
name: 'Version Freshness',
|
|
920
|
+
status: 'warn',
|
|
921
|
+
message: 'Unable to check version freshness'
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Check Claude Code CLI (async with proper env inheritance)
|
|
926
|
+
// ADR-150 — surface MetaHarness availability + harnessFit score in
|
|
927
|
+
// the standard ruflo doctor flow. Graceful degradation: when metaharness
|
|
928
|
+
// is not installed (no network, optionalDep skipped), the check returns
|
|
929
|
+
// `warn` with a hint instead of `fail` — ruflo continues to function.
|
|
930
|
+
/**
|
|
931
|
+
* iter 45 — verify the ruflo-side MetaHarness integration is intact.
|
|
932
|
+
*
|
|
933
|
+
* The existing `checkMetaharness` verifies the UPSTREAM `metaharness`
|
|
934
|
+
* package is reachable (warn if missing — it's optional per ADR-150).
|
|
935
|
+
* This check verifies the INTEGRATION LAYER (plugin scripts, production
|
|
936
|
+
* module, subprocess bridge) is intact. Unlike upstream, the integration
|
|
937
|
+
* layer is shipped with ruflo — missing files mean ruflo's install is
|
|
938
|
+
* corrupted, not that an optional dep is absent.
|
|
939
|
+
*
|
|
940
|
+
* Status mapping:
|
|
941
|
+
* pass — all required files present + module loads + similarity() smoke OK
|
|
942
|
+
* fail — any required file missing OR module fails to import
|
|
943
|
+
* warn — files present but module import errored at runtime
|
|
944
|
+
*
|
|
945
|
+
* Verified files (iter 36-53 surfaces — full ADR-150 deep-integration set):
|
|
946
|
+
* - plugins/ruflo-metaharness/scripts/_harness.mjs (subprocess bridge)
|
|
947
|
+
* - plugins/ruflo-metaharness/scripts/_similarity.mjs (ADR-152 §3.1 module, iter 36)
|
|
948
|
+
* - plugins/ruflo-metaharness/scripts/similarity.mjs (CLI skill, iter 36)
|
|
949
|
+
* - plugins/ruflo-metaharness/scripts/_spike-similarity.mjs (regression anchor, iter 35)
|
|
950
|
+
* - plugins/ruflo-metaharness/scripts/drift-from-history.mjs (1-command primitive, iter 53)
|
|
951
|
+
* - plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md
|
|
952
|
+
* - plugins/ruflo-metaharness/skills/harness-drift-from-history/SKILL.md (iter 53)
|
|
953
|
+
*/
|
|
954
|
+
/**
|
|
955
|
+
* ADR-305 — funnel state audit. Reports the effective enabled/disabled
|
|
956
|
+
* state and, critically for enterprise audit verification, WHICH
|
|
957
|
+
* precedence source decided it (env > enterprise > user > default).
|
|
958
|
+
* Informational: both states are `pass`; only a resolver failure warns.
|
|
959
|
+
*/
|
|
960
|
+
async function checkFunnel() {
|
|
961
|
+
try {
|
|
962
|
+
const { resolveFunnelEnabled, getDisclosure } = await import('../funnel/index.js');
|
|
963
|
+
const decision = resolveFunnelEnabled();
|
|
964
|
+
const disclosure = getDisclosure();
|
|
965
|
+
return {
|
|
966
|
+
name: 'Funnel (ADR-305)',
|
|
967
|
+
status: 'pass',
|
|
968
|
+
message: `${decision.enabled ? 'enabled' : 'disabled'} (decided by: ${decision.decidedBy}; disclosure: ${disclosure.state})`,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
catch (err) {
|
|
972
|
+
return {
|
|
973
|
+
name: 'Funnel (ADR-305)',
|
|
974
|
+
status: 'warn',
|
|
975
|
+
message: `state unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
976
|
+
fix: 'ruflo funnel status',
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/** Meta LLM Proxy — sponsored-downtime health (ADR-313). */
|
|
981
|
+
async function checkProxySponsoredConsent() {
|
|
982
|
+
try {
|
|
983
|
+
const { funnelStateDir, hasConsent, readRateLimitStatus, lastRecordedEvent } = await import('../funnel/index.js');
|
|
984
|
+
const dir = funnelStateDir();
|
|
985
|
+
const installed = existsSync(join(dir, 'proxy-token'));
|
|
986
|
+
const consented = hasConsent('sponsored-downtime');
|
|
987
|
+
const rateLimited = readRateLimitStatus();
|
|
988
|
+
const lastExhausted = lastRecordedEvent('sponsor_capacity_exhausted');
|
|
989
|
+
if (!installed) {
|
|
990
|
+
return {
|
|
991
|
+
name: 'Meta LLM Proxy (ADR-313)',
|
|
992
|
+
status: 'warn',
|
|
993
|
+
message: 'not installed — no proxy-token found; sponsored-downtime capacity is unavailable',
|
|
994
|
+
fix: 'See cognitum-one/meta-proxy (private) for install instructions',
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
const parts = [
|
|
998
|
+
`sponsored consent: ${consented ? 'granted' : 'not granted'}`,
|
|
999
|
+
`rate-limit flag: ${rateLimited.limited ? `set (since ${rateLimited.since})` : 'not set'}`,
|
|
1000
|
+
];
|
|
1001
|
+
if (lastExhausted)
|
|
1002
|
+
parts.push(`last capacity-exhausted: ${lastExhausted}`);
|
|
1003
|
+
if (rateLimited.limited && !consented) {
|
|
1004
|
+
return {
|
|
1005
|
+
name: 'Meta LLM Proxy (ADR-313)',
|
|
1006
|
+
status: 'warn',
|
|
1007
|
+
message: `${parts.join('; ')} — flagged rate-limited but sponsored capacity isn't enabled`,
|
|
1008
|
+
fix: 'ruflo proxy sponsor-enable --yes',
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
return { name: 'Meta LLM Proxy (ADR-313)', status: 'pass', message: parts.join('; ') };
|
|
1012
|
+
}
|
|
1013
|
+
catch (err) {
|
|
1014
|
+
return {
|
|
1015
|
+
name: 'Meta LLM Proxy (ADR-313)',
|
|
1016
|
+
status: 'warn',
|
|
1017
|
+
message: `state unreadable: ${err instanceof Error ? err.message : String(err)}`,
|
|
1018
|
+
fix: 'ruflo proxy sponsor-status',
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Binary presence + tamper check (ADR-307). Deliberately NEVER spawns the
|
|
1024
|
+
* binary to probe a version — confirmed empirically (2026-07-16) that
|
|
1025
|
+
* `meta-proxy` has no `--version`/`--help` flag and starts the live server
|
|
1026
|
+
* as a side effect of ANY invocation, which a doctor health check must never
|
|
1027
|
+
* do. Version info instead comes from install-manifest.json (written at
|
|
1028
|
+
* install time) and, once running, the proxy's own `/status` endpoint via
|
|
1029
|
+
* checkProxyProcess below.
|
|
1030
|
+
*/
|
|
1031
|
+
async function checkProxyBinary() {
|
|
1032
|
+
const NAME = 'Meta LLM Proxy binary (ADR-307)';
|
|
1033
|
+
try {
|
|
1034
|
+
const { proxyBinaryPath, proxyInstallManifestPath } = await import('../proxy/paths.js');
|
|
1035
|
+
const binPath = proxyBinaryPath();
|
|
1036
|
+
if (!existsSync(binPath)) {
|
|
1037
|
+
return { name: NAME, status: 'warn', message: 'not installed', fix: 'ruflo proxy install' };
|
|
1038
|
+
}
|
|
1039
|
+
const manifestPath = proxyInstallManifestPath();
|
|
1040
|
+
if (!existsSync(manifestPath)) {
|
|
1041
|
+
return {
|
|
1042
|
+
name: NAME,
|
|
1043
|
+
status: 'warn',
|
|
1044
|
+
message: 'binary present but no install-manifest.json — provenance unknown (installed outside `ruflo proxy install`?)',
|
|
1045
|
+
fix: 'ruflo proxy update --release <x.y.z>',
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
1049
|
+
const liveSha = createHash('sha256').update(readFileSync(binPath)).digest('hex');
|
|
1050
|
+
if (liveSha !== manifest.sha256) {
|
|
1051
|
+
return {
|
|
1052
|
+
name: NAME,
|
|
1053
|
+
status: 'fail',
|
|
1054
|
+
message: `binary sha256 does not match the recorded install manifest — possible tampering or a manual overwrite (expected ${manifest.sha256.slice(0, 12)}…, got ${liveSha.slice(0, 12)}…)`,
|
|
1055
|
+
fix: 'ruflo proxy update --release <x.y.z>',
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
return { name: NAME, status: 'pass', message: `v${manifest.version}, signature-verified at install (${manifest.verifiedAt})` };
|
|
1059
|
+
}
|
|
1060
|
+
catch (err) {
|
|
1061
|
+
return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/** PID-file liveness (ADR-307) — mirrors daemon.ts's signal-0 probe pattern. */
|
|
1065
|
+
async function checkProxyProcess() {
|
|
1066
|
+
const NAME = 'Meta LLM Proxy process (ADR-307)';
|
|
1067
|
+
try {
|
|
1068
|
+
const { proxyPidFilePath } = await import('../proxy/paths.js');
|
|
1069
|
+
const pidPath = proxyPidFilePath();
|
|
1070
|
+
if (!existsSync(pidPath)) {
|
|
1071
|
+
return { name: NAME, status: 'warn', message: 'not running (no PID file)', fix: 'ruflo proxy start' };
|
|
1072
|
+
}
|
|
1073
|
+
const pidRaw = readFileSync(pidPath, 'utf-8').trim();
|
|
1074
|
+
const pid = parseInt(pidRaw, 10);
|
|
1075
|
+
if (!Number.isFinite(pid)) {
|
|
1076
|
+
return { name: NAME, status: 'warn', message: `PID file is malformed: ${JSON.stringify(pidRaw)}`, fix: 'ruflo proxy stop && ruflo proxy start' };
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
process.kill(pid, 0); // signal-0 liveness probe — throws if the process is dead
|
|
1080
|
+
}
|
|
1081
|
+
catch {
|
|
1082
|
+
return { name: NAME, status: 'warn', message: `PID file points at ${pid}, which is not running — stale PID file`, fix: 'ruflo proxy start' };
|
|
1083
|
+
}
|
|
1084
|
+
// Live version-compat + data-plane info, ONLY once PID liveness already
|
|
1085
|
+
// confirmed a process is running (never spawns anything — see
|
|
1086
|
+
// checkProxyBinary's comment on why probing via process launch is unsafe).
|
|
1087
|
+
// GET /status shape confirmed against the real v0.1.0 binary:
|
|
1088
|
+
// {"version","data_plane","bind","sponsored_available","proxy_token_valid"}.
|
|
1089
|
+
const { proxyConfigPath, proxyTokenPath, proxyInstallManifestPath } = await import('../proxy/paths.js');
|
|
1090
|
+
const bindMatch = existsSync(proxyConfigPath())
|
|
1091
|
+
? readFileSync(proxyConfigPath(), 'utf-8').match(/^bind\s*=\s*"([^"]*)"/m)
|
|
1092
|
+
: null;
|
|
1093
|
+
const bind = bindMatch ? bindMatch[1] : '127.0.0.1:11435';
|
|
1094
|
+
let token;
|
|
1095
|
+
try {
|
|
1096
|
+
token = readFileSync(proxyTokenPath(), 'utf-8').trim();
|
|
1097
|
+
}
|
|
1098
|
+
catch {
|
|
1099
|
+
return { name: NAME, status: 'pass', message: `running (pid ${pid}); no proxy-token to query /status` };
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
const controller = new AbortController();
|
|
1103
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
1104
|
+
const resp = await fetch(`http://${bind}/status`, {
|
|
1105
|
+
headers: { authorization: `Bearer ${token}` },
|
|
1106
|
+
signal: controller.signal,
|
|
1107
|
+
});
|
|
1108
|
+
clearTimeout(timer);
|
|
1109
|
+
if (!resp.ok) {
|
|
1110
|
+
return { name: NAME, status: 'warn', message: `running (pid ${pid}); /status returned HTTP ${resp.status}` };
|
|
1111
|
+
}
|
|
1112
|
+
const body = (await resp.json());
|
|
1113
|
+
let versionNote = '';
|
|
1114
|
+
if (existsSync(proxyInstallManifestPath())) {
|
|
1115
|
+
const manifest = JSON.parse(readFileSync(proxyInstallManifestPath(), 'utf-8'));
|
|
1116
|
+
if (manifest.version && body.version && manifest.version !== body.version) {
|
|
1117
|
+
return {
|
|
1118
|
+
name: NAME,
|
|
1119
|
+
status: 'warn',
|
|
1120
|
+
message: `running (pid ${pid}) reports v${body.version}, but the installed binary is v${manifest.version} — a stale process from a previous version?`,
|
|
1121
|
+
fix: 'ruflo proxy stop && ruflo proxy start',
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
versionNote = body.version ? ` v${body.version}` : '';
|
|
1125
|
+
}
|
|
1126
|
+
return {
|
|
1127
|
+
name: NAME,
|
|
1128
|
+
status: 'pass',
|
|
1129
|
+
message: `running (pid ${pid})${versionNote}; data plane: ${body.data_plane ?? 'unknown'}`,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
catch (err) {
|
|
1133
|
+
// Live process but /status unreachable (still starting up, or a
|
|
1134
|
+
// network hiccup) — not a failure, PID liveness already passed.
|
|
1135
|
+
return {
|
|
1136
|
+
name: NAME,
|
|
1137
|
+
status: 'pass',
|
|
1138
|
+
message: `running (pid ${pid}); /status unreachable (${err instanceof Error ? err.message : String(err)})`,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
catch (err) {
|
|
1143
|
+
return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
/** Non-loopback bind exposure warning (ADR-307's mandated startup warning, surfaced in doctor too). */
|
|
1147
|
+
async function checkProxyBindAddress() {
|
|
1148
|
+
const NAME = 'Meta LLM Proxy bind address (ADR-307)';
|
|
1149
|
+
try {
|
|
1150
|
+
const { proxyConfigPath, isLoopbackBind } = await import('../proxy/paths.js');
|
|
1151
|
+
const cfgPath = proxyConfigPath();
|
|
1152
|
+
if (!existsSync(cfgPath)) {
|
|
1153
|
+
return { name: NAME, status: 'pass', message: 'no config file yet — defaults to loopback-only (127.0.0.1:11435)' };
|
|
1154
|
+
}
|
|
1155
|
+
const raw = readFileSync(cfgPath, 'utf-8');
|
|
1156
|
+
const match = raw.match(/^bind\s*=\s*"([^"]*)"/m);
|
|
1157
|
+
const bind = match ? match[1] : '127.0.0.1:11435';
|
|
1158
|
+
if (!isLoopbackBind(bind)) {
|
|
1159
|
+
return {
|
|
1160
|
+
name: NAME,
|
|
1161
|
+
status: 'warn',
|
|
1162
|
+
message: `bound to non-loopback address ${bind} — this exposes the proxy to your network`,
|
|
1163
|
+
fix: 'Set bind back to 127.0.0.1:<port> in proxy-config.toml unless external exposure is intended',
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
return { name: NAME, status: 'pass', message: `loopback-only (${bind})` };
|
|
1167
|
+
}
|
|
1168
|
+
catch (err) {
|
|
1169
|
+
return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
/** `ruflo auth` health (ADR-306). Warn (never fail) on absence — auth is never required for core functionality. */
|
|
1173
|
+
async function checkAuth() {
|
|
1174
|
+
const NAME = 'Cognitum identity (ADR-306)';
|
|
1175
|
+
try {
|
|
1176
|
+
const { listProfiles } = await import('../auth/state.js');
|
|
1177
|
+
const { domainForScope } = await import('../auth/scopes.js');
|
|
1178
|
+
const { hasConsent } = await import('../funnel/index.js');
|
|
1179
|
+
const { profiles } = listProfiles();
|
|
1180
|
+
if (profiles.length === 0) {
|
|
1181
|
+
return { name: NAME, status: 'warn', message: 'not logged in', fix: 'ruflo auth login' };
|
|
1182
|
+
}
|
|
1183
|
+
let keychainAvailable = 'unknown';
|
|
1184
|
+
try {
|
|
1185
|
+
const sec = await import('@claude-flow/security');
|
|
1186
|
+
keychainAvailable = await (await sec.createKeychainAdapter()).isAvailable();
|
|
1187
|
+
}
|
|
1188
|
+
catch {
|
|
1189
|
+
keychainAvailable = 'unknown'; // security package unavailable — surfaced by checkProxyBinary's sibling concerns, not duplicated here
|
|
1190
|
+
}
|
|
1191
|
+
// Scope-vs-receipt consistency check (ADR-306: "fail-closed... reports").
|
|
1192
|
+
// Unlike every other check in this file, a violation here is a FAIL, not
|
|
1193
|
+
// a warn — a scope present without a matching consent receipt is exactly
|
|
1194
|
+
// the condition ADR-306 says must never silently pass.
|
|
1195
|
+
const violations = [];
|
|
1196
|
+
for (const p of profiles) {
|
|
1197
|
+
for (const scope of p.scopes) {
|
|
1198
|
+
const domain = domainForScope(scope);
|
|
1199
|
+
if (domain && !hasConsent(domain))
|
|
1200
|
+
violations.push(`${p.profile}: ${scope}`);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (violations.length > 0) {
|
|
1204
|
+
return {
|
|
1205
|
+
name: NAME,
|
|
1206
|
+
status: 'fail',
|
|
1207
|
+
message: `scope granted without a matching consent receipt: ${violations.join(', ')}`,
|
|
1208
|
+
fix: 'ruflo auth logout && ruflo auth login',
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
const names = profiles.map((p) => p.profile).join(', ');
|
|
1212
|
+
const sessionOnly = profiles.filter((p) => !p.keychainRef).map((p) => p.profile);
|
|
1213
|
+
const parts = [`profiles: ${names}`];
|
|
1214
|
+
if (keychainAvailable === false)
|
|
1215
|
+
parts.push('keychain backend unreachable — falling back to session-only tokens');
|
|
1216
|
+
if (sessionOnly.length > 0)
|
|
1217
|
+
parts.push(`session-only (no persisted refresh token): ${sessionOnly.join(', ')}`);
|
|
1218
|
+
return { name: NAME, status: 'pass', message: parts.join('; ') };
|
|
1219
|
+
}
|
|
1220
|
+
catch (err) {
|
|
1221
|
+
return { name: NAME, status: 'warn', message: `check failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
async function checkMetaharnessIntegration() {
|
|
1225
|
+
// Locate plugins dir.
|
|
1226
|
+
//
|
|
1227
|
+
// Pre-#2437 fix this only walked up from `process.cwd()` + checked one
|
|
1228
|
+
// hard-coded `<cwd>/node_modules/@claude-flow/cli/...` candidate. That
|
|
1229
|
+
// missed the two cases users actually run from:
|
|
1230
|
+
// (a) `npx @claude-flow/cli@<tag>` → resolves to a per-version cache
|
|
1231
|
+
// under `~/.npm/_npx/<hash>/node_modules/@claude-flow/cli/...`
|
|
1232
|
+
// (b) `npm install -g @claude-flow/cli` → lives at
|
|
1233
|
+
// `$(npm prefix -g)/lib/node_modules/@claude-flow/cli/...`
|
|
1234
|
+
//
|
|
1235
|
+
// The bulletproof fix: resolve relative to THIS file's own location via
|
|
1236
|
+
// `import.meta.url`. The plugins dir is always a sibling of the package
|
|
1237
|
+
// root regardless of where the package was installed. Walk up from
|
|
1238
|
+
// `dist/src/commands/doctor.js` (built) or `src/commands/doctor.ts`
|
|
1239
|
+
// (dev) until we find a directory containing `plugins/ruflo-metaharness/`.
|
|
1240
|
+
const candidates = [];
|
|
1241
|
+
// Strategy 1: walk up from this module's own URL — covers npx + global install.
|
|
1242
|
+
try {
|
|
1243
|
+
const selfDir = dirname(fileURLToPath(import.meta.url));
|
|
1244
|
+
let q = selfDir;
|
|
1245
|
+
for (let i = 0; i < 8; i++) {
|
|
1246
|
+
candidates.push(join(q, 'plugins', 'ruflo-metaharness'));
|
|
1247
|
+
q = dirname(q);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
catch {
|
|
1251
|
+
// import.meta.url unavailable under some bundlers — fall through to cwd walk.
|
|
1252
|
+
}
|
|
1253
|
+
// Strategy 2: walk up from cwd — covers monorepo dev (running from a sub-package).
|
|
1254
|
+
let p = process.cwd();
|
|
1255
|
+
for (let i = 0; i < 8; i++) {
|
|
1256
|
+
candidates.push(join(p, 'plugins', 'ruflo-metaharness'));
|
|
1257
|
+
p = dirname(p);
|
|
1258
|
+
}
|
|
1259
|
+
// Strategy 3: explicit node_modules path relative to cwd — covers project-local install.
|
|
1260
|
+
candidates.push(join(process.cwd(), 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-metaharness'));
|
|
1261
|
+
let pluginDir = null;
|
|
1262
|
+
for (const c of candidates) {
|
|
1263
|
+
if (existsSync(join(c, 'scripts', '_harness.mjs'))) {
|
|
1264
|
+
pluginDir = c;
|
|
1265
|
+
break;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
if (!pluginDir) {
|
|
1269
|
+
// #2437: MetaHarness is documented as an optional dependency in
|
|
1270
|
+
// optionalDependencies (per ADR-150 architectural constraint #2 —
|
|
1271
|
+
// "Optional in package.json"). A genuinely-absent plugin therefore
|
|
1272
|
+
// warrants WARN, not FAIL — same posture as the runtime path which
|
|
1273
|
+
// returns {degraded: true, exit 0}. FAIL is reserved for misconfigured
|
|
1274
|
+
// installs where the plugin SHOULD be present but is broken.
|
|
1275
|
+
return {
|
|
1276
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1277
|
+
status: 'warn',
|
|
1278
|
+
message: 'plugins/ruflo-metaharness/ not found — MetaHarness skills will degrade gracefully',
|
|
1279
|
+
fix: 'Optional: install via `npm i -D @metaharness/darwin metaharness` or run `ruflo plugins install ruflo-metaharness`',
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
// Required files (iter 36+44 surfaces, +ADR-153 darwin surfaces in v3.13.0)
|
|
1283
|
+
const required = [
|
|
1284
|
+
'scripts/_harness.mjs',
|
|
1285
|
+
'scripts/_similarity.mjs',
|
|
1286
|
+
'scripts/similarity.mjs',
|
|
1287
|
+
'scripts/_spike-similarity.mjs',
|
|
1288
|
+
// iter 53 surfaces — gated against silent deletion
|
|
1289
|
+
'scripts/drift-from-history.mjs',
|
|
1290
|
+
'skills/harness-similarity/SKILL.md',
|
|
1291
|
+
'skills/harness-drift-from-history/SKILL.md',
|
|
1292
|
+
// ADR-153 Darwin Mode surfaces (v3.13.0) — added to gate against silent deletion
|
|
1293
|
+
'scripts/_darwin.mjs',
|
|
1294
|
+
'scripts/evolve.mjs',
|
|
1295
|
+
'scripts/security-bench.mjs',
|
|
1296
|
+
'scripts/bench.mjs',
|
|
1297
|
+
'skills/harness-evolve/SKILL.md',
|
|
1298
|
+
'skills/harness-security-bench/SKILL.md',
|
|
1299
|
+
'skills/harness-bench/SKILL.md',
|
|
1300
|
+
];
|
|
1301
|
+
const missing = required.filter((f) => !existsSync(join(pluginDir, f)));
|
|
1302
|
+
if (missing.length > 0) {
|
|
1303
|
+
return {
|
|
1304
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1305
|
+
status: 'fail',
|
|
1306
|
+
message: `Missing files: ${missing.join(', ')}`,
|
|
1307
|
+
fix: 'Reinstall ruflo or restore from git: `git checkout HEAD -- plugins/ruflo-metaharness/`',
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
// Runtime smoke: import the similarity module and exercise it
|
|
1311
|
+
try {
|
|
1312
|
+
const modPath = join(pluginDir, 'scripts', '_similarity.mjs');
|
|
1313
|
+
const mod = await import(modPath);
|
|
1314
|
+
if (typeof mod.similarity !== 'function') {
|
|
1315
|
+
return {
|
|
1316
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1317
|
+
status: 'fail',
|
|
1318
|
+
message: '_similarity.mjs does not export similarity()',
|
|
1319
|
+
fix: 'Reinstall ruflo or restore the file from git',
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
const result = mod.similarity({}, {});
|
|
1323
|
+
if (typeof result?.overall !== 'number') {
|
|
1324
|
+
return {
|
|
1325
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1326
|
+
status: 'warn',
|
|
1327
|
+
message: 'similarity() returned unexpected shape; module may be stale',
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
// iter 52 — also verify the iter-50 mcp-scan text parser exports
|
|
1331
|
+
// correctly. parseMcpScanText is the shared util both mcp-scan.mjs
|
|
1332
|
+
// and oia-audit.mjs depend on; if it's missing the audit-trend
|
|
1333
|
+
// introduced/cleared diff silently degrades to dead code.
|
|
1334
|
+
//
|
|
1335
|
+
// iter 61 — additionally verify iter-56's async exports
|
|
1336
|
+
// (runHarnessAsync / runMetaharnessAsync). These are the
|
|
1337
|
+
// parallelization primitives oia-audit depends on; if they're
|
|
1338
|
+
// missing, oia-audit's import fails and the whole pipeline breaks.
|
|
1339
|
+
const harnessPath = join(pluginDir, 'scripts', '_harness.mjs');
|
|
1340
|
+
const harnessMod = await import(harnessPath);
|
|
1341
|
+
if (typeof harnessMod.parseMcpScanText !== 'function') {
|
|
1342
|
+
return {
|
|
1343
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1344
|
+
status: 'fail',
|
|
1345
|
+
message: '_harness.mjs does not export parseMcpScanText (iter 50 — needed by mcp-scan + oia-audit)',
|
|
1346
|
+
fix: 'Reinstall ruflo or restore _harness.mjs from git',
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
if (typeof harnessMod.runHarnessAsync !== 'function' || typeof harnessMod.runMetaharnessAsync !== 'function') {
|
|
1350
|
+
return {
|
|
1351
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1352
|
+
status: 'fail',
|
|
1353
|
+
message: '_harness.mjs missing iter-56 async exports (runHarnessAsync / runMetaharnessAsync) — oia-audit parallelization will fail',
|
|
1354
|
+
fix: 'Reinstall ruflo or restore _harness.mjs from git',
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
// Smoke: parser handles empty input gracefully
|
|
1358
|
+
const parsed = harnessMod.parseMcpScanText('');
|
|
1359
|
+
if (!Array.isArray(parsed?.findings)) {
|
|
1360
|
+
return {
|
|
1361
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1362
|
+
status: 'warn',
|
|
1363
|
+
message: 'parseMcpScanText returned unexpected shape on empty input',
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
return {
|
|
1367
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1368
|
+
status: 'pass',
|
|
1369
|
+
message: 'plugin scripts intact, _similarity.mjs + parseMcpScanText load, smoke OK',
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
catch (e) {
|
|
1373
|
+
return {
|
|
1374
|
+
name: 'MetaHarness integration (ADR-150)',
|
|
1375
|
+
status: 'warn',
|
|
1376
|
+
message: `Module import errored: ${e.message.slice(0, 60)}`,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function checkMetaharness() {
|
|
1381
|
+
try {
|
|
1382
|
+
const version = await runCommand('npx -y metaharness@latest --version 2>&1', 15000);
|
|
1383
|
+
// metaharness emits multi-line stdout; parse a version-shaped line.
|
|
1384
|
+
const versionMatch = version.match(/(\d+\.\d+\.\d+)/);
|
|
1385
|
+
if (!versionMatch) {
|
|
1386
|
+
return {
|
|
1387
|
+
name: 'MetaHarness (ADR-150)',
|
|
1388
|
+
status: 'warn',
|
|
1389
|
+
message: 'Installed but version-string not parseable; integration may still work',
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
name: 'MetaHarness (ADR-150)',
|
|
1394
|
+
status: 'pass',
|
|
1395
|
+
message: `v${versionMatch[1]} — run \`npx ruflo metaharness score\` for the full scorecard`,
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
catch {
|
|
1399
|
+
return {
|
|
1400
|
+
name: 'MetaHarness (ADR-150)',
|
|
1401
|
+
status: 'warn',
|
|
1402
|
+
message: 'Not installed — `npx ruflo metaharness *` commands will degrade gracefully',
|
|
1403
|
+
fix: 'npm install --include=optional # to enable the metaharness optional dep',
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
async function checkClaudeCode() {
|
|
1408
|
+
try {
|
|
1409
|
+
const version = await runCommand('claude --version');
|
|
1410
|
+
// Parse version from output like "claude 1.0.0" or "Claude Code v1.0.0"
|
|
1411
|
+
const versionMatch = version.match(/v?(\d+\.\d+\.\d+)/);
|
|
1412
|
+
const versionStr = versionMatch ? `v${versionMatch[1]}` : version;
|
|
1413
|
+
return { name: 'Claude Code CLI', status: 'pass', message: versionStr };
|
|
1414
|
+
}
|
|
1415
|
+
catch {
|
|
1416
|
+
return {
|
|
1417
|
+
name: 'Claude Code CLI',
|
|
1418
|
+
status: 'warn',
|
|
1419
|
+
message: 'Not installed',
|
|
1420
|
+
fix: 'npm install -g @anthropic-ai/claude-code'
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
// Install Claude Code CLI
|
|
1425
|
+
async function installClaudeCode() {
|
|
1426
|
+
try {
|
|
1427
|
+
output.writeln();
|
|
1428
|
+
output.writeln(output.bold('Installing Claude Code CLI...'));
|
|
1429
|
+
execSync('npm install -g @anthropic-ai/claude-code', {
|
|
1430
|
+
encoding: 'utf8',
|
|
1431
|
+
stdio: 'inherit'
|
|
1432
|
+
});
|
|
1433
|
+
output.writeln(output.success('Claude Code CLI installed successfully!'));
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
catch (error) {
|
|
1437
|
+
output.writeln(output.error('Failed to install Claude Code CLI'));
|
|
1438
|
+
if (error instanceof Error) {
|
|
1439
|
+
output.writeln(output.dim(error.message));
|
|
1440
|
+
}
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
// Check agentic-flow v3 integration (filesystem-based to avoid slow WASM/DB init)
|
|
1445
|
+
async function checkAgenticFlow() {
|
|
1446
|
+
try {
|
|
1447
|
+
// Walk common node_modules paths to find agentic-flow/package.json
|
|
1448
|
+
const candidates = [
|
|
1449
|
+
join(process.cwd(), 'node_modules', 'agentic-flow', 'package.json'),
|
|
1450
|
+
join(process.cwd(), '..', 'node_modules', 'agentic-flow', 'package.json'),
|
|
1451
|
+
];
|
|
1452
|
+
let pkgJsonPath = null;
|
|
1453
|
+
for (const p of candidates) {
|
|
1454
|
+
if (existsSync(p)) {
|
|
1455
|
+
pkgJsonPath = p;
|
|
1456
|
+
break;
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (!pkgJsonPath) {
|
|
1460
|
+
return {
|
|
1461
|
+
name: 'agentic-flow',
|
|
1462
|
+
status: 'warn',
|
|
1463
|
+
message: 'Not installed (optional — embeddings/routing will use fallbacks)',
|
|
1464
|
+
fix: 'npm install agentic-flow@latest'
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
1468
|
+
const version = pkg.version || 'unknown';
|
|
1469
|
+
const exports = pkg.exports || {};
|
|
1470
|
+
const features = [
|
|
1471
|
+
exports['./reasoningbank'] ? 'ReasoningBank' : null,
|
|
1472
|
+
exports['./router'] ? 'Router' : null,
|
|
1473
|
+
exports['./transport/quic'] ? 'QUIC' : null,
|
|
1474
|
+
].filter(Boolean);
|
|
1475
|
+
return {
|
|
1476
|
+
name: 'agentic-flow',
|
|
1477
|
+
status: 'pass',
|
|
1478
|
+
message: `v${version} (${features.join(', ')})`
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
catch {
|
|
1482
|
+
return { name: 'agentic-flow', status: 'warn', message: 'Check failed' };
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
// Check encryption-at-rest status (ADR-096 Phase 5)
|
|
1486
|
+
//
|
|
1487
|
+
// Reports four facets without disclosing the key itself:
|
|
1488
|
+
// 1. Gate status — is CLAUDE_FLOW_ENCRYPT_AT_REST set?
|
|
1489
|
+
// 2. Key resolution — does CLAUDE_FLOW_ENCRYPTION_KEY resolve to a valid
|
|
1490
|
+
// 32-byte key (env-var path only; keychain/passphrase are deferred)?
|
|
1491
|
+
// 3. Key fingerprint — first 16 hex chars of sha256(key) so users can
|
|
1492
|
+
// sanity-check across machines without ever logging the key bytes.
|
|
1493
|
+
// 4. High-tier store presence — for sessions/, terminals/, .swarm/memory.db
|
|
1494
|
+
// report whether on-disk bytes carry the RFE1 magic (encrypted) or not.
|
|
1495
|
+
async function checkEncryptionAtRest() {
|
|
1496
|
+
if (!isEncryptionEnabled()) {
|
|
1497
|
+
return {
|
|
1498
|
+
name: 'Encryption at Rest',
|
|
1499
|
+
status: 'warn',
|
|
1500
|
+
message: 'Off — session/terminal/memory stores are plaintext (mode 0600 only)',
|
|
1501
|
+
fix: 'export CLAUDE_FLOW_ENCRYPT_AT_REST=1 && export CLAUDE_FLOW_ENCRYPTION_KEY=<64-char-hex>',
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
// Gate is on — try to resolve the key. Fail-closed if missing or malformed.
|
|
1505
|
+
const rawKey = process.env.CLAUDE_FLOW_ENCRYPTION_KEY;
|
|
1506
|
+
if (!rawKey) {
|
|
1507
|
+
return {
|
|
1508
|
+
name: 'Encryption at Rest',
|
|
1509
|
+
status: 'fail',
|
|
1510
|
+
message: 'Gate is on but CLAUDE_FLOW_ENCRYPTION_KEY is unset (fail-closed)',
|
|
1511
|
+
fix: 'Generate a key: openssl rand -hex 32 → export CLAUDE_FLOW_ENCRYPTION_KEY=<value>',
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
let keyFingerprint;
|
|
1515
|
+
try {
|
|
1516
|
+
const key = decodeKey(rawKey);
|
|
1517
|
+
keyFingerprint = createHash('sha256').update(key).digest('hex').slice(0, 16);
|
|
1518
|
+
}
|
|
1519
|
+
catch (err) {
|
|
1520
|
+
return {
|
|
1521
|
+
name: 'Encryption at Rest',
|
|
1522
|
+
status: 'fail',
|
|
1523
|
+
message: `CLAUDE_FLOW_ENCRYPTION_KEY invalid: ${err instanceof Error ? err.message : String(err)}`,
|
|
1524
|
+
fix: 'Provide a 64-char hex or 44-char base64 key (32 bytes)',
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
// Check the three high-tier store paths for RFE1 magic
|
|
1528
|
+
const cwd = process.cwd();
|
|
1529
|
+
const stores = [
|
|
1530
|
+
{ label: 'sessions/', path: join(cwd, '.claude-flow', 'sessions') },
|
|
1531
|
+
{ label: 'terminals', path: join(cwd, '.claude-flow', 'terminals', 'store.json') },
|
|
1532
|
+
{ label: 'memory.db', path: join(cwd, '.swarm', 'memory.db') },
|
|
1533
|
+
];
|
|
1534
|
+
const status = [];
|
|
1535
|
+
for (const s of stores) {
|
|
1536
|
+
if (!existsSync(s.path)) {
|
|
1537
|
+
status.push(`${s.label}=∅`);
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
try {
|
|
1541
|
+
const stat = statSync(s.path);
|
|
1542
|
+
if (stat.isDirectory()) {
|
|
1543
|
+
// Sessions: probe the first .json file
|
|
1544
|
+
const { readdirSync } = await import('fs');
|
|
1545
|
+
const files = readdirSync(s.path).filter(f => f.endsWith('.json'));
|
|
1546
|
+
if (files.length === 0) {
|
|
1547
|
+
status.push(`${s.label}=∅`);
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
const first = readFileSync(join(s.path, files[0]));
|
|
1551
|
+
status.push(`${s.label}=${isEncryptedBlob(first) ? 'enc' : 'plain'}`);
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
const buf = readFileSync(s.path);
|
|
1555
|
+
status.push(`${s.label}=${isEncryptedBlob(buf) ? 'enc' : 'plain'}`);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
catch {
|
|
1559
|
+
status.push(`${s.label}=err`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return {
|
|
1563
|
+
name: 'Encryption at Rest',
|
|
1564
|
+
status: 'pass',
|
|
1565
|
+
message: `On — key fp:${keyFingerprint}… (${status.join(' ')})`,
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
// Format health check result
|
|
1569
|
+
function formatCheck(check) {
|
|
1570
|
+
const icon = check.status === 'pass' ? output.success('✓') :
|
|
1571
|
+
check.status === 'warn' ? output.warning('⚠') :
|
|
1572
|
+
output.error('✗');
|
|
1573
|
+
return `${icon} ${check.name}: ${check.message}`;
|
|
1574
|
+
}
|
|
1575
|
+
// Main doctor command
|
|
1576
|
+
export const doctorCommand = {
|
|
1577
|
+
name: 'doctor',
|
|
1578
|
+
description: 'System diagnostics and health checks',
|
|
1579
|
+
options: [
|
|
1580
|
+
{
|
|
1581
|
+
name: 'fix',
|
|
1582
|
+
short: 'f',
|
|
1583
|
+
// #1791.5 — flag name was misleading: it does NOT auto-apply fixes,
|
|
1584
|
+
// it only prints the suggested commands so the user can run them
|
|
1585
|
+
// themselves. Make that explicit in the help output.
|
|
1586
|
+
description: 'Print suggested fix commands (does not auto-apply — copy/paste them yourself)',
|
|
1587
|
+
type: 'boolean',
|
|
1588
|
+
default: false
|
|
1589
|
+
},
|
|
1590
|
+
{
|
|
1591
|
+
name: 'install',
|
|
1592
|
+
short: 'i',
|
|
1593
|
+
description: 'Auto-install missing dependencies (Claude Code CLI)',
|
|
1594
|
+
type: 'boolean',
|
|
1595
|
+
default: false
|
|
1596
|
+
},
|
|
1597
|
+
{
|
|
1598
|
+
name: 'component',
|
|
1599
|
+
short: 'c',
|
|
1600
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, metaharness)',
|
|
1601
|
+
type: 'string'
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
name: 'verbose',
|
|
1605
|
+
short: 'v',
|
|
1606
|
+
description: 'Verbose output',
|
|
1607
|
+
type: 'boolean',
|
|
1608
|
+
default: false
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
name: 'fix-handles',
|
|
1612
|
+
// Windows-only mitigation for anthropics/claude-code#67888 — Claude Code's
|
|
1613
|
+
// Bash tool spawns cmd.exe/bash.exe without cleaning up child conhost.exe
|
|
1614
|
+
// handles, so a long session accumulates dozens (observed live: 26+
|
|
1615
|
+
// orphaned conhost.exe after a ~4h session). Each holds a kernel object
|
|
1616
|
+
// + ~1MB, and combined with memory pressure this measurably slows the
|
|
1617
|
+
// machine. This flag kills orphan conhost.exe (safe — Windows respawns
|
|
1618
|
+
// on demand). Deliberately does NOT touch cmd.exe/bash.exe — those can
|
|
1619
|
+
// be the invoking shell, and killing them 255's the caller.
|
|
1620
|
+
description: 'Windows only: kill orphaned conhost.exe processes leaked by Claude Code (mitigation for anthropics/claude-code#67888)',
|
|
1621
|
+
type: 'boolean',
|
|
1622
|
+
default: false
|
|
1623
|
+
}
|
|
1624
|
+
],
|
|
1625
|
+
examples: [
|
|
1626
|
+
{ command: 'claude-flow doctor', description: 'Run full health check' },
|
|
1627
|
+
{ command: 'claude-flow doctor --fix', description: 'Print suggested fix commands (does not auto-apply)' },
|
|
1628
|
+
{ command: 'claude-flow doctor --install', description: 'Auto-install missing dependencies' },
|
|
1629
|
+
{ command: 'claude-flow doctor -c version', description: 'Check for stale npx cache' },
|
|
1630
|
+
{ command: 'claude-flow doctor -c claude', description: 'Check Claude Code CLI only' },
|
|
1631
|
+
{ command: 'claude-flow doctor --fix-handles', description: 'Windows: kill leaked conhost.exe from Claude Code sessions' }
|
|
1632
|
+
],
|
|
1633
|
+
action: async (ctx) => {
|
|
1634
|
+
const showFix = ctx.flags.fix;
|
|
1635
|
+
const autoInstall = ctx.flags.install;
|
|
1636
|
+
const component = ctx.flags.component;
|
|
1637
|
+
const verbose = ctx.flags.verbose;
|
|
1638
|
+
// Parser camelCases kebab-case flag names — read via `fixHandles`, not `['fix-handles']`.
|
|
1639
|
+
const fixHandles = ctx.flags.fixHandles;
|
|
1640
|
+
// Early-return short-circuit: `--fix-handles` is a targeted mitigation, not
|
|
1641
|
+
// part of the health-check flow. Runs, reports, exits.
|
|
1642
|
+
if (fixHandles) {
|
|
1643
|
+
output.writeln();
|
|
1644
|
+
output.writeln(output.bold('RuFlo Doctor — fix-handles'));
|
|
1645
|
+
output.writeln(output.dim('─'.repeat(50)));
|
|
1646
|
+
output.writeln();
|
|
1647
|
+
if (process.platform !== 'win32') {
|
|
1648
|
+
output.printInfo('--fix-handles is a Windows-only mitigation. On this platform (' + process.platform + '), no action taken.');
|
|
1649
|
+
return { success: true };
|
|
1650
|
+
}
|
|
1651
|
+
const { spawnSync } = await import('child_process');
|
|
1652
|
+
// PowerShell one-liner: read before-count, kill conhost, read after-count, report delta.
|
|
1653
|
+
const psScript = [
|
|
1654
|
+
"$before = (Get-Process conhost -EA SilentlyContinue).Count",
|
|
1655
|
+
"$mem_before = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)",
|
|
1656
|
+
"$killed = 0",
|
|
1657
|
+
"Get-Process conhost -EA SilentlyContinue | ForEach-Object {",
|
|
1658
|
+
" try { Stop-Process -Id $_.Id -Force -EA Stop; $killed++ } catch {}",
|
|
1659
|
+
"}",
|
|
1660
|
+
"Start-Sleep -Seconds 1",
|
|
1661
|
+
"$after = (Get-Process conhost -EA SilentlyContinue).Count",
|
|
1662
|
+
"$mem_after = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1MB, 2)",
|
|
1663
|
+
"Write-Output ('BEFORE_COUNT=' + $before)",
|
|
1664
|
+
"Write-Output ('KILLED=' + $killed)",
|
|
1665
|
+
"Write-Output ('AFTER_COUNT=' + $after)",
|
|
1666
|
+
"Write-Output ('MEM_BEFORE_GB=' + $mem_before)",
|
|
1667
|
+
"Write-Output ('MEM_AFTER_GB=' + $mem_after)",
|
|
1668
|
+
].join('; ');
|
|
1669
|
+
const res = spawnSync('powershell', ['-NoProfile', '-Command', psScript], {
|
|
1670
|
+
encoding: 'utf-8', timeout: 30000, windowsHide: true,
|
|
1671
|
+
});
|
|
1672
|
+
if (res.status !== 0) {
|
|
1673
|
+
output.printError('PowerShell exited with code ' + res.status);
|
|
1674
|
+
if (res.stderr)
|
|
1675
|
+
output.writeln(output.dim(res.stderr));
|
|
1676
|
+
return { success: false, exitCode: 1 };
|
|
1677
|
+
}
|
|
1678
|
+
const parse = (key) => {
|
|
1679
|
+
const line = (res.stdout || '').split('\n').find((l) => l.startsWith(key + '='));
|
|
1680
|
+
return line ? line.slice(key.length + 1).trim() : '?';
|
|
1681
|
+
};
|
|
1682
|
+
const before = parse('BEFORE_COUNT');
|
|
1683
|
+
const killed = parse('KILLED');
|
|
1684
|
+
const after = parse('AFTER_COUNT');
|
|
1685
|
+
const memBefore = parse('MEM_BEFORE_GB');
|
|
1686
|
+
const memAfter = parse('MEM_AFTER_GB');
|
|
1687
|
+
output.writeln('conhost.exe processes:');
|
|
1688
|
+
output.writeln(' before: ' + before);
|
|
1689
|
+
output.writeln(' killed: ' + output.success(killed));
|
|
1690
|
+
output.writeln(' after: ' + after);
|
|
1691
|
+
output.writeln('');
|
|
1692
|
+
output.writeln('Free RAM:');
|
|
1693
|
+
output.writeln(' before: ' + memBefore + ' GB');
|
|
1694
|
+
output.writeln(' after: ' + memAfter + ' GB');
|
|
1695
|
+
output.writeln('');
|
|
1696
|
+
output.writeln(output.dim('Note: does NOT touch cmd.exe/bash.exe/node.exe — those may be the invoking shell'));
|
|
1697
|
+
output.writeln(output.dim(' or an active MCP server. Kill them manually if you need to.'));
|
|
1698
|
+
output.writeln('');
|
|
1699
|
+
output.writeln(output.dim('Upstream tracking: https://github.com/anthropics/claude-code/issues/67888'));
|
|
1700
|
+
return { success: true };
|
|
1701
|
+
}
|
|
1702
|
+
output.writeln();
|
|
1703
|
+
output.writeln(output.bold('RuFlo Doctor'));
|
|
1704
|
+
output.writeln(output.dim('System diagnostics and health check'));
|
|
1705
|
+
output.writeln(output.dim('─'.repeat(50)));
|
|
1706
|
+
output.writeln();
|
|
1707
|
+
const allChecks = [
|
|
1708
|
+
checkVersionFreshness,
|
|
1709
|
+
checkNodeVersion,
|
|
1710
|
+
checkNpmVersion,
|
|
1711
|
+
checkClaudeCode,
|
|
1712
|
+
checkGit,
|
|
1713
|
+
checkGitRepo,
|
|
1714
|
+
checkConfigFile,
|
|
1715
|
+
checkStaleSettingsNpx, // #2448 — runaway `npx @latest` in statusLine/hooks
|
|
1716
|
+
checkDaemonStatus,
|
|
1717
|
+
checkMemoryDatabase,
|
|
1718
|
+
checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
|
|
1719
|
+
checkApiKeys,
|
|
1720
|
+
checkMcpServers,
|
|
1721
|
+
checkAIDefence, // #1807
|
|
1722
|
+
checkDiskSpace,
|
|
1723
|
+
checkBuildTools,
|
|
1724
|
+
checkAgenticFlow,
|
|
1725
|
+
checkEncryptionAtRest, // ADR-096 Phase 5
|
|
1726
|
+
checkFederationBreaker, // ADR-097 Phase 4
|
|
1727
|
+
checkMetaharness, // ADR-150 — MetaHarness upstream package
|
|
1728
|
+
checkMetaharnessIntegration, // iter 45 — ruflo-side integration layer
|
|
1729
|
+
checkFunnel, // ADR-305 — effective funnel state + deciding precedence source
|
|
1730
|
+
checkProxySponsoredConsent, // ADR-313 — Meta LLM Proxy sponsored-downtime health
|
|
1731
|
+
checkAuth, // ADR-306 — Cognitum identity (warn-only; never fails bare `ruflo doctor`)
|
|
1732
|
+
];
|
|
1733
|
+
// #2677: `--component memory` now runs the whole memory-health suite,
|
|
1734
|
+
// not just the existence check. Values can be a single check or an
|
|
1735
|
+
// array — expanded at execution time. Stuinfla's report showed the
|
|
1736
|
+
// existence-only check reporting PASS on a 99.97%-empty and even a
|
|
1737
|
+
// SQLite-malformed DB; the array here layers integrity → content →
|
|
1738
|
+
// embedding coverage over the existing existence probe, ordered so
|
|
1739
|
+
// the earliest chain-break is always the first red the user sees.
|
|
1740
|
+
const componentMap = {
|
|
1741
|
+
'version': checkVersionFreshness,
|
|
1742
|
+
'freshness': checkVersionFreshness,
|
|
1743
|
+
'node': checkNodeVersion,
|
|
1744
|
+
'npm': checkNpmVersion,
|
|
1745
|
+
'claude': checkClaudeCode,
|
|
1746
|
+
'config': checkConfigFile,
|
|
1747
|
+
'stale-settings': checkStaleSettingsNpx, // #2448
|
|
1748
|
+
'daemon': checkDaemonStatus,
|
|
1749
|
+
'memory': [
|
|
1750
|
+
checkMemoryDatabase, // existing: exists + statable (unchanged)
|
|
1751
|
+
checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
|
|
1752
|
+
checkMemoryContent, // #2677 check 2: memory_entries content coverage
|
|
1753
|
+
checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
|
|
1754
|
+
],
|
|
1755
|
+
'learning': checkLearningBridge, // #2545
|
|
1756
|
+
'learning-bridge': checkLearningBridge, // #2545
|
|
1757
|
+
'api': checkApiKeys,
|
|
1758
|
+
'git': checkGit,
|
|
1759
|
+
'mcp': checkMcpServers,
|
|
1760
|
+
'aidefence': checkAIDefence, // #1807
|
|
1761
|
+
'disk': checkDiskSpace,
|
|
1762
|
+
'typescript': checkBuildTools,
|
|
1763
|
+
'agentic-flow': checkAgenticFlow,
|
|
1764
|
+
'encryption': checkEncryptionAtRest, // ADR-096 Phase 5
|
|
1765
|
+
'federation': checkFederationBreaker, // ADR-097 Phase 4
|
|
1766
|
+
'metaharness': checkMetaharness, // ADR-150 — upstream package
|
|
1767
|
+
'metaharness-integration': checkMetaharnessIntegration, // iter 45 — ruflo-side
|
|
1768
|
+
'funnel': checkFunnel, // ADR-305
|
|
1769
|
+
// ADR-307 — deep-dive array, same pattern as 'memory' above: the cheap
|
|
1770
|
+
// sponsored-consent check first, then binary/process/bind in the order
|
|
1771
|
+
// a user would actually debug them (is it installed? running? exposed?).
|
|
1772
|
+
'proxy': [checkProxySponsoredConsent, checkProxyBinary, checkProxyProcess, checkProxyBindAddress],
|
|
1773
|
+
'auth': checkAuth, // ADR-306
|
|
1774
|
+
};
|
|
1775
|
+
let checksToRun = allChecks;
|
|
1776
|
+
if (component && componentMap[component]) {
|
|
1777
|
+
const entry = componentMap[component];
|
|
1778
|
+
checksToRun = Array.isArray(entry) ? entry : [entry];
|
|
1779
|
+
}
|
|
1780
|
+
const results = [];
|
|
1781
|
+
const fixes = [];
|
|
1782
|
+
// OPTIMIZATION: Run all checks in parallel for 3-5x faster execution
|
|
1783
|
+
const spinner = output.createSpinner({ text: 'Running health checks in parallel...', spinner: 'dots' });
|
|
1784
|
+
spinner.start();
|
|
1785
|
+
try {
|
|
1786
|
+
// Execute all checks concurrently
|
|
1787
|
+
const checkResults = await Promise.allSettled(checksToRun.map(check => check()));
|
|
1788
|
+
spinner.stop();
|
|
1789
|
+
// Process results in order
|
|
1790
|
+
for (const settledResult of checkResults) {
|
|
1791
|
+
if (settledResult.status === 'fulfilled') {
|
|
1792
|
+
const result = settledResult.value;
|
|
1793
|
+
results.push(result);
|
|
1794
|
+
output.writeln(formatCheck(result));
|
|
1795
|
+
if (result.fix && (result.status === 'fail' || result.status === 'warn')) {
|
|
1796
|
+
fixes.push(`${result.name}: ${result.fix}`);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
const errorResult = {
|
|
1801
|
+
name: 'Check',
|
|
1802
|
+
status: 'fail',
|
|
1803
|
+
message: settledResult.reason?.message || 'Unknown error'
|
|
1804
|
+
};
|
|
1805
|
+
results.push(errorResult);
|
|
1806
|
+
output.writeln(formatCheck(errorResult));
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
catch (error) {
|
|
1811
|
+
spinner.stop();
|
|
1812
|
+
output.writeln(output.error('Failed to run health checks'));
|
|
1813
|
+
}
|
|
1814
|
+
// #2545: --fix / --install can actually repair the Learning Bridge by
|
|
1815
|
+
// recording the resolver sidecar. When doctor runs via `npx ruflo`, the CLI
|
|
1816
|
+
// CAN resolve its optional @claude-flow/memory dep (it is in the same npx
|
|
1817
|
+
// cache), so writing the sidecar makes the SessionStart hook find it.
|
|
1818
|
+
if ((showFix || autoInstall)) {
|
|
1819
|
+
const lbResult = results.find(r => r.name === 'Learning Bridge');
|
|
1820
|
+
if (lbResult && lbResult.status === 'fail') {
|
|
1821
|
+
const record = recordMemoryPackagePath(process.cwd(), 'doctor');
|
|
1822
|
+
if (record) {
|
|
1823
|
+
const newCheck = await checkLearningBridge();
|
|
1824
|
+
const idx = results.findIndex(r => r.name === 'Learning Bridge');
|
|
1825
|
+
if (idx !== -1)
|
|
1826
|
+
results[idx] = newCheck;
|
|
1827
|
+
const fixIdx = fixes.findIndex(f => f.startsWith('Learning Bridge:'));
|
|
1828
|
+
if (fixIdx !== -1 && newCheck.status === 'pass')
|
|
1829
|
+
fixes.splice(fixIdx, 1);
|
|
1830
|
+
output.writeln(output.success(`Repaired Learning Bridge — wrote .claude-flow/memory-package.json → ${record.distPath}`));
|
|
1831
|
+
output.writeln(formatCheck(newCheck));
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
// Auto-install missing dependencies if requested
|
|
1836
|
+
if (autoInstall) {
|
|
1837
|
+
const claudeCodeResult = results.find(r => r.name === 'Claude Code CLI');
|
|
1838
|
+
if (claudeCodeResult && claudeCodeResult.status !== 'pass') {
|
|
1839
|
+
const installed = await installClaudeCode();
|
|
1840
|
+
if (installed) {
|
|
1841
|
+
// Re-check Claude Code after installation
|
|
1842
|
+
const newCheck = await checkClaudeCode();
|
|
1843
|
+
const idx = results.findIndex(r => r.name === 'Claude Code CLI');
|
|
1844
|
+
if (idx !== -1) {
|
|
1845
|
+
results[idx] = newCheck;
|
|
1846
|
+
// Update fixes list
|
|
1847
|
+
const fixIdx = fixes.findIndex(f => f.startsWith('Claude Code CLI:'));
|
|
1848
|
+
if (fixIdx !== -1 && newCheck.status === 'pass') {
|
|
1849
|
+
fixes.splice(fixIdx, 1);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
output.writeln(formatCheck(newCheck));
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
// Summary
|
|
1857
|
+
const passed = results.filter(r => r.status === 'pass').length;
|
|
1858
|
+
const warnings = results.filter(r => r.status === 'warn').length;
|
|
1859
|
+
const failed = results.filter(r => r.status === 'fail').length;
|
|
1860
|
+
output.writeln();
|
|
1861
|
+
output.writeln(output.dim('─'.repeat(50)));
|
|
1862
|
+
output.writeln();
|
|
1863
|
+
const summaryParts = [
|
|
1864
|
+
output.success(`${passed} passed`),
|
|
1865
|
+
warnings > 0 ? output.warning(`${warnings} warnings`) : null,
|
|
1866
|
+
failed > 0 ? output.error(`${failed} failed`) : null
|
|
1867
|
+
].filter(Boolean);
|
|
1868
|
+
output.writeln(`Summary: ${summaryParts.join(', ')}`);
|
|
1869
|
+
// Show fixes — #1791.5: header makes it explicit these are commands you
|
|
1870
|
+
// run yourself, not actions doctor took.
|
|
1871
|
+
if (showFix && fixes.length > 0) {
|
|
1872
|
+
output.writeln();
|
|
1873
|
+
output.writeln(output.bold('Suggested commands (run them yourself):'));
|
|
1874
|
+
output.writeln();
|
|
1875
|
+
for (const fix of fixes) {
|
|
1876
|
+
output.writeln(output.dim(` ${fix}`));
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
else if (fixes.length > 0 && !showFix) {
|
|
1880
|
+
output.writeln();
|
|
1881
|
+
output.writeln(output.dim(`Run with --fix to see ${fixes.length} suggested command${fixes.length > 1 ? 's' : ''} (does not auto-apply)`));
|
|
1882
|
+
}
|
|
1883
|
+
// Overall result
|
|
1884
|
+
if (failed > 0) {
|
|
1885
|
+
output.writeln();
|
|
1886
|
+
output.writeln(output.error('Some checks failed. Please address the issues above.'));
|
|
1887
|
+
return { success: false, exitCode: 1, data: { passed, warnings, failed, results } };
|
|
1888
|
+
}
|
|
1889
|
+
else if (warnings > 0) {
|
|
1890
|
+
output.writeln();
|
|
1891
|
+
output.writeln(output.warning('All checks passed with some warnings.'));
|
|
1892
|
+
return { success: true, data: { passed, warnings, failed, results } };
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
output.writeln();
|
|
1896
|
+
output.writeln(output.success('All checks passed! System is healthy.'));
|
|
1897
|
+
return { success: true, data: { passed, warnings, failed, results } };
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
};
|
|
1901
|
+
export default doctorCommand;
|
|
1902
|
+
//# sourceMappingURL=doctor.js.map
|