@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,720 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { WorktreeError } from "../errors.ts";
|
|
7
|
+
import {
|
|
8
|
+
cleanupTempDir,
|
|
9
|
+
commitFile,
|
|
10
|
+
createTempGitRepo,
|
|
11
|
+
getDefaultBranch,
|
|
12
|
+
runGitInDir,
|
|
13
|
+
} from "../test-helpers.ts";
|
|
14
|
+
import {
|
|
15
|
+
createWorktree,
|
|
16
|
+
isBranchMerged,
|
|
17
|
+
listWorktrees,
|
|
18
|
+
removeWorktree,
|
|
19
|
+
rollbackWorktree,
|
|
20
|
+
validateWorktreeCreation,
|
|
21
|
+
} from "./manager.ts";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run a git command in a directory and return stdout. Throws on non-zero exit.
|
|
25
|
+
*/
|
|
26
|
+
async function git(cwd: string, args: string[]): Promise<string> {
|
|
27
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
28
|
+
cwd,
|
|
29
|
+
stdout: "pipe",
|
|
30
|
+
stderr: "pipe",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
34
|
+
new Response(proc.stdout).text(),
|
|
35
|
+
new Response(proc.stderr).text(),
|
|
36
|
+
proc.exited,
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
if (exitCode !== 0) {
|
|
40
|
+
throw new Error(`git ${args.join(" ")} failed (exit ${exitCode}): ${stderr.trim()}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return stdout;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("createWorktree", () => {
|
|
47
|
+
let repoDir: string;
|
|
48
|
+
let worktreesDir: string;
|
|
49
|
+
let defaultBranch: string;
|
|
50
|
+
|
|
51
|
+
beforeEach(async () => {
|
|
52
|
+
// realpathSync resolves macOS /var -> /private/var symlink so paths match git output
|
|
53
|
+
repoDir = realpathSync(await createTempGitRepo());
|
|
54
|
+
defaultBranch = await getDefaultBranch(repoDir);
|
|
55
|
+
worktreesDir = join(repoDir, ".agentplate", "worktrees");
|
|
56
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(async () => {
|
|
60
|
+
await cleanupTempDir(repoDir);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("returns correct path and branch name", async () => {
|
|
64
|
+
const result = await createWorktree({
|
|
65
|
+
repoRoot: repoDir,
|
|
66
|
+
baseDir: worktreesDir,
|
|
67
|
+
agentName: "auth-login",
|
|
68
|
+
baseBranch: defaultBranch,
|
|
69
|
+
taskId: "bead-abc123",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(result.path).toBe(join(worktreesDir, "auth-login"));
|
|
73
|
+
expect(result.branch).toBe("agentplate/auth-login/bead-abc123");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("creates worktree directory on disk", async () => {
|
|
77
|
+
const result = await createWorktree({
|
|
78
|
+
repoRoot: repoDir,
|
|
79
|
+
baseDir: worktreesDir,
|
|
80
|
+
agentName: "auth-login",
|
|
81
|
+
baseBranch: defaultBranch,
|
|
82
|
+
taskId: "bead-abc123",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(existsSync(result.path)).toBe(true);
|
|
86
|
+
// The worktree should contain a .git file (not a directory, since it's a linked worktree)
|
|
87
|
+
expect(existsSync(join(result.path, ".git"))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("creates the branch in the repo", async () => {
|
|
91
|
+
await createWorktree({
|
|
92
|
+
repoRoot: repoDir,
|
|
93
|
+
baseDir: worktreesDir,
|
|
94
|
+
agentName: "auth-login",
|
|
95
|
+
baseBranch: defaultBranch,
|
|
96
|
+
taskId: "bead-abc123",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
100
|
+
expect(branchList).toContain("agentplate/auth-login/bead-abc123");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("throws WorktreeError when creating same worktree twice", async () => {
|
|
104
|
+
await createWorktree({
|
|
105
|
+
repoRoot: repoDir,
|
|
106
|
+
baseDir: worktreesDir,
|
|
107
|
+
agentName: "auth-login",
|
|
108
|
+
baseBranch: defaultBranch,
|
|
109
|
+
taskId: "bead-abc123",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
createWorktree({
|
|
114
|
+
repoRoot: repoDir,
|
|
115
|
+
baseDir: worktreesDir,
|
|
116
|
+
agentName: "auth-login",
|
|
117
|
+
baseBranch: defaultBranch,
|
|
118
|
+
taskId: "bead-abc123",
|
|
119
|
+
}),
|
|
120
|
+
).rejects.toThrow(WorktreeError);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("WorktreeError includes worktree path and branch name", async () => {
|
|
124
|
+
// Create once to occupy the branch name
|
|
125
|
+
await createWorktree({
|
|
126
|
+
repoRoot: repoDir,
|
|
127
|
+
baseDir: worktreesDir,
|
|
128
|
+
agentName: "auth-login",
|
|
129
|
+
baseBranch: defaultBranch,
|
|
130
|
+
taskId: "bead-abc123",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await createWorktree({
|
|
135
|
+
repoRoot: repoDir,
|
|
136
|
+
baseDir: worktreesDir,
|
|
137
|
+
agentName: "auth-login",
|
|
138
|
+
baseBranch: defaultBranch,
|
|
139
|
+
taskId: "bead-abc123",
|
|
140
|
+
});
|
|
141
|
+
// Should not reach here
|
|
142
|
+
expect(true).toBe(false);
|
|
143
|
+
} catch (err: unknown) {
|
|
144
|
+
expect(err).toBeInstanceOf(WorktreeError);
|
|
145
|
+
const wtErr = err as WorktreeError;
|
|
146
|
+
expect(wtErr.worktreePath).toBe(join(worktreesDir, "auth-login"));
|
|
147
|
+
expect(wtErr.branchName).toBe("agentplate/auth-login/bead-abc123");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("rejects creation when target branch is already checked out elsewhere", async () => {
|
|
152
|
+
// Pre-check should fail-fast with a precise diagnostic before git
|
|
153
|
+
// worktree add runs, so the operator sees the actual cause rather
|
|
154
|
+
// than git's generic "already exists" error or, worse, a silently
|
|
155
|
+
// half-built worktree (agentplate-6878).
|
|
156
|
+
const first = await createWorktree({
|
|
157
|
+
repoRoot: repoDir,
|
|
158
|
+
baseDir: worktreesDir,
|
|
159
|
+
agentName: "auth-login",
|
|
160
|
+
baseBranch: defaultBranch,
|
|
161
|
+
taskId: "bead-abc123",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
await createWorktree({
|
|
166
|
+
repoRoot: repoDir,
|
|
167
|
+
baseDir: worktreesDir,
|
|
168
|
+
agentName: "auth-login",
|
|
169
|
+
baseBranch: defaultBranch,
|
|
170
|
+
taskId: "bead-abc123",
|
|
171
|
+
});
|
|
172
|
+
expect(true).toBe(false);
|
|
173
|
+
} catch (err: unknown) {
|
|
174
|
+
expect(err).toBeInstanceOf(WorktreeError);
|
|
175
|
+
const wtErr = err as WorktreeError;
|
|
176
|
+
expect(wtErr.message).toContain("already checked out");
|
|
177
|
+
expect(wtErr.message).toContain(first.path);
|
|
178
|
+
expect(wtErr.branchName).toBe("agentplate/auth-login/bead-abc123");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// The original worktree must remain intact — the pre-check rejected
|
|
182
|
+
// before any state-mutating git command ran.
|
|
183
|
+
expect(existsSync(first.path)).toBe(true);
|
|
184
|
+
const entries = await listWorktrees(repoDir);
|
|
185
|
+
expect(entries.some((e) => e.path === first.path)).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("post-creation: new worktree is registered and contains tracked files", async () => {
|
|
189
|
+
const { path: wtPath } = await createWorktree({
|
|
190
|
+
repoRoot: repoDir,
|
|
191
|
+
baseDir: worktreesDir,
|
|
192
|
+
agentName: "auth-login",
|
|
193
|
+
baseBranch: defaultBranch,
|
|
194
|
+
taskId: "bead-files",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Registration check — listWorktrees must include the new path
|
|
198
|
+
const entries = await listWorktrees(repoDir);
|
|
199
|
+
expect(entries.map((e) => e.path)).toContain(wtPath);
|
|
200
|
+
|
|
201
|
+
// File-presence check — git ls-files inside the worktree must be non-empty
|
|
202
|
+
const lsFiles = await git(wtPath, ["ls-files"]);
|
|
203
|
+
expect(lsFiles.trim().length).toBeGreaterThan(0);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("listWorktrees", () => {
|
|
208
|
+
let repoDir: string;
|
|
209
|
+
let worktreesDir: string;
|
|
210
|
+
let defaultBranch: string;
|
|
211
|
+
|
|
212
|
+
beforeEach(async () => {
|
|
213
|
+
repoDir = realpathSync(await createTempGitRepo());
|
|
214
|
+
defaultBranch = await getDefaultBranch(repoDir);
|
|
215
|
+
worktreesDir = join(repoDir, ".agentplate", "worktrees");
|
|
216
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
afterEach(async () => {
|
|
220
|
+
await cleanupTempDir(repoDir);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("lists main worktree when no additional worktrees exist", async () => {
|
|
224
|
+
const entries = await listWorktrees(repoDir);
|
|
225
|
+
|
|
226
|
+
expect(entries.length).toBeGreaterThanOrEqual(1);
|
|
227
|
+
// The first entry should be the main repo
|
|
228
|
+
const mainEntry = entries[0];
|
|
229
|
+
expect(mainEntry?.path).toBe(repoDir);
|
|
230
|
+
expect(mainEntry?.branch).toMatch(/^(main|master)$/);
|
|
231
|
+
expect(mainEntry?.head).toMatch(/^[a-f0-9]{40}$/);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("lists multiple worktrees after creation", async () => {
|
|
235
|
+
await createWorktree({
|
|
236
|
+
repoRoot: repoDir,
|
|
237
|
+
baseDir: worktreesDir,
|
|
238
|
+
agentName: "auth-login",
|
|
239
|
+
baseBranch: defaultBranch,
|
|
240
|
+
taskId: "bead-abc",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await createWorktree({
|
|
244
|
+
repoRoot: repoDir,
|
|
245
|
+
baseDir: worktreesDir,
|
|
246
|
+
agentName: "data-sync",
|
|
247
|
+
baseBranch: defaultBranch,
|
|
248
|
+
taskId: "bead-xyz",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const entries = await listWorktrees(repoDir);
|
|
252
|
+
|
|
253
|
+
// Main worktree + 2 created = 3
|
|
254
|
+
expect(entries).toHaveLength(3);
|
|
255
|
+
|
|
256
|
+
const paths = entries.map((e) => e.path);
|
|
257
|
+
expect(paths).toContain(repoDir);
|
|
258
|
+
expect(paths).toContain(join(worktreesDir, "auth-login"));
|
|
259
|
+
expect(paths).toContain(join(worktreesDir, "data-sync"));
|
|
260
|
+
|
|
261
|
+
const branches = entries.map((e) => e.branch);
|
|
262
|
+
expect(branches).toContain("agentplate/auth-login/bead-abc");
|
|
263
|
+
expect(branches).toContain("agentplate/data-sync/bead-xyz");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("strips refs/heads/ prefix from branch names", async () => {
|
|
267
|
+
await createWorktree({
|
|
268
|
+
repoRoot: repoDir,
|
|
269
|
+
baseDir: worktreesDir,
|
|
270
|
+
agentName: "feature-worker",
|
|
271
|
+
baseBranch: defaultBranch,
|
|
272
|
+
taskId: "bead-123",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const entries = await listWorktrees(repoDir);
|
|
276
|
+
const worktreeEntry = entries.find((e) => e.path === join(worktreesDir, "feature-worker"));
|
|
277
|
+
|
|
278
|
+
expect(worktreeEntry?.branch).toBe("agentplate/feature-worker/bead-123");
|
|
279
|
+
// Ensure no refs/heads/ prefix leaked through
|
|
280
|
+
expect(worktreeEntry?.branch).not.toContain("refs/heads/");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("each entry has a valid HEAD commit hash", async () => {
|
|
284
|
+
await createWorktree({
|
|
285
|
+
repoRoot: repoDir,
|
|
286
|
+
baseDir: worktreesDir,
|
|
287
|
+
agentName: "auth-login",
|
|
288
|
+
baseBranch: defaultBranch,
|
|
289
|
+
taskId: "bead-abc",
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const entries = await listWorktrees(repoDir);
|
|
293
|
+
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
expect(entry.head).toMatch(/^[a-f0-9]{40}$/);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("throws WorktreeError for non-git directory", async () => {
|
|
300
|
+
// Use a separate temp dir outside the git repo so git won't find a parent .git
|
|
301
|
+
const tmpDir = realpathSync(await mkdtemp(join(tmpdir(), "agentplate-notgit-")));
|
|
302
|
+
try {
|
|
303
|
+
await expect(listWorktrees(tmpDir)).rejects.toThrow(WorktreeError);
|
|
304
|
+
} finally {
|
|
305
|
+
await cleanupTempDir(tmpDir);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe("isBranchMerged", () => {
|
|
311
|
+
let repoDir: string;
|
|
312
|
+
let worktreesDir: string;
|
|
313
|
+
let defaultBranch: string;
|
|
314
|
+
|
|
315
|
+
beforeEach(async () => {
|
|
316
|
+
repoDir = realpathSync(await createTempGitRepo());
|
|
317
|
+
defaultBranch = await getDefaultBranch(repoDir);
|
|
318
|
+
worktreesDir = join(repoDir, ".agentplate", "worktrees");
|
|
319
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
afterEach(async () => {
|
|
323
|
+
await cleanupTempDir(repoDir);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("returns true for a branch that has been merged via git merge", async () => {
|
|
327
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
328
|
+
repoRoot: repoDir,
|
|
329
|
+
baseDir: worktreesDir,
|
|
330
|
+
agentName: "feature-agent",
|
|
331
|
+
baseBranch: defaultBranch,
|
|
332
|
+
taskId: "bead-merged",
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Add a commit to the feature branch
|
|
336
|
+
await commitFile(wtPath, "feature.ts", "export const x = 1;", "add feature");
|
|
337
|
+
|
|
338
|
+
// Merge the feature branch into defaultBranch
|
|
339
|
+
await git(repoDir, ["merge", "--no-ff", branch, "-m", "merge feature"]);
|
|
340
|
+
|
|
341
|
+
const merged = await isBranchMerged(repoDir, branch, defaultBranch);
|
|
342
|
+
expect(merged).toBe(true);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("returns false for a branch with unmerged commits", async () => {
|
|
346
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
347
|
+
repoRoot: repoDir,
|
|
348
|
+
baseDir: worktreesDir,
|
|
349
|
+
agentName: "feature-agent",
|
|
350
|
+
baseBranch: defaultBranch,
|
|
351
|
+
taskId: "bead-unmerged",
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// Add a commit to the feature branch (not merged)
|
|
355
|
+
await commitFile(wtPath, "feature.ts", "export const x = 1;", "add feature");
|
|
356
|
+
|
|
357
|
+
const merged = await isBranchMerged(repoDir, branch, defaultBranch);
|
|
358
|
+
expect(merged).toBe(false);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("returns true for an identical branch (same commit, no additional commits)", async () => {
|
|
362
|
+
// A freshly created worktree branch has the same HEAD as the base branch
|
|
363
|
+
const { branch } = await createWorktree({
|
|
364
|
+
repoRoot: repoDir,
|
|
365
|
+
baseDir: worktreesDir,
|
|
366
|
+
agentName: "feature-agent",
|
|
367
|
+
baseBranch: defaultBranch,
|
|
368
|
+
taskId: "bead-same",
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// The branch was created from defaultBranch with no additional commits,
|
|
372
|
+
// so its tip is an ancestor of (equal to) defaultBranch
|
|
373
|
+
const merged = await isBranchMerged(repoDir, branch, defaultBranch);
|
|
374
|
+
expect(merged).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe("removeWorktree", () => {
|
|
379
|
+
let repoDir: string;
|
|
380
|
+
let worktreesDir: string;
|
|
381
|
+
let defaultBranch: string;
|
|
382
|
+
|
|
383
|
+
beforeEach(async () => {
|
|
384
|
+
repoDir = realpathSync(await createTempGitRepo());
|
|
385
|
+
defaultBranch = await getDefaultBranch(repoDir);
|
|
386
|
+
worktreesDir = join(repoDir, ".agentplate", "worktrees");
|
|
387
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
afterEach(async () => {
|
|
391
|
+
await cleanupTempDir(repoDir);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("removes worktree directory from disk", async () => {
|
|
395
|
+
const { path: wtPath } = await createWorktree({
|
|
396
|
+
repoRoot: repoDir,
|
|
397
|
+
baseDir: worktreesDir,
|
|
398
|
+
agentName: "auth-login",
|
|
399
|
+
baseBranch: defaultBranch,
|
|
400
|
+
taskId: "bead-abc",
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
expect(existsSync(wtPath)).toBe(true);
|
|
404
|
+
|
|
405
|
+
await removeWorktree(repoDir, wtPath);
|
|
406
|
+
|
|
407
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("deletes the associated branch after removal", async () => {
|
|
411
|
+
const { path: wtPath } = await createWorktree({
|
|
412
|
+
repoRoot: repoDir,
|
|
413
|
+
baseDir: worktreesDir,
|
|
414
|
+
agentName: "auth-login",
|
|
415
|
+
baseBranch: defaultBranch,
|
|
416
|
+
taskId: "bead-abc",
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
await removeWorktree(repoDir, wtPath);
|
|
420
|
+
|
|
421
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
422
|
+
expect(branchList).not.toContain("agentplate/auth-login/bead-abc");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("worktree no longer appears in listWorktrees after removal", async () => {
|
|
426
|
+
const { path: wtPath } = await createWorktree({
|
|
427
|
+
repoRoot: repoDir,
|
|
428
|
+
baseDir: worktreesDir,
|
|
429
|
+
agentName: "auth-login",
|
|
430
|
+
baseBranch: defaultBranch,
|
|
431
|
+
taskId: "bead-abc",
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
await removeWorktree(repoDir, wtPath);
|
|
435
|
+
|
|
436
|
+
const entries = await listWorktrees(repoDir);
|
|
437
|
+
const paths = entries.map((e) => e.path);
|
|
438
|
+
expect(paths).not.toContain(wtPath);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("force flag removes worktree with uncommitted changes", async () => {
|
|
442
|
+
const { path: wtPath } = await createWorktree({
|
|
443
|
+
repoRoot: repoDir,
|
|
444
|
+
baseDir: worktreesDir,
|
|
445
|
+
agentName: "auth-login",
|
|
446
|
+
baseBranch: defaultBranch,
|
|
447
|
+
taskId: "bead-abc",
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Create an untracked file in the worktree
|
|
451
|
+
await Bun.write(join(wtPath, "untracked.txt"), "some content");
|
|
452
|
+
|
|
453
|
+
// Without force, git worktree remove may fail on dirty worktrees.
|
|
454
|
+
// With force, it should succeed.
|
|
455
|
+
await removeWorktree(repoDir, wtPath, { force: true, forceBranch: true });
|
|
456
|
+
|
|
457
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test("forceBranch deletes unmerged branch", async () => {
|
|
461
|
+
const { path: wtPath } = await createWorktree({
|
|
462
|
+
repoRoot: repoDir,
|
|
463
|
+
baseDir: worktreesDir,
|
|
464
|
+
agentName: "auth-login",
|
|
465
|
+
baseBranch: defaultBranch,
|
|
466
|
+
taskId: "bead-abc",
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Add a commit in the worktree so the branch diverges (making it "unmerged")
|
|
470
|
+
await commitFile(wtPath, "new-file.ts", "export const x = 1;", "add new file");
|
|
471
|
+
|
|
472
|
+
// forceBranch uses -D instead of -d, so even unmerged branches get deleted
|
|
473
|
+
await removeWorktree(repoDir, wtPath, { force: true, forceBranch: true });
|
|
474
|
+
|
|
475
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
476
|
+
expect(branchList).not.toContain("agentplate/auth-login/bead-abc");
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test("without forceBranch, unmerged branch deletion is silently ignored", async () => {
|
|
480
|
+
const { path: wtPath } = await createWorktree({
|
|
481
|
+
repoRoot: repoDir,
|
|
482
|
+
baseDir: worktreesDir,
|
|
483
|
+
agentName: "auth-login",
|
|
484
|
+
baseBranch: defaultBranch,
|
|
485
|
+
taskId: "bead-abc",
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Add a commit to make the branch unmerged
|
|
489
|
+
await commitFile(wtPath, "new-file.ts", "export const x = 1;", "add new file");
|
|
490
|
+
|
|
491
|
+
// Without forceBranch, branch -d will fail because it's not merged, but
|
|
492
|
+
// removeWorktree should not throw (it catches the error)
|
|
493
|
+
await removeWorktree(repoDir, wtPath, { force: true });
|
|
494
|
+
|
|
495
|
+
// Worktree is gone
|
|
496
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
497
|
+
|
|
498
|
+
// But branch still exists because -d failed silently
|
|
499
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
500
|
+
expect(branchList).toContain("agentplate/auth-login/bead-abc");
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe("rollbackWorktree", () => {
|
|
505
|
+
let repoDir: string;
|
|
506
|
+
let worktreesDir: string;
|
|
507
|
+
let defaultBranch: string;
|
|
508
|
+
|
|
509
|
+
beforeEach(async () => {
|
|
510
|
+
repoDir = realpathSync(await createTempGitRepo());
|
|
511
|
+
defaultBranch = await getDefaultBranch(repoDir);
|
|
512
|
+
worktreesDir = join(repoDir, ".agentplate", "worktrees");
|
|
513
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
afterEach(async () => {
|
|
517
|
+
await cleanupTempDir(repoDir);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("removes worktree directory and branch", async () => {
|
|
521
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
522
|
+
repoRoot: repoDir,
|
|
523
|
+
baseDir: worktreesDir,
|
|
524
|
+
agentName: "auth-login",
|
|
525
|
+
baseBranch: defaultBranch,
|
|
526
|
+
taskId: "bead-abc",
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
expect(existsSync(wtPath)).toBe(true);
|
|
530
|
+
|
|
531
|
+
await rollbackWorktree(repoDir, wtPath, branch);
|
|
532
|
+
|
|
533
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
534
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
535
|
+
expect(branchList).not.toContain("agentplate/auth-login/bead-abc");
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("does not throw for a non-existent worktree path", async () => {
|
|
539
|
+
const fakePath = join(worktreesDir, "does-not-exist");
|
|
540
|
+
await expect(rollbackWorktree(repoDir, fakePath, "")).resolves.toBeUndefined();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("skips branch deletion when branchName is empty", async () => {
|
|
544
|
+
const { path: wtPath } = await createWorktree({
|
|
545
|
+
repoRoot: repoDir,
|
|
546
|
+
baseDir: worktreesDir,
|
|
547
|
+
agentName: "auth-login",
|
|
548
|
+
baseBranch: defaultBranch,
|
|
549
|
+
taskId: "bead-abc",
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Pass empty branch — should not throw and worktree should still be removed
|
|
553
|
+
await rollbackWorktree(repoDir, wtPath, "");
|
|
554
|
+
|
|
555
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
556
|
+
// Branch still exists (we didn't delete it)
|
|
557
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
558
|
+
expect(branchList).toContain("agentplate/auth-login/bead-abc");
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe("validateWorktreeCreation", () => {
|
|
563
|
+
let repoDir: string;
|
|
564
|
+
let worktreesDir: string;
|
|
565
|
+
let defaultBranch: string;
|
|
566
|
+
|
|
567
|
+
beforeEach(async () => {
|
|
568
|
+
repoDir = realpathSync(await createTempGitRepo());
|
|
569
|
+
defaultBranch = await getDefaultBranch(repoDir);
|
|
570
|
+
worktreesDir = join(repoDir, ".agentplate", "worktrees");
|
|
571
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
afterEach(async () => {
|
|
575
|
+
await cleanupTempDir(repoDir);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("passes for a normally created worktree", async () => {
|
|
579
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
580
|
+
repoRoot: repoDir,
|
|
581
|
+
baseDir: worktreesDir,
|
|
582
|
+
agentName: "feature-agent",
|
|
583
|
+
baseBranch: defaultBranch,
|
|
584
|
+
taskId: "bead-ok",
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Re-running validation against the live worktree should be a no-op
|
|
588
|
+
await expect(
|
|
589
|
+
validateWorktreeCreation({
|
|
590
|
+
repoRoot: repoDir,
|
|
591
|
+
worktreePath: wtPath,
|
|
592
|
+
branchName: branch,
|
|
593
|
+
}),
|
|
594
|
+
).resolves.toBeUndefined();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test("throws when worktree path is not registered with git", async () => {
|
|
598
|
+
const fakePath = join(worktreesDir, "ghost-agent");
|
|
599
|
+
|
|
600
|
+
try {
|
|
601
|
+
await validateWorktreeCreation({
|
|
602
|
+
repoRoot: repoDir,
|
|
603
|
+
worktreePath: fakePath,
|
|
604
|
+
branchName: "agentplate/ghost-agent/bead-missing",
|
|
605
|
+
});
|
|
606
|
+
expect(true).toBe(false);
|
|
607
|
+
} catch (err: unknown) {
|
|
608
|
+
expect(err).toBeInstanceOf(WorktreeError);
|
|
609
|
+
const wtErr = err as WorktreeError;
|
|
610
|
+
expect(wtErr.worktreePath).toBe(fakePath);
|
|
611
|
+
expect(wtErr.branchName).toBe("agentplate/ghost-agent/bead-missing");
|
|
612
|
+
expect(wtErr.message).toContain("not registered with git");
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("rolls back the dangling branch when validation fails", async () => {
|
|
617
|
+
// Create a real branch that's not attached to any worktree, then ask
|
|
618
|
+
// validation to check a path it can't possibly be registered at.
|
|
619
|
+
await runGitInDir(repoDir, ["branch", "agentplate/orphan-agent/bead-x", defaultBranch]);
|
|
620
|
+
const fakePath = join(worktreesDir, "orphan-agent");
|
|
621
|
+
|
|
622
|
+
await expect(
|
|
623
|
+
validateWorktreeCreation({
|
|
624
|
+
repoRoot: repoDir,
|
|
625
|
+
worktreePath: fakePath,
|
|
626
|
+
branchName: "agentplate/orphan-agent/bead-x",
|
|
627
|
+
}),
|
|
628
|
+
).rejects.toThrow(WorktreeError);
|
|
629
|
+
|
|
630
|
+
// rollbackWorktree should have force-deleted the orphan branch
|
|
631
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
632
|
+
expect(branchList).not.toContain("agentplate/orphan-agent/bead-x");
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("throws when worktree contains zero tracked files", async () => {
|
|
636
|
+
// Build a base branch that points at an empty tree, then create a
|
|
637
|
+
// worktree from it. git happily registers the worktree, but ls-files
|
|
638
|
+
// returns nothing — the exact silent-failure shape from agentplate-6878.
|
|
639
|
+
const emptyTree = (
|
|
640
|
+
await runGitInDir(repoDir, ["hash-object", "-t", "tree", "/dev/null"])
|
|
641
|
+
).trim();
|
|
642
|
+
const emptyCommit = (
|
|
643
|
+
await runGitInDir(repoDir, ["commit-tree", emptyTree, "-m", "empty base"])
|
|
644
|
+
).trim();
|
|
645
|
+
await runGitInDir(repoDir, ["branch", "empty-base", emptyCommit]);
|
|
646
|
+
|
|
647
|
+
const wtPath = join(worktreesDir, "empty-agent");
|
|
648
|
+
const branchName = "agentplate/empty-agent/bead-empty";
|
|
649
|
+
await runGitInDir(repoDir, ["worktree", "add", "-b", branchName, wtPath, "empty-base"]);
|
|
650
|
+
|
|
651
|
+
try {
|
|
652
|
+
await validateWorktreeCreation({
|
|
653
|
+
repoRoot: repoDir,
|
|
654
|
+
worktreePath: wtPath,
|
|
655
|
+
branchName,
|
|
656
|
+
});
|
|
657
|
+
expect(true).toBe(false);
|
|
658
|
+
} catch (err: unknown) {
|
|
659
|
+
expect(err).toBeInstanceOf(WorktreeError);
|
|
660
|
+
const wtErr = err as WorktreeError;
|
|
661
|
+
expect(wtErr.worktreePath).toBe(wtPath);
|
|
662
|
+
expect(wtErr.branchName).toBe(branchName);
|
|
663
|
+
expect(wtErr.message).toContain("zero tracked files");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Rollback removed both worktree and branch
|
|
667
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
668
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
669
|
+
expect(branchList).not.toContain(branchName);
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("createWorktree rejects when base branch has no tracked files", async () => {
|
|
673
|
+
// End-to-end: createWorktree should surface the same error and clean
|
|
674
|
+
// up after itself, so sling never sees a half-built worktree.
|
|
675
|
+
const emptyTree = (
|
|
676
|
+
await runGitInDir(repoDir, ["hash-object", "-t", "tree", "/dev/null"])
|
|
677
|
+
).trim();
|
|
678
|
+
const emptyCommit = (
|
|
679
|
+
await runGitInDir(repoDir, ["commit-tree", emptyTree, "-m", "empty base"])
|
|
680
|
+
).trim();
|
|
681
|
+
await runGitInDir(repoDir, ["branch", "empty-base", emptyCommit]);
|
|
682
|
+
|
|
683
|
+
await expect(
|
|
684
|
+
createWorktree({
|
|
685
|
+
repoRoot: repoDir,
|
|
686
|
+
baseDir: worktreesDir,
|
|
687
|
+
agentName: "empty-agent",
|
|
688
|
+
baseBranch: "empty-base",
|
|
689
|
+
taskId: "bead-empty",
|
|
690
|
+
}),
|
|
691
|
+
).rejects.toThrow(WorktreeError);
|
|
692
|
+
|
|
693
|
+
// Caller observes a clean repo: no worktree dir, no leaked branch
|
|
694
|
+
expect(existsSync(join(worktreesDir, "empty-agent"))).toBe(false);
|
|
695
|
+
const branchList = await git(repoDir, ["branch", "--list"]);
|
|
696
|
+
expect(branchList).not.toContain("agentplate/empty-agent/bead-empty");
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test("createWorktree rejects when target dir pre-exists with files", async () => {
|
|
700
|
+
// Simulates the witnessed scenario: a stale directory survives at the
|
|
701
|
+
// target path from a previous run. createWorktree must surface a
|
|
702
|
+
// WorktreeError rather than returning a path that points at non-git
|
|
703
|
+
// state — the contract that protects the agent from being trapped.
|
|
704
|
+
const wtPath = join(worktreesDir, "preexisting-agent");
|
|
705
|
+
await mkdir(wtPath, { recursive: true });
|
|
706
|
+
await Bun.write(join(wtPath, "stale.txt"), "leftover from a previous run");
|
|
707
|
+
|
|
708
|
+
await expect(
|
|
709
|
+
createWorktree({
|
|
710
|
+
repoRoot: repoDir,
|
|
711
|
+
baseDir: worktreesDir,
|
|
712
|
+
agentName: "preexisting-agent",
|
|
713
|
+
baseBranch: defaultBranch,
|
|
714
|
+
taskId: "bead-pre",
|
|
715
|
+
}),
|
|
716
|
+
).rejects.toThrow(WorktreeError);
|
|
717
|
+
|
|
718
|
+
await rm(wtPath, { recursive: true, force: true });
|
|
719
|
+
});
|
|
720
|
+
});
|