@ashlr/hub 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1158 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/bin/ashlr +8 -0
- package/dist/api/core.d.ts +28 -0
- package/dist/api/core.js +42 -0
- package/dist/api/core.js.map +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.js +9 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/plugin.d.ts +12 -0
- package/dist/api/plugin.js +13 -0
- package/dist/api/plugin.js.map +1 -0
- package/dist/api/types.d.ts +9 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +23 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/ask.d.ts +22 -0
- package/dist/cli/ask.js +240 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/audit.d.ts +26 -0
- package/dist/cli/audit.js +199 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/backlog.d.ts +26 -0
- package/dist/cli/backlog.js +322 -0
- package/dist/cli/backlog.js.map +1 -0
- package/dist/cli/completions.d.ts +18 -0
- package/dist/cli/completions.js +156 -0
- package/dist/cli/completions.js.map +1 -0
- package/dist/cli/daemon.d.ts +31 -0
- package/dist/cli/daemon.js +334 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/demo-sandbox.d.ts +78 -0
- package/dist/cli/demo-sandbox.js +227 -0
- package/dist/cli/demo-sandbox.js.map +1 -0
- package/dist/cli/demo.d.ts +76 -0
- package/dist/cli/demo.js +285 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/digest.d.ts +46 -0
- package/dist/cli/digest.js +352 -0
- package/dist/cli/digest.js.map +1 -0
- package/dist/cli/doctor-init.d.ts +28 -0
- package/dist/cli/doctor-init.js +335 -0
- package/dist/cli/doctor-init.js.map +1 -0
- package/dist/cli/genome.d.ts +62 -0
- package/dist/cli/genome.js +1061 -0
- package/dist/cli/genome.js.map +1 -0
- package/dist/cli/gh.d.ts +27 -0
- package/dist/cli/gh.js +322 -0
- package/dist/cli/gh.js.map +1 -0
- package/dist/cli/goals.d.ts +55 -0
- package/dist/cli/goals.js +667 -0
- package/dist/cli/goals.js.map +1 -0
- package/dist/cli/health.d.ts +56 -0
- package/dist/cli/health.js +407 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/help.d.ts +51 -0
- package/dist/cli/help.js +436 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inbox.d.ts +28 -0
- package/dist/cli/inbox.js +589 -0
- package/dist/cli/inbox.js.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.js +1208 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/knowledge.d.ts +33 -0
- package/dist/cli/knowledge.js +575 -0
- package/dist/cli/knowledge.js.map +1 -0
- package/dist/cli/mcp.d.ts +26 -0
- package/dist/cli/mcp.js +487 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/models.d.ts +26 -0
- package/dist/cli/models.js +435 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/new.d.ts +32 -0
- package/dist/cli/new.js +565 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/notify.d.ts +20 -0
- package/dist/cli/notify.js +188 -0
- package/dist/cli/notify.js.map +1 -0
- package/dist/cli/onboard.d.ts +85 -0
- package/dist/cli/onboard.js +305 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/open.d.ts +38 -0
- package/dist/cli/open.js +86 -0
- package/dist/cli/open.js.map +1 -0
- package/dist/cli/orient.d.ts +14 -0
- package/dist/cli/orient.js +110 -0
- package/dist/cli/orient.js.map +1 -0
- package/dist/cli/picker.d.ts +21 -0
- package/dist/cli/picker.js +177 -0
- package/dist/cli/picker.js.map +1 -0
- package/dist/cli/plugins.d.ts +21 -0
- package/dist/cli/plugins.js +394 -0
- package/dist/cli/plugins.js.map +1 -0
- package/dist/cli/preflight.d.ts +29 -0
- package/dist/cli/preflight.js +131 -0
- package/dist/cli/preflight.js.map +1 -0
- package/dist/cli/pulse.d.ts +26 -0
- package/dist/cli/pulse.js +482 -0
- package/dist/cli/pulse.js.map +1 -0
- package/dist/cli/reflect.d.ts +42 -0
- package/dist/cli/reflect.js +381 -0
- package/dist/cli/reflect.js.map +1 -0
- package/dist/cli/run.d.ts +29 -0
- package/dist/cli/run.js +687 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/sandbox.d.ts +20 -0
- package/dist/cli/sandbox.js +345 -0
- package/dist/cli/sandbox.js.map +1 -0
- package/dist/cli/seams.d.ts +41 -0
- package/dist/cli/seams.js +167 -0
- package/dist/cli/seams.js.map +1 -0
- package/dist/cli/serve.d.ts +25 -0
- package/dist/cli/serve.js +249 -0
- package/dist/cli/serve.js.map +1 -0
- package/dist/cli/ship.d.ts +33 -0
- package/dist/cli/ship.js +433 -0
- package/dist/cli/ship.js.map +1 -0
- package/dist/cli/spec.d.ts +19 -0
- package/dist/cli/spec.js +478 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/cli/swarm.d.ts +42 -0
- package/dist/cli/swarm.js +1365 -0
- package/dist/cli/swarm.js.map +1 -0
- package/dist/cli/telemetry.d.ts +26 -0
- package/dist/cli/telemetry.js +364 -0
- package/dist/cli/telemetry.js.map +1 -0
- package/dist/cli/tui.d.ts +17 -0
- package/dist/cli/tui.js +52 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +53 -0
- package/dist/cli/ui.js +66 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli/update.d.ts +38 -0
- package/dist/cli/update.js +682 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/vercel.d.ts +20 -0
- package/dist/cli/vercel.js +193 -0
- package/dist/cli/vercel.js.map +1 -0
- package/dist/cli/verify-safety.d.ts +96 -0
- package/dist/cli/verify-safety.js +509 -0
- package/dist/cli/verify-safety.js.map +1 -0
- package/dist/cli/wire.d.ts +22 -0
- package/dist/cli/wire.js +205 -0
- package/dist/cli/wire.js.map +1 -0
- package/dist/core/classify.d.ts +51 -0
- package/dist/core/classify.js +450 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/config.d.ts +63 -0
- package/dist/core/config.js +474 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/daemon/loop.d.ts +74 -0
- package/dist/core/daemon/loop.js +618 -0
- package/dist/core/daemon/loop.js.map +1 -0
- package/dist/core/daemon/state.d.ts +66 -0
- package/dist/core/daemon/state.js +197 -0
- package/dist/core/daemon/state.js.map +1 -0
- package/dist/core/dashboard.d.ts +40 -0
- package/dist/core/dashboard.js +463 -0
- package/dist/core/dashboard.js.map +1 -0
- package/dist/core/digest/build.d.ts +51 -0
- package/dist/core/digest/build.js +230 -0
- package/dist/core/digest/build.js.map +1 -0
- package/dist/core/digest/deliver.d.ts +47 -0
- package/dist/core/digest/deliver.js +230 -0
- package/dist/core/digest/deliver.js.map +1 -0
- package/dist/core/digest/store.d.ts +57 -0
- package/dist/core/digest/store.js +223 -0
- package/dist/core/digest/store.js.map +1 -0
- package/dist/core/doctor-fix.d.ts +21 -0
- package/dist/core/doctor-fix.js +440 -0
- package/dist/core/doctor-fix.js.map +1 -0
- package/dist/core/doctor.d.ts +18 -0
- package/dist/core/doctor.js +806 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/env-bridge.d.ts +64 -0
- package/dist/core/env-bridge.js +103 -0
- package/dist/core/env-bridge.js.map +1 -0
- package/dist/core/genome/capture.d.ts +49 -0
- package/dist/core/genome/capture.js +352 -0
- package/dist/core/genome/capture.js.map +1 -0
- package/dist/core/genome/consolidate.d.ts +38 -0
- package/dist/core/genome/consolidate.js +426 -0
- package/dist/core/genome/consolidate.js.map +1 -0
- package/dist/core/genome/export.d.ts +29 -0
- package/dist/core/genome/export.js +102 -0
- package/dist/core/genome/export.js.map +1 -0
- package/dist/core/genome/playbook.d.ts +33 -0
- package/dist/core/genome/playbook.js +320 -0
- package/dist/core/genome/playbook.js.map +1 -0
- package/dist/core/genome/recall.d.ts +45 -0
- package/dist/core/genome/recall.js +293 -0
- package/dist/core/genome/recall.js.map +1 -0
- package/dist/core/genome/store.d.ts +62 -0
- package/dist/core/genome/store.js +711 -0
- package/dist/core/genome/store.js.map +1 -0
- package/dist/core/git.d.ts +34 -0
- package/dist/core/git.js +115 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/goals/advance.d.ts +82 -0
- package/dist/core/goals/advance.js +263 -0
- package/dist/core/goals/advance.js.map +1 -0
- package/dist/core/goals/planner.d.ts +58 -0
- package/dist/core/goals/planner.js +233 -0
- package/dist/core/goals/planner.js.map +1 -0
- package/dist/core/goals/store.d.ts +128 -0
- package/dist/core/goals/store.js +442 -0
- package/dist/core/goals/store.js.map +1 -0
- package/dist/core/inbox/apply.d.ts +36 -0
- package/dist/core/inbox/apply.js +407 -0
- package/dist/core/inbox/apply.js.map +1 -0
- package/dist/core/inbox/notify-proposal.d.ts +13 -0
- package/dist/core/inbox/notify-proposal.js +21 -0
- package/dist/core/inbox/notify-proposal.js.map +1 -0
- package/dist/core/inbox/store.d.ts +67 -0
- package/dist/core/inbox/store.js +258 -0
- package/dist/core/inbox/store.js.map +1 -0
- package/dist/core/index-engine.d.ts +62 -0
- package/dist/core/index-engine.js +486 -0
- package/dist/core/index-engine.js.map +1 -0
- package/dist/core/integrations/desktop-notify.d.ts +18 -0
- package/dist/core/integrations/desktop-notify.js +42 -0
- package/dist/core/integrations/desktop-notify.js.map +1 -0
- package/dist/core/integrations/editors.d.ts +50 -0
- package/dist/core/integrations/editors.js +216 -0
- package/dist/core/integrations/editors.js.map +1 -0
- package/dist/core/integrations/github.d.ts +66 -0
- package/dist/core/integrations/github.js +332 -0
- package/dist/core/integrations/github.js.map +1 -0
- package/dist/core/integrations/identity.d.ts +29 -0
- package/dist/core/integrations/identity.js +359 -0
- package/dist/core/integrations/identity.js.map +1 -0
- package/dist/core/integrations/notify.d.ts +23 -0
- package/dist/core/integrations/notify.js +96 -0
- package/dist/core/integrations/notify.js.map +1 -0
- package/dist/core/integrations/vercel.d.ts +37 -0
- package/dist/core/integrations/vercel.js +192 -0
- package/dist/core/integrations/vercel.js.map +1 -0
- package/dist/core/knowledge/ask.d.ts +34 -0
- package/dist/core/knowledge/ask.js +325 -0
- package/dist/core/knowledge/ask.js.map +1 -0
- package/dist/core/knowledge/graph.d.ts +51 -0
- package/dist/core/knowledge/graph.js +564 -0
- package/dist/core/knowledge/graph.js.map +1 -0
- package/dist/core/knowledge/index.d.ts +74 -0
- package/dist/core/knowledge/index.js +586 -0
- package/dist/core/knowledge/index.js.map +1 -0
- package/dist/core/learn/playbooks.d.ts +84 -0
- package/dist/core/learn/playbooks.js +241 -0
- package/dist/core/learn/playbooks.js.map +1 -0
- package/dist/core/learn/reflect.d.ts +87 -0
- package/dist/core/learn/reflect.js +435 -0
- package/dist/core/learn/reflect.js.map +1 -0
- package/dist/core/learn/store.d.ts +51 -0
- package/dist/core/learn/store.js +165 -0
- package/dist/core/learn/store.js.map +1 -0
- package/dist/core/learn/tuning.d.ts +48 -0
- package/dist/core/learn/tuning.js +201 -0
- package/dist/core/learn/tuning.js.map +1 -0
- package/dist/core/lifecycle/scaffold.d.ts +43 -0
- package/dist/core/lifecycle/scaffold.js +260 -0
- package/dist/core/lifecycle/scaffold.js.map +1 -0
- package/dist/core/lifecycle/ship.d.ts +48 -0
- package/dist/core/lifecycle/ship.js +513 -0
- package/dist/core/lifecycle/ship.js.map +1 -0
- package/dist/core/lifecycle/templates.d.ts +20 -0
- package/dist/core/lifecycle/templates.js +605 -0
- package/dist/core/lifecycle/templates.js.map +1 -0
- package/dist/core/mcp-gateway.d.ts +59 -0
- package/dist/core/mcp-gateway.js +385 -0
- package/dist/core/mcp-gateway.js.map +1 -0
- package/dist/core/mcp-native.d.ts +53 -0
- package/dist/core/mcp-native.js +507 -0
- package/dist/core/mcp-native.js.map +1 -0
- package/dist/core/mcp-registry.d.ts +36 -0
- package/dist/core/mcp-registry.js +180 -0
- package/dist/core/mcp-registry.js.map +1 -0
- package/dist/core/observability/budget-alert.d.ts +18 -0
- package/dist/core/observability/budget-alert.js +90 -0
- package/dist/core/observability/budget-alert.js.map +1 -0
- package/dist/core/observability/estimate.d.ts +27 -0
- package/dist/core/observability/estimate.js +188 -0
- package/dist/core/observability/estimate.js.map +1 -0
- package/dist/core/observability/forecast.d.ts +19 -0
- package/dist/core/observability/forecast.js +101 -0
- package/dist/core/observability/forecast.js.map +1 -0
- package/dist/core/observability/governance.d.ts +28 -0
- package/dist/core/observability/governance.js +101 -0
- package/dist/core/observability/governance.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +88 -0
- package/dist/core/observability/otlp.js +217 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/observability/rollup.d.ts +35 -0
- package/dist/core/observability/rollup.js +311 -0
- package/dist/core/observability/rollup.js.map +1 -0
- package/dist/core/observability/telemetry-sink.d.ts +63 -0
- package/dist/core/observability/telemetry-sink.js +350 -0
- package/dist/core/observability/telemetry-sink.js.map +1 -0
- package/dist/core/observability/usage-source.d.ts +60 -0
- package/dist/core/observability/usage-source.js +347 -0
- package/dist/core/observability/usage-source.js.map +1 -0
- package/dist/core/onboard.d.ts +27 -0
- package/dist/core/onboard.js +288 -0
- package/dist/core/onboard.js.map +1 -0
- package/dist/core/orient.d.ts +23 -0
- package/dist/core/orient.js +135 -0
- package/dist/core/orient.js.map +1 -0
- package/dist/core/phantom.d.ts +23 -0
- package/dist/core/phantom.js +279 -0
- package/dist/core/phantom.js.map +1 -0
- package/dist/core/plugins/host-api.d.ts +29 -0
- package/dist/core/plugins/host-api.js +113 -0
- package/dist/core/plugins/host-api.js.map +1 -0
- package/dist/core/plugins/integrity.d.ts +36 -0
- package/dist/core/plugins/integrity.js +73 -0
- package/dist/core/plugins/integrity.js.map +1 -0
- package/dist/core/plugins/manifest.d.ts +31 -0
- package/dist/core/plugins/manifest.js +316 -0
- package/dist/core/plugins/manifest.js.map +1 -0
- package/dist/core/plugins/registry.d.ts +87 -0
- package/dist/core/plugins/registry.js +415 -0
- package/dist/core/plugins/registry.js.map +1 -0
- package/dist/core/plugins/types.d.ts +182 -0
- package/dist/core/plugins/types.js +40 -0
- package/dist/core/plugins/types.js.map +1 -0
- package/dist/core/plugins/wrappers.d.ts +40 -0
- package/dist/core/plugins/wrappers.js +229 -0
- package/dist/core/plugins/wrappers.js.map +1 -0
- package/dist/core/portfolio/backlog.d.ts +40 -0
- package/dist/core/portfolio/backlog.js +177 -0
- package/dist/core/portfolio/backlog.js.map +1 -0
- package/dist/core/portfolio/scanners.d.ts +21 -0
- package/dist/core/portfolio/scanners.js +600 -0
- package/dist/core/portfolio/scanners.js.map +1 -0
- package/dist/core/providers.d.ts +34 -0
- package/dist/core/providers.js +250 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quality/conventions.d.ts +35 -0
- package/dist/core/quality/conventions.js +267 -0
- package/dist/core/quality/conventions.js.map +1 -0
- package/dist/core/quality/fixes.d.ts +56 -0
- package/dist/core/quality/fixes.js +209 -0
- package/dist/core/quality/fixes.js.map +1 -0
- package/dist/core/quality/health.d.ts +69 -0
- package/dist/core/quality/health.js +350 -0
- package/dist/core/quality/health.js.map +1 -0
- package/dist/core/quality/store.d.ts +56 -0
- package/dist/core/quality/store.js +195 -0
- package/dist/core/quality/store.js.map +1 -0
- package/dist/core/readiness.d.ts +112 -0
- package/dist/core/readiness.js +431 -0
- package/dist/core/readiness.js.map +1 -0
- package/dist/core/run/agent-loop.d.ts +40 -0
- package/dist/core/run/agent-loop.js +291 -0
- package/dist/core/run/agent-loop.js.map +1 -0
- package/dist/core/run/budget.d.ts +47 -0
- package/dist/core/run/budget.js +113 -0
- package/dist/core/run/budget.js.map +1 -0
- package/dist/core/run/engines.d.ts +75 -0
- package/dist/core/run/engines.js +199 -0
- package/dist/core/run/engines.js.map +1 -0
- package/dist/core/run/model-manager.d.ts +64 -0
- package/dist/core/run/model-manager.js +339 -0
- package/dist/core/run/model-manager.js.map +1 -0
- package/dist/core/run/orchestrator.d.ts +100 -0
- package/dist/core/run/orchestrator.js +1515 -0
- package/dist/core/run/orchestrator.js.map +1 -0
- package/dist/core/run/provider-client.d.ts +46 -0
- package/dist/core/run/provider-client.js +796 -0
- package/dist/core/run/provider-client.js.map +1 -0
- package/dist/core/run/retry.d.ts +19 -0
- package/dist/core/run/retry.js +68 -0
- package/dist/core/run/retry.js.map +1 -0
- package/dist/core/run/router.d.ts +50 -0
- package/dist/core/run/router.js +257 -0
- package/dist/core/run/router.js.map +1 -0
- package/dist/core/run/self-heal.d.ts +52 -0
- package/dist/core/run/self-heal.js +181 -0
- package/dist/core/run/self-heal.js.map +1 -0
- package/dist/core/run/streaming.d.ts +31 -0
- package/dist/core/run/streaming.js +122 -0
- package/dist/core/run/streaming.js.map +1 -0
- package/dist/core/run/verify.d.ts +30 -0
- package/dist/core/run/verify.js +204 -0
- package/dist/core/run/verify.js.map +1 -0
- package/dist/core/sandbox/audit.d.ts +31 -0
- package/dist/core/sandbox/audit.js +169 -0
- package/dist/core/sandbox/audit.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +61 -0
- package/dist/core/sandbox/policy.js +211 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- package/dist/core/sandbox/worktree.d.ts +175 -0
- package/dist/core/sandbox/worktree.js +673 -0
- package/dist/core/sandbox/worktree.js.map +1 -0
- package/dist/core/seams/backlog.d.ts +47 -0
- package/dist/core/seams/backlog.js +47 -0
- package/dist/core/seams/backlog.js.map +1 -0
- package/dist/core/seams/daemon-coordinator.d.ts +72 -0
- package/dist/core/seams/daemon-coordinator.js +76 -0
- package/dist/core/seams/daemon-coordinator.js.map +1 -0
- package/dist/core/seams/genome.d.ts +45 -0
- package/dist/core/seams/genome.js +53 -0
- package/dist/core/seams/genome.js.map +1 -0
- package/dist/core/seams/identity.d.ts +40 -0
- package/dist/core/seams/identity.js +44 -0
- package/dist/core/seams/identity.js.map +1 -0
- package/dist/core/seams/inbox.d.ts +60 -0
- package/dist/core/seams/inbox.js +66 -0
- package/dist/core/seams/inbox.js.map +1 -0
- package/dist/core/seams/index.d.ts +20 -0
- package/dist/core/seams/index.js +21 -0
- package/dist/core/seams/index.js.map +1 -0
- package/dist/core/seams/portfolio.d.ts +50 -0
- package/dist/core/seams/portfolio.js +61 -0
- package/dist/core/seams/portfolio.js.map +1 -0
- package/dist/core/seams/registry.d.ts +42 -0
- package/dist/core/seams/registry.js +128 -0
- package/dist/core/seams/registry.js.map +1 -0
- package/dist/core/seams/run-swarm.d.ts +66 -0
- package/dist/core/seams/run-swarm.js +74 -0
- package/dist/core/seams/run-swarm.js.map +1 -0
- package/dist/core/seams/types.d.ts +123 -0
- package/dist/core/seams/types.js +35 -0
- package/dist/core/seams/types.js.map +1 -0
- package/dist/core/spec/spec-store.d.ts +62 -0
- package/dist/core/spec/spec-store.js +359 -0
- package/dist/core/spec/spec-store.js.map +1 -0
- package/dist/core/swarm/gate.d.ts +45 -0
- package/dist/core/swarm/gate.js +114 -0
- package/dist/core/swarm/gate.js.map +1 -0
- package/dist/core/swarm/planner.d.ts +32 -0
- package/dist/core/swarm/planner.js +293 -0
- package/dist/core/swarm/planner.js.map +1 -0
- package/dist/core/swarm/rollback.d.ts +56 -0
- package/dist/core/swarm/rollback.js +266 -0
- package/dist/core/swarm/rollback.js.map +1 -0
- package/dist/core/swarm/runner.d.ts +62 -0
- package/dist/core/swarm/runner.js +1263 -0
- package/dist/core/swarm/runner.js.map +1 -0
- package/dist/core/swarm/sign.d.ts +71 -0
- package/dist/core/swarm/sign.js +362 -0
- package/dist/core/swarm/sign.js.map +1 -0
- package/dist/core/swarm/store.d.ts +52 -0
- package/dist/core/swarm/store.js +195 -0
- package/dist/core/swarm/store.js.map +1 -0
- package/dist/core/tidy.d.ts +32 -0
- package/dist/core/tidy.js +354 -0
- package/dist/core/tidy.js.map +1 -0
- package/dist/core/tools-registry.d.ts +16 -0
- package/dist/core/tools-registry.js +308 -0
- package/dist/core/tools-registry.js.map +1 -0
- package/dist/core/types.d.ts +2545 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/web/api.d.ts +53 -0
- package/dist/core/web/api.js +698 -0
- package/dist/core/web/api.js.map +1 -0
- package/dist/core/web/public/app.js +1906 -0
- package/dist/core/web/public/index.html +721 -0
- package/dist/core/web/public/styles.css +2007 -0
- package/dist/core/web/server.d.ts +18 -0
- package/dist/core/web/server.js +122 -0
- package/dist/core/web/server.js.map +1 -0
- package/dist/core/web/static.d.ts +18 -0
- package/dist/core/web/static.js +122 -0
- package/dist/core/web/static.js.map +1 -0
- package/dist/tui/app.d.ts +33 -0
- package/dist/tui/app.js +350 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/render.d.ts +20 -0
- package/dist/tui/render.js +558 -0
- package/dist/tui/render.js.map +1 -0
- package/package.json +80 -0
- package/schema/config.schema.json +223 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M21: Append-only audit trail for autonomous/sandbox actions.
|
|
3
|
+
*
|
|
4
|
+
* Writes to ~/.ashlr/audit/<YYYY-MM-DD>.jsonl — one JSON object per line.
|
|
5
|
+
* Rules:
|
|
6
|
+
* - Append-only: never truncate, never rewrite, never delete a prior line.
|
|
7
|
+
* - Never write secrets: summary is metadata only; secret-shaped tokens are
|
|
8
|
+
* stripped defensively before persisting.
|
|
9
|
+
* - audit() never throws; readAudit() skips malformed lines, never throws.
|
|
10
|
+
*/
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { existsSync, mkdirSync, readdirSync, appendFileSync, readFileSync, } from 'node:fs';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Public: auditDir()
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Returns the absolute path to the audit directory: ~/.ashlr/audit.
|
|
19
|
+
* Created lazily by audit() — this function itself does NOT create it.
|
|
20
|
+
*/
|
|
21
|
+
export function auditDir() {
|
|
22
|
+
return join(homedir(), '.ashlr', 'audit');
|
|
23
|
+
}
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Internal helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/** Current date as YYYY-MM-DD (UTC) for the daily file name. */
|
|
28
|
+
function todayDateString() {
|
|
29
|
+
return new Date().toISOString().slice(0, 10);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Defensively strip secret-shaped tokens from a summary string before
|
|
33
|
+
* persisting. Targets common patterns: long hex strings, bearer tokens,
|
|
34
|
+
* base64-looking blobs, and key=value pairs that look like credentials.
|
|
35
|
+
*
|
|
36
|
+
* This is a best-effort guardrail — the contract says "summary is metadata
|
|
37
|
+
* only, never secrets"; callers are the primary enforcement point.
|
|
38
|
+
*/
|
|
39
|
+
function stripSecrets(summary) {
|
|
40
|
+
return (summary
|
|
41
|
+
// bearer / auth headers
|
|
42
|
+
.replace(/\b(Bearer|Token|Authorization)\s+[A-Za-z0-9\-._~+/]+=*/gi, '$1 [REDACTED]')
|
|
43
|
+
// key=value credential patterns (api_key=xxx, secret=xxx, token=xxx, password=xxx)
|
|
44
|
+
.replace(/\b(api[_-]?key|secret|token|password|passwd|auth|credential)[=:\s]+[^\s,;'"]{8,}/gi, '$1=[REDACTED]')
|
|
45
|
+
// Known-prefix provider tokens (anchored on the prefix so we don't redact
|
|
46
|
+
// ordinary identifiers). Covers OpenAI/Anthropic-style sk-, GitHub PAT/OAuth
|
|
47
|
+
// ghp_/gho_/ghu_/ghs_/ghr_, Slack xox[baprs]-, and AWS access key ids.
|
|
48
|
+
.replace(/\bsk-[A-Za-z0-9_-]{16,}/g, '[REDACTED]')
|
|
49
|
+
.replace(/\bgh[poursa]_[A-Za-z0-9]{16,}/g, '[REDACTED]')
|
|
50
|
+
.replace(/\bxox[baprs]-[A-Za-z0-9-]{10,}/gi, '[REDACTED]')
|
|
51
|
+
.replace(/\bAKIA[0-9A-Z]{16}\b/g, '[REDACTED]')
|
|
52
|
+
// JWTs: three base64url segments separated by dots, header starts with eyJ.
|
|
53
|
+
.replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, '[REDACTED]')
|
|
54
|
+
// Long hex strings ≥ 40 hex chars (private keys, raw API keys). Threshold
|
|
55
|
+
// raised above 32 so git SHA-1 (40) is borderline — but git short SHAs and
|
|
56
|
+
// path components are <40, and we WANT full forensic SHAs preserved, so we
|
|
57
|
+
// anchor on a length that real secrets exceed while commit/file metadata
|
|
58
|
+
// generally does not. Conservative: over-redaction would harm forensics.
|
|
59
|
+
.replace(/\b[0-9a-fA-F]{64,}\b/g, '[REDACTED]'));
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Public: audit()
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Append one audit entry to today's JSONL file under ~/.ashlr/audit/.
|
|
66
|
+
* Sets `ts` to the current ISO timestamp; the caller supplies everything else.
|
|
67
|
+
*
|
|
68
|
+
* Append-only: never truncates, never rewrites existing lines.
|
|
69
|
+
* Never throws — errors are swallowed silently to keep callers unblocked.
|
|
70
|
+
*/
|
|
71
|
+
export function audit(entry) {
|
|
72
|
+
try {
|
|
73
|
+
const dir = auditDir();
|
|
74
|
+
// Ensure audit directory exists (lazy mkdir).
|
|
75
|
+
if (!existsSync(dir)) {
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
const record = {
|
|
79
|
+
ts: new Date().toISOString(),
|
|
80
|
+
action: entry.action,
|
|
81
|
+
repo: entry.repo,
|
|
82
|
+
sandboxId: entry.sandboxId,
|
|
83
|
+
// Strip secret-shaped tokens defensively; summary is metadata only.
|
|
84
|
+
summary: stripSecrets(entry.summary),
|
|
85
|
+
result: entry.result,
|
|
86
|
+
};
|
|
87
|
+
const line = JSON.stringify(record) + '\n';
|
|
88
|
+
const filePath = join(dir, `${todayDateString()}.jsonl`);
|
|
89
|
+
// Append-only — appendFileSync creates the file if missing, never truncates.
|
|
90
|
+
appendFileSync(filePath, line, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Intentionally swallowed: audit must never disrupt the caller's flow.
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Public: readAudit()
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Read audit entries across all date files, newest-first.
|
|
101
|
+
* `limit` caps the number returned (undefined or 0 => return all).
|
|
102
|
+
*
|
|
103
|
+
* Malformed JSONL lines are silently skipped; never throws.
|
|
104
|
+
*/
|
|
105
|
+
export function readAudit(limit) {
|
|
106
|
+
try {
|
|
107
|
+
const dir = auditDir();
|
|
108
|
+
if (!existsSync(dir)) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
// List all .jsonl files, sort descending (newest date first).
|
|
112
|
+
let files;
|
|
113
|
+
try {
|
|
114
|
+
files = readdirSync(dir)
|
|
115
|
+
.filter((f) => f.endsWith('.jsonl'))
|
|
116
|
+
.sort()
|
|
117
|
+
.reverse();
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const entries = [];
|
|
123
|
+
const cap = limit !== undefined && limit > 0 ? limit : Infinity;
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
if (entries.length >= cap)
|
|
126
|
+
break;
|
|
127
|
+
const filePath = join(dir, file);
|
|
128
|
+
let raw;
|
|
129
|
+
try {
|
|
130
|
+
raw = readFileSync(filePath, 'utf8');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue; // unreadable file — skip
|
|
134
|
+
}
|
|
135
|
+
// Collect lines in reverse order so newest entries within the file come first.
|
|
136
|
+
const lines = raw.split('\n').filter((l) => l.trim() !== '').reverse();
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
if (entries.length >= cap)
|
|
139
|
+
break;
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(line);
|
|
142
|
+
if (parsed !== null &&
|
|
143
|
+
typeof parsed === 'object' &&
|
|
144
|
+
!Array.isArray(parsed)) {
|
|
145
|
+
// Light structural validation — skip clearly malformed entries.
|
|
146
|
+
const obj = parsed;
|
|
147
|
+
if (typeof obj['ts'] === 'string' &&
|
|
148
|
+
typeof obj['action'] === 'string' &&
|
|
149
|
+
typeof obj['summary'] === 'string' &&
|
|
150
|
+
(obj['result'] === 'ok' ||
|
|
151
|
+
obj['result'] === 'refused' ||
|
|
152
|
+
obj['result'] === 'error')) {
|
|
153
|
+
entries.push(obj);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Malformed JSON line — skip silently.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return entries;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Top-level guard: readAudit must never throw.
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/core/sandbox/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* policy.ts — Enrollment registry + kill switch (the gate).
|
|
3
|
+
*
|
|
4
|
+
* SAFETY RULES:
|
|
5
|
+
* - Enrollment registry persisted at ~/.ashlr/enrollment.json, DEFAULT EMPTY.
|
|
6
|
+
* - Kill switch backed by the presence of ~/.ashlr/KILL file.
|
|
7
|
+
* - assertMayMutate ALWAYS throws when the kill switch is on, regardless of
|
|
8
|
+
* enrollment or allowAnyRepo. allowAnyRepo ONLY bypasses the enrollment
|
|
9
|
+
* check (for tests operating on tmp repos) — it NEVER bypasses the kill switch.
|
|
10
|
+
* - enroll/unenroll normalize repo paths to absolute via path.resolve().
|
|
11
|
+
* - All functions are idempotent; never throw except the intentional assert.
|
|
12
|
+
* - No new runtime deps; node builtins only.
|
|
13
|
+
*/
|
|
14
|
+
/** Path to the enrollment registry file. */
|
|
15
|
+
export declare function enrollmentPath(): string;
|
|
16
|
+
/** Path to the kill-switch sentinel file. */
|
|
17
|
+
export declare function killSwitchPath(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Returns true when the global kill switch is active.
|
|
20
|
+
* Backed by the presence of ~/.ashlr/KILL.
|
|
21
|
+
*/
|
|
22
|
+
export declare function killSwitchOn(): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Turn the kill switch on (creates ~/.ashlr/KILL) or off (removes it).
|
|
25
|
+
* Idempotent in both directions.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setKill(on: boolean): void;
|
|
28
|
+
/**
|
|
29
|
+
* Returns true when `repo` (normalized to absolute path) is enrolled
|
|
30
|
+
* for autonomous work.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isEnrolled(repo: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Enroll `repo` for autonomous/sandbox mutation. Idempotent — enrolling
|
|
35
|
+
* an already-enrolled repo is a no-op. Normalizes to absolute path.
|
|
36
|
+
*/
|
|
37
|
+
export declare function enroll(repo: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Remove `repo` from the enrollment registry. Idempotent — unenrolling
|
|
40
|
+
* an absent repo is a no-op. Normalizes to absolute path.
|
|
41
|
+
*/
|
|
42
|
+
export declare function unenroll(repo: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Return all enrolled repos (absolute paths). Returns [] when nothing is
|
|
45
|
+
* enrolled (the default state — DEFAULT EMPTY).
|
|
46
|
+
*/
|
|
47
|
+
export declare function listEnrolled(): string[];
|
|
48
|
+
/**
|
|
49
|
+
* Assert that autonomous/sandbox mutation of `repo` is permitted.
|
|
50
|
+
*
|
|
51
|
+
* Throws when:
|
|
52
|
+
* 1. The kill switch is on (ALWAYS, regardless of enrollment or opts).
|
|
53
|
+
* 2. `repo` is not enrolled AND `opts.allowAnyRepo` is not true.
|
|
54
|
+
*
|
|
55
|
+
* `opts.allowAnyRepo` is a TEST SEAM only — it bypasses enrollment so tests
|
|
56
|
+
* can operate on tmp repos without enrolling them. It NEVER bypasses the kill
|
|
57
|
+
* switch.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertMayMutate(repo: string, opts?: {
|
|
60
|
+
allowAnyRepo?: boolean;
|
|
61
|
+
}): void;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* policy.ts — Enrollment registry + kill switch (the gate).
|
|
3
|
+
*
|
|
4
|
+
* SAFETY RULES:
|
|
5
|
+
* - Enrollment registry persisted at ~/.ashlr/enrollment.json, DEFAULT EMPTY.
|
|
6
|
+
* - Kill switch backed by the presence of ~/.ashlr/KILL file.
|
|
7
|
+
* - assertMayMutate ALWAYS throws when the kill switch is on, regardless of
|
|
8
|
+
* enrollment or allowAnyRepo. allowAnyRepo ONLY bypasses the enrollment
|
|
9
|
+
* check (for tests operating on tmp repos) — it NEVER bypasses the kill switch.
|
|
10
|
+
* - enroll/unenroll normalize repo paths to absolute via path.resolve().
|
|
11
|
+
* - All functions are idempotent; never throw except the intentional assert.
|
|
12
|
+
* - No new runtime deps; node builtins only.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { join, resolve } from 'node:path';
|
|
17
|
+
// H6 (PART A — audit completeness): emit an audit() record inside
|
|
18
|
+
// enroll/unenroll/setKill so EVERY path (CLI cmdEnroll OR any programmatic
|
|
19
|
+
// caller — fixture, daemon, onboard) is captured — see
|
|
20
|
+
// docs/contracts/CONTRACT-H6.md §A.2. audit.ts imports only node builtins + a
|
|
21
|
+
// `type` from ../types.js and does NOT import policy.ts, so this static import
|
|
22
|
+
// creates NO import cycle (re-verified by an H6 [STATIC] test). Metadata only:
|
|
23
|
+
// action verb + repo abs path (a path is NOT a secret) + 'ok'.
|
|
24
|
+
import { audit } from './audit.js';
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Path helpers (re-resolved at call time so tests can relocate HOME)
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
function ashlrDir() {
|
|
29
|
+
return join(homedir(), '.ashlr');
|
|
30
|
+
}
|
|
31
|
+
/** Path to the enrollment registry file. */
|
|
32
|
+
export function enrollmentPath() {
|
|
33
|
+
return join(ashlrDir(), 'enrollment.json');
|
|
34
|
+
}
|
|
35
|
+
/** Path to the kill-switch sentinel file. */
|
|
36
|
+
export function killSwitchPath() {
|
|
37
|
+
return join(ashlrDir(), 'KILL');
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Internal I/O helpers
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/** Read the enrollment registry. Returns { repos: [] } when absent/malformed. */
|
|
43
|
+
function readRegistry() {
|
|
44
|
+
const p = enrollmentPath();
|
|
45
|
+
if (!existsSync(p))
|
|
46
|
+
return { repos: [] };
|
|
47
|
+
try {
|
|
48
|
+
const raw = readFileSync(p, 'utf8');
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (typeof parsed === 'object' &&
|
|
51
|
+
parsed !== null &&
|
|
52
|
+
!Array.isArray(parsed) &&
|
|
53
|
+
Array.isArray(parsed['repos'])) {
|
|
54
|
+
const repos = parsed['repos']
|
|
55
|
+
.filter((r) => typeof r === 'string');
|
|
56
|
+
return { repos };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// malformed — treat as empty
|
|
61
|
+
}
|
|
62
|
+
return { repos: [] };
|
|
63
|
+
}
|
|
64
|
+
/** Persist the enrollment registry, creating ~/.ashlr if needed. */
|
|
65
|
+
function writeRegistry(reg) {
|
|
66
|
+
const dir = ashlrDir();
|
|
67
|
+
if (!existsSync(dir)) {
|
|
68
|
+
mkdirSync(dir, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
writeFileSync(enrollmentPath(), JSON.stringify(reg, null, 2) + '\n', 'utf8');
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Kill switch
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
/**
|
|
76
|
+
* Returns true when the global kill switch is active.
|
|
77
|
+
* Backed by the presence of ~/.ashlr/KILL.
|
|
78
|
+
*/
|
|
79
|
+
export function killSwitchOn() {
|
|
80
|
+
return existsSync(killSwitchPath());
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Turn the kill switch on (creates ~/.ashlr/KILL) or off (removes it).
|
|
84
|
+
* Idempotent in both directions.
|
|
85
|
+
*/
|
|
86
|
+
export function setKill(on) {
|
|
87
|
+
const kp = killSwitchPath();
|
|
88
|
+
const dir = ashlrDir();
|
|
89
|
+
if (on) {
|
|
90
|
+
if (!existsSync(dir)) {
|
|
91
|
+
mkdirSync(dir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
if (!existsSync(kp)) {
|
|
94
|
+
writeFileSync(kp, 'kill switch active\n', 'utf8');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (existsSync(kp)) {
|
|
99
|
+
unlinkSync(kp);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// H6 (§A.2): audit the kill-switch toggle on EVERY call (idempotent on disk;
|
|
103
|
+
// we audit the requested intent). repo is null (not repo-scoped); summary is
|
|
104
|
+
// metadata only. audit() swallows its own errors, so the "never throws"
|
|
105
|
+
// contract of setKill is preserved.
|
|
106
|
+
audit({
|
|
107
|
+
action: on ? 'kill:on' : 'kill:off',
|
|
108
|
+
repo: null,
|
|
109
|
+
sandboxId: null,
|
|
110
|
+
summary: `kill switch ${on ? 'on' : 'off'}`,
|
|
111
|
+
result: 'ok',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Enrollment registry
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Returns true when `repo` (normalized to absolute path) is enrolled
|
|
119
|
+
* for autonomous work.
|
|
120
|
+
*/
|
|
121
|
+
export function isEnrolled(repo) {
|
|
122
|
+
const abs = resolve(repo);
|
|
123
|
+
const reg = readRegistry();
|
|
124
|
+
return reg.repos.includes(abs);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Enroll `repo` for autonomous/sandbox mutation. Idempotent — enrolling
|
|
128
|
+
* an already-enrolled repo is a no-op. Normalizes to absolute path.
|
|
129
|
+
*/
|
|
130
|
+
export function enroll(repo) {
|
|
131
|
+
const abs = resolve(repo);
|
|
132
|
+
const reg = readRegistry();
|
|
133
|
+
if (!reg.repos.includes(abs)) {
|
|
134
|
+
reg.repos.push(abs);
|
|
135
|
+
writeRegistry(reg);
|
|
136
|
+
}
|
|
137
|
+
// H6 (§A.2): audit AFTER the (idempotent) write so a no-op re-enroll STILL
|
|
138
|
+
// records the requested intent. The repo abs path is metadata, never a secret;
|
|
139
|
+
// audit() swallows its own errors so enroll's "never throws" contract holds.
|
|
140
|
+
audit({
|
|
141
|
+
action: 'enroll:add',
|
|
142
|
+
repo: abs,
|
|
143
|
+
sandboxId: null,
|
|
144
|
+
summary: `enrolled ${abs}`,
|
|
145
|
+
result: 'ok',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Remove `repo` from the enrollment registry. Idempotent — unenrolling
|
|
150
|
+
* an absent repo is a no-op. Normalizes to absolute path.
|
|
151
|
+
*/
|
|
152
|
+
export function unenroll(repo) {
|
|
153
|
+
const abs = resolve(repo);
|
|
154
|
+
const reg = readRegistry();
|
|
155
|
+
const filtered = reg.repos.filter(r => r !== abs);
|
|
156
|
+
if (filtered.length !== reg.repos.length) {
|
|
157
|
+
writeRegistry({ repos: filtered });
|
|
158
|
+
}
|
|
159
|
+
// H6 (§A.2): audit AFTER the (idempotent) write so a no-op unenroll STILL
|
|
160
|
+
// records the requested intent. Metadata only (abs path is not a secret);
|
|
161
|
+
// audit() swallows its own errors so unenroll's "never throws" contract holds.
|
|
162
|
+
audit({
|
|
163
|
+
action: 'enroll:remove',
|
|
164
|
+
repo: abs,
|
|
165
|
+
sandboxId: null,
|
|
166
|
+
summary: `unenrolled ${abs}`,
|
|
167
|
+
result: 'ok',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Return all enrolled repos (absolute paths). Returns [] when nothing is
|
|
172
|
+
* enrolled (the default state — DEFAULT EMPTY).
|
|
173
|
+
*/
|
|
174
|
+
export function listEnrolled() {
|
|
175
|
+
return readRegistry().repos;
|
|
176
|
+
}
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// assertMayMutate — the gate every sandbox-mutating op calls first
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
/**
|
|
181
|
+
* Assert that autonomous/sandbox mutation of `repo` is permitted.
|
|
182
|
+
*
|
|
183
|
+
* Throws when:
|
|
184
|
+
* 1. The kill switch is on (ALWAYS, regardless of enrollment or opts).
|
|
185
|
+
* 2. `repo` is not enrolled AND `opts.allowAnyRepo` is not true.
|
|
186
|
+
*
|
|
187
|
+
* `opts.allowAnyRepo` is a TEST SEAM only — it bypasses enrollment so tests
|
|
188
|
+
* can operate on tmp repos without enrolling them. It NEVER bypasses the kill
|
|
189
|
+
* switch.
|
|
190
|
+
*/
|
|
191
|
+
export function assertMayMutate(repo, opts) {
|
|
192
|
+
// Kill switch check — always enforced, no exceptions.
|
|
193
|
+
if (killSwitchOn()) {
|
|
194
|
+
throw new Error('autonomy kill switch is ON');
|
|
195
|
+
}
|
|
196
|
+
// Enrollment check — bypassed only by the explicit test hatch.
|
|
197
|
+
//
|
|
198
|
+
// H5 CHANGE 3 (env-gate allowAnyRepo): the `allowAnyRepo` hatch is effective
|
|
199
|
+
// ONLY when the process ALSO sets ASHLR_TEST_ALLOW_ANY_REPO=1, so a stray
|
|
200
|
+
// `allowAnyRepo:true` in any PRODUCTION path can NEVER bypass enrollment
|
|
201
|
+
// (mirrors advance.ts:155-157 EXACTLY). The kill-switch check above STAYS
|
|
202
|
+
// first and unconditional — it always wins (verify-safety CHECK 2 still
|
|
203
|
+
// passes). createSandbox passes opts straight through, so it inherits this
|
|
204
|
+
// gate transitively — single source of truth.
|
|
205
|
+
const allowAnyRepo = opts?.allowAnyRepo === true &&
|
|
206
|
+
process.env.ASHLR_TEST_ALLOW_ANY_REPO === '1';
|
|
207
|
+
if (!allowAnyRepo && !isEnrolled(repo)) {
|
|
208
|
+
throw new Error(`repo not enrolled for autonomous work: ${resolve(repo)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree.ts — isolated git-worktree sandbox (M21 SAFETY FOUNDATION).
|
|
3
|
+
*
|
|
4
|
+
* Creates an ISOLATED git worktree of a source repo on a NEW scratch branch
|
|
5
|
+
* under ~/.ashlr/sandboxes/<id>/, so autonomous edits NEVER touch the user's
|
|
6
|
+
* working tree, index, HEAD, or their checked-out branch. The worktree is
|
|
7
|
+
* created, used, then discarded (git worktree remove + scratch-branch delete).
|
|
8
|
+
*
|
|
9
|
+
* ISOLATION + DESTRUCTIVE-SAFETY (the whole point):
|
|
10
|
+
* - Sandbox worktrees live ONLY under ~/.ashlr/sandboxes/.
|
|
11
|
+
* - Creating/removing them MUST NOT modify the source repo's working tree,
|
|
12
|
+
* index, HEAD, or the user's branches.
|
|
13
|
+
* - `git worktree add -b <branch> <path> <baseHead>` off the current HEAD;
|
|
14
|
+
* `git worktree remove --force` + `git branch -D` on cleanup.
|
|
15
|
+
* - NEVER `git reset --hard` / `git checkout` in the source repo. NEVER push.
|
|
16
|
+
* NEVER delete user branches. NEVER touch a repo that is not ENROLLED
|
|
17
|
+
* (except via the explicit allowAnyRepo test hatch on a tmp repo).
|
|
18
|
+
* - Every mutating op (and every refused/errored attempt) is audited.
|
|
19
|
+
* - All git invoked via node:child_process arg ARRAYS (execFile, no shell).
|
|
20
|
+
* - No new runtime deps; node builtins only.
|
|
21
|
+
*/
|
|
22
|
+
import type { Sandbox, SandboxDiff } from '../types.js';
|
|
23
|
+
/**
|
|
24
|
+
* H5 CHANGE 1 — orphan-sweep staleness threshold (ms). This is the FALLBACK
|
|
25
|
+
* liveness proxy used ONLY for a sandbox that carries no usable `ownerPid`
|
|
26
|
+
* marker (older metadata, or a crash fixture that models a GONE owner). The
|
|
27
|
+
* PRIMARY liveness signal is the positive `ownerPid` marker (see Sandbox.ownerPid
|
|
28
|
+
* + ownerAlive() below): a sandbox whose owner pid is still alive is skipped by
|
|
29
|
+
* the sweep / disk-cap pre-sweep REGARDLESS of age, so a live in-flight worktree
|
|
30
|
+
* is never reclaimed out from under a running swarm — even a cross-process one.
|
|
31
|
+
*
|
|
32
|
+
* Because there is NO hard wall-clock cap on a swarm (the runner bounds a swarm
|
|
33
|
+
* by step count <= 200 and the token budget only — there is no elapsed-time
|
|
34
|
+
* deadline), createdAt-age is NOT by itself a sound liveness proxy. So this
|
|
35
|
+
* threshold is set FAR above any plausible 200-step run (6 hours) as a
|
|
36
|
+
* belt-and-suspenders bound for the only residual gap: the rare pid-reuse case
|
|
37
|
+
* where a crashed swarm's recorded pid was recycled by an unrelated live process
|
|
38
|
+
* (ownerAlive() would then falsely read 'alive', so age must also have elapsed
|
|
39
|
+
* before we'd ever reclaim). The cost of a too-LARGE value (a stale orphan
|
|
40
|
+
* lingering a few hours longer) is far cheaper than the cost of a too-SMALL one
|
|
41
|
+
* (force-removing a live worktree), so we err large. Shared by the daemon-start
|
|
42
|
+
* sweep wiring (loop.ts) and the disk-cap pre-sweep below.
|
|
43
|
+
*
|
|
44
|
+
* CROSS-PROCESS LIVENESS LIMITATION (documented): the only way a live worktree
|
|
45
|
+
* could still be reclaimed is if BOTH (a) its owner process crashed AND (b) the
|
|
46
|
+
* OS recycled its exact pid for a new live process AND (c) >6h elapsed — a
|
|
47
|
+
* vanishingly small window that, even if hit, only reclaims a worktree whose
|
|
48
|
+
* original owner is provably dead.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ORPHAN_STALE_MS: number;
|
|
51
|
+
/** Absolute path to the sandboxes root: ~/.ashlr/sandboxes. Created lazily. */
|
|
52
|
+
export declare function sandboxesDir(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Create an isolated git-worktree sandbox of `sourceRepo` on a NEW scratch
|
|
55
|
+
* branch under ~/.ashlr/sandboxes/<id>/.
|
|
56
|
+
*
|
|
57
|
+
* FIRST calls assertMayMutate(sourceRepo, opts) — refuses (throws + audits
|
|
58
|
+
* result:'refused') if the kill switch is on OR the repo is not enrolled and
|
|
59
|
+
* opts.allowAnyRepo is not set. Verifies sourceRepo is a git repo. Reads the
|
|
60
|
+
* source HEAD WITHOUT mutating it. Adds the worktree via
|
|
61
|
+
* `git worktree add -b <branch> <path> <baseHead>` run in sourceRepo — this
|
|
62
|
+
* MUST NOT modify the source working tree, index, HEAD, or any user branch.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createSandbox(sourceRepo: string, opts?: {
|
|
65
|
+
allowAnyRepo?: boolean;
|
|
66
|
+
}): Sandbox;
|
|
67
|
+
/**
|
|
68
|
+
* Capture the git diff of the sandbox worktree vs its base HEAD. Read-only —
|
|
69
|
+
* never mutates the worktree or the source repo. Counts come from --numstat;
|
|
70
|
+
* the unified patch from a plain `git diff <baseHead>` inside the worktree.
|
|
71
|
+
*/
|
|
72
|
+
export declare function sandboxDiff(sb: Sandbox): SandboxDiff;
|
|
73
|
+
/**
|
|
74
|
+
* Remove a sandbox: `git worktree remove --force` then delete the scratch
|
|
75
|
+
* branch (`git branch -D`), both run against the source repo; then clean up
|
|
76
|
+
* persisted metadata. MUST NOT touch the source working tree/index/HEAD/user
|
|
77
|
+
* branches. Idempotent — tolerates an already-removed worktree, never throws
|
|
78
|
+
* on a missing dir.
|
|
79
|
+
*/
|
|
80
|
+
export declare function removeSandbox(sb: Sandbox): void;
|
|
81
|
+
/**
|
|
82
|
+
* Enumerate persisted sandbox metadata under sandboxesDir(). Returns [] when
|
|
83
|
+
* none. Never throws on a malformed/partial entry (skips it).
|
|
84
|
+
*/
|
|
85
|
+
export declare function listSandboxes(): Sandbox[];
|
|
86
|
+
/**
|
|
87
|
+
* Sweep ORPHANED sandboxes on restart (H2 crash-recovery). A swarm creates a
|
|
88
|
+
* worktree, uses it, then `removeSandbox`es it — all within a single in-memory
|
|
89
|
+
* `runSwarm` call. The sandbox lifetime is NOT persisted on the SwarmRun (there
|
|
90
|
+
* is no `sandboxId` field on a SwarmRun; the only `sandboxId` references are
|
|
91
|
+
* audit-log entries), so a process killed AFTER `git worktree add` but BEFORE
|
|
92
|
+
* `removeSandbox` leaves a worktree on disk under ~/.ashlr/sandboxes/<id>/ with
|
|
93
|
+
* no record linking it back to any swarm. Such a leftover would otherwise
|
|
94
|
+
* accumulate forever.
|
|
95
|
+
*
|
|
96
|
+
* LIVENESS (important — read before wiring a caller): a persisted sandbox is NOT
|
|
97
|
+
* necessarily an orphan. A `runSwarm` keeps its sandbox metadata on disk for the
|
|
98
|
+
* WHOLE run (createSandbox writes it at start, removeSandbox deletes it at the
|
|
99
|
+
* end), and the daemon runs several sandboxed swarms CONCURRENTLY, each with a
|
|
100
|
+
* live on-disk sandbox; a separately-launched `ashlr swarm` in another process is
|
|
101
|
+
* also possible (the re-entrancy guards are per-process). To NEVER force-remove a
|
|
102
|
+
* live worktree, the sweep uses TWO guards, in order:
|
|
103
|
+
*
|
|
104
|
+
* 1. POSITIVE liveness (primary, age-independent): a sandbox whose recorded
|
|
105
|
+
* `ownerPid` is still alive (ownerAlive()) is SKIPPED regardless of age — so
|
|
106
|
+
* a long-running swarm (same- OR cross-process) older than any staleMs is
|
|
107
|
+
* never reclaimed out from under its owner. This closes the central gap that
|
|
108
|
+
* createdAt-age alone could not (there is no wall-clock cap on a swarm).
|
|
109
|
+
* 2. AGE fallback (secondary): for a sandbox with NO usable ownerPid (older
|
|
110
|
+
* metadata, or a crash fixture that models a GONE owner), the OPTIONAL
|
|
111
|
+
* `staleMs` guard applies — a sandbox whose `createdAt` is younger than
|
|
112
|
+
* `staleMs` is SKIPPED (conservatively assumed possibly-live). With NO
|
|
113
|
+
* `staleMs` (the default, used by recovery PROOF tests where every sandbox is
|
|
114
|
+
* a deliberately-dropped orphan with a dead/absent owner) it sweeps every
|
|
115
|
+
* listed sandbox not protected by guard 1.
|
|
116
|
+
*
|
|
117
|
+
* This sweep composes the two EXISTING safe primitives — `listSandboxes()` to
|
|
118
|
+
* enumerate persisted sandbox metadata, then `removeSandbox()` for each — so it
|
|
119
|
+
* inherits all of removeSandbox's containment guards verbatim: it only ever runs
|
|
120
|
+
* `git worktree remove` / `git branch -D` against a path RE-DERIVED to live under
|
|
121
|
+
* `sandboxesDir()` and a branch RE-DERIVED into the `ashlr/sandbox/<id>` namespace
|
|
122
|
+
* (a metadata mismatch falls through to LOCAL dir cleanup only — never a git op on
|
|
123
|
+
* an arbitrary branch/path). It therefore can NEVER touch a user's working tree,
|
|
124
|
+
* index, HEAD, or any user branch.
|
|
125
|
+
*
|
|
126
|
+
* LOCAL-ONLY by construction: it adds NO outward capability (cleanup is purely
|
|
127
|
+
* inward — it removes only ashlr/sandbox/* worktrees + scratch refs, pushes
|
|
128
|
+
* nothing, opens no PR, applies no proposal), weakens NO guard (every removal
|
|
129
|
+
* goes through removeSandbox's full guard set), and changes NO happy-path
|
|
130
|
+
* behavior (a clean run already removes its own sandbox, so a healthy install
|
|
131
|
+
* has nothing to sweep). H5 wires this into daemon start (loop.ts, staleMs=
|
|
132
|
+
* ORPHAN_STALE_MS), the disk-cap pre-sweep above, and `ashlr sandbox gc` — every
|
|
133
|
+
* caller passes a staleMs and is additionally protected by the age-independent
|
|
134
|
+
* ownerAlive() guard, so no LIVE worktree is ever force-removed. Idempotent
|
|
135
|
+
* and never throws — a removal failure on one id is swallowed so the sweep always
|
|
136
|
+
* makes maximal progress. Returns the ids it swept so a caller / test can assert
|
|
137
|
+
* what was reclaimed.
|
|
138
|
+
*
|
|
139
|
+
* @param opts.staleMs Optional age guard (ms). Skip any sandbox whose `createdAt`
|
|
140
|
+
* is younger than this — a conservative liveness proxy so a concurrently-live
|
|
141
|
+
* sandbox is never force-removed. Omit to sweep all listed sandboxes.
|
|
142
|
+
*/
|
|
143
|
+
export declare function sweepOrphanSandboxes(opts?: {
|
|
144
|
+
staleMs?: number;
|
|
145
|
+
}): string[];
|
|
146
|
+
/**
|
|
147
|
+
* Reclaim the on-disk sandboxes belonging to ONE specific source repo — the
|
|
148
|
+
* scoped cleanup behind `ashlr onboard --rollback <repo>` (H7 BUILD ITEM 4).
|
|
149
|
+
*
|
|
150
|
+
* WHY a distinct sweep (not sweepOrphanSandboxes): the generic orphan sweep is a
|
|
151
|
+
* BACKGROUND restart heuristic — it protects a possibly-live owner whose pid is
|
|
152
|
+
* unreadable by ALSO requiring `createdAt` to be older than `staleMs` (6h). But a
|
|
153
|
+
* crash-leftover worktree from the VERY activation the user is now explicitly
|
|
154
|
+
* undoing is, by definition, fresh (< 6h old), so the generic sweep would SKIP it
|
|
155
|
+
* and the rollback would leave it on disk — contradicting "one-command undo of a
|
|
156
|
+
* first activation". For an explicit, user-requested cleanup of a NAMED repo that
|
|
157
|
+
* the user just unenrolled, the age heuristic is unwarranted.
|
|
158
|
+
*
|
|
159
|
+
* SAFETY (the real boundary is preserved): this drops ONLY the age guard, never
|
|
160
|
+
* the LIVE-OWNER guard. It still SKIPS any sandbox whose recorded `ownerPid` is
|
|
161
|
+
* still alive (ownerAlive()), so a running in-flight swarm's worktree is NEVER
|
|
162
|
+
* force-removed — even a fresh one. It is SCOPED: only sandboxes whose
|
|
163
|
+
* `sourceRepo` resolves to `resolve(repo)` are considered, so it never touches a
|
|
164
|
+
* different repo's sandboxes. Each removal goes through removeSandbox(), so it
|
|
165
|
+
* inherits the full branch-prefix / path-containment guards verbatim and can
|
|
166
|
+
* NEVER touch a user's working tree, index, HEAD, or any user branch.
|
|
167
|
+
*
|
|
168
|
+
* LOCAL-ONLY by construction: adds NO outward capability (inward cleanup only —
|
|
169
|
+
* removes only ashlr/sandbox/* worktrees + scratch refs), weakens NO guard.
|
|
170
|
+
* Idempotent; never throws — a failure on one id is swallowed so the sweep makes
|
|
171
|
+
* maximal progress. Returns the ids it reclaimed.
|
|
172
|
+
*
|
|
173
|
+
* @param repo The source repo whose sandboxes to reclaim (resolved internally).
|
|
174
|
+
*/
|
|
175
|
+
export declare function sweepRepoSandboxes(repo: string): string[];
|