@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,797 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `agentplate clean` command.
|
|
3
|
+
*
|
|
4
|
+
* Uses real filesystem (temp dirs), real git repos, real SQLite.
|
|
5
|
+
* No mocks. tmux operations are tested indirectly — when no tmux
|
|
6
|
+
* server is running, the command handles it gracefully.
|
|
7
|
+
*
|
|
8
|
+
* Philosophy: "never mock what you can use for real" (mx-252b16).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
import { existsSync } from "node:fs";
|
|
13
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { createEventStore } from "../events/store.ts";
|
|
16
|
+
import { createMailStore } from "../mail/store.ts";
|
|
17
|
+
import { createMergeQueue } from "../merge/queue.ts";
|
|
18
|
+
import { createMetricsStore } from "../metrics/store.ts";
|
|
19
|
+
import { openSessionStore } from "../sessions/compat.ts";
|
|
20
|
+
import { cleanupTempDir, createTempGitRepo } from "../test-helpers.ts";
|
|
21
|
+
import type { AgentSession } from "../types.ts";
|
|
22
|
+
import { cleanCommand } from "./clean.ts";
|
|
23
|
+
|
|
24
|
+
let tempDir: string;
|
|
25
|
+
let agentplateDir: string;
|
|
26
|
+
let originalCwd: string;
|
|
27
|
+
let stdoutOutput: string;
|
|
28
|
+
let _stderrOutput: string;
|
|
29
|
+
let originalStdoutWrite: typeof process.stdout.write;
|
|
30
|
+
let originalStderrWrite: typeof process.stderr.write;
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
tempDir = await createTempGitRepo();
|
|
34
|
+
agentplateDir = join(tempDir, ".agentplate");
|
|
35
|
+
await mkdir(agentplateDir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
// Write minimal config.yaml so loadConfig succeeds
|
|
38
|
+
await Bun.write(
|
|
39
|
+
join(agentplateDir, "config.yaml"),
|
|
40
|
+
`project:\n name: test-project\n root: ${tempDir}\n canonicalBranch: main\n`,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Create the standard directories
|
|
44
|
+
await mkdir(join(agentplateDir, "logs"), { recursive: true });
|
|
45
|
+
await mkdir(join(agentplateDir, "agents"), { recursive: true });
|
|
46
|
+
await mkdir(join(agentplateDir, "specs"), { recursive: true });
|
|
47
|
+
await mkdir(join(agentplateDir, "worktrees"), { recursive: true });
|
|
48
|
+
|
|
49
|
+
originalCwd = process.cwd();
|
|
50
|
+
process.chdir(tempDir);
|
|
51
|
+
|
|
52
|
+
// Capture stdout/stderr
|
|
53
|
+
stdoutOutput = "";
|
|
54
|
+
_stderrOutput = "";
|
|
55
|
+
originalStdoutWrite = process.stdout.write;
|
|
56
|
+
originalStderrWrite = process.stderr.write;
|
|
57
|
+
process.stdout.write = ((chunk: string) => {
|
|
58
|
+
stdoutOutput += chunk;
|
|
59
|
+
return true;
|
|
60
|
+
}) as typeof process.stdout.write;
|
|
61
|
+
process.stderr.write = ((chunk: string) => {
|
|
62
|
+
_stderrOutput += chunk;
|
|
63
|
+
return true;
|
|
64
|
+
}) as typeof process.stderr.write;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(async () => {
|
|
68
|
+
process.chdir(originalCwd);
|
|
69
|
+
process.stdout.write = originalStdoutWrite;
|
|
70
|
+
process.stderr.write = originalStderrWrite;
|
|
71
|
+
await cleanupTempDir(tempDir);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// === validation ===
|
|
75
|
+
|
|
76
|
+
describe("validation", () => {
|
|
77
|
+
test("no flags throws ValidationError", async () => {
|
|
78
|
+
await expect(cleanCommand({})).rejects.toThrow("No cleanup targets specified");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("--agent and --all throws ValidationError", async () => {
|
|
82
|
+
await expect(cleanCommand({ agent: "my-builder", all: true })).rejects.toThrow(
|
|
83
|
+
"--agent and --all are mutually exclusive",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// === --all ===
|
|
89
|
+
|
|
90
|
+
describe("--all", () => {
|
|
91
|
+
test("wipes mail.db and WAL files", async () => {
|
|
92
|
+
// Create a mail DB with messages
|
|
93
|
+
const mailDbPath = join(agentplateDir, "mail.db");
|
|
94
|
+
const store = createMailStore(mailDbPath);
|
|
95
|
+
store.insert({
|
|
96
|
+
id: "msg-1",
|
|
97
|
+
from: "agent-a",
|
|
98
|
+
to: "agent-b",
|
|
99
|
+
subject: "test",
|
|
100
|
+
body: "hello",
|
|
101
|
+
type: "status",
|
|
102
|
+
priority: "normal",
|
|
103
|
+
threadId: null,
|
|
104
|
+
});
|
|
105
|
+
store.close();
|
|
106
|
+
|
|
107
|
+
// Verify DB exists
|
|
108
|
+
expect(await Bun.file(mailDbPath).exists()).toBe(true);
|
|
109
|
+
|
|
110
|
+
await cleanCommand({ all: true });
|
|
111
|
+
|
|
112
|
+
// DB should be gone
|
|
113
|
+
expect(await Bun.file(mailDbPath).exists()).toBe(false);
|
|
114
|
+
expect(stdoutOutput).toContain("Wiped mail.db");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("wipes metrics.db", async () => {
|
|
118
|
+
const metricsDbPath = join(agentplateDir, "metrics.db");
|
|
119
|
+
const store = createMetricsStore(metricsDbPath);
|
|
120
|
+
store.recordSession({
|
|
121
|
+
agentName: "test-agent",
|
|
122
|
+
taskId: "task-1",
|
|
123
|
+
capability: "builder",
|
|
124
|
+
startedAt: new Date().toISOString(),
|
|
125
|
+
completedAt: null,
|
|
126
|
+
durationMs: 0,
|
|
127
|
+
exitCode: null,
|
|
128
|
+
mergeResult: null,
|
|
129
|
+
parentAgent: null,
|
|
130
|
+
inputTokens: 0,
|
|
131
|
+
outputTokens: 0,
|
|
132
|
+
cacheReadTokens: 0,
|
|
133
|
+
cacheCreationTokens: 0,
|
|
134
|
+
estimatedCostUsd: null,
|
|
135
|
+
modelUsed: null,
|
|
136
|
+
runId: null,
|
|
137
|
+
});
|
|
138
|
+
store.close();
|
|
139
|
+
|
|
140
|
+
expect(await Bun.file(metricsDbPath).exists()).toBe(true);
|
|
141
|
+
|
|
142
|
+
await cleanCommand({ all: true });
|
|
143
|
+
|
|
144
|
+
expect(await Bun.file(metricsDbPath).exists()).toBe(false);
|
|
145
|
+
expect(stdoutOutput).toContain("Wiped metrics.db");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("wipes sessions.db", async () => {
|
|
149
|
+
// Use the SessionStore to create sessions.db with data
|
|
150
|
+
const { store } = openSessionStore(agentplateDir);
|
|
151
|
+
store.upsert({
|
|
152
|
+
id: "s1",
|
|
153
|
+
agentName: "test-agent",
|
|
154
|
+
capability: "builder",
|
|
155
|
+
worktreePath: "/tmp/wt",
|
|
156
|
+
branchName: "agentplate/test/task",
|
|
157
|
+
taskId: "task-1",
|
|
158
|
+
tmuxSession: "agentplate-test-agent",
|
|
159
|
+
state: "completed",
|
|
160
|
+
pid: 12345,
|
|
161
|
+
parentAgent: null,
|
|
162
|
+
depth: 1,
|
|
163
|
+
runId: null,
|
|
164
|
+
startedAt: new Date().toISOString(),
|
|
165
|
+
lastActivity: new Date().toISOString(),
|
|
166
|
+
escalationLevel: 0,
|
|
167
|
+
stalledSince: null,
|
|
168
|
+
transcriptPath: null,
|
|
169
|
+
});
|
|
170
|
+
store.close();
|
|
171
|
+
|
|
172
|
+
const sessionsDbPath = join(agentplateDir, "sessions.db");
|
|
173
|
+
expect(await Bun.file(sessionsDbPath).exists()).toBe(true);
|
|
174
|
+
|
|
175
|
+
await cleanCommand({ all: true });
|
|
176
|
+
|
|
177
|
+
expect(await Bun.file(sessionsDbPath).exists()).toBe(false);
|
|
178
|
+
expect(stdoutOutput).toContain("Wiped sessions.db");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("wipes merge-queue.db", async () => {
|
|
182
|
+
const queuePath = join(agentplateDir, "merge-queue.db");
|
|
183
|
+
// Create a queue with an entry so we can verify it gets wiped
|
|
184
|
+
const queue = createMergeQueue(queuePath);
|
|
185
|
+
queue.enqueue({
|
|
186
|
+
branchName: "test-branch",
|
|
187
|
+
taskId: "beads-test",
|
|
188
|
+
agentName: "test",
|
|
189
|
+
filesModified: ["src/test.ts"],
|
|
190
|
+
});
|
|
191
|
+
queue.close();
|
|
192
|
+
|
|
193
|
+
await cleanCommand({ all: true });
|
|
194
|
+
|
|
195
|
+
expect(await Bun.file(queuePath).exists()).toBe(false);
|
|
196
|
+
expect(stdoutOutput).toContain("Wiped merge-queue.db");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("clears logs directory contents", async () => {
|
|
200
|
+
const logsDir = join(agentplateDir, "logs");
|
|
201
|
+
await mkdir(join(logsDir, "agent-a", "2026-01-01"), { recursive: true });
|
|
202
|
+
await writeFile(join(logsDir, "agent-a", "2026-01-01", "session.log"), "log data");
|
|
203
|
+
|
|
204
|
+
await cleanCommand({ all: true });
|
|
205
|
+
|
|
206
|
+
const entries = await readdir(logsDir);
|
|
207
|
+
expect(entries).toHaveLength(0);
|
|
208
|
+
expect(stdoutOutput).toContain("Cleared logs/");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("clears agents directory contents", async () => {
|
|
212
|
+
const agentsDir = join(agentplateDir, "agents");
|
|
213
|
+
await mkdir(join(agentsDir, "test-agent"), { recursive: true });
|
|
214
|
+
await writeFile(join(agentsDir, "test-agent", "identity.yaml"), "name: test-agent");
|
|
215
|
+
|
|
216
|
+
await cleanCommand({ all: true });
|
|
217
|
+
|
|
218
|
+
const entries = await readdir(agentsDir);
|
|
219
|
+
expect(entries).toHaveLength(0);
|
|
220
|
+
expect(stdoutOutput).toContain("Cleared agents/");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("clears specs directory contents", async () => {
|
|
224
|
+
const specsDir = join(agentplateDir, "specs");
|
|
225
|
+
await writeFile(join(specsDir, "task-123.md"), "# Spec");
|
|
226
|
+
|
|
227
|
+
await cleanCommand({ all: true });
|
|
228
|
+
|
|
229
|
+
const entries = await readdir(specsDir);
|
|
230
|
+
expect(entries).toHaveLength(0);
|
|
231
|
+
expect(stdoutOutput).toContain("Cleared specs/");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("deletes nudge-state.json", async () => {
|
|
235
|
+
const nudgePath = join(agentplateDir, "nudge-state.json");
|
|
236
|
+
await Bun.write(nudgePath, "{}");
|
|
237
|
+
|
|
238
|
+
await cleanCommand({ all: true });
|
|
239
|
+
|
|
240
|
+
expect(await Bun.file(nudgePath).exists()).toBe(false);
|
|
241
|
+
expect(stdoutOutput).toContain("Cleared nudge-state.json");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("deletes current-run.txt", async () => {
|
|
245
|
+
const currentRunPath = join(agentplateDir, "current-run.txt");
|
|
246
|
+
await Bun.write(currentRunPath, "run-2026-02-13T10-00-00-000Z");
|
|
247
|
+
|
|
248
|
+
await cleanCommand({ all: true });
|
|
249
|
+
|
|
250
|
+
expect(await Bun.file(currentRunPath).exists()).toBe(false);
|
|
251
|
+
expect(stdoutOutput).toContain("Cleared current-run.txt");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("handles missing current-run.txt gracefully", async () => {
|
|
255
|
+
// current-run.txt does not exist — should not error
|
|
256
|
+
await cleanCommand({ all: true });
|
|
257
|
+
expect(stdoutOutput).not.toContain("Cleared current-run.txt");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// === individual flags ===
|
|
262
|
+
|
|
263
|
+
describe("individual flags", () => {
|
|
264
|
+
test("--mail only wipes mail.db, leaves other state intact", async () => {
|
|
265
|
+
// Create mail and sessions
|
|
266
|
+
const mailDbPath = join(agentplateDir, "mail.db");
|
|
267
|
+
const store = createMailStore(mailDbPath);
|
|
268
|
+
store.insert({
|
|
269
|
+
id: "msg-1",
|
|
270
|
+
from: "a",
|
|
271
|
+
to: "b",
|
|
272
|
+
subject: "test",
|
|
273
|
+
body: "hi",
|
|
274
|
+
type: "status",
|
|
275
|
+
priority: "normal",
|
|
276
|
+
threadId: null,
|
|
277
|
+
});
|
|
278
|
+
store.close();
|
|
279
|
+
|
|
280
|
+
const sessionsPath = join(agentplateDir, "sessions.json");
|
|
281
|
+
await Bun.write(sessionsPath, '[{"id":"s1"}]\n');
|
|
282
|
+
|
|
283
|
+
await cleanCommand({ mail: true });
|
|
284
|
+
|
|
285
|
+
// Mail gone
|
|
286
|
+
expect(await Bun.file(mailDbPath).exists()).toBe(false);
|
|
287
|
+
// Sessions untouched
|
|
288
|
+
const sessionsContent = await Bun.file(sessionsPath).text();
|
|
289
|
+
expect(JSON.parse(sessionsContent)).toEqual([{ id: "s1" }]);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("--sessions only wipes sessions.db", async () => {
|
|
293
|
+
// Create sessions.db with data
|
|
294
|
+
const sessionsDbPath = join(agentplateDir, "sessions.db");
|
|
295
|
+
const { store } = openSessionStore(agentplateDir);
|
|
296
|
+
store.upsert({
|
|
297
|
+
id: "s1",
|
|
298
|
+
agentName: "test-agent",
|
|
299
|
+
capability: "builder",
|
|
300
|
+
worktreePath: "/tmp/wt",
|
|
301
|
+
branchName: "agentplate/test/task",
|
|
302
|
+
taskId: "task-1",
|
|
303
|
+
tmuxSession: "agentplate-test-agent",
|
|
304
|
+
state: "completed",
|
|
305
|
+
pid: 12345,
|
|
306
|
+
parentAgent: null,
|
|
307
|
+
depth: 1,
|
|
308
|
+
runId: null,
|
|
309
|
+
startedAt: new Date().toISOString(),
|
|
310
|
+
lastActivity: new Date().toISOString(),
|
|
311
|
+
escalationLevel: 0,
|
|
312
|
+
stalledSince: null,
|
|
313
|
+
transcriptPath: null,
|
|
314
|
+
});
|
|
315
|
+
store.close();
|
|
316
|
+
|
|
317
|
+
// Create a spec file that should survive
|
|
318
|
+
await writeFile(join(agentplateDir, "specs", "task.md"), "spec");
|
|
319
|
+
|
|
320
|
+
await cleanCommand({ sessions: true });
|
|
321
|
+
|
|
322
|
+
// sessions.db should be gone
|
|
323
|
+
expect(await Bun.file(sessionsDbPath).exists()).toBe(false);
|
|
324
|
+
|
|
325
|
+
// Specs untouched
|
|
326
|
+
const specEntries = await readdir(join(agentplateDir, "specs"));
|
|
327
|
+
expect(specEntries).toHaveLength(1);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("--logs clears logs but nothing else", async () => {
|
|
331
|
+
const logsDir = join(agentplateDir, "logs");
|
|
332
|
+
await mkdir(join(logsDir, "agent-x"), { recursive: true });
|
|
333
|
+
await writeFile(join(logsDir, "agent-x", "session.log"), "data");
|
|
334
|
+
|
|
335
|
+
await writeFile(join(agentplateDir, "specs", "task.md"), "spec");
|
|
336
|
+
|
|
337
|
+
await cleanCommand({ logs: true });
|
|
338
|
+
|
|
339
|
+
const logEntries = await readdir(logsDir);
|
|
340
|
+
expect(logEntries).toHaveLength(0);
|
|
341
|
+
|
|
342
|
+
// Specs untouched
|
|
343
|
+
const specEntries = await readdir(join(agentplateDir, "specs"));
|
|
344
|
+
expect(specEntries).toHaveLength(1);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// === idempotent ===
|
|
349
|
+
|
|
350
|
+
describe("idempotent", () => {
|
|
351
|
+
test("running --all when nothing exists does not error", async () => {
|
|
352
|
+
await cleanCommand({ all: true });
|
|
353
|
+
expect(stdoutOutput).toContain("Nothing to clean");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test("running --all twice does not error", async () => {
|
|
357
|
+
// Create some state
|
|
358
|
+
const mailDbPath = join(agentplateDir, "mail.db");
|
|
359
|
+
const store = createMailStore(mailDbPath);
|
|
360
|
+
store.close();
|
|
361
|
+
|
|
362
|
+
await cleanCommand({ all: true });
|
|
363
|
+
stdoutOutput = "";
|
|
364
|
+
await cleanCommand({ all: true });
|
|
365
|
+
expect(stdoutOutput).toContain("Nothing to clean");
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// === JSON output ===
|
|
370
|
+
|
|
371
|
+
describe("JSON output", () => {
|
|
372
|
+
test("--json flag produces valid JSON", async () => {
|
|
373
|
+
const mailDbPath = join(agentplateDir, "mail.db");
|
|
374
|
+
const store = createMailStore(mailDbPath);
|
|
375
|
+
store.insert({
|
|
376
|
+
id: "msg-1",
|
|
377
|
+
from: "a",
|
|
378
|
+
to: "b",
|
|
379
|
+
subject: "test",
|
|
380
|
+
body: "hi",
|
|
381
|
+
type: "status",
|
|
382
|
+
priority: "normal",
|
|
383
|
+
threadId: null,
|
|
384
|
+
});
|
|
385
|
+
store.close();
|
|
386
|
+
|
|
387
|
+
await cleanCommand({ all: true, json: true });
|
|
388
|
+
|
|
389
|
+
const result = JSON.parse(stdoutOutput);
|
|
390
|
+
expect(result).toHaveProperty("tmuxKilled");
|
|
391
|
+
expect(result).toHaveProperty("mailWiped");
|
|
392
|
+
expect(result).toHaveProperty("sessionsCleared");
|
|
393
|
+
expect(result).toHaveProperty("metricsWiped");
|
|
394
|
+
expect(result.mailWiped).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("--json includes sessionEndEventsLogged field", async () => {
|
|
398
|
+
await cleanCommand({ all: true, json: true });
|
|
399
|
+
const result = JSON.parse(stdoutOutput);
|
|
400
|
+
expect(result).toHaveProperty("sessionEndEventsLogged");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("--json includes currentRunCleared field", async () => {
|
|
404
|
+
const currentRunPath = join(agentplateDir, "current-run.txt");
|
|
405
|
+
await Bun.write(currentRunPath, "run-2026-02-13T10-00-00-000Z");
|
|
406
|
+
|
|
407
|
+
await cleanCommand({ all: true, json: true });
|
|
408
|
+
const result = JSON.parse(stdoutOutput);
|
|
409
|
+
expect(result).toHaveProperty("currentRunCleared");
|
|
410
|
+
expect(result.currentRunCleared).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// === synthetic session-end events ===
|
|
415
|
+
|
|
416
|
+
describe("synthetic session-end events", () => {
|
|
417
|
+
function makeSession(overrides: Partial<AgentSession> = {}): AgentSession {
|
|
418
|
+
return {
|
|
419
|
+
id: "s1",
|
|
420
|
+
agentName: "test-builder",
|
|
421
|
+
capability: "builder",
|
|
422
|
+
worktreePath: "/tmp/wt",
|
|
423
|
+
branchName: "agentplate/test-builder/task-1",
|
|
424
|
+
taskId: "task-1",
|
|
425
|
+
tmuxSession: "agentplate-test-builder",
|
|
426
|
+
state: "working",
|
|
427
|
+
pid: 12345,
|
|
428
|
+
parentAgent: null,
|
|
429
|
+
depth: 1,
|
|
430
|
+
runId: null,
|
|
431
|
+
startedAt: new Date().toISOString(),
|
|
432
|
+
lastActivity: new Date().toISOString(),
|
|
433
|
+
escalationLevel: 0,
|
|
434
|
+
stalledSince: null,
|
|
435
|
+
transcriptPath: null,
|
|
436
|
+
...overrides,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
test("logs session-end events for active agents before killing tmux", async () => {
|
|
441
|
+
// Write sessions.json with an active agent
|
|
442
|
+
const sessionsPath = join(agentplateDir, "sessions.json");
|
|
443
|
+
const sessions = [makeSession({ agentName: "builder-a", state: "working" })];
|
|
444
|
+
await Bun.write(sessionsPath, JSON.stringify(sessions));
|
|
445
|
+
|
|
446
|
+
await cleanCommand({ all: true });
|
|
447
|
+
|
|
448
|
+
// Verify event was written to events.db
|
|
449
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
450
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
451
|
+
const events = eventStore.getByAgent("builder-a");
|
|
452
|
+
eventStore.close();
|
|
453
|
+
|
|
454
|
+
const sessionEndEvents = events.filter((e) => e.eventType === "session_end");
|
|
455
|
+
expect(sessionEndEvents).toHaveLength(1);
|
|
456
|
+
expect(sessionEndEvents[0]?.agentName).toBe("builder-a");
|
|
457
|
+
expect(sessionEndEvents[0]?.level).toBe("info");
|
|
458
|
+
|
|
459
|
+
const data = JSON.parse(sessionEndEvents[0]?.data ?? "{}");
|
|
460
|
+
expect(data.reason).toBe("clean");
|
|
461
|
+
expect(data.capability).toBe("builder");
|
|
462
|
+
|
|
463
|
+
expect(stdoutOutput).toContain("Logged 1 synthetic session-end event");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("logs events for multiple active agents", async () => {
|
|
467
|
+
const sessionsPath = join(agentplateDir, "sessions.json");
|
|
468
|
+
const sessions = [
|
|
469
|
+
makeSession({ id: "s1", agentName: "builder-a", state: "working" }),
|
|
470
|
+
makeSession({ id: "s2", agentName: "scout-b", capability: "scout", state: "booting" }),
|
|
471
|
+
makeSession({ id: "s3", agentName: "builder-c", state: "stalled" }),
|
|
472
|
+
];
|
|
473
|
+
await Bun.write(sessionsPath, JSON.stringify(sessions));
|
|
474
|
+
|
|
475
|
+
await cleanCommand({ all: true });
|
|
476
|
+
|
|
477
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
478
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
479
|
+
|
|
480
|
+
for (const name of ["builder-a", "scout-b", "builder-c"]) {
|
|
481
|
+
const events = eventStore.getByAgent(name);
|
|
482
|
+
const sessionEndEvents = events.filter((e) => e.eventType === "session_end");
|
|
483
|
+
expect(sessionEndEvents).toHaveLength(1);
|
|
484
|
+
}
|
|
485
|
+
eventStore.close();
|
|
486
|
+
|
|
487
|
+
expect(stdoutOutput).toContain("Logged 3 synthetic session-end events");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("skips completed and zombie sessions", async () => {
|
|
491
|
+
const sessionsPath = join(agentplateDir, "sessions.json");
|
|
492
|
+
const sessions = [
|
|
493
|
+
makeSession({ id: "s1", agentName: "completed-agent", state: "completed" }),
|
|
494
|
+
makeSession({ id: "s2", agentName: "zombie-agent", state: "zombie" }),
|
|
495
|
+
];
|
|
496
|
+
await Bun.write(sessionsPath, JSON.stringify(sessions));
|
|
497
|
+
|
|
498
|
+
await cleanCommand({ all: true });
|
|
499
|
+
|
|
500
|
+
// events.db may not even be created if there are no events to log
|
|
501
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
502
|
+
if (await Bun.file(eventsDbPath).exists()) {
|
|
503
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
504
|
+
const events1 = eventStore.getByAgent("completed-agent");
|
|
505
|
+
const events2 = eventStore.getByAgent("zombie-agent");
|
|
506
|
+
eventStore.close();
|
|
507
|
+
expect(events1).toHaveLength(0);
|
|
508
|
+
expect(events2).toHaveLength(0);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test("--worktrees also logs session-end events (not just --all)", async () => {
|
|
513
|
+
const sessionsPath = join(agentplateDir, "sessions.json");
|
|
514
|
+
const sessions = [makeSession({ agentName: "wt-agent", state: "working" })];
|
|
515
|
+
await Bun.write(sessionsPath, JSON.stringify(sessions));
|
|
516
|
+
|
|
517
|
+
await cleanCommand({ worktrees: true });
|
|
518
|
+
|
|
519
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
520
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
521
|
+
const events = eventStore.getByAgent("wt-agent");
|
|
522
|
+
eventStore.close();
|
|
523
|
+
|
|
524
|
+
const sessionEndEvents = events.filter((e) => e.eventType === "session_end");
|
|
525
|
+
expect(sessionEndEvents).toHaveLength(1);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("includes runId and sessionId from agent session", async () => {
|
|
529
|
+
const sessionsPath = join(agentplateDir, "sessions.json");
|
|
530
|
+
const sessions = [
|
|
531
|
+
makeSession({
|
|
532
|
+
agentName: "tracked-agent",
|
|
533
|
+
id: "session-123",
|
|
534
|
+
runId: "run-456",
|
|
535
|
+
state: "working",
|
|
536
|
+
}),
|
|
537
|
+
];
|
|
538
|
+
await Bun.write(sessionsPath, JSON.stringify(sessions));
|
|
539
|
+
|
|
540
|
+
await cleanCommand({ all: true });
|
|
541
|
+
|
|
542
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
543
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
544
|
+
const events = eventStore.getByAgent("tracked-agent");
|
|
545
|
+
eventStore.close();
|
|
546
|
+
|
|
547
|
+
const sessionEndEvents = events.filter((e) => e.eventType === "session_end");
|
|
548
|
+
expect(sessionEndEvents).toHaveLength(1);
|
|
549
|
+
expect(sessionEndEvents[0]?.sessionId).toBe("session-123");
|
|
550
|
+
expect(sessionEndEvents[0]?.runId).toBe("run-456");
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("handles missing sessions.json gracefully", async () => {
|
|
554
|
+
// No sessions.json file — should not error
|
|
555
|
+
await cleanCommand({ all: true });
|
|
556
|
+
// Just verify it didn't crash
|
|
557
|
+
expect(stdoutOutput).toBeDefined();
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// === loam health checks ===
|
|
562
|
+
|
|
563
|
+
describe("loam health checks", () => {
|
|
564
|
+
test("runs loam health checks when --all is passed", async () => {
|
|
565
|
+
// Create a real .loam directory with some data
|
|
566
|
+
const loamDir = join(tempDir, ".loam");
|
|
567
|
+
await mkdir(loamDir, { recursive: true });
|
|
568
|
+
await mkdir(join(loamDir, "domains"), { recursive: true });
|
|
569
|
+
|
|
570
|
+
// Create a domain file with some records
|
|
571
|
+
const domainPath = join(loamDir, "domains", "test-domain.jsonl");
|
|
572
|
+
await writeFile(
|
|
573
|
+
domainPath,
|
|
574
|
+
`{"id":"mx-1","type":"convention","description":"Test record 1","recorded_at":"2026-01-01T00:00:00Z"}\n`,
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
await cleanCommand({ all: true });
|
|
578
|
+
|
|
579
|
+
// Loam health checks should have run (might show warnings or might be clean)
|
|
580
|
+
// The output should not error, and if there are no issues, it's fine
|
|
581
|
+
expect(stdoutOutput).toBeDefined();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test("handles missing .loam directory gracefully", async () => {
|
|
585
|
+
// No .loam directory — should not error
|
|
586
|
+
await cleanCommand({ all: true });
|
|
587
|
+
expect(stdoutOutput).toBeDefined();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test("JSON output includes loamHealth field when loam checks run", async () => {
|
|
591
|
+
// Create a .loam directory
|
|
592
|
+
const loamDir = join(tempDir, ".loam");
|
|
593
|
+
await mkdir(loamDir, { recursive: true });
|
|
594
|
+
await mkdir(join(loamDir, "domains"), { recursive: true });
|
|
595
|
+
|
|
596
|
+
// Create a domain file
|
|
597
|
+
const domainPath = join(loamDir, "domains", "test-domain.jsonl");
|
|
598
|
+
await writeFile(
|
|
599
|
+
domainPath,
|
|
600
|
+
`{"id":"mx-1","type":"convention","description":"Test","recorded_at":"2026-01-01T00:00:00Z"}\n`,
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
await cleanCommand({ all: true, json: true });
|
|
604
|
+
|
|
605
|
+
const result = JSON.parse(stdoutOutput);
|
|
606
|
+
expect(result).toHaveProperty("loamHealth");
|
|
607
|
+
|
|
608
|
+
// If loam checks ran, loamHealth should be an object (not null)
|
|
609
|
+
// If loam was unavailable, it will be null
|
|
610
|
+
if (result.loamHealth !== null) {
|
|
611
|
+
expect(result.loamHealth).toHaveProperty("checked");
|
|
612
|
+
expect(result.loamHealth).toHaveProperty("domainsNearLimit");
|
|
613
|
+
expect(result.loamHealth).toHaveProperty("stalePruneCandidates");
|
|
614
|
+
expect(result.loamHealth).toHaveProperty("doctorIssues");
|
|
615
|
+
expect(result.loamHealth).toHaveProperty("doctorWarnings");
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
test("does not run loam checks when only individual flags are used", async () => {
|
|
620
|
+
// Create a .loam directory
|
|
621
|
+
const loamDir = join(tempDir, ".loam");
|
|
622
|
+
await mkdir(loamDir, { recursive: true });
|
|
623
|
+
|
|
624
|
+
// Run clean with only --mail (not --all)
|
|
625
|
+
const mailDbPath = join(agentplateDir, "mail.db");
|
|
626
|
+
const store = createMailStore(mailDbPath);
|
|
627
|
+
store.close();
|
|
628
|
+
|
|
629
|
+
await cleanCommand({ mail: true, json: true });
|
|
630
|
+
|
|
631
|
+
const result = JSON.parse(stdoutOutput);
|
|
632
|
+
// loamHealth should be null because we didn't use --all
|
|
633
|
+
expect(result.loamHealth).toBeNull();
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("warns about domains approaching governance limits", async () => {
|
|
637
|
+
// Create a .loam directory with a domain that has many records
|
|
638
|
+
const loamDir = join(tempDir, ".loam");
|
|
639
|
+
await mkdir(loamDir, { recursive: true });
|
|
640
|
+
await mkdir(join(loamDir, "domains"), { recursive: true });
|
|
641
|
+
|
|
642
|
+
// Create a domain with 410 records (above the 400 warn threshold)
|
|
643
|
+
const domainPath = join(loamDir, "domains", "large-domain.jsonl");
|
|
644
|
+
const records = [];
|
|
645
|
+
for (let i = 1; i <= 410; i++) {
|
|
646
|
+
records.push(
|
|
647
|
+
`{"id":"mx-${i}","type":"convention","description":"Record ${i}","recorded_at":"2026-01-01T00:00:00Z"}`,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
await writeFile(domainPath, `${records.join("\n")}\n`);
|
|
651
|
+
|
|
652
|
+
// Only run if loam CLI is actually available
|
|
653
|
+
const loamAvailable = existsSync(join(loamDir, "domains", "large-domain.jsonl"));
|
|
654
|
+
if (!loamAvailable) {
|
|
655
|
+
return; // Skip this test if loam setup failed
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
await cleanCommand({ all: true });
|
|
659
|
+
|
|
660
|
+
// Should show warning about domain near limit (if loam status worked)
|
|
661
|
+
// The exact output depends on whether loam CLI is available in the test environment
|
|
662
|
+
expect(stdoutOutput).toBeDefined();
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// === --agent ===
|
|
667
|
+
|
|
668
|
+
describe("--agent", () => {
|
|
669
|
+
function makeSession(overrides: Partial<AgentSession> = {}): AgentSession {
|
|
670
|
+
return {
|
|
671
|
+
id: "s1",
|
|
672
|
+
agentName: "test-builder",
|
|
673
|
+
capability: "builder",
|
|
674
|
+
worktreePath: join(tempDir, ".agentplate", "worktrees", "test-builder"),
|
|
675
|
+
branchName: "agentplate/test-builder/task-1",
|
|
676
|
+
taskId: "task-1",
|
|
677
|
+
tmuxSession: "agentplate-test-project-test-builder",
|
|
678
|
+
state: "working",
|
|
679
|
+
pid: 99999,
|
|
680
|
+
parentAgent: null,
|
|
681
|
+
depth: 1,
|
|
682
|
+
runId: "run-123",
|
|
683
|
+
startedAt: new Date().toISOString(),
|
|
684
|
+
lastActivity: new Date().toISOString(),
|
|
685
|
+
escalationLevel: 0,
|
|
686
|
+
stalledSince: null,
|
|
687
|
+
transcriptPath: null,
|
|
688
|
+
...overrides,
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function saveSession(session: AgentSession): void {
|
|
693
|
+
const { store } = openSessionStore(agentplateDir);
|
|
694
|
+
try {
|
|
695
|
+
store.upsert(session);
|
|
696
|
+
} finally {
|
|
697
|
+
store.close();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
test("throws AgentError when agent not found", async () => {
|
|
702
|
+
await expect(cleanCommand({ agent: "nonexistent" })).rejects.toThrow("not found");
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
test("clears agent and logs directories", async () => {
|
|
706
|
+
const session = makeSession();
|
|
707
|
+
saveSession(session);
|
|
708
|
+
|
|
709
|
+
// Create agent and logs dirs with content
|
|
710
|
+
const agentDir = join(agentplateDir, "agents", "test-builder");
|
|
711
|
+
const logsDir = join(agentplateDir, "logs", "test-builder");
|
|
712
|
+
await mkdir(agentDir, { recursive: true });
|
|
713
|
+
await mkdir(logsDir, { recursive: true });
|
|
714
|
+
await writeFile(join(agentDir, "identity.yaml"), "name: test-builder");
|
|
715
|
+
await writeFile(join(logsDir, "session.log"), "log data");
|
|
716
|
+
|
|
717
|
+
await cleanCommand({ agent: "test-builder" });
|
|
718
|
+
|
|
719
|
+
// Dirs should be cleared (but still exist)
|
|
720
|
+
const agentEntries = await readdir(agentDir);
|
|
721
|
+
const logEntries = await readdir(logsDir);
|
|
722
|
+
expect(agentEntries).toHaveLength(0);
|
|
723
|
+
expect(logEntries).toHaveLength(0);
|
|
724
|
+
|
|
725
|
+
expect(stdoutOutput).toContain("Agent cleaned");
|
|
726
|
+
expect(stdoutOutput).toContain("test-builder");
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("marks agent session as completed", async () => {
|
|
730
|
+
const session = makeSession({ state: "working" });
|
|
731
|
+
saveSession(session);
|
|
732
|
+
|
|
733
|
+
await cleanCommand({ agent: "test-builder" });
|
|
734
|
+
|
|
735
|
+
const { store } = openSessionStore(agentplateDir);
|
|
736
|
+
const updated = store.getByName("test-builder");
|
|
737
|
+
store.close();
|
|
738
|
+
expect(updated?.state).toBe("completed");
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test("logs synthetic session-end event for non-completed agent", async () => {
|
|
742
|
+
const session = makeSession({ state: "working" });
|
|
743
|
+
saveSession(session);
|
|
744
|
+
|
|
745
|
+
await cleanCommand({ agent: "test-builder" });
|
|
746
|
+
|
|
747
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
748
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
749
|
+
const events = eventStore.getByAgent("test-builder");
|
|
750
|
+
eventStore.close();
|
|
751
|
+
|
|
752
|
+
const sessionEndEvents = events.filter((e) => e.eventType === "session_end");
|
|
753
|
+
expect(sessionEndEvents).toHaveLength(1);
|
|
754
|
+
const data = JSON.parse(sessionEndEvents[0]?.data ?? "{}");
|
|
755
|
+
expect(data.reason).toContain("clean --agent");
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test("does not log session-end event for already-completed agent", async () => {
|
|
759
|
+
const session = makeSession({ state: "completed" });
|
|
760
|
+
saveSession(session);
|
|
761
|
+
|
|
762
|
+
await cleanCommand({ agent: "test-builder" });
|
|
763
|
+
|
|
764
|
+
const eventsDbPath = join(agentplateDir, "events.db");
|
|
765
|
+
if (existsSync(eventsDbPath)) {
|
|
766
|
+
const eventStore = createEventStore(eventsDbPath);
|
|
767
|
+
const events = eventStore.getByAgent("test-builder");
|
|
768
|
+
eventStore.close();
|
|
769
|
+
const sessionEndEvents = events.filter((e) => e.eventType === "session_end");
|
|
770
|
+
expect(sessionEndEvents).toHaveLength(0);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("--agent + --json returns JSON with agent result", async () => {
|
|
775
|
+
const session = makeSession({ state: "working" });
|
|
776
|
+
saveSession(session);
|
|
777
|
+
|
|
778
|
+
await cleanCommand({ agent: "test-builder", json: true });
|
|
779
|
+
|
|
780
|
+
const result = JSON.parse(stdoutOutput);
|
|
781
|
+
expect(result).toHaveProperty("agent");
|
|
782
|
+
expect(result.agent).toHaveProperty("agentName", "test-builder");
|
|
783
|
+
expect(result.agent).toHaveProperty("markedCompleted");
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
test("handles missing agent/logs directories gracefully", async () => {
|
|
787
|
+
const session = makeSession({ state: "completed" });
|
|
788
|
+
saveSession(session);
|
|
789
|
+
|
|
790
|
+
// No agent or logs dirs — should not error
|
|
791
|
+
await cleanCommand({ agent: "test-builder" });
|
|
792
|
+
expect(stdoutOutput).toContain("Agent cleaned");
|
|
793
|
+
});
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
// fs utility tests (wipeSqliteDb, resetJsonFile, clearDirectory, deleteFile)
|
|
797
|
+
// moved to src/utils/fs.test.ts
|