@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
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,1158 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to ashlr-hub are documented in this file.
|
|
4
|
+
|
|
5
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
Versions map to milestone series: **2.1.0** = v2.1 "Harden & Prove" (H1–H8),
|
|
7
|
+
**2.0.0** = v2 "Autonomous Engineering Organization" (M21–M30), **1.0.0** = v1
|
|
8
|
+
hub (M1–M20). Entries below detail each milestone; dates are merge dates into `main`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [2.2.0] — 2026-06-12 — v2.2 "Agent-Native Ecosystem" (M31–M33)
|
|
13
|
+
|
|
14
|
+
Makes ashlr's intelligence first-class INSIDE agent sessions — CLI-first, with
|
|
15
|
+
the MCP gateway as a second transport over the same capabilities. No safety
|
|
16
|
+
posture change: reads flow freely, writes stay append-only or proposal-only.
|
|
17
|
+
|
|
18
|
+
- **M31 — Agent-native surface.** The MCP gateway now serves 11 native
|
|
19
|
+
`ashlr_*` tools (orient/ask/recall/learn/backlog/health/status/impact/
|
|
20
|
+
pulse/inbox_list/inbox_propose) with structural safety classes
|
|
21
|
+
(read / append / proposal), kill-switch gating on every write, secret
|
|
22
|
+
scrubbing + 32KB caps on every result, and full `mcp:native-call` audit
|
|
23
|
+
coverage. There is deliberately NO approve/apply tool — approval stays
|
|
24
|
+
human-only via `ashlr inbox`. New `ashlr orient [--repo] [--json]`
|
|
25
|
+
(composite session-start context), `ashlr docs --agent` (generated agent
|
|
26
|
+
cheat sheet), `ashlr wire --claude-md` (CLAUDE.md snippet),
|
|
27
|
+
`ashlr completions zsh|bash`, "did you mean" suggestions, and read-only
|
|
28
|
+
`GET /api/orient|health|backlog|impact` web routes.
|
|
29
|
+
`docs/contracts/CONTRACT-M31.md` · 6 new test files.
|
|
30
|
+
|
|
31
|
+
- **M32 — Living command center.** The web dashboard becomes a real control
|
|
32
|
+
surface: `#inbox` view with approve/reject (gated identically to dispatch —
|
|
33
|
+
routes 404 without `--allow-dispatch`, per-session token, applies only via
|
|
34
|
+
`applyProposal`'s triple gate), `#daemon` view + live nav badge, and a
|
|
35
|
+
dispatch panel with live cost preview. SSE gains `inbox`/`daemon` named
|
|
36
|
+
events (metadata only — never diffs). New pre-flight cost estimator:
|
|
37
|
+
`ashlr run|swarm "<goal>" --estimate` (p25/median/p75 tokens·cost·duration
|
|
38
|
+
from history, confidence-tiered, budget-clamped; also a footer in swarm
|
|
39
|
+
`--dry-run` and `GET /api/estimate`). Help reworked into topics:
|
|
40
|
+
`ashlr help [<topic>] [--search <term>] [--all]`. Knowledge build shows a
|
|
41
|
+
TTY progress line. Opt-in macOS desktop notification (+ webhook) when an
|
|
42
|
+
unattended swarm files a PENDING proposal (`notify.desktop: true`;
|
|
43
|
+
metadata only). `docs/contracts/CONTRACT-M32.md` · 5 new test files.
|
|
44
|
+
|
|
45
|
+
- **M33 — Ecosystem layer.** Plugins: third parties can contribute backlog
|
|
46
|
+
scanners, project templates, model providers, and CLI commands from
|
|
47
|
+
`~/.ashlr/plugins/<name>/` — DEFAULT-OFF (`plugins.enabled: []`),
|
|
48
|
+
manifest-only discovery (no code executes until enabled), sha256 integrity
|
|
49
|
+
pinning, capability declarations enforced, kill-switch gated, fully audited,
|
|
50
|
+
with a least-privilege host API (frozen config projection, never secrets).
|
|
51
|
+
`ashlr plugins list|info|enable|disable`, `ashlr x <name>`. Distribution:
|
|
52
|
+
the package is now `@ashlr/hub` (public, npm provenance) with a tag-gated
|
|
53
|
+
release pipeline (full CI verify → version/changelog gates → publish +
|
|
54
|
+
GitHub release), a CI pack-smoke step keeping the exports map honest, and
|
|
55
|
+
`ashlr update` channel awareness (git checkout vs npm install; npm installs
|
|
56
|
+
only with `--yes`). Public API: curated `@ashlr/hub` / `./core` / `./types`
|
|
57
|
+
/ `./plugin` entry points (`applyProposal` deliberately unexported).
|
|
58
|
+
`docs/contracts/CONTRACT-M33.md`, `docs/PLUGINS.md`, `docs/RELEASING.md`.
|
|
59
|
+
|
|
60
|
+
- **DX follow-ups.** `ashlr plugins init <name> [--capability k]` scaffolds a
|
|
61
|
+
working plugin skeleton (every skeleton integration-proven to load and
|
|
62
|
+
contribute); agent-contract conformance locks pin the OrientResult /
|
|
63
|
+
RunEstimate shapes, the 11 native tool names + safety classes, and the
|
|
64
|
+
AGENT_COMMANDS registry so shape drift fails a named test; public roadmap
|
|
65
|
+
(`docs/ROADMAP.md`); CI made hermetic (doctor `which ashlr` shim,
|
|
66
|
+
check-version argv precedence) — the suite is green on clean runners.
|
|
67
|
+
|
|
68
|
+
## [2.1.0] — 2026-06-11 — v2.1 "Harden & Prove" (H1–H8)
|
|
69
|
+
|
|
70
|
+
Takes v2 from *built + unit-tested* to **proven trustworthy**. Eight hardening
|
|
71
|
+
milestones, each a contracts-first agent workflow with a **test-validity**
|
|
72
|
+
adversarial lens (which repeatedly caught false-green stub tests and real bugs).
|
|
73
|
+
Every change is local-first and adds **no new outward capability** — v2.1 only
|
|
74
|
+
proves and hardens what v2 built. Disposable repos in isolated tmp HOMEs
|
|
75
|
+
throughout; the real portfolio was never touched. 40 commits · 3,374 tests.
|
|
76
|
+
|
|
77
|
+
- **H1 — End-to-end autonomous-chain harness (keystone).** A reusable
|
|
78
|
+
disposable-repo / tmp-HOME testkit + an integration suite that drives the *real*
|
|
79
|
+
chain (enroll → daemon tick → sandboxed swarm → pending proposal → approve →
|
|
80
|
+
`applyProposal`) and proves the working tree stays byte-identical, the proposal
|
|
81
|
+
is the only sink, and every gate holds. `test/helpers/h1-fixture.ts`.
|
|
82
|
+
- **H2 — Crash recovery & resumability.** Fault-injection: daemon-mid-tick and
|
|
83
|
+
swarm-mid-run crashes recover with no double-spend, no orphaned sandboxes, no
|
|
84
|
+
stuck proposals, clean resume, and a clean kill-switch race. Adds
|
|
85
|
+
`sweepOrphanSandboxes`.
|
|
86
|
+
- **H3 — Concurrency & budget stress.** Proves the in-process caps hold under
|
|
87
|
+
load; adds a monotonic `makeId` counter. **Honest finding:** under `parallel>1`
|
|
88
|
+
the per-tick budget is a *bounded overshoot* (≤ (parallel-1)×per-item), not a
|
|
89
|
+
hard cap — documented, not hidden.
|
|
90
|
+
- **H4 — Safety-invariant regression suite + `ashlr verify-safety`.** Turns all
|
|
91
|
+
54 enumerated guards across 7 invariants into a permanent always-on suite
|
|
92
|
+
(closing 12 previously-untested guards) plus a read-only self-check, so no
|
|
93
|
+
future change can silently weaken a guarantee.
|
|
94
|
+
- **H5 — Sandbox lifecycle & leak hardening.** Wires the orphan sweep into daemon
|
|
95
|
+
start (+ `ashlr sandbox gc`), adds a read-only daemon-state reconcile, env-gates
|
|
96
|
+
the `allowAnyRepo` test hatch, and adds a sandbox disk cap. Found + fixed a
|
|
97
|
+
live-worktree-removal bug.
|
|
98
|
+
- **H6 — Audit & observability completeness.** `enroll` / `unenroll` / `setKill`
|
|
99
|
+
are now audited at the primitive; adds a read-only `ashlr audit` viewer; brings
|
|
100
|
+
the two secret-scrub implementations to parity (+ a Stripe-token pattern).
|
|
101
|
+
- **H7 — Guided onboarding & preflight.** `ashlr preflight` (read-only readiness)
|
|
102
|
+
+ 5 new doctor probes, a guided `ashlr onboard` first-activation walkthrough,
|
|
103
|
+
and one-command `ashlr onboard --rollback`. No new outward capability.
|
|
104
|
+
- **H8 — Reproducible demo + reliability docs + activation runbook.** `ashlr demo`
|
|
105
|
+
runs the full chain on a disposable repo so you can watch it before trusting it
|
|
106
|
+
(auto-cleans, never touches your portfolio, never applies); `docs/RELIABILITY.md`;
|
|
107
|
+
the canonical README activation runbook; and a maintainability cleanup pass.
|
|
108
|
+
|
|
109
|
+
**Activation remains the operator's explicit gate** — enrollment ships empty and
|
|
110
|
+
nothing autonomous runs until you `ashlr enroll` a repo and approve proposals via
|
|
111
|
+
`ashlr inbox`. See `docs/RELIABILITY.md` and the README *Activation* section.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## [Unreleased] — M30: Cloud-Ready Seams v2 + Polish (CAPSTONE)
|
|
116
|
+
|
|
117
|
+
The Ashlr v2 capstone. Makes the team/multi-machine future a **drop-in** later
|
|
118
|
+
without a rewrite by defining clean **seam interfaces** for every v2 store — and
|
|
119
|
+
shipping **only the LOCAL side** now. Every seam has a working LOCAL impl (a thin
|
|
120
|
+
adapter over the existing module, zero behaviour change) and a GATED cloud stub
|
|
121
|
+
that THROWS a clear gated error if ever selected. **There is no config flag and
|
|
122
|
+
no code path that can activate a functional cloud backbone** — cloud/team is a
|
|
123
|
+
Mason gate (explicit opt-in, not implemented). Local-first, self-hostable,
|
|
124
|
+
nothing public. Generalises the M19 telemetry-sink seam pattern across the v2
|
|
125
|
+
stores without destabilising them.
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- **`ashlr seams` / `ashlr seams status`** — read-only diagnostic that lists every
|
|
130
|
+
v2 seam, its active implementation (`local`), and its cloud availability
|
|
131
|
+
(`gated` for the seven v2 seams; `false` for the cited telemetry reference
|
|
132
|
+
seam). `--json` emits the `SeamRegistry`; `--help` prints usage. Mutates
|
|
133
|
+
nothing, makes no network connection, and instantiates no seam impl. Backed by
|
|
134
|
+
`src/cli/seams.ts` (`cmdSeams`), mirroring `src/cli/health.ts`.
|
|
135
|
+
|
|
136
|
+
- **Seam layer** (`src/core/seams/`): one cohesive module per seam, each exposing
|
|
137
|
+
the canonical four-part shape — INTERFACE, LOCAL impl (default, delegates 1:1 to
|
|
138
|
+
the existing module), GATED cloud stub (every method throws before any I/O), and
|
|
139
|
+
a `selectX(cfg)` selector (returns LOCAL by default; returns the throwing stub
|
|
140
|
+
ONLY when a cloud endpoint is explicitly configured):
|
|
141
|
+
- `RunSwarmStore` (`seams/run-swarm.ts`) — wraps `core/swarm/store.ts`
|
|
142
|
+
(`listSwarms` / `loadSwarm` / `saveSwarm`).
|
|
143
|
+
- `BacklogSource` (`seams/backlog.ts`) — wraps `core/portfolio/backlog.ts`
|
|
144
|
+
(`loadBacklog` / `buildBacklog`).
|
|
145
|
+
- `InboxStore` (`seams/inbox.ts`) — wraps `core/inbox/store.ts`
|
|
146
|
+
(`listProposals` / `createProposal` / `loadProposal` / `setStatus` /
|
|
147
|
+
`pendingCount`).
|
|
148
|
+
- `DaemonCoordinator` (`seams/daemon-coordinator.ts`) — wraps
|
|
149
|
+
`core/daemon/state.ts` (`loadDaemonState` / `saveDaemonState`); LOCAL is
|
|
150
|
+
single-machine (lease is a no-op), cloud stub is a GATED multi-machine
|
|
151
|
+
lease/lock.
|
|
152
|
+
- `GenomeSync` (`seams/genome.ts`) — wraps `core/genome/store.ts`
|
|
153
|
+
(`loadGenome` / `appendHubEntry` / `genomeHubHealth`).
|
|
154
|
+
- `PortfolioSync` (`seams/portfolio.ts`) — wraps `core/quality/store.ts` +
|
|
155
|
+
`core/dashboard.ts` (health snapshots + portfolio dashboard).
|
|
156
|
+
- `IdentityProvider` (`seams/identity.ts`) — wraps
|
|
157
|
+
`core/integrations/identity.ts` (`getIdentity`); LOCAL is the phantom probe,
|
|
158
|
+
cloud stub is GATED team auth.
|
|
159
|
+
- `TelemetrySink` (`core/observability/telemetry-sink.ts`, M19) — the CANONICAL
|
|
160
|
+
reference seam. CITED via the registry, not duplicated. Its opt-in
|
|
161
|
+
`OtlpHttpSink` is a real local-network sink, not a gated team backbone, so its
|
|
162
|
+
`cloud` field is `false` (distinct from the seven `gated` v2 seams).
|
|
163
|
+
|
|
164
|
+
- **Seam types** (`src/core/seams/types.ts`, single-sourced): `SeamId`,
|
|
165
|
+
`SeamImpl` (`'local' | 'gated'`), `SeamCloud` (`false | 'gated'` — NEVER `true`
|
|
166
|
+
in M30), `SeamStatus`, `SeamRegistry`, and `SeamsConfig` (OPTIONAL per-seam
|
|
167
|
+
`{ endpoint? }`). `CLOUD_GATED_MESSAGE` + `cloudGatedError(seam, method)` are the
|
|
168
|
+
single canonical gated error, centralised so it is identical across seams and
|
|
169
|
+
trivially assertable.
|
|
170
|
+
|
|
171
|
+
- **Seam registry** (`src/core/seams/registry.ts`, READ-ONLY): `buildSeamRegistry(cfg)`
|
|
172
|
+
derives the full registry from the in-memory config + static descriptors —
|
|
173
|
+
triggers NO I/O, instantiates NO seam impl, never touches disk/network, never
|
|
174
|
+
throws. `seamsConfig(cfg)` reads the OPTIONAL `seams` block defensively via a cast
|
|
175
|
+
over an optional property (so `AshlrConfig` is unmodified). `seamEndpoint(cfg, id)`
|
|
176
|
+
returns the explicitly-configured endpoint for a gated seam, or `null` — a
|
|
177
|
+
non-null result routes the selector to the GATED stub (which throws) and NEVER
|
|
178
|
+
enables a functional backbone.
|
|
179
|
+
|
|
180
|
+
- **Barrel** (`src/core/seams/index.ts`) re-exporting the seam interfaces, selectors,
|
|
181
|
+
registry, and types.
|
|
182
|
+
|
|
183
|
+
### Docs
|
|
184
|
+
|
|
185
|
+
- **`docs/SEAMS.md`** — documents the local-first + cloud-ready seam architecture,
|
|
186
|
+
the canonical four-part seam pattern, each seam and the existing module it wraps,
|
|
187
|
+
the Mason gate, the `ashlr seams` diagnostic, and the five hard safety invariants.
|
|
188
|
+
|
|
189
|
+
- **`README.md`** — adds the full v2 command surface (`ask`, `knowledge`,
|
|
190
|
+
`reflect`, `health`, `goals`, `digest`, `daemon`, `inbox`, `backlog`, `enroll`,
|
|
191
|
+
`seams`), a concise **"Ashlr v2 — Autonomous Engineering Organization"** section
|
|
192
|
+
summarising M21–M30, and a clearly-marked **ACTIVATION RUNBOOK ("the human's
|
|
193
|
+
gate")**: enroll real repos (`ashlr enroll add <repo>`) → run `ashlr daemon` →
|
|
194
|
+
review and approve proposals via `ashlr inbox`. Emphasises that default
|
|
195
|
+
enrollment is EMPTY and everything is proposal-only / sandboxed.
|
|
196
|
+
|
|
197
|
+
### Polish
|
|
198
|
+
|
|
199
|
+
- **CI** — `.github/workflows/ci.yml` extended to a Node `["20", "22"]` matrix,
|
|
200
|
+
keeping typecheck / lint / build / test. Must stay green on both.
|
|
201
|
+
|
|
202
|
+
### Tests
|
|
203
|
+
|
|
204
|
+
- **`test/m30.seams.test.ts`** — hermetic invariant tests (in-memory config; never
|
|
205
|
+
the real `~/.ashlr`, never the real portfolio, never a remote call): every cloud
|
|
206
|
+
stub method throws `CLOUD_GATED_MESSAGE`; every selector returns the LOCAL impl on
|
|
207
|
+
the default config; a configured endpoint routes to the throwing stub; the
|
|
208
|
+
registry reports `allLocal` by default.
|
|
209
|
+
|
|
210
|
+
### Safety invariants (M30)
|
|
211
|
+
|
|
212
|
+
- **Interfaces + local only** — no functional cloud/team/remote implementation
|
|
213
|
+
exists. Each cloud stub throws `cloudGatedError(...)` as the FIRST statement of
|
|
214
|
+
every method, before any I/O; no `fetch`/http/socket/disk in any seam cloud stub.
|
|
215
|
+
- **No activation path** — selectors return LOCAL by default; a configured endpoint
|
|
216
|
+
only routes to a throwing stub. There is no value of config that yields a
|
|
217
|
+
functional cloud backbone, and no way for the autonomous loop/daemon to flip to
|
|
218
|
+
cloud.
|
|
219
|
+
- **Non-regression** — zero edits to any wrapped store; local adapters delegate 1:1;
|
|
220
|
+
the seam config is read via a cast over an OPTIONAL property so `AshlrConfig` is
|
|
221
|
+
unmodified. All prior tests stay green.
|
|
222
|
+
- **Nothing public / self-hostable** — no outward action, no registration/telemetry/
|
|
223
|
+
phone-home, no public flip. Docs state local-first + self-hostable + cloud-gated.
|
|
224
|
+
- **Bounded + no new deps** — `package.json` unchanged; only intra-repo imports; the
|
|
225
|
+
registry maps a fixed 8-element descriptor list; read-only diagnostics.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## [Unreleased] — M25: Portfolio Intelligence (`ashlr knowledge` · `ashlr ask` · `ashlr impact`)
|
|
230
|
+
|
|
231
|
+
### Added
|
|
232
|
+
|
|
233
|
+
- **`ashlr knowledge build [--repo <path>]`** — build or incrementally refresh the local semantic knowledge index for all enrolled repos (or a single repo with `--repo`). Walks source files read-only, chunks them, embeds each chunk via local Ollama embeddings (keyword/TF-IDF fallback when no embedding model is available), and stores results to `~/.ashlr/knowledge/<repo-hash>/*.jsonl`. Incremental by mtime — only changed files are re-indexed.
|
|
234
|
+
|
|
235
|
+
- **`ashlr ask "<question>" [--repo <path>] [--allow-cloud]`** — local RAG Q&A across the indexed portfolio. Retrieves the top relevant chunks via embedding similarity or keyword scoring, synthesises a plain-language answer using the **local** model, and cites every source as `repo/file:line`. `--repo` scopes the search to a single enrolled repo. Cloud is structurally OFF by default — the local provider is used for both retrieval and synthesis; `--allow-cloud` is required to route synthesis to a cloud model AND only takes effect when a key is present.
|
|
236
|
+
|
|
237
|
+
- **`ashlr knowledge graph [--repo <path>]`** — build and print a lightweight cross-repo knowledge graph. Nodes are repos, modules, and key dependencies; edges are imports, depends-on, and shared-dep relationships. Cross-repo findings (same vulnerable/outdated dependency, duplicated patterns) are surfaced as `crossRepo` entries. Output is JSON-serialisable for downstream tooling.
|
|
238
|
+
|
|
239
|
+
- **`ashlr impact <file|symbol> [--repo <path>]`** — answer "what depends on this?" across the enrolled portfolio. Returns all references (repo, file, line) and a list of dependent repos/modules. Scoped to enrolled repos only; read-only analysis.
|
|
240
|
+
|
|
241
|
+
- **Knowledge index engine** (`src/core/knowledge/index.ts`):
|
|
242
|
+
- `buildKnowledge(opts?)` — default `repos = listEnrolled()` (DEFAULT EMPTY → empty knowledge, no disk scan). Bounded: skips `node_modules/`, `.git/`, `dist/`, and binary files; enforces file-count and byte caps per repo; local embeddings via `getActiveClient()` from `core/genome/recall.ts`; keyword/TF-IDF fallback. Secret-scrubs chunks before storing (skips `.env`/key files; redacts secret-shaped tokens). Writes `~/.ashlr/knowledge/<repo-hash>/*.jsonl`.
|
|
243
|
+
- `knowledgeDir()` — canonical store path (`~/.ashlr/knowledge/`).
|
|
244
|
+
- `loadChunks(repo?)` — read stored chunks from disk; scoped to one repo when provided.
|
|
245
|
+
|
|
246
|
+
- **Ask engine** (`src/core/knowledge/ask.ts`):
|
|
247
|
+
- `ask(question, { repo?, allowCloud })` — retrieve top chunks (embedding cosine or keyword TF-IDF), call local synthesis via `core/run/provider-client.ts` local provider, return `AskResult { question; answer; sources; method; local }`. `allowCloud` defaults to `false` at every call site; code-to-cloud on the default path is a guardrail violation.
|
|
248
|
+
|
|
249
|
+
- **Graph + impact engine** (`src/core/knowledge/graph.ts`):
|
|
250
|
+
- `buildGraph(repos?)` — static import/dependency analysis; returns `KnowledgeGraph { nodes; edges; crossRepo }`. Cross-repo detection surfaces shared vulnerable/outdated deps and duplicated structural patterns.
|
|
251
|
+
- `impact(target, repos?)` — file or symbol name → `ImpactResult { target; references; dependents }`. Read-only; enrollment-scoped.
|
|
252
|
+
|
|
253
|
+
- **CLI entry points**:
|
|
254
|
+
- `src/cli/ask.ts` (`cmdAsk`) — backs `ashlr ask`; `--repo` and `--allow-cloud` flags; cloud OFF by default.
|
|
255
|
+
- `src/cli/knowledge.ts` (`cmdKnowledge`) — backs `ashlr knowledge build | graph | impact`; delegates to core engines.
|
|
256
|
+
|
|
257
|
+
- **New types in `src/core/types.ts`** (all existing types unchanged):
|
|
258
|
+
- `KnowledgeChunk { repo; file; startLine; endLine; text; vector?; summary? }`
|
|
259
|
+
- `AskHit { chunk: KnowledgeChunk; score: number }`
|
|
260
|
+
- `AskResult { question; answer; sources: {repo; file; line}[]; method: 'embedding'|'keyword'; local: boolean }`
|
|
261
|
+
- `ImpactResult { target; references: {repo; file; line}[]; dependents: string[] }`
|
|
262
|
+
- `KnowledgeGraph { nodes: {id; kind; label}[]; edges: {from; to; kind}[]; crossRepo: {kind; detail; repos}[] }`
|
|
263
|
+
|
|
264
|
+
### Guardrails (M25)
|
|
265
|
+
|
|
266
|
+
- **LOCAL-ONLY BY DEFAULT (privacy)**: indexing, ask synthesis, and embeddings all run on the local provider (Ollama via `getActiveClient()`). Repo code and chunks are **never sent to a cloud model** unless `--allow-cloud` is explicitly passed AND a cloud key is present. Both conditions must be true simultaneously. Sending code to cloud on the default path is a contract violation.
|
|
267
|
+
- **READ-ONLY**: `buildKnowledge`, `ask`, `buildGraph`, and `impact` never modify any enrolled repo. All writes are confined to `knowledgeDir()` (`~/.ashlr/knowledge/`). No `git` mutations, no installs, no project-script execution.
|
|
268
|
+
- **ENROLLMENT-SCOPED**: default repos = `listEnrolled()`. Default enrollment is empty — empty knowledge, no whole-portfolio disk scan. Only explicitly enrolled repos are ever indexed or queried.
|
|
269
|
+
- **BOUNDED**: file-count and byte caps per repo; skips `node_modules/`, `.git/`, `dist/`, and binary files; embedding calls subject to the same time/concurrency caps as genome recall.
|
|
270
|
+
- **NO SECRETS**: `.env` and key files are excluded from indexing. Secret-shaped tokens (high-entropy strings matching common key patterns) are redacted from chunks before storing, embedding, or citing. No secret values appear in `~/.ashlr/knowledge/` or in `ask` answers.
|
|
271
|
+
- All 2443 existing tests preserved. Typecheck passes clean (`tsc --noEmit`). No new runtime dependencies. Reuses `core/genome/recall.ts` (embeddings + `getActiveClient`), `core/sandbox/policy.ts` (`listEnrolled`, `isEnrolled`), `core/run/provider-client.ts` (local provider), `core/git.ts`, and `cli/ui.ts`.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## [Unreleased] — M24: The Autonomous Daemon (`ashlr daemon`)
|
|
276
|
+
|
|
277
|
+
### Added
|
|
278
|
+
|
|
279
|
+
- **`ashlr daemon` — the autonomous operator that makes the org continuous** (`src/core/daemon/state.ts`, `src/core/daemon/loop.ts`, `src/cli/daemon.ts`):
|
|
280
|
+
- Pulls the highest-value backlog items for enrolled repos and dispatches sandboxed swarms whose output becomes PENDING proposals in the Approval Inbox. **It is proposal-only by construction — it has no path to apply, push, open a PR, or deploy.**
|
|
281
|
+
- **`ashlr daemon start`** — begin the operator loop; ticks on an interval until stopped, daily budget is exhausted, or the kill switch is set.
|
|
282
|
+
- **`ashlr daemon start --once`** — run exactly one tick and exit (ideal for cron or manual one-shot use).
|
|
283
|
+
- **`ashlr daemon start --dry-run`** — plan only: shows which backlog items would be worked this tick; no swarm is dispatched, no proposal is created. Safe to run at any time.
|
|
284
|
+
- **`ashlr daemon start --budget <usd>`** — override the daily budget cap for this session.
|
|
285
|
+
- **`ashlr daemon start --interval <ms>`** — override the tick interval.
|
|
286
|
+
- **`ashlr daemon start --parallel <n>`** — override the per-tick concurrency cap.
|
|
287
|
+
- **`ashlr daemon stop`** — sets `~/.ashlr/KILL` and clears running state; all in-flight ticks halt at the next kill-switch check.
|
|
288
|
+
- **`ashlr daemon status`** — running state, PID, last tick time, today's spend vs cap, items processed, pending proposals count.
|
|
289
|
+
|
|
290
|
+
- **Daemon state** (`src/core/daemon/state.ts`) — atomic, never-throws, day-reset-aware:
|
|
291
|
+
- `daemonStatePath()` — `~/.ashlr/daemon.json`.
|
|
292
|
+
- `loadDaemonState()` — returns a fresh zeroed `DaemonState` on missing or corrupt file; never throws.
|
|
293
|
+
- `saveDaemonState(s)` — atomic write (temp file + rename), `mkdir -p` on first save.
|
|
294
|
+
- `resetDayIfNeeded(s)` — zeroes `todaySpentUsd` and `itemsProcessed` when `todayDate` has rolled over; preserves all other state.
|
|
295
|
+
|
|
296
|
+
- **Daemon loop** (`src/core/daemon/loop.ts`) — ordered safety checks before any work is dispatched:
|
|
297
|
+
- `tick(cfg, { dryRun })` — one operator cycle: kill-switch check → budget-exhausted check → enrolled-repos check → load/refresh backlog → select top-K items under per-tick + daily budget cap → (dry-run: log plan only; else: dispatch `runSwarm` with `opts.sandbox=true` + `opts.propose=true` at bounded concurrency) → record spend + audit + daemon state. Returns a `DaemonTick` summary.
|
|
298
|
+
- `runDaemon(cfg, { once, dryRun })` — REFUSES (throws) if `ASHLR_IN_DAEMON` or `ASHLR_IN_SWARM` is set (fork-bomb guard); sets `ASHLR_IN_DAEMON=1` on child processes; `once=true` runs one tick; otherwise loops, re-checking kill switch and budget every iteration. No unbounded loop — exits cleanly on exhaustion or stop.
|
|
299
|
+
- `stopDaemon()` — sets `~/.ashlr/KILL` + clears running state.
|
|
300
|
+
|
|
301
|
+
- **New types in `src/core/types.ts`** (all existing types unchanged):
|
|
302
|
+
- `DaemonConfig { dailyBudgetUsd; perTickItems; parallel; intervalMs }` — caps only; grants no authority.
|
|
303
|
+
- `DaemonTick { ts; itemsConsidered; proposalsCreated; spentUsd; reason }` — one tick summary record.
|
|
304
|
+
- `DaemonState { running; pid; startedAt; lastTickAt; todayDate; todaySpentUsd; itemsProcessed; ticks }` — persisted daemon state.
|
|
305
|
+
- `AshlrConfig.daemon?: Partial<DaemonConfig>` — optional per-installation caps override.
|
|
306
|
+
- `DashboardSnapshot.daemon?: { running; todaySpentUsd; pendingProposals }` — optional; absent = not running.
|
|
307
|
+
|
|
308
|
+
- **Dashboard surface** (TUI + web) — `DashboardSnapshot.daemon` populated from `loadDaemonState()` + `pendingCount()`; read-only display of running state, today's spend, and pending proposal count.
|
|
309
|
+
|
|
310
|
+
### Guardrails (M24)
|
|
311
|
+
|
|
312
|
+
- **PROPOSAL-ONLY (grep-provable)**: the daemon code imports and calls ONLY `createProposal` (via `runSwarm opts.propose`) and `pendingCount` (read-only). It NEVER imports or calls `applyProposal`, `git push`, `gh pr create`, or any deploy/ship path. Every tick's output is a PENDING proposal in the Approval Inbox; applying it requires explicit human `ashlr inbox approve`.
|
|
313
|
+
- **ENROLLMENT-ONLY**: the daemon operates ONLY on `listEnrolled()` repos. Default enrollment is empty — if nothing is enrolled, the daemon idles and does nothing. It never scans or operates on the full 69-repo portfolio.
|
|
314
|
+
- **SANDBOXED**: all swarm work is dispatched with `opts.sandbox=true` (M21 git-worktree sandboxes). The user's working tree, current branch, index, and HEAD are never touched.
|
|
315
|
+
- **BOUNDED**: hard daily budget cap (default modest), per-tick item cap, and concurrency cap enforced on every tick. Budget resets per calendar day via `resetDayIfNeeded`. When exhausted the daemon idles or stops. No unbounded loop.
|
|
316
|
+
- **KILL SWITCH**: `~/.ashlr/KILL` is checked at the top of every tick. `ashlr daemon stop` sets it immediately. Cannot be bypassed.
|
|
317
|
+
- **RE-ENTRANCY GUARD**: `runDaemon` REFUSES to start if `ASHLR_IN_DAEMON=1` or `ASHLR_IN_SWARM=1` is set in the environment. Prevents daemon-inside-daemon and daemon-inside-swarm fork bombs. Respects the existing swarm recursion guard.
|
|
318
|
+
- All 2357 existing tests preserved. Typecheck passes clean (`tsc --noEmit`). No new runtime dependencies.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## [Unreleased] — M23: Approval Inbox (Single Outward Gate)
|
|
323
|
+
|
|
324
|
+
### Added
|
|
325
|
+
- **Approval Inbox — the single human control plane for all outward actions** (`src/core/inbox/store.ts`, `src/core/inbox/apply.ts`, `src/cli/inbox.ts`):
|
|
326
|
+
- Every proposed outward action (patch, PR, deploy) is written as a `Proposal` to `~/.ashlr/inbox/<id>.json` before anything happens. Nothing outward occurs until you explicitly approve.
|
|
327
|
+
- **`ashlr inbox`** — list all pending proposals with counts; shows id, kind, origin, title, and age.
|
|
328
|
+
- **`ashlr inbox show <id>`** — full proposal detail including unified diff (for patch proposals) and summary.
|
|
329
|
+
- **`ashlr inbox approve <id> [--yes]`** — the ONLY path that triggers outward action. Confirm-gated interactively (or bypass with `--yes`); marks the proposal `approved` then calls `applyProposal`. Nothing outward runs before this point.
|
|
330
|
+
- **`ashlr inbox reject <id>`** — marks a proposal `rejected`; discards it. No outward action taken.
|
|
331
|
+
- **Proposal store** (`src/core/inbox/store.ts`) — atomic-write, never throws:
|
|
332
|
+
- `createProposal(p)` — creates a fresh `Proposal` (status `'pending'`, timestamp, fresh id) and persists to `~/.ashlr/inbox/<id>.json`. **Does not apply anything.**
|
|
333
|
+
- `listProposals(filter?)` — returns proposals newest-first; filter by status; read-only, never throws.
|
|
334
|
+
- `loadProposal(id)` — returns a single proposal or `null`.
|
|
335
|
+
- `setStatus(id, status, result?)` — persistence-only; sets `decidedAt` on approve/reject; **applies nothing**.
|
|
336
|
+
- `pendingCount()` — count of pending proposals (used by dashboard snapshot and daily-digest).
|
|
337
|
+
- **Apply engine** (`src/core/inbox/apply.ts`) — the single outward funnel, heavily guarded:
|
|
338
|
+
- `applyProposal(id, { confirmed })` — REFUSES (mutates nothing) unless proposal exists AND `status === 'approved'` AND `confirmed === true`. All three conditions are required simultaneously.
|
|
339
|
+
- By kind: `'patch'` → applies the diff on a **NEW branch** (`BRANCH_PREFIX`) off HEAD in the target repo — **never touches the user's current branch, index, or working tree**; never force-pushes; never pushes at all (local only). `'pr'` → branch + commit then explicit M18 gated `createPr`. `'deploy'` → gated ship path. `'note'` → no-op record.
|
|
340
|
+
- Enrollment-checked (`assertMayMutate`) and kill-switch-checked before any mutation. Every apply is audited. Status set to `'applied'` on success or `'failed'` on error.
|
|
341
|
+
- Never throws — always returns `ApplyResult { ok, status, detail }`.
|
|
342
|
+
- **New types in `src/core/types.ts`** (all existing types unchanged):
|
|
343
|
+
- `ProposalKind = 'patch' | 'pr' | 'deploy' | 'note'`
|
|
344
|
+
- `ProposalStatus = 'pending' | 'approved' | 'rejected' | 'applied' | 'failed'`
|
|
345
|
+
- `Proposal { id; repo; origin: 'backlog'|'swarm'|'manual'; kind; title; summary; diff?; sandboxId?; status; createdAt; decidedAt?; result? }`
|
|
346
|
+
- `ApplyResult { ok; status; detail }`
|
|
347
|
+
- `DashboardSnapshot.inbox: { pending: number }` — pending count surfaced to dashboard and daily digest.
|
|
348
|
+
- **Surfaces**:
|
|
349
|
+
- **TUI** — new Inbox tab (read-only view of pending proposals; approve via `ashlr inbox approve`). Pending count shown in Overview tab.
|
|
350
|
+
- **Web dashboard** — Inbox section at `/inbox` (read-only list + detail); pending count in the snapshot header.
|
|
351
|
+
- **Daily digest** — `inbox.pending` included in the dashboard snapshot for digest-ready consumption.
|
|
352
|
+
|
|
353
|
+
### Guardrails (M23)
|
|
354
|
+
- **PENDING NEVER AUTO-APPLIES.** `applyProposal` runs ONLY when `status === 'approved'` AND triggered by the explicit `inbox approve` command (confirm/`--yes`). Never on `createProposal`, never on list/show, never by any daemon or background process. This is structurally enforced — `applyProposal` checks all three conditions before touching anything.
|
|
355
|
+
- **Single outward funnel.** Every outward mutation in v2 (patch, PR, deploy) passes through `applyProposal`. There is no other path.
|
|
356
|
+
- **Patch on a new branch only.** `'patch'` kind applies to a new `ashlr/`-prefixed branch off HEAD via `git apply`. The user's working tree, current branch, and index are never touched. No `git reset --hard`, no checkout in the source repo, no push, no branch deletion of user branches.
|
|
357
|
+
- **PR via explicit gated path.** `'pr'` kind uses the M18 `createPr` which is itself confirm-gated and explicit — no auto-PR.
|
|
358
|
+
- **Enrollment + kill switch enforced on every apply.** `assertMayMutate` runs before any mutation; kill switch is checked first and cannot be bypassed.
|
|
359
|
+
- **No secrets in proposals.** `Proposal` fields and `~/.ashlr/inbox/` contain only metadata. No token values, env vars, prompt text, or secret names are ever written.
|
|
360
|
+
- No new runtime dependencies. All 2266 existing tests preserved. Typecheck passes clean (`tsc --noEmit`).
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## [Unreleased] — M22: Work Discovery (`ashlr backlog`)
|
|
365
|
+
|
|
366
|
+
### Added
|
|
367
|
+
- **`ashlr backlog` — prioritized, scored work queue across enrolled repos** (`src/cli/backlog.ts`, `src/core/portfolio/backlog.ts`, `src/core/portfolio/scanners.ts`):
|
|
368
|
+
- Aggregates open work items across all enrolled repos from six read-only sources: GitHub issues, TODO/FIXME/HACK/XXX code comments, CI/test state, outdated/vulnerable deps, docs health, and binshield security findings.
|
|
369
|
+
- Each item is scored by `value / effort` heuristic (higher = do first) and persisted to `~/.ashlr/backlog.json`.
|
|
370
|
+
- **`ashlr backlog`** — list the scored queue; flags: `--repo <path>` (single repo), `--source <issue|todo|test|dep|doc|security>` (filter by source), `--limit N` (top N), `--json` (machine-readable).
|
|
371
|
+
- **`ashlr backlog refresh`** — re-scan all enrolled repos and rebuild the backlog.
|
|
372
|
+
- **Six read-only scanners** (`src/core/portfolio/scanners.ts`) — each returns `WorkItem[]`, bounded, never throws:
|
|
373
|
+
- `scanIssues` — open GitHub issues via `gh` (M18 `listIssues`); skips repos without a GitHub remote.
|
|
374
|
+
- `scanTodos` — TODO/FIXME/HACK/XXX comments in source via `rg`/`grep`; skips `node_modules/`, `.git/`, `dist/`; capped per-repo.
|
|
375
|
+
- `scanTests` — CI state via `gh run list` (latest run); notes test-script presence heuristic; **never runs `npm test` or any project script**.
|
|
376
|
+
- `scanDeps` — `npm outdated --json` (stale) + `npm audit --json` (vulnerability severity counts); bounded with timeouts; read-only metadata only.
|
|
377
|
+
- `scanDocs` — heuristic checks: missing/thin README, missing LICENSE, missing CONTRIBUTING, low test-file presence.
|
|
378
|
+
- `scanSecurity` — `binshield` findings if installed; skipped gracefully when absent.
|
|
379
|
+
- **Backlog engine** (`src/core/portfolio/backlog.ts`):
|
|
380
|
+
- `buildBacklog(opts?)` — runs all scanners over `listEnrolled()` repos (default) or a provided subset; dedupes by id; sorts descending by score; persists to `~/.ashlr/backlog.json`.
|
|
381
|
+
- `loadBacklog()` — reads the persisted backlog; returns `null` when absent.
|
|
382
|
+
- `scoreItem(value, effort)` — pure `value/effort` heuristic, clamped, no side effects.
|
|
383
|
+
- `backlogPath()` — `~/.ashlr/backlog.json`.
|
|
384
|
+
- **New types in `src/core/types.ts`** (all existing types unchanged):
|
|
385
|
+
- `WorkSource = 'issue' | 'todo' | 'test' | 'dep' | 'doc' | 'security'`
|
|
386
|
+
- `WorkItem { id; repo; source; title; detail; value(1-5); effort(1-5); score; tags; ts }` — one scored work item.
|
|
387
|
+
- `Backlog { generatedAt; repos; items }` — persisted backlog shape.
|
|
388
|
+
|
|
389
|
+
### Guardrails (M22)
|
|
390
|
+
- **READ-ONLY**: scanners never modify any repo — no writes, no git mutations, no installs, no fixes. All subprocess calls use `execFile` with explicit arg arrays (no shell injection).
|
|
391
|
+
- **ENROLLMENT-SCOPED**: only repos returned by `listEnrolled()` are scanned. Default enrollment is empty → empty backlog. The scanner never walks the disk outside enrolled paths.
|
|
392
|
+
- **Bounded**: all scanners skip `node_modules/`, `.git/`, `dist/`; per-repo caps on file count and output size; `npm outdated`/`npm audit` run with timeouts. No project scripts (`npm test`, `npm run build`, etc.) are ever executed.
|
|
393
|
+
- **Never throws**: every scanner catches all errors and returns `[]`; a failing scanner never aborts the rest of the backlog build.
|
|
394
|
+
- **No secrets**: `WorkItem` fields and `~/.ashlr/backlog.json` contain only metadata (title, detail, score, tags). No token values, env vars, or secret names are written.
|
|
395
|
+
- No new runtime dependencies. All 2202 existing tests preserved. Typecheck passes clean (`tsc --noEmit`).
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## [Unreleased] — M21: Safety Foundation (Sandboxed Execution, Audit Trail, Enrollment + Kill Switch)
|
|
400
|
+
|
|
401
|
+
### Added
|
|
402
|
+
- **Git-worktree sandbox** (`src/core/sandbox/worktree.ts`) — the isolation primitive all future autonomous work runs inside:
|
|
403
|
+
- `createSandbox(sourceRepo, opts?)` — asserts enrollment + kill-switch gate, then runs `git worktree add -b ashlr/sandbox/<id>` under `~/.ashlr/sandboxes/<id>/` off the current HEAD. The source repo's working tree, index, HEAD, and user branches are **never touched**.
|
|
404
|
+
- `sandboxDiff(sb)` — returns `SandboxDiff { sandboxId, files, insertions, deletions, patch }` via `git diff` against `baseHead`. Read-only; never mutates.
|
|
405
|
+
- `removeSandbox(sb)` — `git worktree remove --force` + `git branch -D` on the scratch branch. Idempotent; never touches the source tree. All sandbox paths are forced under `~/.ashlr/sandboxes/`.
|
|
406
|
+
- `sandboxesDir()` / `listSandboxes()` — path helper + persisted metadata listing; never throws on bad entries.
|
|
407
|
+
- **Append-only audit trail** (`src/core/sandbox/audit.ts`):
|
|
408
|
+
- `audit(entry)` — sets `ts`, appends one JSONL line to `~/.ashlr/audit/<YYYY-MM-DD>.jsonl`. Never truncates, rewrites, or deletes. Malformed lines are skipped on read; never throws.
|
|
409
|
+
- `readAudit(limit?)` — returns entries newest-first; `limit` caps count.
|
|
410
|
+
- `auditDir()` — `~/.ashlr/audit`. **No secrets ever written** — summary is metadata only.
|
|
411
|
+
- **Enrollment registry + kill switch** (`src/core/sandbox/policy.ts`) — the gate every autonomous mutation passes through:
|
|
412
|
+
- `isEnrolled(repo)` / `enroll(repo)` / `unenroll(repo)` / `listEnrolled()` — registry persisted in `cfg.autonomy` / `~/.ashlr/enrollment.json`. **Default empty — nothing enrolled means nothing autonomous can mutate.**
|
|
413
|
+
- `killSwitchOn()` / `setKill(on)` — kill switch backed by `~/.ashlr/KILL` file / cfg flag. When set, ALL sandbox-mutating ops refuse regardless of enrollment.
|
|
414
|
+
- `assertMayMutate(repo, opts?)` — throws (and records `result:'refused'` in audit) if kill switch is on OR repo is not enrolled. The `allowAnyRepo` test hatch never overrides the kill switch.
|
|
415
|
+
- **CLI surface** (`src/cli/sandbox.ts`):
|
|
416
|
+
- `ashlr sandbox list` — list active sandboxes.
|
|
417
|
+
- `ashlr sandbox diff <id>` — show the diff accumulated inside a sandbox.
|
|
418
|
+
- `ashlr sandbox cleanup <id>` — remove a sandbox (worktree + scratch branch).
|
|
419
|
+
- `ashlr audit [N]` — tail the audit trail, newest-first, optional limit.
|
|
420
|
+
- `ashlr enroll list` — show enrolled repos.
|
|
421
|
+
- `ashlr enroll add <repo>` — enroll a repo for autonomous work.
|
|
422
|
+
- `ashlr enroll remove <repo>` — unenroll a repo.
|
|
423
|
+
- `ashlr enroll kill on|off` — set or clear the global kill switch.
|
|
424
|
+
- **New types in `src/core/types.ts`** (all existing types unchanged):
|
|
425
|
+
- `Sandbox { id; sourceRepo; worktreePath; branch; baseHead; createdAt }` — live sandbox descriptor.
|
|
426
|
+
- `SandboxDiff { sandboxId; files; insertions; deletions; patch }` — diff of a sandbox vs its base HEAD.
|
|
427
|
+
- `AuditEntry { ts; action; repo; sandboxId; summary; result:'ok'|'refused'|'error' }` — one audit record.
|
|
428
|
+
- `Enrollment { repos }` — persisted enrollment registry shape.
|
|
429
|
+
- `SwarmOptions.sandbox?:boolean` — seam for M24 daemon (OFF by default; swarm behavior today is unchanged).
|
|
430
|
+
|
|
431
|
+
### Guardrails (M21)
|
|
432
|
+
- **Isolation is absolute**: sandbox worktrees live ONLY under `~/.ashlr/sandboxes/`. Create and remove operations are structurally incapable of modifying the source repo's working tree, index, HEAD, or user branches. No `git reset --hard`, no checkout in source repo, no push, no user-branch deletion.
|
|
433
|
+
- **Enrollment default empty**: until you run `ashlr enroll add <repo>`, no real repo can be autonomously mutated — the gate throws before any worktree is created.
|
|
434
|
+
- **Kill switch**: `ashlr enroll kill on` sets `~/.ashlr/KILL`; every `assertMayMutate` call checks it first and refuses (audit `result:'refused'`). Cannot be bypassed by enrollment or the `allowAnyRepo` test hatch.
|
|
435
|
+
- **Audit is append-only**: no secret values, no prompt/completion content — metadata (action, repo, sandbox id, summary, result) only.
|
|
436
|
+
- **No new runtime deps**: node builtins + existing `core/git.ts` / `core/config.ts` / `cli/ui.ts` only.
|
|
437
|
+
- **Swarm seam is inert**: `SwarmOptions.sandbox` field plumbed; M12 swarm runner behaviour unchanged until M24 wires it.
|
|
438
|
+
- All 2110 existing tests preserved. Typecheck passes clean (`tsc --noEmit`).
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## [Unreleased] — M20: One-Command Onboarding + Self-Healing (CAPSTONE)
|
|
443
|
+
|
|
444
|
+
### Added
|
|
445
|
+
- **`ashlr init` — complete, idempotent, NON-TTY-safe onboarding** (`src/core/onboard.ts`, updated `src/cli/doctor-init.ts`):
|
|
446
|
+
- A single `ashlr init` (or `ashlr init --wire --yes`) takes a brand-new machine from zero to fully set-up. Re-runnable safely at any time — every step is idempotent.
|
|
447
|
+
- Seven ordered steps: **config** (ensure `~/.ashlr/config.json` from defaults), **models** (detect Ollama / LM Studio and report — never auto-downloads), **editors** (detect Claude / Cursor / Codex; wire all when `--wire`), **symlink** (ensure `ashlr` → `~/.local/bin`), **genome** (seed empty genome dir), **phantom** (status report only), **doctor** (roll-up as final gate).
|
|
448
|
+
- `--wire` — the only mutating optional step; wires every detected editor's MCP config (backup-first, idempotent, M18 pattern).
|
|
449
|
+
- `--yes` (or non-TTY stdin) — accepts all defaults without interactive prompts; fully CI-safe.
|
|
450
|
+
- `--json` — emits `OnboardResult { steps, ready, nextSteps }` for machine consumption.
|
|
451
|
+
- Finishes with a crisp `you're set up — try: ashlr run / ashlr swarm / ashlr tui` next-steps summary.
|
|
452
|
+
- **NEVER** auto-downloads models, modifies secrets, modifies shell profiles, or makes any network/outward call.
|
|
453
|
+
- **`ashlr doctor --fix` — self-healing doctor** (`src/core/doctor-fix.ts`, updated `src/cli/doctor-init.ts`):
|
|
454
|
+
- Runs `runDoctor`, then applies one safe automated remediation per failing/warn check in the SAFE-FIXABLE set:
|
|
455
|
+
- **`config`** — creates missing `~/.ashlr/config.json` from `defaultConfig()` + `saveConfig()`. Create-only; never overwrites an existing config.
|
|
456
|
+
- **`index`** — rebuilds a stale/missing index via `buildIndex` + `writeIndex`. Non-destructive (regenerates derived data only).
|
|
457
|
+
- **`local-bin`** — creates the `ashlr` → `~/.local/bin` symlink when missing and the source resolves. PATH is left as a `manual` action.
|
|
458
|
+
- **`genome-memory`** — creates the genome directory when missing (mkdir-only; never seeds or edits entries).
|
|
459
|
+
- **`mcp-plugin`** — registers the ashlr MCP gateway into a detected editor config via `wireEditor` (backup-first + idempotent, M18 pattern).
|
|
460
|
+
- Every other failing check → `FixAction { applied:false, manual:true }` with a one-line guidance hint.
|
|
461
|
+
- `--fix --json` emits `FixAction[]` for scripting; without `--json` prints a split **fixed** / **needs manual action** table.
|
|
462
|
+
- Exits non-zero only when blocking failures remain after fixes.
|
|
463
|
+
- **HARD GUARDRAILS**: NEVER deletes/overwrites user data, auto-downloads models, modifies secrets or shell profiles, or makes any outward/network call.
|
|
464
|
+
- **Bounded runtime self-heal** (`src/core/run/self-heal.ts`):
|
|
465
|
+
- `withHeal<T>(fn, policy, onHeal)` — BOUNDED wrapper around any runtime operation (MCP downstream spawn, model call). Classifies failures and emits `HealEvent` via `onHeal` before a bounded retry:
|
|
466
|
+
- `kind:'mcp-restart'` — restart a crashed MCP downstream; after `maxRestarts`, falls back to the existing M3 skip-on-failure behavior.
|
|
467
|
+
- `kind:'model-downgrade'` — on local model OOM/error, downgrade to a SMALLER LOCAL model via `chooseRoute` (only when `policy.allowDowngrade`; NEVER escalates to cloud, NEVER increases cost).
|
|
468
|
+
- `kind:'rate-backoff'` — exponential backoff on cloud rate-limit (ONLY when `allowCloud` is already set by the caller; never enables cloud on its own).
|
|
469
|
+
- Reuses `withRetry` from `core/run/retry.ts` for the bounded loop and backoff — no reimplemented backoff logic.
|
|
470
|
+
- Rethrows the last error on exhaustion or non-recoverable failure. Bounded by construction — `policy.maxRestarts` hard ceiling; no infinite loop.
|
|
471
|
+
- `defaultHealPolicy()` — conservative default (`maxRestarts: 3, allowDowngrade: true`).
|
|
472
|
+
- Opt-out: set `ASHLR_NO_HEAL=1` env var to bypass heal wrapping at any call site.
|
|
473
|
+
- MCP gateway downstream spawn wrapped in `withHeal` (bounded restart → M3 skip-on-failure fallback).
|
|
474
|
+
- Model call site wrapped in `withHeal` (local OOM → downgrade; cloud rate-limit → backoff — opt-out preserved).
|
|
475
|
+
- **New types in `src/core/types.ts`** (all existing types unchanged):
|
|
476
|
+
- `FixAction { checkId; label; applied; detail; manual }` — result of one `fixDoctor` remediation attempt.
|
|
477
|
+
- `OnboardStep { name; status:'ok'|'wired'|'detected'|'skipped'|'manual'; detail }` — one step of `onboard`.
|
|
478
|
+
- `OnboardResult { steps; ready; nextSteps }` — full onboarding result; `--json` output shape of `ashlr init`.
|
|
479
|
+
- `HealPolicy { maxRestarts; allowDowngrade }` — governs the self-heal loop; `allowDowngrade` enables local model downgrade only.
|
|
480
|
+
- `HealEvent { kind:'mcp-restart'|'model-downgrade'|'rate-backoff'; detail; attempt }` — emitted by `withHeal` on each heal action.
|
|
481
|
+
|
|
482
|
+
### Changed
|
|
483
|
+
- `src/cli/doctor-init.ts` — `cmdInit` drives full onboarding via `onboard(cfg, {wire, yes})`; `cmdDoctor` accepts `--fix` → `fixDoctor` + split fixed/manual report. Existing flags and behavior preserved in all non-`--fix` paths.
|
|
484
|
+
- `src/core/mcp-gateway.ts` — downstream spawn/connect wrapped in `withHeal` (bounded restart, then M3 skip-on-failure). Opt-out via `ASHLR_NO_HEAL`.
|
|
485
|
+
- `src/core/run/router.ts` (model call site) — wrapped in `withHeal` for local OOM downgrade and cloud rate-limit backoff. M15 local-first + escalation gates unchanged.
|
|
486
|
+
|
|
487
|
+
### Guardrails (M20)
|
|
488
|
+
- **`ashlr init` is idempotent + NON-TTY-safe**: `--wire`/`--yes` gate the optional mutating steps; default is detect + report + safe ensures only.
|
|
489
|
+
- **`doctor --fix` is safe/local/non-destructive**: creates only (never overwrites), no auto-download, no secret access, no shell profile modification, no network calls. Editor-config writes are backup-first + idempotent (M18). Every fix is reversible-ish and logged.
|
|
490
|
+
- **Self-heal is BOUNDED**: hard `maxRestarts` ceiling; no infinite loop. Downgrade is always to a SMALLER LOCAL model; cloud backoff only when `allowCloud` already set by the caller.
|
|
491
|
+
- All 2026 existing tests preserved. No new runtime dependencies. M3/M11/M15/M18 semantics and tests unchanged.
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## [Unreleased] — M19: Real Telemetry (OTLP) + Spend Governance
|
|
496
|
+
|
|
497
|
+
### Added
|
|
498
|
+
- **OTLP/HTTP-JSON trace emitter** (`src/core/observability/otlp.ts`):
|
|
499
|
+
- `buildGenAiTrace(spans)` — builds a valid OTLP/HTTP-JSON payload (`resourceSpans → scopeSpans → spans`) with GenAI semantic-convention attributes only: `gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, a cost attribute, `ashlr.run.id`, `ashlr.provider`, `ashlr.tier`, span status, and `start/endTimeUnixNano`. **Metadata only — never prompts, completions, tool arguments, file contents, or secrets.**
|
|
500
|
+
- `spansFromRun(run)` — produces one `GenAiSpan` per executed task from the `RunState` (usage, status, ids, provider, tier, duration). Pure, no I/O.
|
|
501
|
+
- `spansFromSwarm(s)` — same, from a `SwarmRun`. Pure, no I/O.
|
|
502
|
+
- **TelemetrySink seam** (`src/core/observability/telemetry-sink.ts`) — the documented cloud-ready seam:
|
|
503
|
+
- `TelemetrySink` interface: `emit(spans: GenAiSpan[]): Promise<TelemetryEmitResult>`.
|
|
504
|
+
- `LocalFileSink` (default) — appends spans and run summaries as JSONL to `~/.ashlr/telemetry/*.jsonl`; this is what `ashlr pulse` already aggregates locally. Active whenever no OTLP endpoint + PAT are configured.
|
|
505
|
+
- `OtlpHttpSink` — active only when `cfg.telemetry.pulse` is set **and** a PAT is available. Calls `buildGenAiTrace` → POSTs to the endpoint with `Authorization: Bearer <PAT>` and `Content-Type: application/json`. Bounded timeout, fire-and-forget, never throws or blocks the run. PAT is placed only in the Authorization header — never logged, printed, stored in span attributes, or returned.
|
|
506
|
+
- `getSink(cfg)` — returns `OtlpHttpSink` iff `cfg.telemetry.pulse && patAvailable(cfg)`, else `LocalFileSink`. **Default is 100% local.**
|
|
507
|
+
- `patAvailable(cfg)` — returns a `boolean` only; prefers Phantom, falls back to `ASHLR_PULSE_TOKEN` env var; never returns, logs, or exposes the value.
|
|
508
|
+
- `localTelemetryDir()` — returns `~/.ashlr/telemetry`.
|
|
509
|
+
- **Spend governance** (`src/core/observability/governance.ts`):
|
|
510
|
+
- `evalGovernance(cfg)` — reuses `buildForecast` / `buildRollup` to compute actual spend vs `cfg.telemetry.budgetUsd` over `cfg.telemetry.budgetWindow`. Returns a `GovernanceStatus`: `ok` (< 80% of cap), `warn` (≥ 80%), or `over` (> cap). `capUsd: null` + `ok` when no cap is configured. Never throws.
|
|
511
|
+
- `ashlr pulse` shows a governance summary line: `Governance: ok | ⚠ warn (82% of $50.00) | ✗ over ($52.10 of $50.00 / 30d)`.
|
|
512
|
+
- `ashlr doctor` gains a "Spend governance" check (degrades to warn when no cap is set).
|
|
513
|
+
- **`ashlr telemetry` command** (`src/cli/telemetry.ts`):
|
|
514
|
+
- `ashlr telemetry status` — prints whether an endpoint is configured (boolean), whether a PAT is available (boolean), the active sink (`local` or `otlp`), and a governance summary. **Never prints the endpoint URL value or PAT value.**
|
|
515
|
+
- `ashlr telemetry test` — emits a best-effort synthetic metadata-only span via the configured sink and reports the `TelemetryEmitResult` (sink type, ok/fail, non-secret detail). Useful for verifying the pipeline without a real run.
|
|
516
|
+
- **New types in `src/core/types.ts`** (all existing types preserved):
|
|
517
|
+
- `GenAiSpan { name; runId; model; provider; tier; tokensIn; tokensOut; estCostUsd; status; startTs; endTs }` — metadata only; no prompts, completions, tool args, file contents, or secrets.
|
|
518
|
+
- `TelemetryEmitResult { sink: 'local' | 'otlp'; ok: boolean; detail: string }` — `detail` never holds a PAT or content.
|
|
519
|
+
- `GovernanceStatus { level: 'ok' | 'warn' | 'over'; spentUsd: number; capUsd: number | null; window: string; message: string }`.
|
|
520
|
+
- `cfg.telemetry.govAction?: 'warn' | 'block'` (default `'warn'`); existing `pulse?`, `budgetUsd?`, `budgetTokens?`, `budgetWindow?` fields unchanged.
|
|
521
|
+
|
|
522
|
+
### Changed
|
|
523
|
+
- **`src/core/run/orchestrator.ts`** — `runGoal` replaces the M9 bespoke `reportToPulse()` with `getSink(cfg).emit(spansFromRun(run))`. Called post-completion, fire-and-forget, never blocks or throws. Governance check (`evalGovernance`) runs before execution: `warn` prints a prominent advisory; `over` + `govAction === 'block'` requires `--over-budget` to proceed (never silently blocks; per-run hard budget remains the only hard ceiling).
|
|
524
|
+
- **`src/core/swarm/runner.ts`** — `runSwarm` replaces any prior pulse reporting with `getSink(cfg).emit(spansFromSwarm(s))`. Same post-completion, opt-in, best-effort semantics. Governance check runs before swarm start.
|
|
525
|
+
- **`ashlr pulse`** — extended with a governance summary line (`ok` / `warn` / `over`) derived from `evalGovernance`.
|
|
526
|
+
- **`ashlr doctor`** — extended with a "Spend governance" check.
|
|
527
|
+
|
|
528
|
+
### Guardrails (M19)
|
|
529
|
+
- **Opt-in + best-effort**: OTLP emission happens ONLY when `cfg.telemetry.pulse` is set AND a PAT is available. It is fire-and-forget, bounded timeout, NEVER blocks, slows, or throws during a run or swarm. Failures log to stderr only.
|
|
530
|
+
- **Local-first default**: when no endpoint + PAT are configured (the default), the `LocalFileSink` is active and all telemetry stays 100% local under `~/.ashlr/telemetry/`.
|
|
531
|
+
- **Metadata-only**: span attributes and JSONL records contain model, token counts, cost estimate, run/swarm id, provider, tier, status, and duration — never prompt/response text, tool arguments, file contents, or secret values.
|
|
532
|
+
- **PAT safety**: the PAT lives only in the `Authorization` header. It is never logged, printed, put in span attributes, returned by any function, or committed. `patAvailable()` returns a boolean; `ashlr telemetry status` shows boolean flags — never values.
|
|
533
|
+
- **Governance is advisory, not a silent blocker**: `warn` prints a visible message; `over` + `block` requires `--over-budget` (which the user must pass explicitly). The per-run hard `RunBudget` ceiling remains the only hard ceiling and is unchanged.
|
|
534
|
+
- **No new runtime dependencies**: OTLP POST uses Node.js `fetch` builtin (Node 22+). No third-party packages added.
|
|
535
|
+
- All 1899 existing tests preserved.
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## [Unreleased] — M18: Deep Integrations
|
|
540
|
+
|
|
541
|
+
### Added
|
|
542
|
+
- **`ashlr gh` — GitHub read + guarded mutations** (`src/cli/gh.ts`, `src/core/integrations/github.ts`):
|
|
543
|
+
- `ashlr gh pr` — list open PRs for the current repo (number, title, URL, state, author).
|
|
544
|
+
- `ashlr gh issue` — list open issues (same fields).
|
|
545
|
+
- `ashlr gh ci` — latest CI/checks status for HEAD (`passing` / `failing` / `pending` / `none`).
|
|
546
|
+
- All reads go through the **`gh` CLI** (which owns auth) — no raw tokens are ever handled by the hub.
|
|
547
|
+
- `ashlr gh pr create` — the only mutation; prints a confirm prompt before any `gh pr create` call; never runs automatically.
|
|
548
|
+
- `githubStatus(cwd)` — read-only, never throws; degrades gracefully when cwd is not a git repo or `gh` is unavailable.
|
|
549
|
+
- **`ashlr vercel` — Vercel read-only surface** (`src/cli/vercel.ts`, `src/core/integrations/vercel.ts`):
|
|
550
|
+
- `ashlr vercel ls` — recent deployments (URL, state, createdAt, target) via the **`vercel` CLI**.
|
|
551
|
+
- `ashlr vercel logs` — tail logs for the latest deployment.
|
|
552
|
+
- `vercelStatus(cwd)` — read-only, never throws; degrades gracefully when no project is linked.
|
|
553
|
+
- Deploy actions remain in `ashlr ship --deploy vercel --confirm` (already gated); no new deploy paths added.
|
|
554
|
+
- **`ashlr wire` — editor auto-wire** (`src/cli/wire.ts`, `src/core/integrations/editors.ts`):
|
|
555
|
+
- `ashlr wire [claude|codex|cursor|all]` — wire the ashlr MCP gateway (and note genome) into each target editor's MCP config. Defaults to all detected editors.
|
|
556
|
+
- Reuses the M3 `mcp install` pattern: backup-first, deep-merge of `mcpServers`, idempotent, never clobbers, local-only. Accepts `configPath` for temp-safe test operation.
|
|
557
|
+
- `detectEditors()` — returns the subset of `claude`/`codex`/`cursor` whose config directories are present.
|
|
558
|
+
- `wireEditor(target, opts)` — returns `{ok, detail}`; the only file writes are to editor config dirs (or `opts.configPath` in tests).
|
|
559
|
+
- **Phantom identity in status + doctor** (`src/core/integrations/identity.ts`):
|
|
560
|
+
- `getIdentity()` — reads `phantom cloud status` and `phantom team` (via the installed **`phantom` CLI**); surfaces `{loggedIn, user, tier, team}`. Returns names/status only — secret values are never read, printed, or logged. Never throws; degrades to `loggedIn: false` when phantom is absent or logged out.
|
|
561
|
+
- `ashlr status` gains a "You: `<user>` · tier `<t>` · team `<team>`" identity line (suppressed when not logged in).
|
|
562
|
+
- `ashlr doctor` gains an identity check that degrades gracefully (warn, not fail) when phantom is not available.
|
|
563
|
+
- **`ashlr notify` — opt-in completion notifications** (`src/cli/notify.ts`, `src/core/integrations/notify.ts`):
|
|
564
|
+
- `ashlr notify test` — sends a test ping to configured webhooks (Slack and/or Discord). Strict no-op when no webhook is configured — no network call, no error.
|
|
565
|
+
- `notify(text, cfg)` — posts a concise, secret-free run/swarm completion summary. Returns `false` with zero side-effects when `cfg.notify.slackWebhook` and `cfg.notify.discordWebhook` are both unset. Never posts without an explicitly configured webhook.
|
|
566
|
+
- Config: `cfg.notify.slackWebhook` and/or `cfg.notify.discordWebhook` in `~/.ashlr/config.json`; both are optional; both unset = feature is completely dormant.
|
|
567
|
+
- **GitHub + Vercel one-liners in `ashlr status`**:
|
|
568
|
+
- When cwd is a GitHub repo and `gh` is available: `GitHub: N open PRs · CI passing/failing`.
|
|
569
|
+
- When a Vercel project is linked and `vercel` is available: `Vercel: <latest deploy state> <url>`.
|
|
570
|
+
- Both lines are omitted when the respective CLI is absent or the repo/project is not linked.
|
|
571
|
+
- **New types in `src/core/types.ts`** (all existing types preserved):
|
|
572
|
+
- `GithubStatus { isRepo: boolean; openPrs: number; openIssues: number; ci: 'passing'|'failing'|'pending'|'none'; repo: string|null }`
|
|
573
|
+
- `VercelStatus { linked: boolean; latestState: string|null; url: string|null }`
|
|
574
|
+
- `Identity { loggedIn: boolean; user: string|null; tier: string|null; team: string|null }`
|
|
575
|
+
- `NotifyTarget { slackWebhook?: string; discordWebhook?: string }`
|
|
576
|
+
- `AshlrConfig.notify?: NotifyTarget`
|
|
577
|
+
- Supporting read-model types: `PrSummary`, `IssueSummary`, `CreatePrOpts`, `CreatePrResult`, `DeploySummary`.
|
|
578
|
+
|
|
579
|
+
### Changed
|
|
580
|
+
- `ashlr status` output extended with GitHub, Vercel, and identity lines (each omitted when the source is unavailable).
|
|
581
|
+
- `ashlr doctor` extended with an identity check for phantom cloud login (degrades to warn, not fail).
|
|
582
|
+
- Architecture table in `src/core/` updated: new `integrations/` subdirectory (`github.ts`, `vercel.ts`, `editors.ts`, `identity.ts`, `notify.ts`).
|
|
583
|
+
|
|
584
|
+
### Guardrails (M18)
|
|
585
|
+
- **Read-first, always.** All status/list/identity reads (`githubStatus`, `vercelStatus`, `getIdentity`) are safe, read-only, and never throw. They delegate to the installed CLIs (`gh`, `vercel`, `phantom`) — the hub never handles raw tokens.
|
|
586
|
+
- **Mutations are explicit, confirm-gated, never automatic.** `ashlr gh pr create` requires an explicit subcommand + confirm prompt. Notifications require a configured webhook. Deploy stays in `ashlr ship --confirm`. No other write or outward action is introduced.
|
|
587
|
+
- **Identity = names/status only.** `getIdentity` never reads, stores, or prints secret values. Phantom vault contents are never accessed; only `phantom cloud status` and `phantom team` output is parsed.
|
|
588
|
+
- **Editor-wire is backup-first + idempotent + local.** `wireEditor` never overwrites a config unconditionally; it deep-merges `mcpServers` and backs up the target file before any write. Tests use `configPath` for temp-file safety.
|
|
589
|
+
- **Notify is strictly opt-in.** `notify()` returns `false` immediately with zero network calls when no webhook is configured. No webhook = feature is completely dormant.
|
|
590
|
+
- **No new runtime dependencies.** All new modules use Node builtins and the existing `gh`/`vercel`/`phantom` CLIs already installed on the system.
|
|
591
|
+
- All 1745 existing tests preserved.
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## [Unreleased] — M17: Verified Orchestration
|
|
596
|
+
|
|
597
|
+
### Added
|
|
598
|
+
- **Tamper-evident task signing** (`src/core/swarm/sign.ts`):
|
|
599
|
+
- `signOutput(content, cfg)` — HMAC-SHA256 signs a task result (content hash + HMAC). Key source: Phantom best-effort, else a local key auto-generated once at `~/.ashlr/keys/swarm.key` (0600, `crypto.randomBytes`). Signature stored on `SwarmTaskRun.signature` (`OutputSignature { alg, hash, sig, signer, ts }`). Signature contains only hashes — no payload secrets, never logged.
|
|
600
|
+
- `verifyOutput(content, sig, cfg)` — verifies a stored signature using `timingSafeEqual`; returns `boolean`; never throws.
|
|
601
|
+
- `ensureLocalKey()` — returns the key path, creating the file at 0600 with 32 random bytes if absent. Key is never printed, logged, or committed.
|
|
602
|
+
- **Downstream signature verification** (`src/core/swarm/runner.ts`):
|
|
603
|
+
- Before a task consumes a dependency's output, `runner.ts` calls `verifyOutput` on that dependency's stored signature. A mismatch (tampered or corrupted result) skips consumption and triggers an escalation gate rather than silently proceeding.
|
|
604
|
+
- **Exception-driven escalation gates** (`src/core/swarm/gate.ts`):
|
|
605
|
+
- `riskScan(text)` — case-insensitive heuristic scan for destructive/outward operations: `rm -rf`, `git push --force`, `deploy`, SQL `DROP`, and secret-exfiltration patterns. Returns `{ risky: boolean; reason: string }`; never throws.
|
|
606
|
+
- `shouldEscalate(ctx)` — pure function; priority order `tamper > verify-failed > over-budget > risk > low-confidence`. Returns the `EscalationReasonKind` that applies, or `null`. Only decides — the caller persists the `EscalationEvent`, sets `status: 'needs-approval'`, and **stops**. Never auto-approves.
|
|
607
|
+
- Gate trip conditions: downstream verify failure, over-budget, low-confidence / failed `verifyTask` on a critical task, or a RISK heuristic match on a task goal or result.
|
|
608
|
+
- **Swarm pause + `ashlr swarm approve <id>`** (`src/cli/swarm.ts`):
|
|
609
|
+
- When a gate trips, `runSwarm` persists the `EscalationEvent` (task id, kind, detail, timestamp) to the `SwarmRun`, sets `status: 'needs-approval'`, and halts. No work continues automatically.
|
|
610
|
+
- `ashlr swarm approve <id>` — explicit human action; resumes a `needs-approval` swarm from where it stopped. Only valid when status is `needs-approval`; errors otherwise.
|
|
611
|
+
- **`ashlr swarm verify <id>`** (`src/cli/swarm.ts`):
|
|
612
|
+
- Verifies all stored task signatures in a completed or paused swarm. Exit code 0 when every signature is valid; exit code 1 on any failure or if the swarm is not found. Safe to run at any time.
|
|
613
|
+
- **Rollback-aware snapshots** (`src/core/swarm/rollback.ts`):
|
|
614
|
+
- `snapshotProject(project)` — read-only; records the project's git `HEAD` commit ref and a stash ref (`stashRef`) for any dirty working tree into `RollbackSnapshot`. Non-git dirs or `null` project → `isRepo: false`; never throws.
|
|
615
|
+
- `rollbackTo(snap, { force })` — **caller must confirm before invoking** (CLI prompts or `--yes`). Refuses if `isRepo: false`, if the snapshot has no `head`, or if the tree is dirty without `--force`. Restores `HEAD` via `git reset --hard` and re-applies the stash if `stashRef` is set. **Never runs `git push --force`, never deletes branches, never force-resets without `force: true`.**
|
|
616
|
+
- `RollbackSnapshot` stored on `SwarmRun.rollback` at swarm start (before any tasks run).
|
|
617
|
+
- **`ashlr swarm rollback <id> [--yes] [--force]`** (`src/cli/swarm.ts`):
|
|
618
|
+
- Prints exactly what it will restore (project path, HEAD ref, stash ref) before doing anything.
|
|
619
|
+
- Requires `--yes` (or interactive confirmation) to proceed — never automatic.
|
|
620
|
+
- `--force` required to restore over a dirty working tree (without it, refuses and explains).
|
|
621
|
+
- Refuses on a non-git project or detached/ambiguous HEAD state, with guidance.
|
|
622
|
+
- This is the **only potentially-destructive operation** in M17; all other new paths are read-only or additive.
|
|
623
|
+
- **New types in `src/core/types.ts`** (all existing types preserved):
|
|
624
|
+
- `OutputSignature { alg: 'hmac-sha256' | 'phantom'; hash: string; sig: string; signer: string; ts: string }` — hashes only, no secrets.
|
|
625
|
+
- `EscalationReasonKind = 'verify-failed' | 'over-budget' | 'tamper' | 'risk' | 'low-confidence'`
|
|
626
|
+
- `EscalationEvent { taskId: string | null; kind: EscalationReasonKind; detail: string; ts: string }`
|
|
627
|
+
- `RollbackSnapshot { project: string | null; isRepo: boolean; head: string | null; dirty: boolean; stashRef: string | null; ts: string }`
|
|
628
|
+
- `SwarmTaskRun.signature?: OutputSignature`
|
|
629
|
+
- `SwarmRun.escalations?: EscalationEvent[]`, `SwarmRun.rollback?: RollbackSnapshot`
|
|
630
|
+
- `SwarmRun.status` union extended with `'needs-approval'`.
|
|
631
|
+
|
|
632
|
+
### Changed
|
|
633
|
+
- `src/core/swarm/runner.ts`: `runSwarm` snapshots project state at start (before any tasks), signs each task output on completion, verifies dependency signatures before consumption, calls `shouldEscalate` / `riskScan` at each gate point, persists `EscalationEvent` and halts on a positive gate, calls `captureFromSwarm` on normal completion.
|
|
634
|
+
- `src/cli/swarm.ts`: three new subcommands (`verify`, `approve`, `rollback`) wired and documented; rollback confirm-gate enforced at the CLI layer.
|
|
635
|
+
|
|
636
|
+
### Guardrails (M17)
|
|
637
|
+
- **Rollback is the only destructive operation.** It requires `ashlr swarm rollback <id>` + an explicit `--yes` confirm (or interactive prompt). It never runs automatically. `--force` is required to reset over a dirty tree. No `git push --force`; no branch deletion; refuses on non-git dirs and detached HEAD.
|
|
638
|
+
- **Keys: 0600, never logged.** `~/.ashlr/keys/swarm.key` is created with `crypto.randomBytes`, `chmod 0600`, and is never printed, logged, or captured in any run artifact. Phantom secrets never expose values — signatures contain only derived hashes.
|
|
639
|
+
- **Escalation gates pause, never auto-approve.** A gate trip sets `status: 'needs-approval'` and stops; `ashlr swarm approve <id>` is the only path forward.
|
|
640
|
+
- **Recursion guard + hard budget intact.** `ASHLR_IN_SWARM` env guard and the global `RunBudget` ceiling are unchanged.
|
|
641
|
+
- **Zero new runtime dependencies.** All signing and hashing use `node:crypto` (Node builtin). No third-party packages added.
|
|
642
|
+
- All 1619 existing tests preserved.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## [Unreleased] — M16: Compounding Genome
|
|
647
|
+
|
|
648
|
+
### Added
|
|
649
|
+
- **Auto-capture from runs and swarms** (`src/core/genome/capture.ts`):
|
|
650
|
+
- `captureFromRun(run, cfg)` and `captureFromSwarm(s, cfg)` — fire-and-forget hooks called on run/swarm completion. Append a structured `GenomeEntry` (goal, concise approach/outcome summary, tags for project/status/tool/engine, and result gist) to `~/.ashlr/genome/hub.jsonl` via `appendHubEntry`.
|
|
651
|
+
- `summarizeForGenome({goal, result, tasks})` — pure, deterministic helper that produces a secret-free, hard-capped (~800 chars) summary suitable for storage. Captures metadata/summary only — never raw prompts, completions, tool arguments, file contents, or secrets.
|
|
652
|
+
- Opt-out: set `cfg.genome.autoCapture: false` (default `true`) or pass `--no-capture` on `ashlr run` / `ashlr swarm`. Auto-capture is **dedupe-aware** and **never throws or blocks** — it fires in the background after completion.
|
|
653
|
+
- **`ashlr genome consolidate`** (`src/core/genome/consolidate.ts`):
|
|
654
|
+
- `consolidateGenome(cfg)` — merges near-duplicate entries (same goal/project with high text overlap) into one canonical entry. Preserves full provenance: merged `count`, `firstSeen`/`lastSeen` timestamps, and a union of all tags. Returns a `ConsolidationResult { before, after, merged, backupPath }`.
|
|
655
|
+
- **Writes a timestamped backup of `hub.jsonl` before any mutation.** Nothing is silently deleted; all content is preserved in the merged entry. Bounded: only touches the hub store.
|
|
656
|
+
- **`ashlr genome playbook "<goal>"`** (`src/core/genome/playbook.ts`):
|
|
657
|
+
- `buildPlaybook(goal, cfg, opts?)` — recalls similar past entries and synthesises a concise "how we approached this before — what worked / what failed / cost" playbook using the **local provider only**. Falls back to a concatenated recall summary when synthesis is unavailable or over budget. Never throws.
|
|
658
|
+
- `playbookText(p, maxChars)` — pure, hard-capped serialiser for injecting into agent prompts.
|
|
659
|
+
- Used by `orchestrator.runGoal`: when `cfg.genome.playbookOnRun !== false` (and `--no-memory` is not set), the M7 raw-recall injection is upgraded to a synthesised playbook injected (bounded by char cap) into the planning context.
|
|
660
|
+
- **`ashlr genome export <file>`** (`src/core/genome/export.ts`):
|
|
661
|
+
- `exportGenome(cfg, dest, format)` — dumps the full genome to a portable JSON or Markdown file. Read-only, never throws, no lock-in. Returns `{ok, count, path}`.
|
|
662
|
+
- `format: 'json'` — newline-delimited array of `GenomeEntry` objects.
|
|
663
|
+
- `format: 'md'` — human-readable Markdown with one section per entry.
|
|
664
|
+
- **New CLI subcommands in `ashlr genome`** (`src/cli/genome.ts`):
|
|
665
|
+
- `ashlr genome --teach "<note>" [--project p] [--tags a,b]` — append a high-value manual note (tagged `teach`).
|
|
666
|
+
- `ashlr genome consolidate` — dedupe and merge near-duplicate entries (backup-first).
|
|
667
|
+
- `ashlr genome export <file> [--format json|md]` — portable export of the full genome.
|
|
668
|
+
- `ashlr genome playbook "<goal>"` — synthesise and print a playbook for the given goal.
|
|
669
|
+
- All existing `cmdRecall` / `cmdLearn` / `cmdGenome` (health) commands preserved unchanged.
|
|
670
|
+
- **New types in `src/core/types.ts`** (all existing types preserved):
|
|
671
|
+
- `GenomeCapture { goal: string; project: string|null; summary: string; tags: string[]; outcome: 'done'|'aborted'|'failed'; source: 'run'|'swarm'|'teach' }`
|
|
672
|
+
- `Playbook { goal: string; entries: RecallHit[]; synthesis: string }`
|
|
673
|
+
- `ConsolidationResult { before: number; after: number; merged: number; backupPath: string }`
|
|
674
|
+
- `cfg.genome.autoCapture?: boolean` (default `true`) and `cfg.genome.playbookOnRun?: boolean` (default `true`).
|
|
675
|
+
|
|
676
|
+
### Changed
|
|
677
|
+
- `src/core/run/orchestrator.ts`: `runGoal` calls `captureFromRun` on completion and injects `playbookText` into planning context (upgrading M7 raw-recall); honours `--no-capture` flag and `cfg.genome.autoCapture`.
|
|
678
|
+
- `src/core/swarm/runner.ts`: `runSwarm` calls `captureFromSwarm` on completion; honours `--no-capture`.
|
|
679
|
+
- `src/cli/run.ts` and `src/cli/swarm.ts`: `--no-capture` flag added; sets `cfg.genome.autoCapture = false` for that invocation only.
|
|
680
|
+
|
|
681
|
+
### Guardrails (M16)
|
|
682
|
+
- **Privacy**: auto-capture stores metadata/summary only. Raw prompts, completions, tool call arguments, and file contents are never written to the genome. `summarizeForGenome` is hard-capped at ~800 chars and is deterministic with no I/O.
|
|
683
|
+
- **No data loss**: `consolidateGenome` writes a timestamped backup before any mutation; merged entries retain all key content; the genome is append-only everywhere else; `exportGenome` is strictly read-only.
|
|
684
|
+
- **Local-only**: playbook synthesis uses the local provider only (best-effort); falls back to concatenated recall on failure or budget exhaustion; no cloud calls. Auto-capture fires in the background and never throws.
|
|
685
|
+
- **Non-blocking**: capture is fire-and-forget — it never delays a run result or swarm completion.
|
|
686
|
+
- All 1504 existing tests preserved.
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
## [Unreleased] — M15: Cost-Optimal Local-First Routing
|
|
691
|
+
|
|
692
|
+
### Added
|
|
693
|
+
- **Per-task model routing** (`src/core/run/router.ts`): `chooseRoute(taskGoal, cfg, opts)` picks the best available local model (Ollama / LM Studio) for every task according to `cfg.models.providerChain`. Optional `cfg.models.routing[]` rules match task goals by pattern and override the default model. Returns a `RouteDecision` with `{provider, model, tier, reason}` so the caller always knows exactly what was chosen and why.
|
|
694
|
+
- **Local-first, hard-enforced**: cloud provider is selected only when `opts.allowCloud === true` AND `opts.lastReason !== 'none'` (an escalation reason is present) AND a cloud API key is actually available (`cloudKeyAvailable(provider)`). Any other combination stays local. No silent cloud spend is ever possible.
|
|
695
|
+
- `cloudKeyAvailable(provider)`: reads the standard API-key env var for a provider; returns a boolean — never logs or leaks the value.
|
|
696
|
+
- `wouldBeCloudCost(tokensIn, tokensOut)`: returns a clearly-labeled estimate of what the same token counts would have cost on the default cloud provider, for savings comparison only. Never used as a billing figure.
|
|
697
|
+
- **Auto-escalation on failure/latency** (integrated into `src/core/run/orchestrator.ts`):
|
|
698
|
+
- When a task result is empty/errored, or `verifyTask` returns `!ok` (M11 verify loop), the orchestrator calls `chooseRoute` with `lastReason: 'task-failed'` / `'verify-failed'` for the retry pass.
|
|
699
|
+
- When a task exceeds `cfg.models.escalate.latencyMs` (optional), the next attempt is routed with `lastReason: 'latency'`.
|
|
700
|
+
- **Cloud escalation requires both `--allow-cloud` AND a present key.** Without both, the retry stays local or marks the task `needs-attention`. There is no automatic cloud fallback.
|
|
701
|
+
- Escalation is a single routed retry — it ties into the existing M11 `withRetry` / `verifyTask` path; the global `RunBudget` ceiling is never lifted.
|
|
702
|
+
- **Cost attribution per provider** (orchestrator + `src/core/observability/rollup.ts`):
|
|
703
|
+
- Each `RunTask` now records `provider` and `tier` from its `RouteDecision`.
|
|
704
|
+
- Local tasks (tier `'local'`) contribute `$0.00` actual cost; cloud tasks carry real `estCostUsd`.
|
|
705
|
+
- `buildRollup` aggregates actual spend by provider and separately computes a "would-have-been-cloud" estimate (via `wouldBeCloudCost`) for every local task — giving a concrete savings figure.
|
|
706
|
+
- **Cost forecasting** (`src/core/observability/forecast.ts`): `buildForecast(window, cfg)` returns a `CostForecast` with:
|
|
707
|
+
- `spentUsd` — actual cost in the window (local = $0, cloud = real estimate).
|
|
708
|
+
- `localSavingsUsd` — cloud-equivalent cost for tokens handled locally, clearly labeled as an estimate.
|
|
709
|
+
- `projectedMonthlyUsd` — simple linear projection from the window rate.
|
|
710
|
+
- All numbers are **estimates, labeled as such**; no precision is fabricated.
|
|
711
|
+
- **`ashlr pulse` savings + forecast line**: `ashlr pulse` now shows a savings/forecast line beneath its summary output:
|
|
712
|
+
```
|
|
713
|
+
Local savings (est): $X.XX | Cloud would-have-been: $Y.YY | Projected 30d: $Z.ZZ
|
|
714
|
+
```
|
|
715
|
+
The line is printed only when at least one local task has run in the window; suppressed on `--json` (the `CostForecast` is merged into the JSON rollup instead).
|
|
716
|
+
- **`ashlr models` — local model management** (`src/cli/models.ts`, `src/core/run/model-manager.ts`):
|
|
717
|
+
- `ashlr models` — list all local models from Ollama (`/api/tags`) and LM Studio (`/api/models`). Shows name, provider, approximate size label, and whether it is the currently active/default model per config.
|
|
718
|
+
- `ashlr models pull <name>` — explicit Ollama pull. Prints a size warning and requires interactive confirmation (`y/yes`) before downloading. **Never invoked automatically** during a run, route, or any other command.
|
|
719
|
+
- `ashlr models start` — best-effort attempt to start a locally installed Ollama daemon when it is installed but not responding. **Never invoked automatically**; bounded to the local Ollama process; never installs or downloads anything.
|
|
720
|
+
- `ollamaInstalled()`: checks `PATH` for the `ollama` binary — no network call, no side effects.
|
|
721
|
+
- **New types** in `src/core/types.ts` (all existing types preserved):
|
|
722
|
+
- `ModelTier = 'local' | 'cloud'`
|
|
723
|
+
- `RouteDecision { provider: string; model: string; tier: ModelTier; reason: string }`
|
|
724
|
+
- `RoutingRule { match: string; model: string }`
|
|
725
|
+
- `EscalationReason = 'task-failed' | 'verify-failed' | 'latency' | 'none'`
|
|
726
|
+
- `LocalModelInfo { provider: 'ollama' | 'lmstudio'; name: string; sizeLabel?: string; active: boolean }`
|
|
727
|
+
- `CostForecast { window: string; spentUsd: number; localSavingsUsd: number; projectedMonthlyUsd: number }`
|
|
728
|
+
- `cfg.models.routing?: RoutingRule[]` and `cfg.models.escalate?: { onFailure: boolean; latencyMs?: number }` config fields.
|
|
729
|
+
|
|
730
|
+
### Guardrails (M15)
|
|
731
|
+
- **Local-first, no silent cloud**: cloud is reachable only via `--allow-cloud` + a present API key + a non-`'none'` escalation reason. The default path is 100% local. Every run and swarm is still bounded by the hard `RunBudget` ceiling.
|
|
732
|
+
- **No auto-download**: `ollama pull` runs only on the explicit `ashlr models pull <name>` command (with a confirmation prompt). It is never called during a run, route, or escalation.
|
|
733
|
+
- **No auto-start**: `ashlr models start` is the only path that attempts to start a local Ollama process. It is never called automatically.
|
|
734
|
+
- **Estimates clearly labeled**: all savings and forecast numbers are estimates; no precision is fabricated; every cost label includes `(est)`.
|
|
735
|
+
- **No secrets logged**: `cloudKeyAvailable` returns a boolean only; key values never appear in logs, run state, or rollup output.
|
|
736
|
+
- **Zero new runtime dependencies**: `router.ts`, `model-manager.ts`, `forecast.ts`, and `cli/models.ts` use only Node builtins and existing hub modules (`provider-client`, `budget`, `rollup`, `ui`).
|
|
737
|
+
- All 1396 existing tests preserved.
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## [Unreleased] — M14: Surfaces II (Local Web Dashboard)
|
|
742
|
+
|
|
743
|
+
### Added
|
|
744
|
+
- **`ashlr serve [--port N] [--open] [--allow-dispatch]` — local web dashboard** (`src/core/web/server.ts`, `src/core/web/api.ts`, `src/core/web/static.ts`, `src/cli/serve.ts`):
|
|
745
|
+
- Starts a localhost HTTP server (Node `http` builtin) serving a JSON API and a single-page dashboard (static assets bundled in the repo — **no CDN, fully offline**). Default port 7777; `--open` launches the browser automatically.
|
|
746
|
+
- **Five dashboard views** (vanilla JS + inline SVG/Canvas, dark theme, brand aesthetic, live via EventSource):
|
|
747
|
+
- **Overview** — aggregated snapshot of the local ecosystem (git health, tools, 7-day activity).
|
|
748
|
+
- **Runs** — paginated list of recent agent runs with status, goal, and token/cost usage.
|
|
749
|
+
- **Swarms** — swarm detail with an **SVG dependency-graph** (nodes per task colored by phase/status, edges for declared dependencies) and a live burndown chart updating in real time via SSE.
|
|
750
|
+
- **Pulse** — SVG bar charts of cost/token usage by project, day, and model.
|
|
751
|
+
- **Genome browser** — full genome list with a search box that hits `/api/genome?q=` for instant recall.
|
|
752
|
+
- **Live updates via SSE** — `GET /api/events` uses Server-Sent Events to push run/swarm state changes (bounded poll interval) so the page live-streams burndown without a reload. Cleared on disconnect and on server close (no timer leaks).
|
|
753
|
+
- **JSON read-only API** (all endpoints metadata-only; no secrets served):
|
|
754
|
+
- `GET /api/snapshot` — `buildSnapshot(cfg)` aggregate (M13 dashboard snapshot).
|
|
755
|
+
- `GET /api/runs` / `GET /api/run/:id` — `listRuns` / `loadRun` (404 on unknown id).
|
|
756
|
+
- `GET /api/swarms` / `GET /api/swarm/:id` — `listSwarms` / `loadSwarm` (404 on unknown id).
|
|
757
|
+
- `GET /api/pulse[?window=1d|7d|30d]` — `buildRollup` (default 7d).
|
|
758
|
+
- `GET /api/genome[?q=<query>]` — `recall(q, cfg)` when `q` supplied, else `loadGenome(cfg)`.
|
|
759
|
+
- `GET /api/events` — SSE stream (see above).
|
|
760
|
+
- **Opt-in dispatch endpoint** (`POST /api/run`) — registered **only** when `--allow-dispatch` is passed. Protected by a per-session token (printed at server start, required in a header, compared constant-time) to defeat CSRF/drive-by POSTs. Body clamped to local-first budget caps; `allowCloud` never set. **Default server has zero mutating endpoints.**
|
|
761
|
+
- **New types** in `src/core/types.ts`: `WebServerOptions`, `WebServerHandle`.
|
|
762
|
+
|
|
763
|
+
### Security (non-negotiable, documented in CONTRACT-M14.md)
|
|
764
|
+
- **Binds `127.0.0.1` only** — never `0.0.0.0`; not externally reachable.
|
|
765
|
+
- **Host-header allowlist** (`localhost` / `127.0.0.1` / `::1` ± port) enforced as the first pipeline step — all other `Host` values → 403. Defeats DNS-rebinding attacks.
|
|
766
|
+
- **Read-only by default** — no mutating endpoints exist unless `--allow-dispatch` is explicitly passed.
|
|
767
|
+
- **Token-guarded dispatch** — constant-time comparison; token printed once at startup; never in logs or snapshot responses.
|
|
768
|
+
- **Path-traversal-safe static serving** — decode + join under assets dir, resolve, reject `..` / absolute / null-byte / symlink-escape → 404.
|
|
769
|
+
- **No outward/SSRF calls** from the server process.
|
|
770
|
+
- **No CDN / no external fonts or scripts** — all assets bundled in the repo and served locally; fully functional offline.
|
|
771
|
+
- **Ephemeral + clean close** — `Ctrl-C` stops the server; SSE poll timers cleared; no leaks.
|
|
772
|
+
- **Zero new runtime dependencies** (`http` / `crypto` / `fs` / `path` / `url` builtins only).
|
|
773
|
+
|
|
774
|
+
### Guardrails (M14)
|
|
775
|
+
- All 1314 existing tests preserved.
|
|
776
|
+
- Reuses `core/dashboard.ts buildSnapshot`, `core/run/orchestrator.ts listRuns/loadRun/runGoal`, `core/swarm/store.ts listSwarms/loadSwarm`, `core/observability/rollup.ts buildRollup`, `core/genome/store.ts loadGenome/genomeHealth`, `core/genome/recall.ts recall`, and `cli/ui.ts`.
|
|
777
|
+
- Zero new runtime dependencies in `core/` and `cli/`.
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
## [Unreleased] — M13: Surfaces I (Interactive TUI + Real-Time Raycast)
|
|
782
|
+
|
|
783
|
+
### Added
|
|
784
|
+
- **`ashlr tui` / `ashlr dash` — interactive live terminal dashboard** (`src/tui/app.ts`, `src/tui/render.ts`, `src/cli/tui.ts`):
|
|
785
|
+
- Runs in an alt-screen buffer with raw-mode key handling and automatic resize awareness. Auto-refreshes every ~2 s by re-reading local data sources (bounded, never blocks the event loop).
|
|
786
|
+
- **Five tabs** (switch with `Tab` / `Shift-Tab` or `1`–`5`):
|
|
787
|
+
- **Overview** — repo health (dirty/stale counts), ecosystem tool availability, 7-day activity summary.
|
|
788
|
+
- **Runs** — recent agent runs with live status, goal summary, and token usage.
|
|
789
|
+
- **Swarms** — live phase/task burndown for active and recent swarms (done/total per phase).
|
|
790
|
+
- **Pulse** — 7-day cost, tokens, and per-project activity from the local observability rollup.
|
|
791
|
+
- **MCP** — discovered MCP server health (name, tool count, ok/fail).
|
|
792
|
+
- **Key bindings**: `Tab` / `Shift-Tab` or `1`–`5` to switch tabs; `j` / `k` to move selection; `r` to force-refresh; `Enter` to show detail; `q` / `Ctrl-C` to quit.
|
|
793
|
+
- **`--once` flag**: render one frame to stdout and exit — safe for headless use, scripting, and test assertions.
|
|
794
|
+
- **Non-TTY graceful degradation**: when stdout is not a TTY (pipe, redirect, CI), automatically prints one frame without entering raw mode or alt-screen.
|
|
795
|
+
- **Terminal safety guarantee**: alt-screen, cursor visibility, and raw mode are **always restored** on quit, signal (`SIGINT`, `SIGTERM`), or thrown exception — the terminal is never left corrupted.
|
|
796
|
+
- **Zero new runtime dependencies**: built entirely on Node.js builtins and `src/cli/ui.ts` ANSI helpers.
|
|
797
|
+
- **`src/core/dashboard.ts`** — `buildSnapshot(cfg)`: aggregates index/git (dirty/stale), tools-registry, observability rollup, runs (orchestrator), swarm store, MCP registry, and genome health into a single `DashboardSnapshot`. Bounded and fault-tolerant — never throws; any failed data source degrades to zeroed/empty fields.
|
|
798
|
+
- **New types** in `src/core/types.ts`: `DashboardSnapshot`, `TuiTab`.
|
|
799
|
+
- **Raycast extension upgrades** (`src/raycast/`):
|
|
800
|
+
- **Dispatch Run** command: form UI (goal, budget, parallel, engine flags) that invokes `ashlr run --json` and shows live output. Bounded and local-first, matching CLI guardrails.
|
|
801
|
+
- **Swarms** command: lists active and recent swarms with live done/total task counts and per-phase progress; action to show full detail or open the target project.
|
|
802
|
+
- **Auto-revalidation**: existing Pulse and Attention views now use `usePromise`/`useExec` with a short poll interval so they refresh without manual reloads.
|
|
803
|
+
- All new commands registered in `src/raycast/package.json`.
|
|
804
|
+
|
|
805
|
+
### Guardrails (M13)
|
|
806
|
+
- TUI is **reads-only** — no destructive or outward actions from any tab.
|
|
807
|
+
- Raycast dispatch is the only outward action; it is bounded (budget ceiling), local-first by default (`--allow-cloud` required for cloud endpoints), and uses the same `ashlr run` path as the CLI.
|
|
808
|
+
- ZERO new runtime dependencies added to CLI/TUI (Node builtins + `src/cli/ui.ts` only); Raycast retains its existing `@raycast/api`.
|
|
809
|
+
- All 1184 existing tests preserved.
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## [Unreleased] — M12: Spec-Driven Swarms
|
|
814
|
+
|
|
815
|
+
### Added
|
|
816
|
+
- **`ashlr spec` — end-state specs as first-class artifacts** (`src/core/spec/spec-store.ts`, `src/cli/spec.ts`):
|
|
817
|
+
- `ashlr spec new "<goal>" [--project <path>]`: drafts a structured end-state spec with the local model (sections: Context, North Star, Operating Principles, Pillars, Roadmap/phases, Verification). Stored versioned at `<project>/.ashlr/specs/<slug>-v<N>.md` plus a sidecar `.json` (id, goal, version, createdAt, status). Never overwrites an existing version.
|
|
818
|
+
- `ashlr spec list [--project <path>]`: table of all specs (id, version, status, goal).
|
|
819
|
+
- `ashlr spec show <id>`: print the full markdown body + metadata.
|
|
820
|
+
- `ashlr spec refine <id> "<note>"`: produce v+1 incorporating the note. Versioned and append-only — prior versions are always recoverable.
|
|
821
|
+
- **`ashlr swarm` — contracts-first agent-fleet orchestration** (`src/core/swarm/planner.ts`, `src/core/swarm/runner.ts`, `src/core/swarm/store.ts`, `src/cli/swarm.ts`):
|
|
822
|
+
- `ashlr swarm "<goal>" | <specId> [--budget N] [--parallel N] [--background] [--resume <id>] [--dry-run] [--allow-cloud]`: decomposes a goal or spec into a `SwarmPlan` (phases: SCAFFOLD → BUILD → INTEGRATE → VERIFY → REVIEW) and executes a fleet of agents through it. Each task is an `orchestrator.runGoal` invocation, local-first by default. BUILD phase fans out in parallel (cap `--parallel`, default 3, max 8). Planner caps tasks per phase at 6.
|
|
823
|
+
- `ashlr swarms [--json]`: list all past swarm runs (id, status, phase, cost).
|
|
824
|
+
- `ashlr swarm show <id>`: full `SwarmRun` detail including per-task status, usage, and errors.
|
|
825
|
+
- **New types in `src/core/types.ts`**: `SpecArtifact`, `SwarmPhaseName`, `SwarmTaskSpec`, `SwarmTaskRun`, `SwarmPlan`, `SwarmRun`, `SwarmOptions`.
|
|
826
|
+
- **`--dry-run`**: planner runs, all tasks printed, no agents invoked — zero cost, safe to run anywhere.
|
|
827
|
+
- **`--background`**: launch a detached worker process that runs the swarm and writes progress to the swarm record; foreground returns the swarm id immediately. Total budget still bounds all background work.
|
|
828
|
+
|
|
829
|
+
### Guardrails
|
|
830
|
+
- **Hard total budget**: a single `RunBudget` ceiling spans the entire swarm (all tasks, all phases). Any task that would push usage over the limit is skipped; the swarm aborts cleanly with full partial state preserved.
|
|
831
|
+
- **Bounded concurrency**: `--parallel` (default 3, max 8); planner enforces ≤6 tasks per phase.
|
|
832
|
+
- **Local-first**: tasks run on local models (builtin/Ollama) by default. `--allow-cloud` required for cloud endpoints — no silent billing.
|
|
833
|
+
- **No recursion / no fork bomb**: swarm tasks set `ASHLR_IN_SWARM=1` in subprocess env. `ashlr swarm` refuses to start if that marker is already set, preventing nested swarms.
|
|
834
|
+
- **No outward/destructive actions by default**: tasks operate within the target project dir; push, deploy, repo creation, and destructive `tidy --apply` / `ship --confirm` are blocked unless explicitly opted in.
|
|
835
|
+
- **Resumable**: `SwarmRun` persisted to `~/.ashlr/swarms/<id>.json` after every step; `--resume <id>` restarts from the last completed checkpoint.
|
|
836
|
+
- **Streaming progress** (M11 `StreamSink`): phase start/done, per-task start/done, live burndown counts streamed to stderr in real time.
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
840
|
+
## [Unreleased] — M11: Watchable, Robust Runs
|
|
841
|
+
|
|
842
|
+
### Added
|
|
843
|
+
- **Hardened engine delegation** (`src/core/run/engines.ts`): replaced the guessed
|
|
844
|
+
`['--goal', goal]` spawn with per-engine adapter functions that emit the real,
|
|
845
|
+
confirmed CLI argv for each tool. `buildEngineCommand` returns `null` for
|
|
846
|
+
`builtin` (local loop) or an `EngineCommand` with the exact invocation:
|
|
847
|
+
- `claude` (Claude Code): `claude -p "<goal>" --model <M> --output-format json`
|
|
848
|
+
(JSON carry usage + cost automatically).
|
|
849
|
+
- `aw` (ashlr-workbench): `aw auto "<goal>" --cwd <dir>` plus `--model <M>` when
|
|
850
|
+
a model is set.
|
|
851
|
+
- `ashlrcode` (absent on this host): `ac --goal "<goal>"`; absence is detected via
|
|
852
|
+
`engineInstalled` and routes to the builtin loop with a clear message.
|
|
853
|
+
`spawnEngine` applies `withToolEnv(cfg)` to every spawn and wraps via
|
|
854
|
+
`phantom exec --` when `cfg.phantom?.enabled` and `phantom` is on PATH.
|
|
855
|
+
`phantomWrap` is exported for unit testing the exact argv without real delegation.
|
|
856
|
+
- **Streaming output** (`src/core/run/streaming.ts`): `RunStreamEvent` (with kinds
|
|
857
|
+
`task-start`, `model-delta`, `tool-call`, `task-done`, `retry`, `verify`, `log`)
|
|
858
|
+
flows from the agent loop to the CLI in real time. `makeCliSink` renders a live,
|
|
859
|
+
human-readable stream to **stderr** (keeping stdout clean for `--json`);
|
|
860
|
+
`nullSink` is a no-op for programmatic consumers. `StreamSink` type exported.
|
|
861
|
+
- **`--stream` / `--no-stream` flags** (`src/cli/run.ts`): streaming defaults on
|
|
862
|
+
when stderr is a TTY; `--no-stream` suppresses live output. `--json` keeps stdout
|
|
863
|
+
clean JSON while the event stream goes to stderr. All existing flags preserved.
|
|
864
|
+
- **Retry + verification loop** (`src/core/run/retry.ts`, `src/core/run/verify.ts`):
|
|
865
|
+
`withRetry` wraps any async fn with bounded exponential back-off
|
|
866
|
+
(`baseDelayMs × 2^(attempt-1)`, capped at `maxAttempts`; caller supplies
|
|
867
|
+
`isRetryable`). `verifyTask` checks a completed task result with a cheap
|
|
868
|
+
heuristic first; if the budget allows, it optionally asks the model for a
|
|
869
|
+
verdict — but never exceeds the global budget ceiling. `VerifyVerdict` signals
|
|
870
|
+
`ok`, `reason`, and `method` (`'heuristic'` or `'model'`).
|
|
871
|
+
- **Phantom-exec proxy** (`src/core/run/engines.ts` `phantomWrap`): when
|
|
872
|
+
`cfg.phantom?.enabled` is true and `phantom` is installed, all engine spawns are
|
|
873
|
+
wrapped as `phantom exec -- <bin> [...args]` so secrets are injected by Phantom
|
|
874
|
+
rather than by the hub. Best-effort: absent or disabled Phantom falls back to
|
|
875
|
+
direct spawn. Secret values are never logged or injected into the env allowlist.
|
|
876
|
+
- New types in `src/core/types.ts`: `RunStreamEvent`, `RetryPolicy`,
|
|
877
|
+
`VerifyVerdict`, `EngineId`, `EngineCommand`.
|
|
878
|
+
- `ProviderClient` extended with optional `chatStream?(messages, tools, onDelta)`
|
|
879
|
+
for Ollama (NDJSON `/api/chat stream:true`) and LM Studio (SSE
|
|
880
|
+
`/v1/chat/completions stream:true`); both fall back to `chat()` when streaming
|
|
881
|
+
is unavailable. Callers guard with `?.`.
|
|
882
|
+
|
|
883
|
+
### Changed
|
|
884
|
+
- `src/core/run/orchestrator.ts` updated to consume `StreamSink` and forward
|
|
885
|
+
events from the agent loop to the CLI sink; engine delegation paths updated to
|
|
886
|
+
use `buildEngineCommand` / `spawnEngine` from `engines.ts`.
|
|
887
|
+
- `src/cli/run.ts` wires `makeCliSink` (TTY) or `nullSink` (non-TTY / `--no-stream`)
|
|
888
|
+
and passes it through to the orchestrator.
|
|
889
|
+
|
|
890
|
+
### Fixed
|
|
891
|
+
- Engine delegation previously passed a guessed argv to spawned sub-agents; the
|
|
892
|
+
adapter layer now asserts exact argv in unit tests (no real delegated runs during
|
|
893
|
+
build or CI).
|
|
894
|
+
|
|
895
|
+
---
|
|
896
|
+
|
|
897
|
+
## [Unreleased] — M10: Ecosystem Cohesion
|
|
898
|
+
|
|
899
|
+
### Added
|
|
900
|
+
- **Config → env bridge** (`src/core/env-bridge.ts`): `buildToolEnv` / `withToolEnv`
|
|
901
|
+
project `~/.ashlr/config.json` into every spawned child's environment so all
|
|
902
|
+
independently-shipped ecosystem tools (ashlrcode, aw, MCP downstreams, stack,
|
|
903
|
+
vercel, gh, morphkit) honor one unified config without modification. Maps
|
|
904
|
+
endpoints (`OLLAMA_HOST`, `OLLAMA_BASE_URL`, `LM_STUDIO_URL`, `OPENAI_BASE_URL`),
|
|
905
|
+
provider identity (`ASHLR_LLM_PROVIDER`, `ASHLR_PROVIDER_CHAIN`, `ASHLR_MODEL`,
|
|
906
|
+
`AC_MODEL`), paths (`ASHLR_CONFIG`, `ASHLR_GENOME_DIR`, `ASHLR_ROOTS`), and a
|
|
907
|
+
local-first flag (`ASHLR_LOCAL_FIRST=1`). No secret values are ever injected —
|
|
908
|
+
Phantom owns credentials; they flow to children only via normal `process.env`
|
|
909
|
+
inheritance.
|
|
910
|
+
- `ToolEnv` type alias exported from `src/core/types.ts` (optional alias for
|
|
911
|
+
`Record<string,string>` — the non-secret env map projected into spawned children).
|
|
912
|
+
|
|
913
|
+
### Fixed
|
|
914
|
+
- **Orchestrator resume-before-delegation reorder** (`src/core/run/orchestrator.ts`):
|
|
915
|
+
the engine-delegation block previously ran before the `--resume` short-circuit,
|
|
916
|
+
causing `ashlr run --engine x --resume <id>` to re-run an already-completed run
|
|
917
|
+
instead of resuming it. The resume path now short-circuits first; env-bridge is
|
|
918
|
+
applied to the delegation spawn after the reorder.
|
|
919
|
+
- **Genome recall TTY alignment** (`src/cli/genome.ts`): the recall table
|
|
920
|
+
header/separator was misaligned in a TTY. Fixed column padding. Hits with
|
|
921
|
+
`score <= 0` are now dropped from the display.
|
|
922
|
+
- **Doctor version awareness** (`src/core/tools-registry.ts`, `src/core/doctor.ts`):
|
|
923
|
+
`ashlr doctor` now surfaces the installed version of each detected ecosystem tool
|
|
924
|
+
(phantom, ashlrcode, aw, stack, morphkit, etc.) via lightweight `--version` probes,
|
|
925
|
+
giving at-a-glance version awareness without requiring any new runtime dependencies.
|
|
926
|
+
|
|
927
|
+
### Changed
|
|
928
|
+
- `src/core/run/orchestrator.ts`, `src/core/mcp-gateway.ts`,
|
|
929
|
+
`src/core/lifecycle/ship.ts` spawn sites updated to call `withToolEnv(cfg)` so
|
|
930
|
+
every child inherits the unified config projection.
|
|
931
|
+
- MCP gateway downstream spawns merge `spec.env` AFTER `withToolEnv` base so
|
|
932
|
+
per-server env overrides win over bridge defaults.
|
|
933
|
+
|
|
934
|
+
---
|
|
935
|
+
|
|
936
|
+
## [M9] — Hardening, CI, and Polish
|
|
937
|
+
|
|
938
|
+
_Commit: `71cb197` · `dfef853` · `b302e3e`_
|
|
939
|
+
|
|
940
|
+
### Added
|
|
941
|
+
- `ashlr update` command: self-update from the git remote + rebuild.
|
|
942
|
+
- CI workflow: typecheck, lint, build, and vitest on Node 22 for every push and PR.
|
|
943
|
+
- 932-test suite milestone reached.
|
|
944
|
+
|
|
945
|
+
### Fixed
|
|
946
|
+
- P0 bug fixes identified by internal audit: budget hard-ceiling enforcement,
|
|
947
|
+
run-persistence atomicity, provider probe timeouts.
|
|
948
|
+
|
|
949
|
+
### Changed
|
|
950
|
+
- Shared CLI helpers extracted into `src/cli/ui.ts` and `src/cli/args.ts` (DRY
|
|
951
|
+
refactor across 12 CLI files).
|
|
952
|
+
- Release polish: `CONTRIBUTING.md`, `ARCHITECTURE.md`, `install.sh` updated for
|
|
953
|
+
public-repo presentation.
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## [M7] — Shared Memory / Genome
|
|
958
|
+
|
|
959
|
+
_Commit: `26df2e0`_
|
|
960
|
+
|
|
961
|
+
### Added
|
|
962
|
+
- `ashlr learn "<note>"` — append a memory entry to `~/.ashlr/genome/hub.jsonl`
|
|
963
|
+
(append-only; never overwrites).
|
|
964
|
+
- `ashlr recall "<query>"` — keyword/TF-IDF search across the aggregated genome
|
|
965
|
+
(all indexed repos' `.ashlrcode/genome/` dirs + hub store). Optional
|
|
966
|
+
embedding-rerank via local Ollama (`bge-m3` or similar); never calls a cloud API.
|
|
967
|
+
- `ashlr genome` — health status: entry count, projects covered, store size,
|
|
968
|
+
staleness, embeddings availability.
|
|
969
|
+
- `src/core/genome/store.ts` — `loadGenome`, `appendHubEntry`, `genomeHealth`.
|
|
970
|
+
- `src/core/genome/recall.ts` — `recall`, `keywordScore`.
|
|
971
|
+
- `ashlr run` memory injection: top-k recall hits injected into sub-agent prompts
|
|
972
|
+
(bounded by `cfg.genome.maxRecall`; disable per-run with `--no-memory`).
|
|
973
|
+
- `AshlrConfig.genome?: { maxRecall, injectOnRun }` config field.
|
|
974
|
+
- Types: `GenomeEntry`, `RecallHit`, `GenomeHealth`, `LearnInput`.
|
|
975
|
+
|
|
976
|
+
---
|
|
977
|
+
|
|
978
|
+
## [M6] — Project Lifecycle (`ashlr new` + `ashlr ship`)
|
|
979
|
+
|
|
980
|
+
_Commit: `febb800`_
|
|
981
|
+
|
|
982
|
+
### Added
|
|
983
|
+
- `ashlr new <name>` — scaffold an ecosystem-wired project (CLAUDE.md,
|
|
984
|
+
`.mcp.json` gateway, genome stub, entry point) and register it in the index.
|
|
985
|
+
Templates: `minimal`, `node-cli`, `mcp-server`, `next-app`.
|
|
986
|
+
- `ashlr ship [path]` — pre-ship gate (supply-chain + test/lint/build), then
|
|
987
|
+
optional deploy. Read-only and dry-run by default; `--confirm` required for any
|
|
988
|
+
outward action. Deploy targets: `vercel`, `stack`, `gh`, `morphkit`.
|
|
989
|
+
- `src/core/lifecycle/templates.ts` — `TEMPLATES`, `getTemplate`, `listTemplates`.
|
|
990
|
+
- `src/core/lifecycle/scaffold.ts` — `scaffoldProject`, `defaultCategory`,
|
|
991
|
+
`targetDir`. Refuses to overwrite an existing directory.
|
|
992
|
+
- `src/core/lifecycle/ship.ts` — `runShipGate` (read-only), `deploy` (dry-run
|
|
993
|
+
by default; real deploy requires `--confirm`).
|
|
994
|
+
- Types: `ProjectTemplate`, `ScaffoldSpec`, `ScaffoldResult`, `ShipCheck`,
|
|
995
|
+
`ShipGate`, `ShipResult`.
|
|
996
|
+
|
|
997
|
+
---
|
|
998
|
+
|
|
999
|
+
## [M5] — Observability (`ashlr pulse`)
|
|
1000
|
+
|
|
1001
|
+
_Commit: `72a8714`_
|
|
1002
|
+
|
|
1003
|
+
### Added
|
|
1004
|
+
- `ashlr pulse` — local usage dashboard: window summary (tokens/cost), by-project
|
|
1005
|
+
table, top models, budget status. Honors `--window 1d|7d|30d`, `--project`,
|
|
1006
|
+
`--json`. All numbers computed offline.
|
|
1007
|
+
- `src/core/observability/usage-source.ts` — `collectUsageEvents`: streams
|
|
1008
|
+
metadata-only from `~/.claude/projects/**/*.jsonl` and `~/.ashlr/runs/*.json`.
|
|
1009
|
+
Never reads message content.
|
|
1010
|
+
- `src/core/observability/rollup.ts` — `buildRollup`, `windowToMs`: aggregates
|
|
1011
|
+
by project, day, and model; joins with git commit counts.
|
|
1012
|
+
- `src/core/observability/budget-alert.ts` — `evalBudget`: warn at >=80% of any
|
|
1013
|
+
cap; over when exceeded.
|
|
1014
|
+
- `AshlrConfig.telemetry` extended with `budgetUsd`, `budgetTokens`, `budgetWindow`.
|
|
1015
|
+
- Types: `UsageEvent`, `ProjectActivity`, `DailyUsage`, `ModelUsage`,
|
|
1016
|
+
`BudgetAlert`, `ActivityRollup`.
|
|
1017
|
+
|
|
1018
|
+
---
|
|
1019
|
+
|
|
1020
|
+
## [M4] — Agent Orchestrator (`ashlr run`)
|
|
1021
|
+
|
|
1022
|
+
_Commit: `56b1626`_
|
|
1023
|
+
|
|
1024
|
+
### Added
|
|
1025
|
+
- `ashlr run "<goal>"` — plan → parallel DAG fan-out → synthesize. Resumable
|
|
1026
|
+
(`--resume <id>`); persisted to `~/.ashlr/runs/`. Hard budget and step ceilings
|
|
1027
|
+
(`--budget N`, `--max-steps N`). Local-first: refuses cloud unless `--allow-cloud`.
|
|
1028
|
+
- `ashlr runs` — list past runs (id, status, tokens, cost).
|
|
1029
|
+
- `ashlr run show <id>` — print full `RunState` for a past run.
|
|
1030
|
+
- `src/core/run/provider-client.ts` — `getActiveClient`, `estimateTokens`.
|
|
1031
|
+
Supports Ollama (native `/api/chat`) and LM Studio (OpenAI-compatible). Enforces
|
|
1032
|
+
local-first: errors rather than silently billing when cloud is the only option.
|
|
1033
|
+
- `src/core/run/budget.ts` — `newUsage`, `addUsage`, `overBudget`, `estCostUsd`.
|
|
1034
|
+
- `src/core/run/agent-loop.ts` — `runTask`: bounded chat loop with tool-call
|
|
1035
|
+
support (degrades gracefully when unsupported). Hard-stops on budget.
|
|
1036
|
+
- `src/core/run/orchestrator.ts` — `planGoal`, `runGoal`, `loadRun`, `listRuns`,
|
|
1037
|
+
`saveRun`. Parallel task fan-out (up to `--parallel N`). Engine delegation to
|
|
1038
|
+
`ashlrcode` / `aw` when installed.
|
|
1039
|
+
- Types: `RunBudget`, `RunUsage`, `RunTask`, `RunStep`, `RunState`, `RunOptions`,
|
|
1040
|
+
`ChatMessage`, `ChatResult`, `ProviderClient`.
|
|
1041
|
+
|
|
1042
|
+
---
|
|
1043
|
+
|
|
1044
|
+
## [M3] — MCP Aggregation Gateway
|
|
1045
|
+
|
|
1046
|
+
_Commit: `aa59450`_
|
|
1047
|
+
|
|
1048
|
+
### Added
|
|
1049
|
+
- `ashlr mcp` — run the single stdio MCP aggregation gateway. Discovers all
|
|
1050
|
+
configured MCP servers, starts each as a managed child, and proxies their tools
|
|
1051
|
+
namespaced as `<server>__<tool>`.
|
|
1052
|
+
- `ashlr mcp list` — registry + per-server tool counts (env values redacted).
|
|
1053
|
+
- `ashlr mcp doctor` — per-server health (starts? tool count?).
|
|
1054
|
+
- `ashlr mcp install <claude|ashlrcode>` — idempotently register the gateway in a
|
|
1055
|
+
target agent config (backs up the file first; never clobbers).
|
|
1056
|
+
- `src/core/mcp-registry.ts` — `discoverMcpServers`, `knownConfigPaths`: reads
|
|
1057
|
+
`~/.claude.json`, `~/.claude/settings.json`, `~/.mcp.json`,
|
|
1058
|
+
`~/.ashlrcode/settings.json`; deduped, env values redacted in display.
|
|
1059
|
+
- `src/core/mcp-gateway.ts` — `startGateway`, `probeServer`. Per-downstream
|
|
1060
|
+
startup timeout 8s; failed downstreams skipped, gateway never crashes.
|
|
1061
|
+
- `src/core/tools-registry.ts` — `getToolsRegistry`: detects installed ecosystem
|
|
1062
|
+
tools (phantom, ashlrcode, aw, stack, morphkit, …) via PATH.
|
|
1063
|
+
- Runtime dependency: `@modelcontextprotocol/sdk` (gateway only; rest stays
|
|
1064
|
+
zero-dep).
|
|
1065
|
+
- Types: `McpServerSpec`, `McpRegistry`, `AggregatedTool`, `McpServerHealth`,
|
|
1066
|
+
`ToolInfo`, `ToolsRegistry`.
|
|
1067
|
+
|
|
1068
|
+
---
|
|
1069
|
+
|
|
1070
|
+
## [M2] — Identity and Model Awareness
|
|
1071
|
+
|
|
1072
|
+
_Commit: `3a52826`_
|
|
1073
|
+
|
|
1074
|
+
### Added
|
|
1075
|
+
- `ashlr doctor` — one-glance health check across runtime, config, index, Phantom,
|
|
1076
|
+
MCP plugin, and every provider endpoint. Exits non-zero on any `fail`.
|
|
1077
|
+
- `ashlr init [--yes]` — idempotent onboarding: writes config defaults, detects
|
|
1078
|
+
local models, enables Phantom if present. Non-TTY safe (`--yes` accepts all
|
|
1079
|
+
defaults without prompting).
|
|
1080
|
+
- `src/core/providers.ts` — `probeEndpoint`, `getProviderRegistry`,
|
|
1081
|
+
`resolveActiveProvider`: probes LM Studio (`:1234`) and Ollama (`:11434`);
|
|
1082
|
+
builds a registry; resolves the active provider via the configured chain.
|
|
1083
|
+
Probes never throw.
|
|
1084
|
+
- `src/core/phantom.ts` — `phantomInstalled`, `getPhantomStatus`: read-only
|
|
1085
|
+
introspection of the `phantom` CLI. Returns names and status only — never secret
|
|
1086
|
+
values.
|
|
1087
|
+
- `src/core/doctor.ts` — `runDoctor`: aggregates config, phantom, and provider
|
|
1088
|
+
health into a single `DoctorReport`. Never throws.
|
|
1089
|
+
- `AshlrConfig` extended with `models.providerChain`, `phantom.enabled`.
|
|
1090
|
+
- Types: `ProviderEndpoint`, `ProviderRegistry`, `PhantomStatus`, `DoctorCheck`,
|
|
1091
|
+
`DoctorReport`.
|
|
1092
|
+
|
|
1093
|
+
---
|
|
1094
|
+
|
|
1095
|
+
## [M1] — Foundation
|
|
1096
|
+
|
|
1097
|
+
_Commit: `814f3c3`_
|
|
1098
|
+
|
|
1099
|
+
### Added
|
|
1100
|
+
- `ashlr index [--refresh]` — scan project tree, persist `~/.ashlr/index.json`.
|
|
1101
|
+
- `ashlr status` — index summary: counts by kind/category, dirty/stale repos,
|
|
1102
|
+
7-day activity line.
|
|
1103
|
+
- `ashlr go [query]` — fuzzy-jump to a project (`--open` / `--cd`).
|
|
1104
|
+
- `ashlr ls [category]` — list indexed items.
|
|
1105
|
+
- `ashlr open <query>` — resolve and open in configured editor.
|
|
1106
|
+
- `ashlr tidy [--apply]` — plan or apply moves of loose top-level files.
|
|
1107
|
+
- `ashlr config get|set|path` — read/write `~/.ashlr/config.json`.
|
|
1108
|
+
- `ashlr help` — usage.
|
|
1109
|
+
- `src/core/config.ts` — `loadConfig`, `saveConfig`, `defaultConfig`,
|
|
1110
|
+
`CONFIG_DIR`, `CONFIG_PATH`, `INDEX_PATH`.
|
|
1111
|
+
- `src/core/git.ts` — `isRepo`, `getGitStatus`, `getRemoteOrg`. Tolerates
|
|
1112
|
+
worktrees, missing upstream, zero-commit repos.
|
|
1113
|
+
- `src/core/classify.ts` — `categoryOf`, `describe`, `primaryLanguage`, `kindOf`.
|
|
1114
|
+
- `src/core/index-engine.ts` — `buildIndex`, `loadIndex`, `writeIndex`. Detects
|
|
1115
|
+
symlinks; no double-counting.
|
|
1116
|
+
- `src/core/tidy.ts` — `planTidy`, `applyTidy`.
|
|
1117
|
+
- `src/cli/open.ts` — `openInEditor`, `openInFinder`, `openInTerminal`.
|
|
1118
|
+
- `src/cli/picker.ts` — `pick`: uses `fzf` if present on PATH, else built-in
|
|
1119
|
+
readline numbered picker.
|
|
1120
|
+
- `src/raycast/` — Raycast extension (own `package.json`): list view over
|
|
1121
|
+
`IndexedItem`, open in editor, reveal in Finder, copy path.
|
|
1122
|
+
- `AshlrConfig`, `IndexedItem`, `AshlrIndex`, `GitStatus`, `TidyRule`,
|
|
1123
|
+
`TidyMove`, `TidyPlan` types established as THE canonical contract in
|
|
1124
|
+
`src/core/types.ts`.
|
|
1125
|
+
- Zero runtime dependencies in `core/` and `cli/` (Node builtins only).
|
|
1126
|
+
- `install.sh`: idempotent symlink of `bin/ashlr` into `~/.local/bin`.
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
## Milestone Roadmap — M1 through M20 (COMPLETE)
|
|
1132
|
+
|
|
1133
|
+
The full M1–M20 roadmap is now complete. Every milestone shipped, all 2026 tests green.
|
|
1134
|
+
|
|
1135
|
+
| Milestone | Theme | Status |
|
|
1136
|
+
|---|---|---|
|
|
1137
|
+
| M1 | Foundation — index, navigate, config, Raycast | COMPLETE |
|
|
1138
|
+
| M2 | Identity and model awareness — `ashlr doctor`, `ashlr init`, provider probing | COMPLETE |
|
|
1139
|
+
| M3 | MCP aggregation gateway — single stdio entry point, namespaced tools | COMPLETE |
|
|
1140
|
+
| M4 | Agent orchestrator — `ashlr run`, parallel DAG, budget, local-first | COMPLETE |
|
|
1141
|
+
| M5 | Observability — `ashlr pulse`, metadata-only usage rollup, budget alerts | COMPLETE |
|
|
1142
|
+
| M6 | Project lifecycle — `ashlr new`, `ashlr ship`, templates, deploy gate | COMPLETE |
|
|
1143
|
+
| M7 | Shared memory / genome — `ashlr learn`, `ashlr recall`, cross-project store | COMPLETE |
|
|
1144
|
+
| M8 | _(internal polish / test infrastructure)_ | COMPLETE |
|
|
1145
|
+
| M9 | Hardening, CI, and polish — self-update, 932-test milestone, P0 fixes | COMPLETE |
|
|
1146
|
+
| M10 | Ecosystem cohesion — config→env bridge, unified config projection across all tools | COMPLETE |
|
|
1147
|
+
| M11 | Watchable, robust runs — streaming, retry+verify loop, hardened engine delegation | COMPLETE |
|
|
1148
|
+
| M12 | Spec-driven swarms — `ashlr spec`, `ashlr swarm`, phase fan-out, resumable | COMPLETE |
|
|
1149
|
+
| M13 | Surfaces I — interactive TUI (`ashlr tui`), real-time Raycast extension | COMPLETE |
|
|
1150
|
+
| M14 | Surfaces II — local web dashboard (`ashlr serve`), SSE live updates, security posture | COMPLETE |
|
|
1151
|
+
| M15 | Cost-optimal local-first routing — per-task model routing, savings forecast, `ashlr models` | COMPLETE |
|
|
1152
|
+
| M16 | Compounding genome — auto-capture, consolidation, playbook synthesis, export | COMPLETE |
|
|
1153
|
+
| M17 | Verified orchestration — tamper-evident signing, escalation gates, safe rollback | COMPLETE |
|
|
1154
|
+
| M18 | Deep integrations — GitHub, Vercel, editor auto-wire, Phantom identity, notifications | COMPLETE |
|
|
1155
|
+
| M19 | Real telemetry (OTLP) + spend governance — GenAI semantic conventions, `TelemetrySink`, `ashlr telemetry` | COMPLETE |
|
|
1156
|
+
| M20 | One-command onboarding + self-healing — `ashlr init` capstone, `doctor --fix`, bounded runtime heal | COMPLETE |
|
|
1157
|
+
|
|
1158
|
+
The command surface is complete. From M1 through M20, `ashlr` grew from a local project navigator into a full agentic engineering platform — local-first, private by construction, and now trivial to adopt and self-healing in production.
|