@ag-eco/agentplate-cli 0.13.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/LICENSE +21 -0
- package/README.md +462 -0
- package/agents/ap-co-creation.md +90 -0
- package/agents/builder.md +144 -0
- package/agents/coordinator.md +377 -0
- package/agents/lead.md +435 -0
- package/agents/merger.md +164 -0
- package/agents/monitor.md +214 -0
- package/agents/orchestrator.md +239 -0
- package/agents/reviewer.md +140 -0
- package/agents/scout.md +125 -0
- package/agents/supervisor.md +427 -0
- package/package.json +66 -0
- package/src/agents/capabilities.test.ts +85 -0
- package/src/agents/capabilities.ts +125 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +101 -0
- package/src/agents/copilot-hooks-deployer.test.ts +162 -0
- package/src/agents/copilot-hooks-deployer.ts +93 -0
- package/src/agents/guard-rules.test.ts +372 -0
- package/src/agents/guard-rules.ts +97 -0
- package/src/agents/headless-mail-injector.test.ts +709 -0
- package/src/agents/headless-mail-injector.ts +377 -0
- package/src/agents/headless-prompt.test.ts +102 -0
- package/src/agents/headless-prompt.ts +68 -0
- package/src/agents/hooks-deployer.test.ts +3119 -0
- package/src/agents/hooks-deployer.ts +804 -0
- package/src/agents/identity.test.ts +604 -0
- package/src/agents/identity.ts +384 -0
- package/src/agents/lifecycle.test.ts +196 -0
- package/src/agents/lifecycle.ts +183 -0
- package/src/agents/mail-poll-detect.test.ts +153 -0
- package/src/agents/mail-poll-detect.ts +73 -0
- package/src/agents/manifest.test.ts +1026 -0
- package/src/agents/manifest.ts +376 -0
- package/src/agents/overlay.test.ts +1058 -0
- package/src/agents/overlay.ts +490 -0
- package/src/agents/scope-detect.test.ts +190 -0
- package/src/agents/scope-detect.ts +146 -0
- package/src/agents/turn-lock.test.ts +181 -0
- package/src/agents/turn-lock.ts +235 -0
- package/src/agents/turn-runner-dispatch.test.ts +182 -0
- package/src/agents/turn-runner-dispatch.ts +105 -0
- package/src/agents/turn-runner.test.ts +2312 -0
- package/src/agents/turn-runner.ts +1383 -0
- package/src/beads/client.test.ts +217 -0
- package/src/beads/client.ts +230 -0
- package/src/beads/molecules.test.ts +338 -0
- package/src/beads/molecules.ts +198 -0
- package/src/commands/agents.test.ts +328 -0
- package/src/commands/agents.ts +299 -0
- package/src/commands/clean.test.ts +797 -0
- package/src/commands/clean.ts +791 -0
- package/src/commands/completions.test.ts +348 -0
- package/src/commands/completions.ts +981 -0
- package/src/commands/coordinator.test.ts +2975 -0
- package/src/commands/coordinator.ts +1841 -0
- package/src/commands/costs.test.ts +1183 -0
- package/src/commands/costs.ts +599 -0
- package/src/commands/dashboard.test.ts +954 -0
- package/src/commands/dashboard.ts +1212 -0
- package/src/commands/discover.test.ts +288 -0
- package/src/commands/discover.ts +202 -0
- package/src/commands/doctor.test.ts +303 -0
- package/src/commands/doctor.ts +311 -0
- package/src/commands/ecosystem.test.ts +226 -0
- package/src/commands/ecosystem.ts +248 -0
- package/src/commands/errors.test.ts +654 -0
- package/src/commands/errors.ts +197 -0
- package/src/commands/feed.test.ts +709 -0
- package/src/commands/feed.ts +260 -0
- package/src/commands/group.test.ts +475 -0
- package/src/commands/group.ts +546 -0
- package/src/commands/hooks.test.ts +458 -0
- package/src/commands/hooks.ts +263 -0
- package/src/commands/init.test.ts +1011 -0
- package/src/commands/init.ts +967 -0
- package/src/commands/inspect.test.ts +1239 -0
- package/src/commands/inspect.ts +648 -0
- package/src/commands/log.test.ts +1913 -0
- package/src/commands/log.ts +958 -0
- package/src/commands/logs.test.ts +801 -0
- package/src/commands/logs.ts +483 -0
- package/src/commands/mail.test.ts +1501 -0
- package/src/commands/mail.ts +848 -0
- package/src/commands/merge.test.ts +864 -0
- package/src/commands/merge.ts +381 -0
- package/src/commands/metrics.test.ts +458 -0
- package/src/commands/metrics.ts +129 -0
- package/src/commands/monitor.test.ts +191 -0
- package/src/commands/monitor.ts +409 -0
- package/src/commands/nudge.test.ts +579 -0
- package/src/commands/nudge.ts +646 -0
- package/src/commands/orchestrator.ts +42 -0
- package/src/commands/prime.test.ts +612 -0
- package/src/commands/prime.ts +359 -0
- package/src/commands/replay.test.ts +757 -0
- package/src/commands/replay.ts +231 -0
- package/src/commands/run.test.ts +469 -0
- package/src/commands/run.ts +353 -0
- package/src/commands/serve/agent-actions.test.ts +210 -0
- package/src/commands/serve/agent-actions.ts +192 -0
- package/src/commands/serve/build.test.ts +202 -0
- package/src/commands/serve/build.ts +206 -0
- package/src/commands/serve/coordinator-actions.test.ts +339 -0
- package/src/commands/serve/coordinator-actions.ts +410 -0
- package/src/commands/serve/dev.test.ts +168 -0
- package/src/commands/serve/dev.ts +117 -0
- package/src/commands/serve/mail-actions.test.ts +312 -0
- package/src/commands/serve/mail-actions.ts +167 -0
- package/src/commands/serve/rest.test.ts +1680 -0
- package/src/commands/serve/rest.ts +1130 -0
- package/src/commands/serve/static.ts +51 -0
- package/src/commands/serve/ws.test.ts +361 -0
- package/src/commands/serve/ws.ts +332 -0
- package/src/commands/serve.test.ts +459 -0
- package/src/commands/serve.ts +654 -0
- package/src/commands/sling.test.ts +1583 -0
- package/src/commands/sling.ts +1351 -0
- package/src/commands/spec.test.ts +179 -0
- package/src/commands/spec.ts +105 -0
- package/src/commands/status.test.ts +614 -0
- package/src/commands/status.ts +403 -0
- package/src/commands/stop.test.ts +964 -0
- package/src/commands/stop.ts +319 -0
- package/src/commands/supervisor.test.ts +185 -0
- package/src/commands/supervisor.ts +537 -0
- package/src/commands/trace.test.ts +762 -0
- package/src/commands/trace.ts +205 -0
- package/src/commands/update.test.ts +466 -0
- package/src/commands/update.ts +263 -0
- package/src/commands/upgrade.test.ts +48 -0
- package/src/commands/upgrade.ts +240 -0
- package/src/commands/watch.test.ts +257 -0
- package/src/commands/watch.ts +308 -0
- package/src/commands/worktree.test.ts +1297 -0
- package/src/commands/worktree.ts +451 -0
- package/src/config.test.ts +1535 -0
- package/src/config.ts +1064 -0
- package/src/doctor/agents.test.ts +523 -0
- package/src/doctor/agents.ts +399 -0
- package/src/doctor/config-check.test.ts +191 -0
- package/src/doctor/config-check.ts +183 -0
- package/src/doctor/consistency.test.ts +807 -0
- package/src/doctor/consistency.ts +347 -0
- package/src/doctor/databases.test.ts +350 -0
- package/src/doctor/databases.ts +243 -0
- package/src/doctor/dependencies.test.ts +296 -0
- package/src/doctor/dependencies.ts +272 -0
- package/src/doctor/ecosystem.test.ts +308 -0
- package/src/doctor/ecosystem.ts +156 -0
- package/src/doctor/logs.test.ts +253 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +315 -0
- package/src/doctor/merge-queue.ts +167 -0
- package/src/doctor/providers.test.ts +409 -0
- package/src/doctor/providers.ts +250 -0
- package/src/doctor/serve.test.ts +95 -0
- package/src/doctor/serve.ts +86 -0
- package/src/doctor/structure.test.ts +423 -0
- package/src/doctor/structure.ts +285 -0
- package/src/doctor/types.ts +43 -0
- package/src/doctor/version.test.ts +241 -0
- package/src/doctor/version.ts +132 -0
- package/src/doctor/watchdog.test.ts +167 -0
- package/src/doctor/watchdog.ts +214 -0
- package/src/e2e/init-sling-lifecycle.test.ts +283 -0
- package/src/errors.test.ts +350 -0
- package/src/errors.ts +217 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +369 -0
- package/src/events/tailer.test.ts +719 -0
- package/src/events/tailer.ts +332 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/index.ts +533 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/insights/quality-gates.test.ts +141 -0
- package/src/insights/quality-gates.ts +156 -0
- package/src/json.test.ts +72 -0
- package/src/json.ts +53 -0
- package/src/loam/client.test.ts +752 -0
- package/src/loam/client.ts +664 -0
- package/src/logging/color.test.ts +252 -0
- package/src/logging/color.ts +105 -0
- package/src/logging/format.test.ts +110 -0
- package/src/logging/format.ts +255 -0
- package/src/logging/logger.test.ts +814 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +259 -0
- package/src/logging/reporter.ts +110 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/logging/theme.ts +140 -0
- package/src/mail/broadcast.test.ts +204 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +774 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +898 -0
- package/src/mail/store.ts +425 -0
- package/src/merge/lock.test.ts +149 -0
- package/src/merge/lock.ts +140 -0
- package/src/merge/predict.test.ts +387 -0
- package/src/merge/predict.ts +249 -0
- package/src/merge/queue.test.ts +426 -0
- package/src/merge/queue.ts +246 -0
- package/src/merge/resolver.test.ts +1993 -0
- package/src/merge/resolver.ts +926 -0
- package/src/metrics/pricing.test.ts +258 -0
- package/src/metrics/pricing.ts +135 -0
- package/src/metrics/store.test.ts +978 -0
- package/src/metrics/store.ts +501 -0
- package/src/metrics/summary.test.ts +398 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +483 -0
- package/src/metrics/transcript.ts +114 -0
- package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
- package/src/runtimes/aider.test.ts +124 -0
- package/src/runtimes/aider.ts +147 -0
- package/src/runtimes/amp.test.ts +164 -0
- package/src/runtimes/amp.ts +154 -0
- package/src/runtimes/claude.test.ts +1474 -0
- package/src/runtimes/claude.ts +579 -0
- package/src/runtimes/codex.test.ts +805 -0
- package/src/runtimes/codex.ts +273 -0
- package/src/runtimes/connections.test.ts +214 -0
- package/src/runtimes/connections.ts +103 -0
- package/src/runtimes/copilot.test.ts +707 -0
- package/src/runtimes/copilot.ts +316 -0
- package/src/runtimes/cursor.test.ts +497 -0
- package/src/runtimes/cursor.ts +205 -0
- package/src/runtimes/gemini.test.ts +537 -0
- package/src/runtimes/gemini.ts +243 -0
- package/src/runtimes/goose.test.ts +133 -0
- package/src/runtimes/goose.ts +157 -0
- package/src/runtimes/headless-connection.test.ts +264 -0
- package/src/runtimes/headless-connection.ts +158 -0
- package/src/runtimes/opencode.test.ts +325 -0
- package/src/runtimes/opencode.ts +188 -0
- package/src/runtimes/pi-guards.test.ts +486 -0
- package/src/runtimes/pi-guards.ts +367 -0
- package/src/runtimes/pi.test.ts +789 -0
- package/src/runtimes/pi.ts +305 -0
- package/src/runtimes/registry.test.ts +196 -0
- package/src/runtimes/registry.ts +99 -0
- package/src/runtimes/sapling.test.ts +1267 -0
- package/src/runtimes/sapling.ts +710 -0
- package/src/runtimes/types.ts +266 -0
- package/src/schema-consistency.test.ts +246 -0
- package/src/sessions/compat.test.ts +281 -0
- package/src/sessions/compat.ts +105 -0
- package/src/sessions/store.test.ts +1748 -0
- package/src/sessions/store.ts +858 -0
- package/src/test-helpers.test.ts +124 -0
- package/src/test-helpers.ts +145 -0
- package/src/test-setup.test.ts +31 -0
- package/src/test-setup.ts +28 -0
- package/src/tools/loam/api.ts +368 -0
- package/src/tools/loam/cli.ts +278 -0
- package/src/tools/loam/commands/add.ts +52 -0
- package/src/tools/loam/commands/archive.ts +214 -0
- package/src/tools/loam/commands/audit.ts +276 -0
- package/src/tools/loam/commands/compact.ts +1062 -0
- package/src/tools/loam/commands/completions.ts +79 -0
- package/src/tools/loam/commands/config.ts +381 -0
- package/src/tools/loam/commands/delete-domain.ts +121 -0
- package/src/tools/loam/commands/delete.ts +316 -0
- package/src/tools/loam/commands/diff.ts +200 -0
- package/src/tools/loam/commands/doctor.ts +1113 -0
- package/src/tools/loam/commands/edit.ts +226 -0
- package/src/tools/loam/commands/init.ts +31 -0
- package/src/tools/loam/commands/learn.ts +179 -0
- package/src/tools/loam/commands/move.ts +323 -0
- package/src/tools/loam/commands/onboard.ts +374 -0
- package/src/tools/loam/commands/outcome.ts +185 -0
- package/src/tools/loam/commands/prime.ts +688 -0
- package/src/tools/loam/commands/prune.ts +614 -0
- package/src/tools/loam/commands/query.ts +218 -0
- package/src/tools/loam/commands/rank.ts +180 -0
- package/src/tools/loam/commands/ready.ts +189 -0
- package/src/tools/loam/commands/record.ts +1210 -0
- package/src/tools/loam/commands/restore.ts +166 -0
- package/src/tools/loam/commands/search.ts +327 -0
- package/src/tools/loam/commands/setup.ts +887 -0
- package/src/tools/loam/commands/status.ts +103 -0
- package/src/tools/loam/commands/sync.ts +298 -0
- package/src/tools/loam/commands/update.ts +19 -0
- package/src/tools/loam/commands/upgrade.ts +93 -0
- package/src/tools/loam/commands/validate.ts +190 -0
- package/src/tools/loam/index.ts +62 -0
- package/src/tools/loam/log.ts +127 -0
- package/src/tools/loam/registry/builtins.ts +409 -0
- package/src/tools/loam/registry/custom.ts +431 -0
- package/src/tools/loam/registry/init.ts +55 -0
- package/src/tools/loam/registry/template.ts +40 -0
- package/src/tools/loam/registry/type-registry.ts +113 -0
- package/src/tools/loam/schemas/config-schema.ts +489 -0
- package/src/tools/loam/schemas/config.ts +245 -0
- package/src/tools/loam/schemas/index.ts +18 -0
- package/src/tools/loam/schemas/record-schema.ts +191 -0
- package/src/tools/loam/schemas/record.ts +115 -0
- package/src/tools/loam/utils/active-work.ts +205 -0
- package/src/tools/loam/utils/anchor-validity.ts +80 -0
- package/src/tools/loam/utils/archive.ts +146 -0
- package/src/tools/loam/utils/audit.ts +667 -0
- package/src/tools/loam/utils/bm25.ts +238 -0
- package/src/tools/loam/utils/budget.ts +142 -0
- package/src/tools/loam/utils/config.ts +344 -0
- package/src/tools/loam/utils/dir-anchors.ts +62 -0
- package/src/tools/loam/utils/domain-rules.ts +114 -0
- package/src/tools/loam/utils/expertise.ts +393 -0
- package/src/tools/loam/utils/format-helpers.ts +96 -0
- package/src/tools/loam/utils/format.ts +1234 -0
- package/src/tools/loam/utils/git-context.ts +50 -0
- package/src/tools/loam/utils/git.ts +183 -0
- package/src/tools/loam/utils/hooks.ts +299 -0
- package/src/tools/loam/utils/index.ts +52 -0
- package/src/tools/loam/utils/json-output.ts +13 -0
- package/src/tools/loam/utils/lock.ts +76 -0
- package/src/tools/loam/utils/markers.ts +48 -0
- package/src/tools/loam/utils/numeric-flags.ts +20 -0
- package/src/tools/loam/utils/palette.ts +44 -0
- package/src/tools/loam/utils/prime-ranking.ts +135 -0
- package/src/tools/loam/utils/recipe-discovery.ts +195 -0
- package/src/tools/loam/utils/runtime-flags.ts +28 -0
- package/src/tools/loam/utils/scoring.ts +94 -0
- package/src/tools/loam/utils/version.ts +116 -0
- package/src/tools/sprout/commands/block.ts +64 -0
- package/src/tools/sprout/commands/blocked.ts +86 -0
- package/src/tools/sprout/commands/close.ts +129 -0
- package/src/tools/sprout/commands/completions.ts +198 -0
- package/src/tools/sprout/commands/config.ts +238 -0
- package/src/tools/sprout/commands/create.ts +164 -0
- package/src/tools/sprout/commands/dep.ts +148 -0
- package/src/tools/sprout/commands/doctor.ts +979 -0
- package/src/tools/sprout/commands/init.ts +83 -0
- package/src/tools/sprout/commands/label.ts +178 -0
- package/src/tools/sprout/commands/list.ts +210 -0
- package/src/tools/sprout/commands/migrate.ts +133 -0
- package/src/tools/sprout/commands/onboard.ts +207 -0
- package/src/tools/sprout/commands/plan-show.ts +278 -0
- package/src/tools/sprout/commands/plan.ts +2526 -0
- package/src/tools/sprout/commands/prime.ts +399 -0
- package/src/tools/sprout/commands/ready.ts +245 -0
- package/src/tools/sprout/commands/search.ts +221 -0
- package/src/tools/sprout/commands/show.ts +277 -0
- package/src/tools/sprout/commands/stats.ts +146 -0
- package/src/tools/sprout/commands/sync.ts +134 -0
- package/src/tools/sprout/commands/tpl.ts +364 -0
- package/src/tools/sprout/commands/unblock.ts +115 -0
- package/src/tools/sprout/commands/update.ts +257 -0
- package/src/tools/sprout/commands/upgrade.ts +91 -0
- package/src/tools/sprout/config-schema.ts +152 -0
- package/src/tools/sprout/config.ts +355 -0
- package/src/tools/sprout/filter.ts +107 -0
- package/src/tools/sprout/format.ts +43 -0
- package/src/tools/sprout/id.ts +22 -0
- package/src/tools/sprout/index.ts +204 -0
- package/src/tools/sprout/log.ts +76 -0
- package/src/tools/sprout/markers.ts +22 -0
- package/src/tools/sprout/output.ts +121 -0
- package/src/tools/sprout/plan-backref.ts +93 -0
- package/src/tools/sprout/plan-context.ts +81 -0
- package/src/tools/sprout/plan-domain.ts +139 -0
- package/src/tools/sprout/plan-lifecycle.ts +65 -0
- package/src/tools/sprout/plan-loam.ts +207 -0
- package/src/tools/sprout/plan-schema.ts +209 -0
- package/src/tools/sprout/sort.ts +31 -0
- package/src/tools/sprout/store.ts +172 -0
- package/src/tools/sprout/types.ts +118 -0
- package/src/tools/sprout/validation.ts +119 -0
- package/src/tools/sprout/version.ts +1 -0
- package/src/tools/sprout/yaml.ts +387 -0
- package/src/tools/trellis/commands/archive.ts +87 -0
- package/src/tools/trellis/commands/completions.ts +610 -0
- package/src/tools/trellis/commands/config.ts +382 -0
- package/src/tools/trellis/commands/create.ts +252 -0
- package/src/tools/trellis/commands/diff.ts +150 -0
- package/src/tools/trellis/commands/doctor.ts +771 -0
- package/src/tools/trellis/commands/emit.ts +365 -0
- package/src/tools/trellis/commands/history.ts +83 -0
- package/src/tools/trellis/commands/import.ts +198 -0
- package/src/tools/trellis/commands/init.ts +81 -0
- package/src/tools/trellis/commands/list.ts +103 -0
- package/src/tools/trellis/commands/onboard.ts +156 -0
- package/src/tools/trellis/commands/pin.ts +172 -0
- package/src/tools/trellis/commands/prime.ts +193 -0
- package/src/tools/trellis/commands/render.ts +122 -0
- package/src/tools/trellis/commands/schema.ts +353 -0
- package/src/tools/trellis/commands/show.ts +115 -0
- package/src/tools/trellis/commands/stats.ts +65 -0
- package/src/tools/trellis/commands/sync.ts +112 -0
- package/src/tools/trellis/commands/tree.ts +123 -0
- package/src/tools/trellis/commands/update.ts +330 -0
- package/src/tools/trellis/commands/upgrade.ts +95 -0
- package/src/tools/trellis/commands/validate.ts +166 -0
- package/src/tools/trellis/config-schema.ts +81 -0
- package/src/tools/trellis/config.ts +108 -0
- package/src/tools/trellis/frontmatter.ts +348 -0
- package/src/tools/trellis/id.ts +24 -0
- package/src/tools/trellis/index.ts +209 -0
- package/src/tools/trellis/markers.ts +28 -0
- package/src/tools/trellis/output.ts +84 -0
- package/src/tools/trellis/render.ts +212 -0
- package/src/tools/trellis/store.ts +144 -0
- package/src/tools/trellis/types.ts +82 -0
- package/src/tools/trellis/validate.ts +199 -0
- package/src/tools/trellis/yaml.ts +309 -0
- package/src/tracker/beads.test.ts +454 -0
- package/src/tracker/beads.ts +56 -0
- package/src/tracker/factory.test.ts +90 -0
- package/src/tracker/factory.ts +65 -0
- package/src/tracker/sprout.test.ts +461 -0
- package/src/tracker/sprout.ts +182 -0
- package/src/tracker/types.ts +52 -0
- package/src/trellis/client.test.ts +107 -0
- package/src/trellis/client.ts +179 -0
- package/src/types.ts +970 -0
- package/src/utils/bin.test.ts +10 -0
- package/src/utils/bin.ts +37 -0
- package/src/utils/browser.test.ts +49 -0
- package/src/utils/browser.ts +48 -0
- package/src/utils/fs.test.ts +119 -0
- package/src/utils/fs.ts +62 -0
- package/src/utils/pid.test.ts +152 -0
- package/src/utils/pid.ts +130 -0
- package/src/utils/process-scan.test.ts +53 -0
- package/src/utils/process-scan.ts +76 -0
- package/src/utils/time.test.ts +43 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/version.test.ts +33 -0
- package/src/utils/version.ts +70 -0
- package/src/version.ts +5 -0
- package/src/watchdog/daemon.test.ts +3721 -0
- package/src/watchdog/daemon.ts +1257 -0
- package/src/watchdog/health.test.ts +830 -0
- package/src/watchdog/health.ts +434 -0
- package/src/watchdog/triage.test.ts +205 -0
- package/src/watchdog/triage.ts +205 -0
- package/src/worktree/manager.test.ts +720 -0
- package/src/worktree/manager.ts +405 -0
- package/src/worktree/process.test.ts +172 -0
- package/src/worktree/process.ts +131 -0
- package/src/worktree/tmux.test.ts +1616 -0
- package/src/worktree/tmux.ts +721 -0
- package/templates/CLAUDE.md.tmpl +100 -0
- package/templates/copilot-hooks.json.tmpl +13 -0
- package/templates/hooks.json.tmpl +109 -0
- package/templates/overlay.md.tmpl +88 -0
- package/ui/dist/apple-touch-icon-bdy6teep.png +0 -0
- package/ui/dist/chunk-8s31f05k.css +1 -0
- package/ui/dist/chunk-vm5rz679.js +300 -0
- package/ui/dist/favicon-nzb39vza.svg +4 -0
- package/ui/dist/index.html +17 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: ap clean [--all] [--mail] [--sessions] [--metrics]
|
|
3
|
+
* [--logs] [--worktrees] [--branches] [--agents] [--specs]
|
|
4
|
+
*
|
|
5
|
+
* Nuclear cleanup of agentplate runtime state.
|
|
6
|
+
* --all does everything. Individual flags allow selective cleanup.
|
|
7
|
+
*
|
|
8
|
+
* Execution order for --all (processes → filesystem → databases):
|
|
9
|
+
* 0. Run loam health checks (informational, non-destructive):
|
|
10
|
+
* - Check domains approaching governance limits
|
|
11
|
+
* - Run loam prune --dry-run (report stale record counts)
|
|
12
|
+
* - Run loam doctor (report health issues)
|
|
13
|
+
* 1. Kill all agentplate tmux sessions
|
|
14
|
+
* 2. Remove all worktrees
|
|
15
|
+
* 3. Delete orphaned agentplate/* branches
|
|
16
|
+
* 4. Delete SQLite databases (mail.db, metrics.db)
|
|
17
|
+
* 5. Wipe sessions.db, merge-queue.db
|
|
18
|
+
* 6. Clear directory contents (logs/, agents/, specs/)
|
|
19
|
+
* 7. Delete nudge-state.json
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { loadConfig } from "../config.ts";
|
|
25
|
+
import { AgentError, ValidationError } from "../errors.ts";
|
|
26
|
+
import { createEventStore } from "../events/store.ts";
|
|
27
|
+
import { jsonOutput } from "../json.ts";
|
|
28
|
+
import { createLoamClient } from "../loam/client.ts";
|
|
29
|
+
import { printHint, printSuccess } from "../logging/color.ts";
|
|
30
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
31
|
+
import type { AgentSession, LoamDoctorResult, LoamPruneResult, LoamStatus } from "../types.ts";
|
|
32
|
+
import { clearDirectory, deleteFile, resetJsonFile, wipeSqliteDb } from "../utils/fs.ts";
|
|
33
|
+
import { listWorktrees, removeWorktree } from "../worktree/manager.ts";
|
|
34
|
+
import {
|
|
35
|
+
isProcessAlive,
|
|
36
|
+
isSessionAlive,
|
|
37
|
+
killProcessTree,
|
|
38
|
+
killSession,
|
|
39
|
+
listSessions,
|
|
40
|
+
sanitizeTmuxName,
|
|
41
|
+
} from "../worktree/tmux.ts";
|
|
42
|
+
|
|
43
|
+
export interface CleanOptions {
|
|
44
|
+
agent?: string;
|
|
45
|
+
all?: boolean;
|
|
46
|
+
mail?: boolean;
|
|
47
|
+
sessions?: boolean;
|
|
48
|
+
metrics?: boolean;
|
|
49
|
+
logs?: boolean;
|
|
50
|
+
worktrees?: boolean;
|
|
51
|
+
branches?: boolean;
|
|
52
|
+
agents?: boolean;
|
|
53
|
+
specs?: boolean;
|
|
54
|
+
json?: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load active agent sessions from SessionStore for session-end event logging.
|
|
59
|
+
* Returns sessions that are in an active state (booting, working, stalled).
|
|
60
|
+
*
|
|
61
|
+
* Checks for sessions.db or sessions.json existence first to avoid creating
|
|
62
|
+
* an empty database file as a side effect (which would interfere with
|
|
63
|
+
* the "Nothing to clean" detection later in the pipeline).
|
|
64
|
+
*/
|
|
65
|
+
function loadActiveSessions(agentplateDir: string): AgentSession[] {
|
|
66
|
+
try {
|
|
67
|
+
const dbPath = join(agentplateDir, "sessions.db");
|
|
68
|
+
const jsonPath = join(agentplateDir, "sessions.json");
|
|
69
|
+
if (!existsSync(dbPath) && !existsSync(jsonPath)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const { store } = openSessionStore(agentplateDir);
|
|
73
|
+
try {
|
|
74
|
+
return store.getActive();
|
|
75
|
+
} finally {
|
|
76
|
+
store.close();
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Log synthetic session-end events for all active agents before killing tmux sessions.
|
|
85
|
+
*
|
|
86
|
+
* When clean --all or --worktrees kills tmux sessions, the Stop hook never fires
|
|
87
|
+
* because the process is killed externally. This function writes session_end events
|
|
88
|
+
* to the EventStore with reason='clean' so observability records are complete.
|
|
89
|
+
*/
|
|
90
|
+
async function logSyntheticSessionEndEvents(agentplateDir: string): Promise<number> {
|
|
91
|
+
let logged = 0;
|
|
92
|
+
try {
|
|
93
|
+
const activeSessions = loadActiveSessions(agentplateDir);
|
|
94
|
+
if (activeSessions.length === 0) {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
99
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
100
|
+
try {
|
|
101
|
+
for (const session of activeSessions) {
|
|
102
|
+
eventStore.insert({
|
|
103
|
+
runId: session.runId,
|
|
104
|
+
agentName: session.agentName,
|
|
105
|
+
sessionId: session.id,
|
|
106
|
+
eventType: "session_end",
|
|
107
|
+
toolName: null,
|
|
108
|
+
toolArgs: null,
|
|
109
|
+
toolDurationMs: null,
|
|
110
|
+
level: "info",
|
|
111
|
+
data: JSON.stringify({ reason: "clean", capability: session.capability }),
|
|
112
|
+
});
|
|
113
|
+
logged++;
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
eventStore.close();
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// Best effort: event logging should not block cleanup
|
|
120
|
+
}
|
|
121
|
+
return logged;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface CleanResult {
|
|
125
|
+
sessionEndEventsLogged: number;
|
|
126
|
+
tmuxKilled: number;
|
|
127
|
+
orphanPidsReaped: number;
|
|
128
|
+
worktreesCleaned: number;
|
|
129
|
+
branchesDeleted: number;
|
|
130
|
+
mailWiped: boolean;
|
|
131
|
+
sessionsCleared: boolean;
|
|
132
|
+
mergeQueueCleared: boolean;
|
|
133
|
+
metricsWiped: boolean;
|
|
134
|
+
logsCleared: boolean;
|
|
135
|
+
agentsCleared: boolean;
|
|
136
|
+
specsCleared: boolean;
|
|
137
|
+
nudgeStateCleared: boolean;
|
|
138
|
+
currentRunCleared: boolean;
|
|
139
|
+
loamHealth: {
|
|
140
|
+
checked: boolean;
|
|
141
|
+
domainsNearLimit: Array<{ domain: string; recordCount: number; warnThreshold: number }>;
|
|
142
|
+
stalePruneCandidates: number;
|
|
143
|
+
doctorIssues: number;
|
|
144
|
+
doctorWarnings: number;
|
|
145
|
+
} | null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Kill agentplate tmux sessions registered in THIS project's SessionStore.
|
|
150
|
+
*
|
|
151
|
+
* Project-scoped: only kills tmux sessions whose names appear in the
|
|
152
|
+
* project's sessions.db (or sessions.json). This prevents cross-project
|
|
153
|
+
* kills during dogfooding, where `bun test` might run inside a live swarm.
|
|
154
|
+
*
|
|
155
|
+
* Falls back to killing all "agentplate-{projectName}-" prefixed tmux sessions
|
|
156
|
+
* only if the SessionStore is unavailable (graceful degradation for broken state).
|
|
157
|
+
*/
|
|
158
|
+
async function killAllTmuxSessions(agentplateDir: string, projectName: string): Promise<number> {
|
|
159
|
+
let killed = 0;
|
|
160
|
+
const projectPrefix = `agentplate-${sanitizeTmuxName(projectName)}-`;
|
|
161
|
+
try {
|
|
162
|
+
const tmuxSessions = await listSessions();
|
|
163
|
+
const agentPlateSessions = tmuxSessions.filter((s) => s.name.startsWith(projectPrefix));
|
|
164
|
+
if (agentPlateSessions.length === 0) {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Build a set of tmux session names registered in this project's SessionStore.
|
|
169
|
+
const registeredNames = loadRegisteredTmuxNames(agentplateDir);
|
|
170
|
+
|
|
171
|
+
// If we got registered names, only kill those. Otherwise fall back to all
|
|
172
|
+
// agentplate-{projectName}-* sessions.
|
|
173
|
+
const toKill =
|
|
174
|
+
registeredNames !== null
|
|
175
|
+
? agentPlateSessions.filter((s) => registeredNames.has(s.name))
|
|
176
|
+
: agentPlateSessions;
|
|
177
|
+
|
|
178
|
+
for (const session of toKill) {
|
|
179
|
+
try {
|
|
180
|
+
await killSession(session.name);
|
|
181
|
+
killed++;
|
|
182
|
+
} catch {
|
|
183
|
+
// Best effort
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// tmux not available or no server running
|
|
188
|
+
}
|
|
189
|
+
return killed;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Load the set of tmux session names registered in this project's SessionStore.
|
|
194
|
+
*
|
|
195
|
+
* Returns null if the SessionStore cannot be opened (signals the caller to
|
|
196
|
+
* fall back to the legacy "kill all agentplate-*" behavior).
|
|
197
|
+
*/
|
|
198
|
+
function loadRegisteredTmuxNames(agentplateDir: string): Set<string> | null {
|
|
199
|
+
try {
|
|
200
|
+
const dbPath = join(agentplateDir, "sessions.db");
|
|
201
|
+
const jsonPath = join(agentplateDir, "sessions.json");
|
|
202
|
+
if (!existsSync(dbPath) && !existsSync(jsonPath)) {
|
|
203
|
+
// No session data at all -- return empty set (not null).
|
|
204
|
+
// This is distinct from "store unavailable": it means the project
|
|
205
|
+
// has no registered sessions, so nothing should be killed.
|
|
206
|
+
return new Set();
|
|
207
|
+
}
|
|
208
|
+
const { store } = openSessionStore(agentplateDir);
|
|
209
|
+
try {
|
|
210
|
+
const allSessions = store.getAll();
|
|
211
|
+
return new Set(allSessions.map((s) => s.tmuxSession));
|
|
212
|
+
} finally {
|
|
213
|
+
store.close();
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// SessionStore is broken -- fall back to legacy behavior
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Reap any spawn PIDs in sessions.db that survived tmux teardown.
|
|
223
|
+
*
|
|
224
|
+
* `killAllTmuxSessions` walks descendants of the live tmux pane PID and is
|
|
225
|
+
* sufficient for sessions whose tmux container is still up. This handles the
|
|
226
|
+
* leftover case: a stored pid that is still alive but its tmux session is
|
|
227
|
+
* gone (claude was reparented to init when its bash wrapper got SIGHUP) or
|
|
228
|
+
* the session is in a terminal state but the spawn never exited. Best-effort.
|
|
229
|
+
* (agentplate-505d)
|
|
230
|
+
*/
|
|
231
|
+
async function reapOrphanedPids(agentplateDir: string): Promise<number> {
|
|
232
|
+
let reaped = 0;
|
|
233
|
+
try {
|
|
234
|
+
const dbPath = join(agentplateDir, "sessions.db");
|
|
235
|
+
const jsonPath = join(agentplateDir, "sessions.json");
|
|
236
|
+
if (!existsSync(dbPath) && !existsSync(jsonPath)) {
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
const { store } = openSessionStore(agentplateDir);
|
|
240
|
+
try {
|
|
241
|
+
for (const session of store.getAll()) {
|
|
242
|
+
if (session.pid === null) continue;
|
|
243
|
+
if (!isProcessAlive(session.pid)) continue;
|
|
244
|
+
try {
|
|
245
|
+
await killProcessTree(session.pid);
|
|
246
|
+
reaped++;
|
|
247
|
+
} catch {
|
|
248
|
+
// Best effort
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
} finally {
|
|
252
|
+
store.close();
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
// Best effort
|
|
256
|
+
}
|
|
257
|
+
return reaped;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Remove all agentplate worktrees (force remove with branch deletion).
|
|
262
|
+
*/
|
|
263
|
+
async function cleanAllWorktrees(root: string): Promise<number> {
|
|
264
|
+
let cleaned = 0;
|
|
265
|
+
try {
|
|
266
|
+
const worktrees = await listWorktrees(root);
|
|
267
|
+
const agentplateWts = worktrees.filter((wt) => wt.branch.startsWith("agentplate/"));
|
|
268
|
+
for (const wt of agentplateWts) {
|
|
269
|
+
try {
|
|
270
|
+
await removeWorktree(root, wt.path, { force: true, forceBranch: true });
|
|
271
|
+
cleaned++;
|
|
272
|
+
} catch {
|
|
273
|
+
// Best effort
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
// No worktrees or git error
|
|
278
|
+
}
|
|
279
|
+
return cleaned;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Delete orphaned agentplate/* branch refs not tied to a worktree.
|
|
284
|
+
*/
|
|
285
|
+
async function deleteOrphanedBranches(root: string): Promise<number> {
|
|
286
|
+
let deleted = 0;
|
|
287
|
+
try {
|
|
288
|
+
const proc = Bun.spawn(
|
|
289
|
+
["git", "for-each-ref", "refs/heads/agentplate/", "--format=%(refname:short)"],
|
|
290
|
+
{ cwd: root, stdout: "pipe", stderr: "pipe" },
|
|
291
|
+
);
|
|
292
|
+
const stdout = await new Response(proc.stdout).text();
|
|
293
|
+
await proc.exited;
|
|
294
|
+
|
|
295
|
+
const branches = stdout
|
|
296
|
+
.trim()
|
|
297
|
+
.split("\n")
|
|
298
|
+
.filter((b) => b.length > 0);
|
|
299
|
+
for (const branch of branches) {
|
|
300
|
+
try {
|
|
301
|
+
const del = Bun.spawn(["git", "branch", "-D", branch], {
|
|
302
|
+
cwd: root,
|
|
303
|
+
stdout: "pipe",
|
|
304
|
+
stderr: "pipe",
|
|
305
|
+
});
|
|
306
|
+
const exitCode = await del.exited;
|
|
307
|
+
if (exitCode === 0) deleted++;
|
|
308
|
+
} catch {
|
|
309
|
+
// Best effort
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
// Git error
|
|
314
|
+
}
|
|
315
|
+
return deleted;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Check loam repository health and return diagnostic information.
|
|
320
|
+
*
|
|
321
|
+
* Governance limits warn threshold (based on loam defaults):
|
|
322
|
+
* - Max records per domain: 500 (warn at 400 = 80%)
|
|
323
|
+
*
|
|
324
|
+
* This is informational only — no data is modified.
|
|
325
|
+
*/
|
|
326
|
+
async function checkLoamHealth(repoRoot: string): Promise<{
|
|
327
|
+
domainsNearLimit: Array<{ domain: string; recordCount: number; warnThreshold: number }>;
|
|
328
|
+
stalePruneCandidates: number;
|
|
329
|
+
doctorIssues: number;
|
|
330
|
+
doctorWarnings: number;
|
|
331
|
+
} | null> {
|
|
332
|
+
try {
|
|
333
|
+
const loam = createLoamClient(repoRoot);
|
|
334
|
+
|
|
335
|
+
// 1. Check domain sizes against governance limits
|
|
336
|
+
let status: LoamStatus;
|
|
337
|
+
try {
|
|
338
|
+
status = await loam.status();
|
|
339
|
+
} catch {
|
|
340
|
+
// Loam not available or no .loam directory
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const warnThreshold = 400; // 80% of 500 max
|
|
345
|
+
const domainsNearLimit = status.domains
|
|
346
|
+
.filter((d) => d.recordCount >= warnThreshold)
|
|
347
|
+
.map((d) => ({ domain: d.name, recordCount: d.recordCount, warnThreshold }));
|
|
348
|
+
|
|
349
|
+
// 2. Run prune --dry-run to count stale records
|
|
350
|
+
let pruneResult: LoamPruneResult;
|
|
351
|
+
try {
|
|
352
|
+
pruneResult = await loam.prune({ dryRun: true });
|
|
353
|
+
} catch {
|
|
354
|
+
// Prune failed — skip this check
|
|
355
|
+
pruneResult = { success: false, command: "prune", dryRun: true, totalPruned: 0, results: [] };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const stalePruneCandidates = pruneResult.totalPruned;
|
|
359
|
+
|
|
360
|
+
// 3. Run doctor to check repository health
|
|
361
|
+
let doctorResult: LoamDoctorResult;
|
|
362
|
+
try {
|
|
363
|
+
doctorResult = await loam.doctor({ fix: false });
|
|
364
|
+
} catch {
|
|
365
|
+
// Doctor failed — skip this check
|
|
366
|
+
doctorResult = {
|
|
367
|
+
success: false,
|
|
368
|
+
command: "doctor",
|
|
369
|
+
checks: [],
|
|
370
|
+
summary: { pass: 0, warn: 0, fail: 0 },
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const doctorIssues = doctorResult.summary.fail;
|
|
375
|
+
const doctorWarnings = doctorResult.summary.warn;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
domainsNearLimit,
|
|
379
|
+
stalePruneCandidates,
|
|
380
|
+
doctorIssues,
|
|
381
|
+
doctorWarnings,
|
|
382
|
+
};
|
|
383
|
+
} catch {
|
|
384
|
+
// Loam not available or other error — skip health checks
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
interface AgentCleanResult {
|
|
390
|
+
agentName: string;
|
|
391
|
+
tmuxKilled: boolean;
|
|
392
|
+
pidKilled: boolean;
|
|
393
|
+
worktreeRemoved: boolean;
|
|
394
|
+
branchDeleted: boolean;
|
|
395
|
+
agentDirCleared: boolean;
|
|
396
|
+
logsDirCleared: boolean;
|
|
397
|
+
sessionEndEventLogged: boolean;
|
|
398
|
+
markedCompleted: boolean;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Delete a git branch (best-effort).
|
|
403
|
+
*/
|
|
404
|
+
async function deleteBranch(repoRoot: string, branch: string): Promise<boolean> {
|
|
405
|
+
try {
|
|
406
|
+
const proc = Bun.spawn(["git", "branch", "-D", branch], {
|
|
407
|
+
cwd: repoRoot,
|
|
408
|
+
stdout: "pipe",
|
|
409
|
+
stderr: "pipe",
|
|
410
|
+
});
|
|
411
|
+
const exitCode = await proc.exited;
|
|
412
|
+
return exitCode === 0;
|
|
413
|
+
} catch {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Perform targeted cleanup of a single agent.
|
|
420
|
+
*
|
|
421
|
+
* Kills its tmux session or process, removes its worktree, deletes its branch,
|
|
422
|
+
* clears its agent and log directories, logs a synthetic session-end event,
|
|
423
|
+
* and marks the session as completed.
|
|
424
|
+
*/
|
|
425
|
+
async function cleanSingleAgent(
|
|
426
|
+
agentName: string,
|
|
427
|
+
agentplateDir: string,
|
|
428
|
+
projectRoot: string,
|
|
429
|
+
): Promise<AgentCleanResult> {
|
|
430
|
+
const result: AgentCleanResult = {
|
|
431
|
+
agentName,
|
|
432
|
+
tmuxKilled: false,
|
|
433
|
+
pidKilled: false,
|
|
434
|
+
worktreeRemoved: false,
|
|
435
|
+
branchDeleted: false,
|
|
436
|
+
agentDirCleared: false,
|
|
437
|
+
logsDirCleared: false,
|
|
438
|
+
sessionEndEventLogged: false,
|
|
439
|
+
markedCompleted: false,
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const { store } = openSessionStore(agentplateDir);
|
|
443
|
+
let session: AgentSession | undefined;
|
|
444
|
+
try {
|
|
445
|
+
const found = store.getByName(agentName);
|
|
446
|
+
if (!found) {
|
|
447
|
+
throw new AgentError(`Agent "${agentName}" not found`, { agentName });
|
|
448
|
+
}
|
|
449
|
+
session = found;
|
|
450
|
+
|
|
451
|
+
// Log synthetic session-end event for non-completed agents
|
|
452
|
+
if (session.state !== "completed") {
|
|
453
|
+
try {
|
|
454
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
455
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
456
|
+
try {
|
|
457
|
+
eventStore.insert({
|
|
458
|
+
runId: session.runId,
|
|
459
|
+
agentName: session.agentName,
|
|
460
|
+
sessionId: session.id,
|
|
461
|
+
eventType: "session_end",
|
|
462
|
+
toolName: null,
|
|
463
|
+
toolArgs: null,
|
|
464
|
+
toolDurationMs: null,
|
|
465
|
+
level: "info",
|
|
466
|
+
data: JSON.stringify({ reason: "clean --agent", capability: session.capability }),
|
|
467
|
+
});
|
|
468
|
+
result.sessionEndEventLogged = true;
|
|
469
|
+
} finally {
|
|
470
|
+
eventStore.close();
|
|
471
|
+
}
|
|
472
|
+
} catch {
|
|
473
|
+
// Best effort
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const isHeadless = session.tmuxSession === "" && session.pid !== null;
|
|
478
|
+
|
|
479
|
+
// Kill tmux session or process
|
|
480
|
+
if (isHeadless && session.pid !== null) {
|
|
481
|
+
try {
|
|
482
|
+
if (isProcessAlive(session.pid)) {
|
|
483
|
+
await killProcessTree(session.pid);
|
|
484
|
+
result.pidKilled = true;
|
|
485
|
+
}
|
|
486
|
+
} catch {
|
|
487
|
+
// Best effort
|
|
488
|
+
}
|
|
489
|
+
} else if (session.tmuxSession) {
|
|
490
|
+
try {
|
|
491
|
+
if (await isSessionAlive(session.tmuxSession)) {
|
|
492
|
+
await killSession(session.tmuxSession);
|
|
493
|
+
result.tmuxKilled = true;
|
|
494
|
+
}
|
|
495
|
+
} catch {
|
|
496
|
+
// Best effort
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Remove worktree (force)
|
|
501
|
+
if (session.worktreePath) {
|
|
502
|
+
try {
|
|
503
|
+
await removeWorktree(projectRoot, session.worktreePath, {
|
|
504
|
+
force: true,
|
|
505
|
+
forceBranch: false,
|
|
506
|
+
});
|
|
507
|
+
result.worktreeRemoved = true;
|
|
508
|
+
} catch {
|
|
509
|
+
// Best effort
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Delete branch
|
|
514
|
+
if (session.branchName) {
|
|
515
|
+
result.branchDeleted = await deleteBranch(projectRoot, session.branchName);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Mark completed
|
|
519
|
+
if (session.state !== "completed") {
|
|
520
|
+
store.updateState(agentName, "completed");
|
|
521
|
+
store.updateLastActivity(agentName);
|
|
522
|
+
result.markedCompleted = true;
|
|
523
|
+
}
|
|
524
|
+
} finally {
|
|
525
|
+
store.close();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Clear agent identity directory
|
|
529
|
+
if (session) {
|
|
530
|
+
const agentDir = join(agentplateDir, "agents", agentName);
|
|
531
|
+
result.agentDirCleared = await clearDirectory(agentDir);
|
|
532
|
+
|
|
533
|
+
// Clear agent logs directory
|
|
534
|
+
const logsDir = join(agentplateDir, "logs", agentName);
|
|
535
|
+
result.logsDirCleared = await clearDirectory(logsDir);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Entry point for `ap clean [flags]`.
|
|
543
|
+
*
|
|
544
|
+
* @param opts - Command options
|
|
545
|
+
*/
|
|
546
|
+
export async function cleanCommand(opts: CleanOptions): Promise<void> {
|
|
547
|
+
const json = opts.json ?? false;
|
|
548
|
+
const all = opts.all ?? false;
|
|
549
|
+
const agentName = opts.agent;
|
|
550
|
+
|
|
551
|
+
// --agent and --all are mutually exclusive
|
|
552
|
+
if (agentName && all) {
|
|
553
|
+
throw new ValidationError(
|
|
554
|
+
"--agent and --all are mutually exclusive. Use --agent <name> for single-agent cleanup or --all for full cleanup.",
|
|
555
|
+
{ field: "flags" },
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const doWorktrees = all || (opts.worktrees ?? false);
|
|
560
|
+
const doBranches = all || (opts.branches ?? false);
|
|
561
|
+
const doMail = all || (opts.mail ?? false);
|
|
562
|
+
const doSessions = all || (opts.sessions ?? false);
|
|
563
|
+
const doMetrics = all || (opts.metrics ?? false);
|
|
564
|
+
const doLogs = all || (opts.logs ?? false);
|
|
565
|
+
const doAgents = all || (opts.agents ?? false);
|
|
566
|
+
const doSpecs = all || (opts.specs ?? false);
|
|
567
|
+
|
|
568
|
+
const anySelected =
|
|
569
|
+
agentName ||
|
|
570
|
+
doWorktrees ||
|
|
571
|
+
doBranches ||
|
|
572
|
+
doMail ||
|
|
573
|
+
doSessions ||
|
|
574
|
+
doMetrics ||
|
|
575
|
+
doLogs ||
|
|
576
|
+
doAgents ||
|
|
577
|
+
doSpecs;
|
|
578
|
+
|
|
579
|
+
if (!anySelected) {
|
|
580
|
+
throw new ValidationError(
|
|
581
|
+
"No cleanup targets specified. Use --all for full cleanup, --agent <name> for single-agent cleanup, or individual flags (--mail, --sessions, --metrics, --logs, --worktrees, --branches, --agents, --specs).",
|
|
582
|
+
{ field: "flags" },
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const config = await loadConfig(process.cwd());
|
|
587
|
+
const root = config.project.root;
|
|
588
|
+
const agentplateDir = join(root, ".agentplate");
|
|
589
|
+
|
|
590
|
+
// Per-agent cleanup: targeted single-agent cleanup
|
|
591
|
+
if (agentName) {
|
|
592
|
+
const agentResult = await cleanSingleAgent(agentName, agentplateDir, root);
|
|
593
|
+
if (json) {
|
|
594
|
+
jsonOutput("clean", { agent: agentResult });
|
|
595
|
+
} else {
|
|
596
|
+
printSuccess("Agent cleaned", agentName);
|
|
597
|
+
if (agentResult.tmuxKilled) process.stdout.write(` Tmux session killed\n`);
|
|
598
|
+
if (agentResult.pidKilled) process.stdout.write(` Process killed (PID)\n`);
|
|
599
|
+
if (agentResult.worktreeRemoved) process.stdout.write(` Worktree removed\n`);
|
|
600
|
+
if (agentResult.branchDeleted)
|
|
601
|
+
process.stdout.write(` Branch deleted: ${agentResult.agentName}\n`);
|
|
602
|
+
if (agentResult.agentDirCleared) process.stdout.write(` Cleared agents/${agentName}/\n`);
|
|
603
|
+
if (agentResult.logsDirCleared) process.stdout.write(` Cleared logs/${agentName}/\n`);
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const result: CleanResult = {
|
|
609
|
+
sessionEndEventsLogged: 0,
|
|
610
|
+
tmuxKilled: 0,
|
|
611
|
+
orphanPidsReaped: 0,
|
|
612
|
+
worktreesCleaned: 0,
|
|
613
|
+
branchesDeleted: 0,
|
|
614
|
+
mailWiped: false,
|
|
615
|
+
sessionsCleared: false,
|
|
616
|
+
mergeQueueCleared: false,
|
|
617
|
+
metricsWiped: false,
|
|
618
|
+
logsCleared: false,
|
|
619
|
+
agentsCleared: false,
|
|
620
|
+
specsCleared: false,
|
|
621
|
+
nudgeStateCleared: false,
|
|
622
|
+
currentRunCleared: false,
|
|
623
|
+
loamHealth: null,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
// 0. Run loam health checks BEFORE cleanup operations (when --all is set).
|
|
627
|
+
// This is informational only — no data is modified.
|
|
628
|
+
if (all) {
|
|
629
|
+
const healthCheck = await checkLoamHealth(root);
|
|
630
|
+
if (healthCheck) {
|
|
631
|
+
result.loamHealth = {
|
|
632
|
+
checked: true,
|
|
633
|
+
domainsNearLimit: healthCheck.domainsNearLimit,
|
|
634
|
+
stalePruneCandidates: healthCheck.stalePruneCandidates,
|
|
635
|
+
doctorIssues: healthCheck.doctorIssues,
|
|
636
|
+
doctorWarnings: healthCheck.doctorWarnings,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// 1. Log synthetic session-end events BEFORE killing tmux sessions.
|
|
642
|
+
// When processes are killed externally, the Stop hook never fires,
|
|
643
|
+
// so session_end events would be lost without this step.
|
|
644
|
+
if (doWorktrees || all) {
|
|
645
|
+
result.sessionEndEventsLogged = await logSyntheticSessionEndEvents(agentplateDir);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// 2. Kill tmux sessions (must happen before worktree removal)
|
|
649
|
+
if (doWorktrees || all) {
|
|
650
|
+
result.tmuxKilled = await killAllTmuxSessions(agentplateDir, config.project.name);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// 2b. Reap any orphaned spawn PIDs that survived tmux teardown.
|
|
654
|
+
// Must run after killAllTmuxSessions (which collects descendants of live
|
|
655
|
+
// panes) but before sessions.db is wiped (we need pid records to find
|
|
656
|
+
// orphans). (agentplate-505d)
|
|
657
|
+
if (doWorktrees || all) {
|
|
658
|
+
result.orphanPidsReaped = await reapOrphanedPids(agentplateDir);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// 3. Remove worktrees
|
|
662
|
+
if (doWorktrees) {
|
|
663
|
+
result.worktreesCleaned = await cleanAllWorktrees(root);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// 4. Delete orphaned branches
|
|
667
|
+
if (doBranches) {
|
|
668
|
+
result.branchesDeleted = await deleteOrphanedBranches(root);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// 5. Wipe databases
|
|
672
|
+
if (doMail) {
|
|
673
|
+
result.mailWiped = await wipeSqliteDb(join(agentplateDir, "mail.db"));
|
|
674
|
+
}
|
|
675
|
+
if (doMetrics) {
|
|
676
|
+
result.metricsWiped = await wipeSqliteDb(join(agentplateDir, "metrics.db"));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// 6. Wipe sessions.db + legacy sessions.json
|
|
680
|
+
if (doSessions) {
|
|
681
|
+
result.sessionsCleared = await wipeSqliteDb(join(agentplateDir, "sessions.db"));
|
|
682
|
+
// Also clean legacy sessions.json if it still exists
|
|
683
|
+
await resetJsonFile(join(agentplateDir, "sessions.json"));
|
|
684
|
+
}
|
|
685
|
+
if (all) {
|
|
686
|
+
result.mergeQueueCleared = await wipeSqliteDb(join(agentplateDir, "merge-queue.db"));
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// 7. Clear directories
|
|
690
|
+
if (doLogs) {
|
|
691
|
+
result.logsCleared = await clearDirectory(join(agentplateDir, "logs"));
|
|
692
|
+
}
|
|
693
|
+
if (doAgents) {
|
|
694
|
+
result.agentsCleared = await clearDirectory(join(agentplateDir, "agents"));
|
|
695
|
+
}
|
|
696
|
+
if (doSpecs) {
|
|
697
|
+
result.specsCleared = await clearDirectory(join(agentplateDir, "specs"));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// 8. Delete nudge state + pending nudge markers + current-run.txt
|
|
701
|
+
if (all) {
|
|
702
|
+
result.nudgeStateCleared = await deleteFile(join(agentplateDir, "nudge-state.json"));
|
|
703
|
+
await clearDirectory(join(agentplateDir, "pending-nudges"));
|
|
704
|
+
result.currentRunCleared = await deleteFile(join(agentplateDir, "current-run.txt"));
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Output
|
|
708
|
+
if (json) {
|
|
709
|
+
jsonOutput("clean", { ...result });
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const lines: string[] = [];
|
|
714
|
+
if (result.sessionEndEventsLogged > 0) {
|
|
715
|
+
lines.push(
|
|
716
|
+
`Logged ${result.sessionEndEventsLogged} synthetic session-end event${result.sessionEndEventsLogged === 1 ? "" : "s"}`,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
if (result.tmuxKilled > 0) {
|
|
720
|
+
lines.push(`Killed ${result.tmuxKilled} tmux session${result.tmuxKilled === 1 ? "" : "s"}`);
|
|
721
|
+
}
|
|
722
|
+
if (result.orphanPidsReaped > 0) {
|
|
723
|
+
lines.push(
|
|
724
|
+
`Reaped ${result.orphanPidsReaped} orphaned spawn process${result.orphanPidsReaped === 1 ? "" : "es"}`,
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
if (result.worktreesCleaned > 0) {
|
|
728
|
+
lines.push(
|
|
729
|
+
`Removed ${result.worktreesCleaned} worktree${result.worktreesCleaned === 1 ? "" : "s"}`,
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
if (result.branchesDeleted > 0) {
|
|
733
|
+
lines.push(
|
|
734
|
+
`Deleted ${result.branchesDeleted} orphaned branch${result.branchesDeleted === 1 ? "" : "es"}`,
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
if (result.mailWiped) lines.push("Wiped mail.db");
|
|
738
|
+
if (result.metricsWiped) lines.push("Wiped metrics.db");
|
|
739
|
+
if (result.sessionsCleared) lines.push("Wiped sessions.db");
|
|
740
|
+
if (result.mergeQueueCleared) lines.push("Wiped merge-queue.db");
|
|
741
|
+
if (result.logsCleared) lines.push("Cleared logs/");
|
|
742
|
+
if (result.agentsCleared) lines.push("Cleared agents/");
|
|
743
|
+
if (result.specsCleared) lines.push("Cleared specs/");
|
|
744
|
+
if (result.nudgeStateCleared) lines.push("Cleared nudge-state.json");
|
|
745
|
+
if (result.currentRunCleared) lines.push("Cleared current-run.txt");
|
|
746
|
+
|
|
747
|
+
// Loam health diagnostics (shown before cleanup results)
|
|
748
|
+
if (result.loamHealth?.checked) {
|
|
749
|
+
const health = result.loamHealth;
|
|
750
|
+
const healthLines: string[] = [];
|
|
751
|
+
|
|
752
|
+
if (health.domainsNearLimit.length > 0) {
|
|
753
|
+
healthLines.push("\nWarning: Loam domains approaching governance limits:");
|
|
754
|
+
for (const d of health.domainsNearLimit) {
|
|
755
|
+
healthLines.push(
|
|
756
|
+
` ${d.domain}: ${d.recordCount} records (warn threshold: ${d.warnThreshold})`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (health.stalePruneCandidates > 0) {
|
|
762
|
+
healthLines.push(
|
|
763
|
+
`\nStale records found: ${health.stalePruneCandidates} candidate${health.stalePruneCandidates === 1 ? "" : "s"} (run 'loam prune' to remove)`,
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if (health.doctorWarnings > 0 || health.doctorIssues > 0) {
|
|
768
|
+
healthLines.push(
|
|
769
|
+
`\nLoam health check: ${health.doctorWarnings} warning${health.doctorWarnings === 1 ? "" : "s"}, ${health.doctorIssues} issue${health.doctorIssues === 1 ? "" : "s"} (run 'loam doctor' for details)`,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (healthLines.length > 0) {
|
|
774
|
+
for (const line of healthLines) {
|
|
775
|
+
process.stdout.write(`${line}\n`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (lines.length === 0) {
|
|
781
|
+
printHint("Nothing to clean");
|
|
782
|
+
} else {
|
|
783
|
+
if (result.loamHealth?.checked) {
|
|
784
|
+
process.stdout.write("\n--- Cleanup Results ---\n");
|
|
785
|
+
}
|
|
786
|
+
for (const line of lines) {
|
|
787
|
+
process.stdout.write(`${line}\n`);
|
|
788
|
+
}
|
|
789
|
+
printSuccess("Clean complete");
|
|
790
|
+
}
|
|
791
|
+
}
|