@ashlr/hub 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1158 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/bin/ashlr +8 -0
- package/dist/api/core.d.ts +28 -0
- package/dist/api/core.js +42 -0
- package/dist/api/core.js.map +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.js +9 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/plugin.d.ts +12 -0
- package/dist/api/plugin.js +13 -0
- package/dist/api/plugin.js.map +1 -0
- package/dist/api/types.d.ts +9 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +23 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/ask.d.ts +22 -0
- package/dist/cli/ask.js +240 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/audit.d.ts +26 -0
- package/dist/cli/audit.js +199 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/backlog.d.ts +26 -0
- package/dist/cli/backlog.js +322 -0
- package/dist/cli/backlog.js.map +1 -0
- package/dist/cli/completions.d.ts +18 -0
- package/dist/cli/completions.js +156 -0
- package/dist/cli/completions.js.map +1 -0
- package/dist/cli/daemon.d.ts +31 -0
- package/dist/cli/daemon.js +334 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/demo-sandbox.d.ts +78 -0
- package/dist/cli/demo-sandbox.js +227 -0
- package/dist/cli/demo-sandbox.js.map +1 -0
- package/dist/cli/demo.d.ts +76 -0
- package/dist/cli/demo.js +285 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/digest.d.ts +46 -0
- package/dist/cli/digest.js +352 -0
- package/dist/cli/digest.js.map +1 -0
- package/dist/cli/doctor-init.d.ts +28 -0
- package/dist/cli/doctor-init.js +335 -0
- package/dist/cli/doctor-init.js.map +1 -0
- package/dist/cli/genome.d.ts +62 -0
- package/dist/cli/genome.js +1061 -0
- package/dist/cli/genome.js.map +1 -0
- package/dist/cli/gh.d.ts +27 -0
- package/dist/cli/gh.js +322 -0
- package/dist/cli/gh.js.map +1 -0
- package/dist/cli/goals.d.ts +55 -0
- package/dist/cli/goals.js +667 -0
- package/dist/cli/goals.js.map +1 -0
- package/dist/cli/health.d.ts +56 -0
- package/dist/cli/health.js +407 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/help.d.ts +51 -0
- package/dist/cli/help.js +436 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inbox.d.ts +28 -0
- package/dist/cli/inbox.js +589 -0
- package/dist/cli/inbox.js.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.js +1208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/knowledge.d.ts +33 -0
- package/dist/cli/knowledge.js +575 -0
- package/dist/cli/knowledge.js.map +1 -0
- package/dist/cli/mcp.d.ts +26 -0
- package/dist/cli/mcp.js +487 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/models.d.ts +26 -0
- package/dist/cli/models.js +435 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/new.d.ts +32 -0
- package/dist/cli/new.js +565 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/notify.d.ts +20 -0
- package/dist/cli/notify.js +188 -0
- package/dist/cli/notify.js.map +1 -0
- package/dist/cli/onboard.d.ts +85 -0
- package/dist/cli/onboard.js +305 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/open.d.ts +38 -0
- package/dist/cli/open.js +86 -0
- package/dist/cli/open.js.map +1 -0
- package/dist/cli/orient.d.ts +14 -0
- package/dist/cli/orient.js +110 -0
- package/dist/cli/orient.js.map +1 -0
- package/dist/cli/picker.d.ts +21 -0
- package/dist/cli/picker.js +177 -0
- package/dist/cli/picker.js.map +1 -0
- package/dist/cli/plugins.d.ts +21 -0
- package/dist/cli/plugins.js +394 -0
- package/dist/cli/plugins.js.map +1 -0
- package/dist/cli/preflight.d.ts +29 -0
- package/dist/cli/preflight.js +131 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/pulse.d.ts +26 -0
- package/dist/cli/pulse.js +482 -0
- package/dist/cli/pulse.js.map +1 -0
- package/dist/cli/reflect.d.ts +42 -0
- package/dist/cli/reflect.js +381 -0
- package/dist/cli/reflect.js.map +1 -0
- package/dist/cli/run.d.ts +29 -0
- package/dist/cli/run.js +687 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/sandbox.d.ts +20 -0
- package/dist/cli/sandbox.js +345 -0
- package/dist/cli/sandbox.js.map +1 -0
- package/dist/cli/seams.d.ts +41 -0
- package/dist/cli/seams.js +167 -0
- package/dist/cli/seams.js.map +1 -0
- package/dist/cli/serve.d.ts +25 -0
- package/dist/cli/serve.js +249 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/ship.d.ts +33 -0
- package/dist/cli/ship.js +433 -0
- package/dist/cli/ship.js.map +1 -0
- package/dist/cli/spec.d.ts +19 -0
- package/dist/cli/spec.js +478 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/cli/swarm.d.ts +42 -0
- package/dist/cli/swarm.js +1365 -0
- package/dist/cli/swarm.js.map +1 -0
- package/dist/cli/telemetry.d.ts +26 -0
- package/dist/cli/telemetry.js +364 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/cli/tui.d.ts +17 -0
- package/dist/cli/tui.js +52 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +53 -0
- package/dist/cli/ui.js +66 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/update.d.ts +38 -0
- package/dist/cli/update.js +682 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/vercel.d.ts +20 -0
- package/dist/cli/vercel.js +193 -0
- package/dist/cli/vercel.js.map +1 -0
- package/dist/cli/verify-safety.d.ts +96 -0
- package/dist/cli/verify-safety.js +509 -0
- package/dist/cli/verify-safety.js.map +1 -0
- package/dist/cli/wire.d.ts +22 -0
- package/dist/cli/wire.js +205 -0
- package/dist/cli/wire.js.map +1 -0
- package/dist/core/classify.d.ts +51 -0
- package/dist/core/classify.js +450 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/config.d.ts +63 -0
- package/dist/core/config.js +474 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/daemon/loop.d.ts +74 -0
- package/dist/core/daemon/loop.js +618 -0
- package/dist/core/daemon/loop.js.map +1 -0
- package/dist/core/daemon/state.d.ts +66 -0
- package/dist/core/daemon/state.js +197 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/dashboard.d.ts +40 -0
- package/dist/core/dashboard.js +463 -0
- package/dist/core/dashboard.js.map +1 -0
- package/dist/core/digest/build.d.ts +51 -0
- package/dist/core/digest/build.js +230 -0
- package/dist/core/digest/build.js.map +1 -0
- package/dist/core/digest/deliver.d.ts +47 -0
- package/dist/core/digest/deliver.js +230 -0
- package/dist/core/digest/deliver.js.map +1 -0
- package/dist/core/digest/store.d.ts +57 -0
- package/dist/core/digest/store.js +223 -0
- package/dist/core/digest/store.js.map +1 -0
- package/dist/core/doctor-fix.d.ts +21 -0
- package/dist/core/doctor-fix.js +440 -0
- package/dist/core/doctor-fix.js.map +1 -0
- package/dist/core/doctor.d.ts +18 -0
- package/dist/core/doctor.js +806 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/env-bridge.d.ts +64 -0
- package/dist/core/env-bridge.js +103 -0
- package/dist/core/env-bridge.js.map +1 -0
- package/dist/core/genome/capture.d.ts +49 -0
- package/dist/core/genome/capture.js +352 -0
- package/dist/core/genome/capture.js.map +1 -0
- package/dist/core/genome/consolidate.d.ts +38 -0
- package/dist/core/genome/consolidate.js +426 -0
- package/dist/core/genome/consolidate.js.map +1 -0
- package/dist/core/genome/export.d.ts +29 -0
- package/dist/core/genome/export.js +102 -0
- package/dist/core/genome/export.js.map +1 -0
- package/dist/core/genome/playbook.d.ts +33 -0
- package/dist/core/genome/playbook.js +320 -0
- package/dist/core/genome/playbook.js.map +1 -0
- package/dist/core/genome/recall.d.ts +45 -0
- package/dist/core/genome/recall.js +293 -0
- package/dist/core/genome/recall.js.map +1 -0
- package/dist/core/genome/store.d.ts +62 -0
- package/dist/core/genome/store.js +711 -0
- package/dist/core/genome/store.js.map +1 -0
- package/dist/core/git.d.ts +34 -0
- package/dist/core/git.js +115 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/goals/advance.d.ts +82 -0
- package/dist/core/goals/advance.js +263 -0
- package/dist/core/goals/advance.js.map +1 -0
- package/dist/core/goals/planner.d.ts +58 -0
- package/dist/core/goals/planner.js +233 -0
- package/dist/core/goals/planner.js.map +1 -0
- package/dist/core/goals/store.d.ts +128 -0
- package/dist/core/goals/store.js +442 -0
- package/dist/core/goals/store.js.map +1 -0
- package/dist/core/inbox/apply.d.ts +36 -0
- package/dist/core/inbox/apply.js +407 -0
- package/dist/core/inbox/apply.js.map +1 -0
- package/dist/core/inbox/notify-proposal.d.ts +13 -0
- package/dist/core/inbox/notify-proposal.js +21 -0
- package/dist/core/inbox/notify-proposal.js.map +1 -0
- package/dist/core/inbox/store.d.ts +67 -0
- package/dist/core/inbox/store.js +258 -0
- package/dist/core/inbox/store.js.map +1 -0
- package/dist/core/index-engine.d.ts +62 -0
- package/dist/core/index-engine.js +486 -0
- package/dist/core/index-engine.js.map +1 -0
- package/dist/core/integrations/desktop-notify.d.ts +18 -0
- package/dist/core/integrations/desktop-notify.js +42 -0
- package/dist/core/integrations/desktop-notify.js.map +1 -0
- package/dist/core/integrations/editors.d.ts +50 -0
- package/dist/core/integrations/editors.js +216 -0
- package/dist/core/integrations/editors.js.map +1 -0
- package/dist/core/integrations/github.d.ts +66 -0
- package/dist/core/integrations/github.js +332 -0
- package/dist/core/integrations/github.js.map +1 -0
- package/dist/core/integrations/identity.d.ts +29 -0
- package/dist/core/integrations/identity.js +359 -0
- package/dist/core/integrations/identity.js.map +1 -0
- package/dist/core/integrations/notify.d.ts +23 -0
- package/dist/core/integrations/notify.js +96 -0
- package/dist/core/integrations/notify.js.map +1 -0
- package/dist/core/integrations/vercel.d.ts +37 -0
- package/dist/core/integrations/vercel.js +192 -0
- package/dist/core/integrations/vercel.js.map +1 -0
- package/dist/core/knowledge/ask.d.ts +34 -0
- package/dist/core/knowledge/ask.js +325 -0
- package/dist/core/knowledge/ask.js.map +1 -0
- package/dist/core/knowledge/graph.d.ts +51 -0
- package/dist/core/knowledge/graph.js +564 -0
- package/dist/core/knowledge/graph.js.map +1 -0
- package/dist/core/knowledge/index.d.ts +74 -0
- package/dist/core/knowledge/index.js +586 -0
- package/dist/core/knowledge/index.js.map +1 -0
- package/dist/core/learn/playbooks.d.ts +84 -0
- package/dist/core/learn/playbooks.js +241 -0
- package/dist/core/learn/playbooks.js.map +1 -0
- package/dist/core/learn/reflect.d.ts +87 -0
- package/dist/core/learn/reflect.js +435 -0
- package/dist/core/learn/reflect.js.map +1 -0
- package/dist/core/learn/store.d.ts +51 -0
- package/dist/core/learn/store.js +165 -0
- package/dist/core/learn/store.js.map +1 -0
- package/dist/core/learn/tuning.d.ts +48 -0
- package/dist/core/learn/tuning.js +201 -0
- package/dist/core/learn/tuning.js.map +1 -0
- package/dist/core/lifecycle/scaffold.d.ts +43 -0
- package/dist/core/lifecycle/scaffold.js +260 -0
- package/dist/core/lifecycle/scaffold.js.map +1 -0
- package/dist/core/lifecycle/ship.d.ts +48 -0
- package/dist/core/lifecycle/ship.js +513 -0
- package/dist/core/lifecycle/ship.js.map +1 -0
- package/dist/core/lifecycle/templates.d.ts +20 -0
- package/dist/core/lifecycle/templates.js +605 -0
- package/dist/core/lifecycle/templates.js.map +1 -0
- package/dist/core/mcp-gateway.d.ts +59 -0
- package/dist/core/mcp-gateway.js +385 -0
- package/dist/core/mcp-gateway.js.map +1 -0
- package/dist/core/mcp-native.d.ts +53 -0
- package/dist/core/mcp-native.js +507 -0
- package/dist/core/mcp-native.js.map +1 -0
- package/dist/core/mcp-registry.d.ts +36 -0
- package/dist/core/mcp-registry.js +180 -0
- package/dist/core/mcp-registry.js.map +1 -0
- package/dist/core/observability/budget-alert.d.ts +18 -0
- package/dist/core/observability/budget-alert.js +90 -0
- package/dist/core/observability/budget-alert.js.map +1 -0
- package/dist/core/observability/estimate.d.ts +27 -0
- package/dist/core/observability/estimate.js +188 -0
- package/dist/core/observability/estimate.js.map +1 -0
- package/dist/core/observability/forecast.d.ts +19 -0
- package/dist/core/observability/forecast.js +101 -0
- package/dist/core/observability/forecast.js.map +1 -0
- package/dist/core/observability/governance.d.ts +28 -0
- package/dist/core/observability/governance.js +101 -0
- package/dist/core/observability/governance.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +88 -0
- package/dist/core/observability/otlp.js +217 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/observability/rollup.d.ts +35 -0
- package/dist/core/observability/rollup.js +311 -0
- package/dist/core/observability/rollup.js.map +1 -0
- package/dist/core/observability/telemetry-sink.d.ts +63 -0
- package/dist/core/observability/telemetry-sink.js +350 -0
- package/dist/core/observability/telemetry-sink.js.map +1 -0
- package/dist/core/observability/usage-source.d.ts +60 -0
- package/dist/core/observability/usage-source.js +347 -0
- package/dist/core/observability/usage-source.js.map +1 -0
- package/dist/core/onboard.d.ts +27 -0
- package/dist/core/onboard.js +288 -0
- package/dist/core/onboard.js.map +1 -0
- package/dist/core/orient.d.ts +23 -0
- package/dist/core/orient.js +135 -0
- package/dist/core/orient.js.map +1 -0
- package/dist/core/phantom.d.ts +23 -0
- package/dist/core/phantom.js +279 -0
- package/dist/core/phantom.js.map +1 -0
- package/dist/core/plugins/host-api.d.ts +29 -0
- package/dist/core/plugins/host-api.js +113 -0
- package/dist/core/plugins/host-api.js.map +1 -0
- package/dist/core/plugins/integrity.d.ts +36 -0
- package/dist/core/plugins/integrity.js +73 -0
- package/dist/core/plugins/integrity.js.map +1 -0
- package/dist/core/plugins/manifest.d.ts +31 -0
- package/dist/core/plugins/manifest.js +316 -0
- package/dist/core/plugins/manifest.js.map +1 -0
- package/dist/core/plugins/registry.d.ts +87 -0
- package/dist/core/plugins/registry.js +415 -0
- package/dist/core/plugins/registry.js.map +1 -0
- package/dist/core/plugins/types.d.ts +182 -0
- package/dist/core/plugins/types.js +40 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/plugins/wrappers.d.ts +40 -0
- package/dist/core/plugins/wrappers.js +229 -0
- package/dist/core/plugins/wrappers.js.map +1 -0
- package/dist/core/portfolio/backlog.d.ts +40 -0
- package/dist/core/portfolio/backlog.js +177 -0
- package/dist/core/portfolio/backlog.js.map +1 -0
- package/dist/core/portfolio/scanners.d.ts +21 -0
- package/dist/core/portfolio/scanners.js +600 -0
- package/dist/core/portfolio/scanners.js.map +1 -0
- package/dist/core/providers.d.ts +34 -0
- package/dist/core/providers.js +250 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quality/conventions.d.ts +35 -0
- package/dist/core/quality/conventions.js +267 -0
- package/dist/core/quality/conventions.js.map +1 -0
- package/dist/core/quality/fixes.d.ts +56 -0
- package/dist/core/quality/fixes.js +209 -0
- package/dist/core/quality/fixes.js.map +1 -0
- package/dist/core/quality/health.d.ts +69 -0
- package/dist/core/quality/health.js +350 -0
- package/dist/core/quality/health.js.map +1 -0
- package/dist/core/quality/store.d.ts +56 -0
- package/dist/core/quality/store.js +195 -0
- package/dist/core/quality/store.js.map +1 -0
- package/dist/core/readiness.d.ts +112 -0
- package/dist/core/readiness.js +431 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core/run/agent-loop.d.ts +40 -0
- package/dist/core/run/agent-loop.js +291 -0
- package/dist/core/run/agent-loop.js.map +1 -0
- package/dist/core/run/budget.d.ts +47 -0
- package/dist/core/run/budget.js +113 -0
- package/dist/core/run/budget.js.map +1 -0
- package/dist/core/run/engines.d.ts +75 -0
- package/dist/core/run/engines.js +199 -0
- package/dist/core/run/engines.js.map +1 -0
- package/dist/core/run/model-manager.d.ts +64 -0
- package/dist/core/run/model-manager.js +339 -0
- package/dist/core/run/model-manager.js.map +1 -0
- package/dist/core/run/orchestrator.d.ts +100 -0
- package/dist/core/run/orchestrator.js +1515 -0
- package/dist/core/run/orchestrator.js.map +1 -0
- package/dist/core/run/provider-client.d.ts +46 -0
- package/dist/core/run/provider-client.js +796 -0
- package/dist/core/run/provider-client.js.map +1 -0
- package/dist/core/run/retry.d.ts +19 -0
- package/dist/core/run/retry.js +68 -0
- package/dist/core/run/retry.js.map +1 -0
- package/dist/core/run/router.d.ts +50 -0
- package/dist/core/run/router.js +257 -0
- package/dist/core/run/router.js.map +1 -0
- package/dist/core/run/self-heal.d.ts +52 -0
- package/dist/core/run/self-heal.js +181 -0
- package/dist/core/run/self-heal.js.map +1 -0
- package/dist/core/run/streaming.d.ts +31 -0
- package/dist/core/run/streaming.js +122 -0
- package/dist/core/run/streaming.js.map +1 -0
- package/dist/core/run/verify.d.ts +30 -0
- package/dist/core/run/verify.js +204 -0
- package/dist/core/run/verify.js.map +1 -0
- package/dist/core/sandbox/audit.d.ts +31 -0
- package/dist/core/sandbox/audit.js +169 -0
- package/dist/core/sandbox/audit.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +61 -0
- package/dist/core/sandbox/policy.js +211 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- package/dist/core/sandbox/worktree.d.ts +175 -0
- package/dist/core/sandbox/worktree.js +673 -0
- package/dist/core/sandbox/worktree.js.map +1 -0
- package/dist/core/seams/backlog.d.ts +47 -0
- package/dist/core/seams/backlog.js +47 -0
- package/dist/core/seams/backlog.js.map +1 -0
- package/dist/core/seams/daemon-coordinator.d.ts +72 -0
- package/dist/core/seams/daemon-coordinator.js +76 -0
- package/dist/core/seams/daemon-coordinator.js.map +1 -0
- package/dist/core/seams/genome.d.ts +45 -0
- package/dist/core/seams/genome.js +53 -0
- package/dist/core/seams/genome.js.map +1 -0
- package/dist/core/seams/identity.d.ts +40 -0
- package/dist/core/seams/identity.js +44 -0
- package/dist/core/seams/identity.js.map +1 -0
- package/dist/core/seams/inbox.d.ts +60 -0
- package/dist/core/seams/inbox.js +66 -0
- package/dist/core/seams/inbox.js.map +1 -0
- package/dist/core/seams/index.d.ts +20 -0
- package/dist/core/seams/index.js +21 -0
- package/dist/core/seams/index.js.map +1 -0
- package/dist/core/seams/portfolio.d.ts +50 -0
- package/dist/core/seams/portfolio.js +61 -0
- package/dist/core/seams/portfolio.js.map +1 -0
- package/dist/core/seams/registry.d.ts +42 -0
- package/dist/core/seams/registry.js +128 -0
- package/dist/core/seams/registry.js.map +1 -0
- package/dist/core/seams/run-swarm.d.ts +66 -0
- package/dist/core/seams/run-swarm.js +74 -0
- package/dist/core/seams/run-swarm.js.map +1 -0
- package/dist/core/seams/types.d.ts +123 -0
- package/dist/core/seams/types.js +35 -0
- package/dist/core/seams/types.js.map +1 -0
- package/dist/core/spec/spec-store.d.ts +62 -0
- package/dist/core/spec/spec-store.js +359 -0
- package/dist/core/spec/spec-store.js.map +1 -0
- package/dist/core/swarm/gate.d.ts +45 -0
- package/dist/core/swarm/gate.js +114 -0
- package/dist/core/swarm/gate.js.map +1 -0
- package/dist/core/swarm/planner.d.ts +32 -0
- package/dist/core/swarm/planner.js +293 -0
- package/dist/core/swarm/planner.js.map +1 -0
- package/dist/core/swarm/rollback.d.ts +56 -0
- package/dist/core/swarm/rollback.js +266 -0
- package/dist/core/swarm/rollback.js.map +1 -0
- package/dist/core/swarm/runner.d.ts +62 -0
- package/dist/core/swarm/runner.js +1263 -0
- package/dist/core/swarm/runner.js.map +1 -0
- package/dist/core/swarm/sign.d.ts +71 -0
- package/dist/core/swarm/sign.js +362 -0
- package/dist/core/swarm/sign.js.map +1 -0
- package/dist/core/swarm/store.d.ts +52 -0
- package/dist/core/swarm/store.js +195 -0
- package/dist/core/swarm/store.js.map +1 -0
- package/dist/core/tidy.d.ts +32 -0
- package/dist/core/tidy.js +354 -0
- package/dist/core/tidy.js.map +1 -0
- package/dist/core/tools-registry.d.ts +16 -0
- package/dist/core/tools-registry.js +308 -0
- package/dist/core/tools-registry.js.map +1 -0
- package/dist/core/types.d.ts +2545 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/web/api.d.ts +53 -0
- package/dist/core/web/api.js +698 -0
- package/dist/core/web/api.js.map +1 -0
- package/dist/core/web/public/app.js +1906 -0
- package/dist/core/web/public/index.html +721 -0
- package/dist/core/web/public/styles.css +2007 -0
- package/dist/core/web/server.d.ts +18 -0
- package/dist/core/web/server.js +122 -0
- package/dist/core/web/server.js.map +1 -0
- package/dist/core/web/static.d.ts +18 -0
- package/dist/core/web/static.js +122 -0
- package/dist/core/web/static.js.map +1 -0
- package/dist/tui/app.d.ts +33 -0
- package/dist/tui/app.js +350 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/render.d.ts +20 -0
- package/dist/tui/render.js +558 -0
- package/dist/tui/render.js.map +1 -0
- package/package.json +80 -0
- package/schema/config.schema.json +223 -0
|
@@ -0,0 +1,806 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/doctor.ts — one-glance health check for ashlr-hub.
|
|
3
|
+
*
|
|
4
|
+
* `runDoctor` probes all configured integrations and returns a typed
|
|
5
|
+
* DoctorReport. It NEVER throws — a failed probe becomes a 'fail' DoctorCheck.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { CONFIG_PATH, INDEX_PATH, loadConfig } from './config.js';
|
|
12
|
+
import { getPhantomStatus } from './phantom.js';
|
|
13
|
+
import { getProviderRegistry } from './providers.js';
|
|
14
|
+
// H7 — 5 NEW read-only probes share the SAME read-only readiness facets that
|
|
15
|
+
// `ashlr preflight` uses, from the shared readiness module (single source of
|
|
16
|
+
// truth; no drift). See docs/contracts/CONTRACT-H7.md (BUILD ITEM 2). These
|
|
17
|
+
// helpers MUTATE NOTHING (the lone write is checkAshlrWriteable's self-cleaning
|
|
18
|
+
// sentinel under ~/.ashlr). Imported so doctor + preflight never diverge.
|
|
19
|
+
import { readEnrollmentState, readDaemonHealth, readKillState, checkAshlrWriteable, readSandboxHealth, SANDBOX_ORPHAN_WARN_THRESHOLD, } from './readiness.js';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function check(id, label, status, detail, fix) {
|
|
24
|
+
const c = { id, label, status, detail };
|
|
25
|
+
if (fix !== undefined)
|
|
26
|
+
c.fix = fix;
|
|
27
|
+
return c;
|
|
28
|
+
}
|
|
29
|
+
/** Run a command synchronously, returning stdout trimmed or null on error. */
|
|
30
|
+
function runCmd(cmd, args) {
|
|
31
|
+
try {
|
|
32
|
+
const result = spawnSync(cmd, args, { encoding: 'utf8', timeout: 5000 });
|
|
33
|
+
if (result.error || result.status !== 0)
|
|
34
|
+
return null;
|
|
35
|
+
return (result.stdout ?? '').trim() || null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// M33: plugin-layer probe (read-only; manifests + integrity pins only)
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
async function checkPlugins(cfg) {
|
|
45
|
+
try {
|
|
46
|
+
const { discoverPlugins } = await import('./plugins/registry.js');
|
|
47
|
+
const { hashEntry } = await import('./plugins/integrity.js');
|
|
48
|
+
const { join } = await import('node:path');
|
|
49
|
+
const enabled = cfg.plugins?.enabled ?? [];
|
|
50
|
+
if (enabled.length === 0) {
|
|
51
|
+
return check('plugins', 'Plugins', 'pass', 'none enabled (default-off)');
|
|
52
|
+
}
|
|
53
|
+
const found = discoverPlugins();
|
|
54
|
+
const problems = [];
|
|
55
|
+
for (const name of enabled) {
|
|
56
|
+
const p = found.find((f) => f.manifest?.name === name);
|
|
57
|
+
if (!p) {
|
|
58
|
+
problems.push(`${name}: enabled but missing under ~/.ashlr/plugins/`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (!p.ok || !p.manifest) {
|
|
62
|
+
problems.push(`${name}: invalid manifest (${p.reason ?? 'unknown'})`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const pin = cfg.plugins?.integrity?.[name];
|
|
66
|
+
const live = hashEntry(join(p.dir, p.manifest.entry));
|
|
67
|
+
if (!pin)
|
|
68
|
+
problems.push(`${name}: no integrity pin — re-run \`ashlr plugins enable ${name}\``);
|
|
69
|
+
else if (live !== pin)
|
|
70
|
+
problems.push(`${name}: entry file changed since enable (integrity mismatch)`);
|
|
71
|
+
}
|
|
72
|
+
if (problems.length > 0) {
|
|
73
|
+
return check('plugins', 'Plugins', 'warn', problems.join('; '), 'Fix or re-enable the listed plugins (`ashlr plugins enable <name>`), or disable them.');
|
|
74
|
+
}
|
|
75
|
+
return check('plugins', 'Plugins', 'pass', `${enabled.length} enabled, all pins verified`);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return check('plugins', 'Plugins', 'pass', 'plugin layer not built — skipped');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Dynamic import helpers for M3 modules — gracefully no-ops if not built yet
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
/**
|
|
85
|
+
* Attempt to load discoverMcpServers from mcp-registry.
|
|
86
|
+
* Returns null if the module is unavailable (not yet built).
|
|
87
|
+
*/
|
|
88
|
+
async function tryDiscoverMcpServers() {
|
|
89
|
+
try {
|
|
90
|
+
// Dynamic import so doctor.ts compiles and runs even before mcp-registry is built.
|
|
91
|
+
const mod = await import('./mcp-registry.js');
|
|
92
|
+
return mod.discoverMcpServers();
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Attempt to load getToolsRegistry from tools-registry.
|
|
100
|
+
* Returns null if the module is unavailable (not yet built).
|
|
101
|
+
*/
|
|
102
|
+
async function tryGetToolsRegistry() {
|
|
103
|
+
try {
|
|
104
|
+
const mod = await import('./tools-registry.js');
|
|
105
|
+
return mod.getToolsRegistry();
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Attempt to build a rollup via the M5 observability module for the budget
|
|
113
|
+
* window configured in cfg (default '7d'). Returns null when the module is
|
|
114
|
+
* not yet built or if any error occurs — caller must degrade gracefully.
|
|
115
|
+
*/
|
|
116
|
+
async function tryBuildBudgetRollup(cfg) {
|
|
117
|
+
try {
|
|
118
|
+
const win = cfg.telemetry?.budgetWindow ?? '7d';
|
|
119
|
+
// Defeat static resolution so tsc doesn't error when the M5 module is not
|
|
120
|
+
// yet built. Same pattern used for the M3 mcp-registry / tools-registry
|
|
121
|
+
// dynamic imports above. At runtime Node resolves the real .js path.
|
|
122
|
+
const specifier = './observability/rollup.js';
|
|
123
|
+
const mod = await import(/* @vite-ignore */ specifier);
|
|
124
|
+
return mod.buildRollup(win, cfg);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Attempt to call evalGovernance from the M19 governance module.
|
|
132
|
+
* Returns null if the module is unavailable (not yet built) or throws.
|
|
133
|
+
* NEVER returns PAT values — only the GovernanceStatus metadata shape.
|
|
134
|
+
*/
|
|
135
|
+
async function tryEvalGovernance(cfg) {
|
|
136
|
+
try {
|
|
137
|
+
const specifier = './observability/governance.js';
|
|
138
|
+
const mod = await import(/* @vite-ignore */ specifier);
|
|
139
|
+
return mod.evalGovernance(cfg);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Attempt to load patAvailable from the M19 telemetry-sink module and derive
|
|
147
|
+
* the active sink type. Returns null if the module is unavailable or throws.
|
|
148
|
+
* Boolean flags ONLY — PAT value is never returned, logged, or stored here.
|
|
149
|
+
*/
|
|
150
|
+
async function tryGetTelemetrySinkInfo(cfg) {
|
|
151
|
+
try {
|
|
152
|
+
const specifier = './observability/telemetry-sink.js';
|
|
153
|
+
const mod = await import(/* @vite-ignore */ specifier);
|
|
154
|
+
const endpointConfigured = Boolean(cfg.telemetry?.pulse);
|
|
155
|
+
const patConfigured = mod.patAvailable(cfg); // boolean only — never logs/returns the value
|
|
156
|
+
// OtlpHttpSink is selected only when BOTH endpoint AND PAT are present.
|
|
157
|
+
const sinkType = endpointConfigured && patConfigured ? 'otlp' : 'local';
|
|
158
|
+
return { sinkType, endpointConfigured, patConfigured };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Attempt to load genomeHealth from the M7 genome store module.
|
|
166
|
+
* Returns null if the module is unavailable (not yet built) or throws.
|
|
167
|
+
*/
|
|
168
|
+
async function tryGetGenomeHealth(cfg) {
|
|
169
|
+
try {
|
|
170
|
+
const specifier = './genome/store.js';
|
|
171
|
+
const mod = await import(/* @vite-ignore */ specifier);
|
|
172
|
+
return mod.genomeHealth(cfg);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Attempt to load getIdentity from the M18 identity integration module.
|
|
180
|
+
* Returns null if the module is unavailable (not yet built) or throws.
|
|
181
|
+
* NAMES/status only — NEVER secret values.
|
|
182
|
+
*/
|
|
183
|
+
async function tryGetIdentity() {
|
|
184
|
+
try {
|
|
185
|
+
const specifier = './integrations/identity.js';
|
|
186
|
+
const mod = await import(/* @vite-ignore */ specifier);
|
|
187
|
+
return mod.getIdentity();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
// Individual check implementations — each returns a DoctorCheck, never throws
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
/**
|
|
197
|
+
* Check: Spend budget (M5 observability).
|
|
198
|
+
* id: 'budget-spend'
|
|
199
|
+
*
|
|
200
|
+
* Builds a rollup for the configured budget window (default '7d') and
|
|
201
|
+
* inspects the BudgetAlert level:
|
|
202
|
+
* - 'ok' or no cap configured → pass
|
|
203
|
+
* - 'warn' (>= 80 % of cap) → warn
|
|
204
|
+
* - 'over' (cap exceeded) → fail
|
|
205
|
+
*
|
|
206
|
+
* If the rollup module is not yet available or throws, degrades to pass/skip
|
|
207
|
+
* so that doctor never hangs or crashes due to missing M5 modules.
|
|
208
|
+
*
|
|
209
|
+
* @param rollup - pre-fetched rollup result (or null on unavailability)
|
|
210
|
+
*/
|
|
211
|
+
function checkSpendBudget(rollup) {
|
|
212
|
+
// Module not available yet or rollup failed — degrade silently to pass.
|
|
213
|
+
if (rollup === null) {
|
|
214
|
+
return check('budget-spend', 'Spend budget', 'pass', 'Observability module not yet available — skipping budget check');
|
|
215
|
+
}
|
|
216
|
+
const { budget } = rollup;
|
|
217
|
+
// No cap configured on either dimension → nothing to alert on.
|
|
218
|
+
if (budget.capUsd === null && budget.capTokens === null) {
|
|
219
|
+
return check('budget-spend', 'Spend budget', 'pass', `No budget cap configured for ${budget.window} window`);
|
|
220
|
+
}
|
|
221
|
+
if (budget.level === 'over') {
|
|
222
|
+
return check('budget-spend', 'Spend budget', 'fail', budget.message, 'review ashlr pulse; raise cap or reduce spend');
|
|
223
|
+
}
|
|
224
|
+
if (budget.level === 'warn') {
|
|
225
|
+
return check('budget-spend', 'Spend budget', 'warn', budget.message, 'review ashlr pulse; raise cap or reduce spend');
|
|
226
|
+
}
|
|
227
|
+
// level === 'ok'
|
|
228
|
+
return check('budget-spend', 'Spend budget', 'pass', budget.message);
|
|
229
|
+
}
|
|
230
|
+
/** node version >= 18 */
|
|
231
|
+
function checkNodeVersion() {
|
|
232
|
+
try {
|
|
233
|
+
const ver = process.version; // e.g. "v22.1.0"
|
|
234
|
+
const major = parseInt(ver.replace(/^v/, '').split('.')[0] ?? '0', 10);
|
|
235
|
+
if (major >= 18) {
|
|
236
|
+
return check('node', 'Node.js version', 'pass', `${ver} (>= 18 required)`);
|
|
237
|
+
}
|
|
238
|
+
return check('node', 'Node.js version', 'fail', `${ver} — need >= v18`, 'Install Node.js 18+ from https://nodejs.org');
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
return check('node', 'Node.js version', 'fail', String(err));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** git present on PATH */
|
|
245
|
+
function checkGit() {
|
|
246
|
+
const out = runCmd('git', ['--version']);
|
|
247
|
+
if (out) {
|
|
248
|
+
return check('git', 'git installed', 'pass', out);
|
|
249
|
+
}
|
|
250
|
+
return check('git', 'git installed', 'fail', 'git not found on PATH', 'Install git: https://git-scm.com');
|
|
251
|
+
}
|
|
252
|
+
/** ~/.local/bin on PATH */
|
|
253
|
+
function checkLocalBin() {
|
|
254
|
+
const localBin = join(homedir(), '.local/bin');
|
|
255
|
+
const pathDirs = (process.env['PATH'] ?? '').split(':');
|
|
256
|
+
if (pathDirs.includes(localBin)) {
|
|
257
|
+
return check('local-bin', '~/.local/bin on PATH', 'pass', `${localBin} is on PATH`);
|
|
258
|
+
}
|
|
259
|
+
return check('local-bin', '~/.local/bin on PATH', 'warn', `${localBin} is NOT on PATH`, `Add to your shell profile: export PATH="$HOME/.local/bin:$PATH"`);
|
|
260
|
+
}
|
|
261
|
+
/** ashlr binary present (which ashlr) */
|
|
262
|
+
function checkAshlrInstalled() {
|
|
263
|
+
const out = runCmd('which', ['ashlr']);
|
|
264
|
+
if (out) {
|
|
265
|
+
return check('ashlr', 'ashlr installed', 'pass', out);
|
|
266
|
+
}
|
|
267
|
+
return check('ashlr', 'ashlr installed', 'fail', 'ashlr not found on PATH', 'Run: npm install -g ashlr (or ensure ~/.local/bin is on PATH)');
|
|
268
|
+
}
|
|
269
|
+
/** ~/.ashlr/config.json exists and parses */
|
|
270
|
+
function checkConfig() {
|
|
271
|
+
try {
|
|
272
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
273
|
+
return check('config', 'Config file exists', 'fail', `${CONFIG_PATH} not found`, 'Run: ashlr init');
|
|
274
|
+
}
|
|
275
|
+
loadConfig(); // will throw on parse failure
|
|
276
|
+
return check('config', 'Config file exists', 'pass', `${CONFIG_PATH} is valid JSON`);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
return check('config', 'Config file exists', 'fail', `${CONFIG_PATH} is invalid: ${String(err)}`, 'Fix JSON syntax errors in ~/.ashlr/config.json');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** ~/.ashlr/index.json present + not stale (> 7 days old = warn) */
|
|
283
|
+
function checkIndex() {
|
|
284
|
+
try {
|
|
285
|
+
if (!existsSync(INDEX_PATH)) {
|
|
286
|
+
return check('index', 'Index file present', 'warn', `${INDEX_PATH} not found — run ashlr index to build it`, 'ashlr index');
|
|
287
|
+
}
|
|
288
|
+
const raw = readFileSync(INDEX_PATH, 'utf8');
|
|
289
|
+
let generatedAt = null;
|
|
290
|
+
try {
|
|
291
|
+
const parsed = JSON.parse(raw);
|
|
292
|
+
if (parsed.generatedAt) {
|
|
293
|
+
generatedAt = new Date(parsed.generatedAt);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// If we can't parse it, fall back to mtime
|
|
298
|
+
}
|
|
299
|
+
const ageMs = generatedAt
|
|
300
|
+
? Date.now() - generatedAt.getTime()
|
|
301
|
+
: Date.now() - statSync(INDEX_PATH).mtimeMs;
|
|
302
|
+
const ageDays = ageMs / (1000 * 60 * 60 * 24);
|
|
303
|
+
const ageLabel = `${ageDays.toFixed(1)} days old`;
|
|
304
|
+
if (ageDays > 7) {
|
|
305
|
+
return check('index', 'Index file freshness', 'warn', `Index is ${ageLabel} (> 7 days)`, 'ashlr index');
|
|
306
|
+
}
|
|
307
|
+
return check('index', 'Index file freshness', 'pass', `Index is ${ageLabel}`);
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
return check('index', 'Index file present', 'warn', `Could not read index: ${String(err)}`, 'ashlr index');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/** phantom installed + initialized */
|
|
314
|
+
function checkPhantom() {
|
|
315
|
+
try {
|
|
316
|
+
const status = getPhantomStatus();
|
|
317
|
+
if (!status.installed) {
|
|
318
|
+
return check('phantom', 'Phantom secrets CLI', 'warn', 'phantom not found on PATH', 'Install: brew install ashlrai/tap/phantom or https://phantom.sh');
|
|
319
|
+
}
|
|
320
|
+
const ver = status.version ? ` v${status.version}` : '';
|
|
321
|
+
if (!status.initialized) {
|
|
322
|
+
return check('phantom', 'Phantom secrets CLI', 'warn', `phantom${ver} installed but vault not initialized`, 'phantom init');
|
|
323
|
+
}
|
|
324
|
+
const count = status.secretNames.length;
|
|
325
|
+
return check('phantom', 'Phantom secrets CLI', 'pass', `phantom${ver} installed, vault initialized (${count} secret${count !== 1 ? 's' : ''})`);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
return check('phantom', 'Phantom secrets CLI', 'warn', `Could not determine phantom status: ${String(err)}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Detect whether the ashlr MCP server is registered in any of the known
|
|
333
|
+
* Claude Code config locations. Upgraded in M3: also accepts discovery via
|
|
334
|
+
* discoverMcpServers() so it passes if the "ashlr" server is found in any
|
|
335
|
+
* known config file (including ~/.ashlrcode/settings.json, ashlr-workbench, etc.).
|
|
336
|
+
*
|
|
337
|
+
* The mcpRegistry parameter is the pre-fetched result of discoverMcpServers()
|
|
338
|
+
* (or null when the module is not available), passed in from runDoctor to avoid
|
|
339
|
+
* duplicate invocations.
|
|
340
|
+
*/
|
|
341
|
+
function checkMcpPlugin(mcpRegistry) {
|
|
342
|
+
// First: check via the M3 mcp-registry if available — covers all known paths
|
|
343
|
+
// including ~/.ashlrcode/settings.json and ashlr-workbench agent settings.
|
|
344
|
+
if (mcpRegistry !== null) {
|
|
345
|
+
const ashlrServer = mcpRegistry.servers.find((s) => s.name === 'ashlr' || s.name.includes('ashlr'));
|
|
346
|
+
if (ashlrServer) {
|
|
347
|
+
return check('mcp-plugin', 'ashlr MCP plugin registered', 'pass', `Found server "${ashlrServer.name}" in ${ashlrServer.source}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Fallback: manual scan of the canonical three paths (works even without mcp-registry).
|
|
351
|
+
const candidates = [
|
|
352
|
+
join(homedir(), '.claude/settings.json'),
|
|
353
|
+
join(homedir(), '.mcp.json'),
|
|
354
|
+
join(homedir(), '.claude.json'),
|
|
355
|
+
];
|
|
356
|
+
for (const filePath of candidates) {
|
|
357
|
+
try {
|
|
358
|
+
if (!existsSync(filePath))
|
|
359
|
+
continue;
|
|
360
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
361
|
+
const parsed = JSON.parse(raw);
|
|
362
|
+
// mcpServers is the standard key in both settings.json and .mcp.json
|
|
363
|
+
const servers = parsed['mcpServers'];
|
|
364
|
+
if (typeof servers === 'object' && servers !== null) {
|
|
365
|
+
const keys = Object.keys(servers);
|
|
366
|
+
// Look for any key that is exactly "ashlr" or contains "ashlr"
|
|
367
|
+
const found = keys.find((k) => k === 'ashlr' || k.includes('ashlr'));
|
|
368
|
+
if (found) {
|
|
369
|
+
return check('mcp-plugin', 'ashlr MCP plugin registered', 'pass', `Found server "${found}" in ${filePath}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// Ignore unreadable/unparseable files
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return check('mcp-plugin', 'ashlr MCP plugin registered', 'warn', 'ashlr MCP server not found in ~/.claude/settings.json, ~/.mcp.json, or ~/.claude.json', 'Add the ashlr MCP server via: ashlr init or install the ashlr Claude Code plugin');
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Check: MCP servers discovered.
|
|
381
|
+
* id: 'mcp-servers-discovered'
|
|
382
|
+
* pass if >=1 server found, warn if 0.
|
|
383
|
+
*
|
|
384
|
+
* The mcpRegistry parameter is the pre-fetched result of discoverMcpServers()
|
|
385
|
+
* (or null when the module is not available).
|
|
386
|
+
*/
|
|
387
|
+
function checkMcpServersDiscovered(mcpRegistry) {
|
|
388
|
+
if (mcpRegistry === null) {
|
|
389
|
+
// Module not available (not yet built) — soft warn rather than fail.
|
|
390
|
+
return check('mcp-servers-discovered', 'MCP servers discovered', 'warn', 'MCP registry module not available', 'configure MCP servers or run ashlr init');
|
|
391
|
+
}
|
|
392
|
+
const count = mcpRegistry.servers.length;
|
|
393
|
+
if (count >= 1) {
|
|
394
|
+
return check('mcp-servers-discovered', 'MCP servers discovered', 'pass', `${count} MCP server${count !== 1 ? 's' : ''} discovered across known configs`);
|
|
395
|
+
}
|
|
396
|
+
return check('mcp-servers-discovered', 'MCP servers discovered', 'warn', 'No MCP servers discovered in known config locations', 'configure MCP servers or run ashlr init');
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Build a compact version-aware summary for installed ecosystem tools.
|
|
400
|
+
*
|
|
401
|
+
* Returns a string like:
|
|
402
|
+
* "phantom 0.6.0, ashlrcode 1.2.3, aw (no version); 3/10 installed"
|
|
403
|
+
*
|
|
404
|
+
* Rules:
|
|
405
|
+
* - Only installed tools are listed (installed === true).
|
|
406
|
+
* - Version shown when non-null; "(no version)" appended when null.
|
|
407
|
+
* - At most MAX_LISTED tool names shown inline; excess collapsed to "+ N more".
|
|
408
|
+
* - Trailing summary "X/Y installed" always appended.
|
|
409
|
+
* - Never throws.
|
|
410
|
+
*/
|
|
411
|
+
function buildVersionDetail(tools, count, total) {
|
|
412
|
+
// Max tools to list by name before collapsing — keeps the detail line readable.
|
|
413
|
+
const MAX_LISTED = 6;
|
|
414
|
+
const installed = tools.filter((t) => t.installed);
|
|
415
|
+
const listed = installed.slice(0, MAX_LISTED);
|
|
416
|
+
const overflow = installed.length - listed.length;
|
|
417
|
+
const parts = listed.map((t) => t.version ? `${t.id} ${t.version}` : `${t.id} (no version)`);
|
|
418
|
+
if (overflow > 0) {
|
|
419
|
+
parts.push(`+${overflow} more`);
|
|
420
|
+
}
|
|
421
|
+
const suffix = `${count}/${total} installed`;
|
|
422
|
+
if (parts.length === 0) {
|
|
423
|
+
return suffix;
|
|
424
|
+
}
|
|
425
|
+
return `${parts.join(', ')}; ${suffix}`;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Check: Ashlr ecosystem tools installed.
|
|
429
|
+
* id: 'ashlr-tools-installed'
|
|
430
|
+
* pass with count, warn if <2 tools installed.
|
|
431
|
+
*
|
|
432
|
+
* The toolsRegistry parameter is the pre-fetched result of getToolsRegistry()
|
|
433
|
+
* (or null when the module is not available).
|
|
434
|
+
*
|
|
435
|
+
* M10: detail now includes per-tool version strings so `ashlr doctor` surfaces
|
|
436
|
+
* lightweight version awareness without any network calls.
|
|
437
|
+
* Example detail: "phantom 0.6.0, ashlrcode 1.2.3, aw (no version); 3/10 installed"
|
|
438
|
+
*/
|
|
439
|
+
function checkAshlrToolsInstalled(toolsRegistry) {
|
|
440
|
+
if (toolsRegistry === null) {
|
|
441
|
+
// Module not available (not yet built) — soft warn rather than fail.
|
|
442
|
+
return check('ashlr-tools-installed', 'Ashlr tools installed', 'warn', 'Tools registry module not available', 'run ashlr init to set up the ecosystem');
|
|
443
|
+
}
|
|
444
|
+
const count = toolsRegistry.installedCount;
|
|
445
|
+
const total = toolsRegistry.tools.length;
|
|
446
|
+
const detail = buildVersionDetail(toolsRegistry.tools, count, total);
|
|
447
|
+
if (count >= 2) {
|
|
448
|
+
return check('ashlr-tools-installed', 'Ashlr tools installed', 'pass', detail);
|
|
449
|
+
}
|
|
450
|
+
return check('ashlr-tools-installed', 'Ashlr tools installed', 'warn', count === 0 ? `No ecosystem tools installed; ${count}/${total} installed` : detail, 'Install ashlr ecosystem tools (phantom, ashlrcode, aw, etc.)');
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Check: Genome memory (M7).
|
|
454
|
+
* id: 'genome-memory'
|
|
455
|
+
*
|
|
456
|
+
* Passes when the aggregated genome has at least one entry; warns when empty
|
|
457
|
+
* so the user knows to seed it. Never throws.
|
|
458
|
+
*
|
|
459
|
+
* @param health - pre-fetched genomeHealth result (or null when module unavailable)
|
|
460
|
+
*/
|
|
461
|
+
function checkGenomeMemory(health) {
|
|
462
|
+
// Module not yet built — degrade silently to pass so doctor never breaks.
|
|
463
|
+
if (health === null) {
|
|
464
|
+
return check('genome-memory', 'Genome memory', 'pass', 'Genome module not yet available — skipping memory check');
|
|
465
|
+
}
|
|
466
|
+
if (health.totalEntries > 0) {
|
|
467
|
+
const embLabel = health.embeddingsAvailable ? 'embeddings available' : 'keyword-only';
|
|
468
|
+
return check('genome-memory', 'Genome memory', 'pass', `${health.totalEntries} entr${health.totalEntries !== 1 ? 'ies' : 'y'} across ${health.projects} project${health.projects !== 1 ? 's' : ''} (${embLabel})`);
|
|
469
|
+
}
|
|
470
|
+
return check('genome-memory', 'Genome memory', 'warn', 'No genome entries found', 'run ashlr learn to seed memory, or ensure .ashlrcode/genome exists');
|
|
471
|
+
}
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// M18: Identity check
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
/**
|
|
476
|
+
* Check: Phantom cloud identity (M18).
|
|
477
|
+
* id: 'identity'
|
|
478
|
+
*
|
|
479
|
+
* Passes when phantom is logged in; warns (not fails) when phantom is not
|
|
480
|
+
* logged in or the module is unavailable — identity is opt-in. NAMES/status
|
|
481
|
+
* only — NEVER secret values. Degrades gracefully.
|
|
482
|
+
*
|
|
483
|
+
* @param identity - pre-fetched Identity result (or null when module unavailable)
|
|
484
|
+
*/
|
|
485
|
+
function checkIdentity(identity) {
|
|
486
|
+
// Module not yet built — degrade to pass so doctor never breaks.
|
|
487
|
+
if (identity === null) {
|
|
488
|
+
return check('identity', 'Phantom identity', 'pass', 'Identity module not yet available — skipping');
|
|
489
|
+
}
|
|
490
|
+
if (identity.loggedIn) {
|
|
491
|
+
const parts = [];
|
|
492
|
+
if (identity.user)
|
|
493
|
+
parts.push(identity.user);
|
|
494
|
+
if (identity.tier)
|
|
495
|
+
parts.push(`tier ${identity.tier}`);
|
|
496
|
+
if (identity.team)
|
|
497
|
+
parts.push(`team ${identity.team}`);
|
|
498
|
+
const detail = parts.length > 0 ? parts.join(' · ') : 'logged in';
|
|
499
|
+
return check('identity', 'Phantom identity', 'pass', detail);
|
|
500
|
+
}
|
|
501
|
+
return check('identity', 'Phantom identity', 'warn', 'Not logged in to Phantom cloud — identity unavailable', 'phantom login (or phantom cloud login)');
|
|
502
|
+
}
|
|
503
|
+
// ---------------------------------------------------------------------------
|
|
504
|
+
// M19: Spend governance check
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
/**
|
|
507
|
+
* Check: Spend governance (M19).
|
|
508
|
+
* id: 'spend-governance'
|
|
509
|
+
*
|
|
510
|
+
* Evaluates the period spend vs. the configured cap via evalGovernance:
|
|
511
|
+
* - ok or no cap → pass
|
|
512
|
+
* - warn (>= 80%) → warn
|
|
513
|
+
* - over (> cap) → fail with fix hint
|
|
514
|
+
*
|
|
515
|
+
* Degrades to pass when the governance module is not yet available.
|
|
516
|
+
* Never throws. NEVER exposes PAT, endpoint URL body, or spend detail beyond
|
|
517
|
+
* what GovernanceStatus.message already contains (metadata only).
|
|
518
|
+
*
|
|
519
|
+
* @param governance - pre-fetched GovernanceStatus result (or null when unavailable)
|
|
520
|
+
*/
|
|
521
|
+
function checkSpendGovernance(governance) {
|
|
522
|
+
if (governance === null) {
|
|
523
|
+
return check('spend-governance', 'Spend governance', 'pass', 'Governance module not yet available — skipping');
|
|
524
|
+
}
|
|
525
|
+
if (governance.capUsd === null) {
|
|
526
|
+
return check('spend-governance', 'Spend governance', 'pass', `No spend cap configured (window: ${governance.window})`);
|
|
527
|
+
}
|
|
528
|
+
if (governance.level === 'over') {
|
|
529
|
+
return check('spend-governance', 'Spend governance', 'fail', governance.message, 'Review `ashlr pulse`; raise cfg.telemetry.budgetUsd or reduce spend. Pass --over-budget to proceed when govAction is block.');
|
|
530
|
+
}
|
|
531
|
+
if (governance.level === 'warn') {
|
|
532
|
+
return check('spend-governance', 'Spend governance', 'warn', governance.message, 'Review `ashlr pulse`; consider raising cfg.telemetry.budgetUsd or reducing spend.');
|
|
533
|
+
}
|
|
534
|
+
// level === 'ok'
|
|
535
|
+
return check('spend-governance', 'Spend governance', 'pass', governance.message);
|
|
536
|
+
}
|
|
537
|
+
// ---------------------------------------------------------------------------
|
|
538
|
+
// M19: Telemetry sink check
|
|
539
|
+
// ---------------------------------------------------------------------------
|
|
540
|
+
/**
|
|
541
|
+
* Check: Telemetry sink (M19).
|
|
542
|
+
* id: 'telemetry-sink'
|
|
543
|
+
*
|
|
544
|
+
* Reports the active sink type (local vs otlp) and whether an endpoint + PAT
|
|
545
|
+
* are configured — as booleans only (never values). This is an informational
|
|
546
|
+
* check (info → 'pass'); it warns if the module is unavailable. Never throws.
|
|
547
|
+
* PAT value is NEVER logged, returned, or placed in any detail field.
|
|
548
|
+
*
|
|
549
|
+
* @param sinkInfo - pre-fetched telemetry sink info (or null when unavailable)
|
|
550
|
+
*/
|
|
551
|
+
function checkTelemetrySink(sinkInfo) {
|
|
552
|
+
if (sinkInfo === null) {
|
|
553
|
+
return check('telemetry-sink', 'Telemetry sink', 'pass', 'Telemetry sink module not yet available — default: local');
|
|
554
|
+
}
|
|
555
|
+
const { sinkType, endpointConfigured, patConfigured } = sinkInfo;
|
|
556
|
+
const endpointLabel = endpointConfigured ? 'endpoint: configured' : 'endpoint: not configured';
|
|
557
|
+
const patLabel = patConfigured ? 'PAT: configured' : 'PAT: not configured';
|
|
558
|
+
if (sinkType === 'otlp') {
|
|
559
|
+
return check('telemetry-sink', 'Telemetry sink', 'pass', `Active sink: otlp (${endpointLabel}, ${patLabel})`);
|
|
560
|
+
}
|
|
561
|
+
// Local sink — always passes; note if endpoint/PAT partially set so user
|
|
562
|
+
// can diagnose why OTLP is not active without revealing any values.
|
|
563
|
+
const detail = endpointConfigured && !patConfigured
|
|
564
|
+
? `Active sink: local (${endpointLabel}, ${patLabel} — set ASHLR_PULSE_TOKEN or add via phantom to activate OTLP)`
|
|
565
|
+
: `Active sink: local (${endpointLabel}, ${patLabel})`;
|
|
566
|
+
return check('telemetry-sink', 'Telemetry sink', 'pass', detail);
|
|
567
|
+
}
|
|
568
|
+
// ---------------------------------------------------------------------------
|
|
569
|
+
// H7: 5 NEW read-only probes (enrollment / daemon-state / kill-switch /
|
|
570
|
+
// ~/.ashlr writeable / sandbox health). Each returns a DoctorCheck via the
|
|
571
|
+
// existing check() helper, reads via the shared readiness facets, and MUTATES
|
|
572
|
+
// NOTHING. STUBS — BUILD fills in per docs/contracts/CONTRACT-H7.md (BUILD
|
|
573
|
+
// ITEM 2). Status rules: pass/warn/info, except ~/.ashlr-not-writeable ⇒ fail.
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
/**
|
|
576
|
+
* Probe: enrollment registry (H7).
|
|
577
|
+
* id: 'enrollment'
|
|
578
|
+
*
|
|
579
|
+
* READ-ONLY via readEnrollmentState() → listEnrolled(). ALWAYS passes — an
|
|
580
|
+
* empty registry is the legitimate fresh-install default (DEFAULT EMPTY), so 0
|
|
581
|
+
* enrolled is a pass with a "none yet — run `ashlr onboard`" note rather than a
|
|
582
|
+
* warning. Mutates nothing. Never throws.
|
|
583
|
+
*/
|
|
584
|
+
function checkEnrollment() {
|
|
585
|
+
try {
|
|
586
|
+
const { count } = readEnrollmentState();
|
|
587
|
+
if (count > 0) {
|
|
588
|
+
return check('enrollment', 'Enrollment registry', 'pass', `${count} repo${count !== 1 ? 's' : ''} enrolled`);
|
|
589
|
+
}
|
|
590
|
+
return check('enrollment', 'Enrollment registry', 'pass', 'No repos enrolled yet — a fresh install is fine', 'Run `ashlr onboard` to safely enroll your first repo');
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
// readEnrollmentState never throws, but degrade defensively regardless.
|
|
594
|
+
return check('enrollment', 'Enrollment registry', 'pass', `enrollment unreadable: ${String(err)}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Probe: daemon state (H7).
|
|
599
|
+
* id: 'daemon-state'
|
|
600
|
+
*
|
|
601
|
+
* READ-ONLY via readDaemonHealth() → loadDaemonState() (which applies the H5
|
|
602
|
+
* reconcileDaemonState self-heal at the load chokepoint). Reports:
|
|
603
|
+
* - a live running daemon (running flag + a still-alive pid) as a PASS
|
|
604
|
+
* (truthful, expected during autonomy);
|
|
605
|
+
* - a stopped daemon as a PASS — this INCLUDES a stale dead-pid `running:true`
|
|
606
|
+
* flag, which the H5 reconcile has already flipped to running:false at load,
|
|
607
|
+
* so the probe truthfully reports "stopped" rather than a phantom-live daemon.
|
|
608
|
+
* Never fails — a daemon's run-state is observability, not a blocker. The load's
|
|
609
|
+
* self-heal is observability-only and is not persisted by this read. Mutates
|
|
610
|
+
* nothing. Never throws.
|
|
611
|
+
*/
|
|
612
|
+
function checkDaemonState() {
|
|
613
|
+
try {
|
|
614
|
+
const { running, pid, pidAlive } = readDaemonHealth();
|
|
615
|
+
if (running && pidAlive) {
|
|
616
|
+
return check('daemon-state', 'Daemon state', 'pass', `Daemon running (pid ${pid ?? '?'})`);
|
|
617
|
+
}
|
|
618
|
+
// running:false OR a running:true flag whose pid the H5 load already
|
|
619
|
+
// self-healed to a truthful stopped state.
|
|
620
|
+
return check('daemon-state', 'Daemon state', 'pass', 'Daemon stopped');
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
return check('daemon-state', 'Daemon state', 'pass', `daemon state unreadable: ${String(err)}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Probe: kill switch (H7).
|
|
628
|
+
* id: 'kill-switch'
|
|
629
|
+
*
|
|
630
|
+
* READ-ONLY via readKillState() → killSwitchOn(). PASS when OFF; WARN when ON
|
|
631
|
+
* (autonomy is paused — nothing will run, which a user may or may not intend).
|
|
632
|
+
* Mutates nothing. Never throws.
|
|
633
|
+
*/
|
|
634
|
+
function checkKillSwitch() {
|
|
635
|
+
try {
|
|
636
|
+
const { on } = readKillState();
|
|
637
|
+
if (on) {
|
|
638
|
+
return check('kill-switch', 'Kill switch', 'warn', 'Kill switch is ON — autonomy paused (nothing will run)', 'Clear it when ready: `ashlr kill off` (or remove ~/.ashlr/KILL)');
|
|
639
|
+
}
|
|
640
|
+
return check('kill-switch', 'Kill switch', 'pass', 'Kill switch is OFF');
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
return check('kill-switch', 'Kill switch', 'pass', `kill state unreadable: ${String(err)}`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Probe: ~/.ashlr writeable (H7).
|
|
648
|
+
* id: 'ashlr-writeable'
|
|
649
|
+
*
|
|
650
|
+
* The lone WRITE among the 5 — but it writes then IMMEDIATELY UNLINKS a private,
|
|
651
|
+
* self-cleaning sentinel under ~/.ashlr (shared with preflight via
|
|
652
|
+
* checkAshlrWriteable in readiness.ts). PASS when writeable; FAIL when not
|
|
653
|
+
* (nothing can persist — config/enrollment/daemon state all live under ~/.ashlr).
|
|
654
|
+
* Touches no repo, no enrollment, no kill, no daemon state; leaves no artifact.
|
|
655
|
+
* Never throws.
|
|
656
|
+
*/
|
|
657
|
+
function checkAshlrWriteableProbe() {
|
|
658
|
+
try {
|
|
659
|
+
if (checkAshlrWriteable()) {
|
|
660
|
+
return check('ashlr-writeable', '~/.ashlr writeable', 'pass', '~/.ashlr is writeable');
|
|
661
|
+
}
|
|
662
|
+
return check('ashlr-writeable', '~/.ashlr writeable', 'fail', '~/.ashlr is NOT writeable — nothing can persist', 'Fix permissions on ~/.ashlr (chmod u+rwx ~/.ashlr) or free disk space');
|
|
663
|
+
}
|
|
664
|
+
catch (err) {
|
|
665
|
+
// checkAshlrWriteable never throws, but treat any surprise as a hard fail
|
|
666
|
+
// (we could not prove ~/.ashlr is writeable).
|
|
667
|
+
return check('ashlr-writeable', '~/.ashlr writeable', 'fail', `could not verify ~/.ashlr is writeable: ${String(err)}`, 'Fix permissions on ~/.ashlr (chmod u+rwx ~/.ashlr) or free disk space');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Probe: sandbox health (H7).
|
|
672
|
+
* id: 'sandbox-health'
|
|
673
|
+
*
|
|
674
|
+
* READ-ONLY via readSandboxHealth() → listSandboxes() (counts only — removes
|
|
675
|
+
* NOTHING). PASS when there are zero/low orphans; WARN when the orphan count
|
|
676
|
+
* reaches SANDBOX_ORPHAN_WARN_THRESHOLD, with a `ashlr sandbox gc` fix hint.
|
|
677
|
+
* Mutates nothing. Never throws.
|
|
678
|
+
*/
|
|
679
|
+
function checkSandboxHealth() {
|
|
680
|
+
try {
|
|
681
|
+
const { total, orphans } = readSandboxHealth();
|
|
682
|
+
if (orphans >= SANDBOX_ORPHAN_WARN_THRESHOLD) {
|
|
683
|
+
return check('sandbox-health', 'Sandbox health', 'warn', `${orphans} orphan sandbox${orphans !== 1 ? 'es' : ''} of ${total} on disk`, 'Reclaim them: `ashlr sandbox gc`');
|
|
684
|
+
}
|
|
685
|
+
return check('sandbox-health', 'Sandbox health', 'pass', `${total} sandbox${total !== 1 ? 'es' : ''} on disk (${orphans} orphan${orphans !== 1 ? 's' : ''})`);
|
|
686
|
+
}
|
|
687
|
+
catch (err) {
|
|
688
|
+
return check('sandbox-health', 'Sandbox health', 'pass', `sandbox health unreadable: ${String(err)}`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// ---------------------------------------------------------------------------
|
|
692
|
+
// runDoctor
|
|
693
|
+
// ---------------------------------------------------------------------------
|
|
694
|
+
/**
|
|
695
|
+
* Run all health checks and return a DoctorReport.
|
|
696
|
+
* Never throws — each check is isolated and returns a typed result.
|
|
697
|
+
*
|
|
698
|
+
* New checks added in M3:
|
|
699
|
+
* - 'mcp-servers-discovered': MCP servers discovered via mcp-registry
|
|
700
|
+
* - 'ashlr-tools-installed': Ashlr ecosystem tools via tools-registry
|
|
701
|
+
* New checks added in M7:
|
|
702
|
+
* - 'genome-memory': Genome memory health via genome/store.genomeHealth
|
|
703
|
+
*/
|
|
704
|
+
export async function runDoctor(cfg) {
|
|
705
|
+
const checks = [];
|
|
706
|
+
// --- Pre-fetch M3 + M5 + M7 + M19 registry results (all async-safe, never throw) ---
|
|
707
|
+
const [mcpRegistry, toolsRegistry, budgetRollup, genomeHealth, identity, governance, telemetrySinkInfo] = await Promise.all([
|
|
708
|
+
tryDiscoverMcpServers(),
|
|
709
|
+
tryGetToolsRegistry(),
|
|
710
|
+
tryBuildBudgetRollup(cfg),
|
|
711
|
+
tryGetGenomeHealth(cfg),
|
|
712
|
+
tryGetIdentity(),
|
|
713
|
+
tryEvalGovernance(cfg),
|
|
714
|
+
tryGetTelemetrySinkInfo(cfg),
|
|
715
|
+
]);
|
|
716
|
+
// --- Synchronous checks ---
|
|
717
|
+
checks.push(checkNodeVersion());
|
|
718
|
+
checks.push(checkGit());
|
|
719
|
+
checks.push(checkLocalBin());
|
|
720
|
+
checks.push(checkAshlrInstalled());
|
|
721
|
+
checks.push(checkConfig());
|
|
722
|
+
checks.push(checkIndex());
|
|
723
|
+
checks.push(checkPhantom());
|
|
724
|
+
// Upgraded: also accepts discovery via discoverMcpServers (mcpRegistry)
|
|
725
|
+
checks.push(checkMcpPlugin(mcpRegistry));
|
|
726
|
+
// --- M3 checks ---
|
|
727
|
+
checks.push(checkMcpServersDiscovered(mcpRegistry));
|
|
728
|
+
checks.push(checkAshlrToolsInstalled(toolsRegistry));
|
|
729
|
+
// --- M5 checks ---
|
|
730
|
+
checks.push(checkSpendBudget(budgetRollup));
|
|
731
|
+
// --- M7 checks ---
|
|
732
|
+
checks.push(checkGenomeMemory(genomeHealth));
|
|
733
|
+
// --- M18 checks ---
|
|
734
|
+
checks.push(checkIdentity(identity));
|
|
735
|
+
// --- M19 checks ---
|
|
736
|
+
checks.push(checkSpendGovernance(governance));
|
|
737
|
+
checks.push(checkTelemetrySink(telemetrySinkInfo));
|
|
738
|
+
// --- H7 checks (5 NEW read-only probes). All read-only; the lone write is
|
|
739
|
+
// checkAshlrWriteable's self-cleaning sentinel under ~/.ashlr (written then
|
|
740
|
+
// immediately unlinked). NONE mutates enrollment / kill / daemon / sandbox /
|
|
741
|
+
// repo state. See docs/contracts/CONTRACT-H7.md §2 (BUILD ITEM 2).
|
|
742
|
+
checks.push(checkEnrollment());
|
|
743
|
+
checks.push(checkDaemonState());
|
|
744
|
+
checks.push(checkKillSwitch());
|
|
745
|
+
checks.push(checkAshlrWriteableProbe());
|
|
746
|
+
checks.push(checkSandboxHealth());
|
|
747
|
+
// --- M33: plugin layer (read-only; discovery never executes plugin code) ---
|
|
748
|
+
checks.push(await checkPlugins(cfg));
|
|
749
|
+
// --- Async: provider registry ---
|
|
750
|
+
try {
|
|
751
|
+
const registry = await getProviderRegistry(cfg);
|
|
752
|
+
for (const provider of registry.providers) {
|
|
753
|
+
const id = `provider:${provider.id}`;
|
|
754
|
+
const label = `Local provider: ${provider.id}`;
|
|
755
|
+
if (provider.up) {
|
|
756
|
+
const modelCount = provider.models.length;
|
|
757
|
+
checks.push(check(id, label, 'pass', `${provider.url} is up (${modelCount} model${modelCount !== 1 ? 's' : ''})`));
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
const detail = provider.error
|
|
761
|
+
? `${provider.url} is down — ${provider.error}`
|
|
762
|
+
: `${provider.url} is down`;
|
|
763
|
+
let fixHint;
|
|
764
|
+
if (provider.id === 'lmstudio') {
|
|
765
|
+
fixHint = 'Start LM Studio and enable the local server';
|
|
766
|
+
}
|
|
767
|
+
else if (provider.id === 'ollama') {
|
|
768
|
+
fixHint = 'Start Ollama: ollama serve';
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
fixHint = `Start the ${provider.id} provider`;
|
|
772
|
+
}
|
|
773
|
+
checks.push(check(id, label, 'warn', detail, fixHint));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
// At least one local provider must be up
|
|
777
|
+
if (registry.activeProvider === null) {
|
|
778
|
+
checks.push(check('provider:active', 'Active local provider', 'fail', 'No local provider is reachable — cannot run local-first AI', 'Start LM Studio or Ollama (ollama serve)'));
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
// activeProvider may be a local endpoint OR a cloud fallback (e.g.
|
|
782
|
+
// 'anthropic' when its env key is set and locals are down). Label
|
|
783
|
+
// accordingly so the status text is accurate.
|
|
784
|
+
const isLocal = registry.providers.some(p => p.id === registry.activeProvider);
|
|
785
|
+
const detail = isLocal
|
|
786
|
+
? `Active provider: ${registry.activeProvider} (local)`
|
|
787
|
+
: `Active provider: ${registry.activeProvider} (cloud fallback)`;
|
|
788
|
+
checks.push(check('provider:active', 'Active provider', 'pass', detail));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
// Probe machinery itself failed — record as fail rather than crashing
|
|
793
|
+
checks.push(check('provider:registry', 'Local provider registry', 'fail', `Could not probe providers: ${String(err)}`, 'Check network access and restart LM Studio / Ollama'));
|
|
794
|
+
}
|
|
795
|
+
// --- Summary ---
|
|
796
|
+
const summary = { pass: 0, warn: 0, fail: 0 };
|
|
797
|
+
for (const c of checks) {
|
|
798
|
+
summary[c.status]++;
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
generatedAt: new Date().toISOString(),
|
|
802
|
+
checks,
|
|
803
|
+
summary,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
//# sourceMappingURL=doctor.js.map
|