@proletariat/cli 0.2.0 → 0.3.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/README.md +512 -253
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +23 -0
- package/dist/commands/action/create.d.ts +21 -0
- package/dist/commands/action/create.js +126 -0
- package/dist/commands/action/delete.d.ts +17 -0
- package/dist/commands/action/delete.js +78 -0
- package/dist/commands/action/index.d.ts +15 -0
- package/dist/commands/action/index.js +107 -0
- package/dist/commands/action/list.d.ts +14 -0
- package/dist/commands/action/list.js +89 -0
- package/dist/commands/action/run.d.ts +19 -0
- package/dist/commands/action/run.js +179 -0
- package/dist/commands/action/show.d.ts +15 -0
- package/dist/commands/action/show.js +47 -0
- package/dist/commands/action/update.d.ts +22 -0
- package/dist/commands/action/update.js +168 -0
- package/dist/commands/agent/index.d.ts +13 -0
- package/dist/commands/agent/index.js +131 -0
- package/dist/commands/agent/list.d.ts +7 -0
- package/dist/commands/agent/list.js +126 -0
- package/dist/commands/agent/login.d.ts +16 -0
- package/dist/commands/agent/login.js +146 -0
- package/dist/commands/agent/rebuild.d.ts +18 -0
- package/dist/commands/agent/rebuild.js +133 -0
- package/dist/commands/agent/restart.d.ts +17 -0
- package/dist/commands/agent/restart.js +116 -0
- package/dist/commands/agent/shell.d.ts +23 -0
- package/dist/commands/agent/shell.js +378 -0
- package/dist/commands/agent/staff/add.d.ts +15 -0
- package/dist/commands/agent/staff/add.js +281 -0
- package/dist/commands/agent/staff/index.d.ts +14 -0
- package/dist/commands/agent/staff/index.js +90 -0
- package/dist/commands/agent/staff/list.d.ts +7 -0
- package/dist/commands/agent/staff/list.js +90 -0
- package/dist/commands/agent/staff/remove.d.ts +16 -0
- package/dist/commands/agent/staff/remove.js +137 -0
- package/dist/commands/agent/status.d.ts +17 -0
- package/dist/commands/agent/status.js +139 -0
- package/dist/commands/agent/temp/cleanup.d.ts +23 -0
- package/dist/commands/agent/temp/cleanup.js +388 -0
- package/dist/commands/agent/temp/index.d.ts +14 -0
- package/dist/commands/agent/temp/index.js +82 -0
- package/dist/commands/agent/temp/list.d.ts +7 -0
- package/dist/commands/agent/temp/list.js +108 -0
- package/dist/commands/agent/themes/add-names.d.ts +10 -0
- package/dist/commands/agent/themes/add-names.js +67 -0
- package/dist/commands/agent/themes/create.d.ts +13 -0
- package/dist/commands/agent/themes/create.js +66 -0
- package/dist/commands/agent/themes/index.d.ts +9 -0
- package/dist/commands/agent/themes/index.js +194 -0
- package/dist/commands/agent/themes/list.d.ts +6 -0
- package/dist/commands/agent/themes/list.js +41 -0
- package/dist/commands/agent/themes/set.d.ts +12 -0
- package/dist/commands/agent/themes/set.js +77 -0
- package/dist/commands/agent/visit.d.ts +16 -0
- package/dist/commands/agent/visit.js +88 -0
- package/dist/commands/autocomplete/setup.d.ts +14 -0
- package/dist/commands/autocomplete/setup.js +154 -0
- package/dist/commands/board/index.d.ts +17 -0
- package/dist/commands/board/index.js +255 -0
- package/dist/commands/board/watch.d.ts +13 -0
- package/dist/commands/board/watch.js +52 -0
- package/dist/commands/branch/create.d.ts +50 -0
- package/dist/commands/branch/create.js +624 -0
- package/dist/commands/branch/index.d.ts +13 -0
- package/dist/commands/branch/index.js +50 -0
- package/dist/commands/branch/list.d.ts +17 -0
- package/dist/commands/branch/list.js +120 -0
- package/dist/commands/branch/validate.d.ts +15 -0
- package/dist/commands/branch/validate.js +73 -0
- package/dist/commands/commit.d.ts +71 -0
- package/dist/commands/commit.js +499 -0
- package/dist/commands/docker/clean.d.ts +13 -0
- package/dist/commands/docker/clean.js +224 -0
- package/dist/commands/docker/index.d.ts +19 -0
- package/dist/commands/docker/index.js +274 -0
- package/dist/commands/docker/list.d.ts +16 -0
- package/dist/commands/docker/list.js +200 -0
- package/dist/commands/docker/logs.d.ts +14 -0
- package/dist/commands/docker/logs.js +118 -0
- package/dist/commands/docker/prune.d.ts +14 -0
- package/dist/commands/docker/prune.js +211 -0
- package/dist/commands/docker/restart.d.ts +14 -0
- package/dist/commands/docker/restart.js +129 -0
- package/dist/commands/docker/shell.d.ts +14 -0
- package/dist/commands/docker/shell.js +103 -0
- package/dist/commands/docker/start.d.ts +12 -0
- package/dist/commands/docker/start.js +92 -0
- package/dist/commands/docker/status.d.ts +7 -0
- package/dist/commands/docker/status.js +40 -0
- package/dist/commands/docker/stop.d.ts +14 -0
- package/dist/commands/docker/stop.js +134 -0
- package/dist/commands/docker/sync.d.ts +15 -0
- package/dist/commands/docker/sync.js +112 -0
- package/dist/commands/epic/activate.d.ts +13 -0
- package/dist/commands/epic/activate.js +118 -0
- package/dist/commands/epic/archive.d.ts +14 -0
- package/dist/commands/epic/archive.js +132 -0
- package/dist/commands/epic/create.d.ts +15 -0
- package/dist/commands/epic/create.js +137 -0
- package/dist/commands/epic/index.d.ts +13 -0
- package/dist/commands/epic/index.js +88 -0
- package/dist/commands/epic/link/block.d.ts +14 -0
- package/dist/commands/epic/link/block.js +79 -0
- package/dist/commands/epic/link/duplicates.d.ts +14 -0
- package/dist/commands/epic/link/duplicates.js +66 -0
- package/dist/commands/epic/link/index.d.ts +19 -0
- package/dist/commands/epic/link/index.js +242 -0
- package/dist/commands/epic/link/relates.d.ts +14 -0
- package/dist/commands/epic/link/relates.js +66 -0
- package/dist/commands/epic/link/remove.d.ts +16 -0
- package/dist/commands/epic/link/remove.js +89 -0
- package/dist/commands/epic/list.d.ts +11 -0
- package/dist/commands/epic/list.js +87 -0
- package/dist/commands/epic/move.d.ts +15 -0
- package/dist/commands/epic/move.js +184 -0
- package/dist/commands/epic/progress.d.ts +16 -0
- package/dist/commands/epic/progress.js +166 -0
- package/dist/commands/epic/project.d.ts +15 -0
- package/dist/commands/epic/project.js +219 -0
- package/dist/commands/epic/reorder.d.ts +21 -0
- package/dist/commands/epic/reorder.js +160 -0
- package/dist/commands/epic/spec.d.ts +15 -0
- package/dist/commands/epic/spec.js +191 -0
- package/dist/commands/epic/ticket.d.ts +18 -0
- package/dist/commands/epic/ticket.js +291 -0
- package/dist/commands/epic/view.d.ts +13 -0
- package/dist/commands/epic/view.js +117 -0
- package/dist/commands/execution/index.d.ts +13 -0
- package/dist/commands/execution/index.js +70 -0
- package/dist/commands/execution/list.d.ts +15 -0
- package/dist/commands/execution/list.js +144 -0
- package/dist/commands/execution/logs.d.ts +18 -0
- package/dist/commands/execution/logs.js +161 -0
- package/dist/commands/execution/stop.d.ts +22 -0
- package/dist/commands/execution/stop.js +248 -0
- package/dist/commands/gh/index.d.ts +9 -0
- package/dist/commands/gh/index.js +53 -0
- package/dist/commands/gh/login.d.ts +6 -0
- package/dist/commands/gh/login.js +57 -0
- package/dist/commands/gh/status.d.ts +6 -0
- package/dist/commands/gh/status.js +48 -0
- package/dist/commands/gh/token.d.ts +6 -0
- package/dist/commands/gh/token.js +59 -0
- package/dist/commands/init.d.ts +26 -0
- package/dist/commands/init.js +200 -0
- package/dist/commands/phase/create.d.ts +22 -0
- package/dist/commands/phase/create.js +123 -0
- package/dist/commands/phase/delete.d.ts +17 -0
- package/dist/commands/phase/delete.js +73 -0
- package/dist/commands/phase/list.d.ts +12 -0
- package/dist/commands/phase/list.js +76 -0
- package/dist/commands/phase/move.d.ts +17 -0
- package/dist/commands/phase/move.js +115 -0
- package/dist/commands/phase/template/apply.d.ts +17 -0
- package/dist/commands/phase/template/apply.js +106 -0
- package/dist/commands/phase/template/create.d.ts +16 -0
- package/dist/commands/phase/template/create.js +58 -0
- package/dist/commands/phase/template/delete.d.ts +17 -0
- package/dist/commands/phase/template/delete.js +98 -0
- package/dist/commands/phase/template/index.d.ts +15 -0
- package/dist/commands/phase/template/index.js +128 -0
- package/dist/commands/phase/template/list.d.ts +16 -0
- package/dist/commands/phase/template/list.js +95 -0
- package/dist/commands/phase/template/update.d.ts +17 -0
- package/dist/commands/phase/template/update.js +89 -0
- package/dist/commands/phase/update.d.ts +23 -0
- package/dist/commands/phase/update.js +174 -0
- package/dist/commands/pmo/init.d.ts +25 -0
- package/dist/commands/pmo/init.js +341 -0
- package/dist/commands/pr/create.d.ts +17 -0
- package/dist/commands/pr/create.js +242 -0
- package/dist/commands/pr/index.d.ts +9 -0
- package/dist/commands/pr/index.js +68 -0
- package/dist/commands/pr/link.d.ts +14 -0
- package/dist/commands/pr/link.js +212 -0
- package/dist/commands/pr/status.d.ts +12 -0
- package/dist/commands/pr/status.js +161 -0
- package/dist/commands/project/archive.d.ts +17 -0
- package/dist/commands/project/archive.js +83 -0
- package/dist/commands/project/create.d.ts +22 -0
- package/dist/commands/project/create.js +143 -0
- package/dist/commands/project/delete.d.ts +17 -0
- package/dist/commands/project/delete.js +128 -0
- package/dist/commands/project/index.d.ts +13 -0
- package/dist/commands/project/index.js +64 -0
- package/dist/commands/project/list.d.ts +14 -0
- package/dist/commands/project/list.js +96 -0
- package/dist/commands/project/spec.d.ts +18 -0
- package/dist/commands/project/spec.js +216 -0
- package/dist/commands/project/unarchive.d.ts +15 -0
- package/dist/commands/project/unarchive.js +35 -0
- package/dist/commands/project/view.d.ts +16 -0
- package/dist/commands/project/view.js +94 -0
- package/dist/commands/repo/add.d.ts +21 -0
- package/dist/commands/repo/add.js +118 -0
- package/dist/commands/repo/index.d.ts +13 -0
- package/dist/commands/repo/index.js +114 -0
- package/dist/commands/repo/list.d.ts +13 -0
- package/dist/commands/repo/list.js +96 -0
- package/dist/commands/repo/remove.d.ts +23 -0
- package/dist/commands/repo/remove.js +217 -0
- package/dist/commands/repo/view.d.ts +15 -0
- package/dist/commands/repo/view.js +99 -0
- package/dist/commands/session/attach.d.ts +40 -0
- package/dist/commands/session/attach.js +307 -0
- package/dist/commands/session/index.d.ts +13 -0
- package/dist/commands/session/index.js +64 -0
- package/dist/commands/session/list.d.ts +21 -0
- package/dist/commands/session/list.js +181 -0
- package/dist/commands/spec/create.d.ts +19 -0
- package/dist/commands/spec/create.js +130 -0
- package/dist/commands/spec/index.d.ts +13 -0
- package/dist/commands/spec/index.js +68 -0
- package/dist/commands/spec/link/depends.d.ts +14 -0
- package/dist/commands/spec/link/depends.js +64 -0
- package/dist/commands/spec/link/duplicates.d.ts +14 -0
- package/dist/commands/spec/link/duplicates.js +63 -0
- package/dist/commands/spec/link/index.d.ts +19 -0
- package/dist/commands/spec/link/index.js +200 -0
- package/dist/commands/spec/link/relates.d.ts +14 -0
- package/dist/commands/spec/link/relates.js +63 -0
- package/dist/commands/spec/link/remove.d.ts +16 -0
- package/dist/commands/spec/link/remove.js +94 -0
- package/dist/commands/spec/list.d.ts +12 -0
- package/dist/commands/spec/list.js +75 -0
- package/dist/commands/spec/plan.d.ts +15 -0
- package/dist/commands/spec/plan.js +108 -0
- package/dist/commands/spec/ticket.d.ts +18 -0
- package/dist/commands/spec/ticket.js +160 -0
- package/dist/commands/spec/view.d.ts +15 -0
- package/dist/commands/spec/view.js +163 -0
- package/dist/commands/status/create.d.ts +21 -0
- package/dist/commands/status/create.js +140 -0
- package/dist/commands/status/delete.d.ts +13 -0
- package/dist/commands/status/delete.js +77 -0
- package/dist/commands/status/index.d.ts +14 -0
- package/dist/commands/status/index.js +91 -0
- package/dist/commands/status/list.d.ts +12 -0
- package/dist/commands/status/list.js +93 -0
- package/dist/commands/status/move.d.ts +14 -0
- package/dist/commands/status/move.js +120 -0
- package/dist/commands/status/update.d.ts +20 -0
- package/dist/commands/status/update.js +180 -0
- package/dist/commands/template/delete.d.ts +15 -0
- package/dist/commands/template/delete.js +142 -0
- package/dist/commands/template/index.d.ts +10 -0
- package/dist/commands/template/index.js +64 -0
- package/dist/commands/template/list.d.ts +18 -0
- package/dist/commands/template/list.js +157 -0
- package/dist/commands/template/phase/apply.d.ts +14 -0
- package/dist/commands/template/phase/apply.js +41 -0
- package/dist/commands/template/phase/create.d.ts +12 -0
- package/dist/commands/template/phase/create.js +29 -0
- package/dist/commands/template/phase/delete.d.ts +13 -0
- package/dist/commands/template/phase/delete.js +34 -0
- package/dist/commands/template/phase/index.d.ts +10 -0
- package/dist/commands/template/phase/index.js +62 -0
- package/dist/commands/template/phase/list.d.ts +11 -0
- package/dist/commands/template/phase/list.js +34 -0
- package/dist/commands/template/phase/update.d.ts +13 -0
- package/dist/commands/template/phase/update.js +35 -0
- package/dist/commands/template/ticket/apply.d.ts +17 -0
- package/dist/commands/template/ticket/apply.js +58 -0
- package/dist/commands/template/ticket/delete.d.ts +13 -0
- package/dist/commands/template/ticket/delete.js +34 -0
- package/dist/commands/template/ticket/index.d.ts +10 -0
- package/dist/commands/template/ticket/index.js +62 -0
- package/dist/commands/template/ticket/list.d.ts +11 -0
- package/dist/commands/template/ticket/list.js +34 -0
- package/dist/commands/template/ticket/save.d.ts +13 -0
- package/dist/commands/template/ticket/save.js +35 -0
- package/dist/commands/ticket/bulk.d.ts +13 -0
- package/dist/commands/ticket/bulk.js +145 -0
- package/dist/commands/ticket/complete.d.ts +16 -0
- package/dist/commands/ticket/complete.js +170 -0
- package/dist/commands/ticket/create.d.ts +22 -0
- package/dist/commands/ticket/create.js +390 -0
- package/dist/commands/ticket/delete.d.ts +16 -0
- package/dist/commands/ticket/delete.js +178 -0
- package/dist/commands/ticket/edit.d.ts +27 -0
- package/dist/commands/ticket/edit.js +322 -0
- package/dist/commands/ticket/epic.d.ts +20 -0
- package/dist/commands/ticket/epic.js +333 -0
- package/dist/commands/ticket/index.d.ts +13 -0
- package/dist/commands/ticket/index.js +103 -0
- package/dist/commands/ticket/link/block.d.ts +14 -0
- package/dist/commands/ticket/link/block.js +94 -0
- package/dist/commands/ticket/link/duplicates.d.ts +14 -0
- package/dist/commands/ticket/link/duplicates.js +93 -0
- package/dist/commands/ticket/link/index.d.ts +19 -0
- package/dist/commands/ticket/link/index.js +239 -0
- package/dist/commands/ticket/link/relates.d.ts +14 -0
- package/dist/commands/ticket/link/relates.js +93 -0
- package/dist/commands/ticket/link/remove.d.ts +16 -0
- package/dist/commands/ticket/link/remove.js +128 -0
- package/dist/commands/ticket/list.d.ts +24 -0
- package/dist/commands/ticket/list.js +431 -0
- package/dist/commands/ticket/move.d.ts +18 -0
- package/dist/commands/ticket/move.js +212 -0
- package/dist/commands/ticket/project.d.ts +18 -0
- package/dist/commands/ticket/project.js +254 -0
- package/dist/commands/ticket/reassign.d.ts +19 -0
- package/dist/commands/ticket/reassign.js +279 -0
- package/dist/commands/ticket/spec.d.ts +18 -0
- package/dist/commands/ticket/spec.js +259 -0
- package/dist/commands/ticket/status.d.ts +13 -0
- package/dist/commands/ticket/status.js +87 -0
- package/dist/commands/ticket/template/apply.d.ts +25 -0
- package/dist/commands/ticket/template/apply.js +249 -0
- package/dist/commands/ticket/template/create.d.ts +19 -0
- package/dist/commands/ticket/template/create.js +210 -0
- package/dist/commands/ticket/template/delete.d.ts +17 -0
- package/dist/commands/ticket/template/delete.js +92 -0
- package/dist/commands/ticket/template/index.d.ts +15 -0
- package/dist/commands/ticket/template/index.js +118 -0
- package/dist/commands/ticket/template/list.d.ts +16 -0
- package/dist/commands/ticket/template/list.js +110 -0
- package/dist/commands/ticket/template/save.d.ts +14 -0
- package/dist/commands/ticket/template/save.js +110 -0
- package/dist/commands/ticket/update.d.ts +18 -0
- package/dist/commands/ticket/update.js +325 -0
- package/dist/commands/ticket/view.d.ts +13 -0
- package/dist/commands/ticket/view.js +80 -0
- package/dist/commands/whoami.d.ts +9 -0
- package/dist/commands/whoami.js +103 -0
- package/dist/commands/work/complete.d.ts +13 -0
- package/dist/commands/work/complete.js +121 -0
- package/dist/commands/work/index.d.ts +13 -0
- package/dist/commands/work/index.js +70 -0
- package/dist/commands/work/ready.d.ts +24 -0
- package/dist/commands/work/ready.js +290 -0
- package/dist/commands/work/revise.d.ts +19 -0
- package/dist/commands/work/revise.js +377 -0
- package/dist/commands/work/spawn-all.d.ts +17 -0
- package/dist/commands/work/spawn-all.js +58 -0
- package/dist/commands/work/spawn.d.ts +29 -0
- package/dist/commands/work/spawn.js +728 -0
- package/dist/commands/work/start.d.ts +39 -0
- package/dist/commands/work/start.js +1393 -0
- package/dist/commands/work/watch.d.ts +31 -0
- package/dist/commands/work/watch.js +359 -0
- package/dist/commands/workflow/create.d.ts +18 -0
- package/dist/commands/workflow/create.js +119 -0
- package/dist/commands/workflow/delete.d.ts +17 -0
- package/dist/commands/workflow/delete.js +119 -0
- package/dist/commands/workflow/index.d.ts +15 -0
- package/dist/commands/workflow/index.js +75 -0
- package/dist/commands/workflow/list.d.ts +15 -0
- package/dist/commands/workflow/list.js +75 -0
- package/dist/commands/workflow/switch.d.ts +13 -0
- package/dist/commands/workflow/switch.js +117 -0
- package/dist/commands/workflow/view.d.ts +16 -0
- package/dist/commands/workflow/view.js +114 -0
- package/dist/commands/workspace/add.d.ts +12 -0
- package/dist/commands/workspace/add.js +74 -0
- package/dist/commands/workspace/list.d.ts +9 -0
- package/dist/commands/workspace/list.js +153 -0
- package/dist/commands/workspace/remove.d.ts +13 -0
- package/dist/commands/workspace/remove.js +98 -0
- package/dist/commands/workspace/use.d.ts +12 -0
- package/dist/commands/workspace/use.js +111 -0
- package/dist/hooks/init.d.ts +11 -0
- package/dist/hooks/init.js +57 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/agents/commands.d.ts +189 -0
- package/dist/lib/agents/commands.js +893 -0
- package/dist/lib/agents/index.d.ts +54 -0
- package/dist/lib/agents/index.js +382 -0
- package/dist/lib/branch/index.d.ts +120 -0
- package/dist/lib/branch/index.js +334 -0
- package/dist/lib/colors.d.ts +94 -0
- package/dist/lib/colors.js +68 -0
- package/dist/lib/commands/docker-command.d.ts +21 -0
- package/dist/lib/commands/docker-command.js +27 -0
- package/dist/lib/database/index.d.ts +176 -0
- package/dist/lib/database/index.js +581 -0
- package/dist/lib/docker/resolve.d.ts +38 -0
- package/dist/lib/docker/resolve.js +175 -0
- package/dist/lib/execution/config.d.ts +150 -0
- package/dist/lib/execution/config.js +541 -0
- package/dist/lib/execution/devcontainer.d.ts +85 -0
- package/dist/lib/execution/devcontainer.js +594 -0
- package/dist/lib/execution/index.d.ts +10 -0
- package/dist/lib/execution/index.js +10 -0
- package/dist/lib/execution/runners.d.ts +53 -0
- package/dist/lib/execution/runners.js +1182 -0
- package/dist/lib/execution/spawner.d.ts +85 -0
- package/dist/lib/execution/spawner.js +548 -0
- package/dist/lib/execution/storage.d.ts +159 -0
- package/dist/lib/execution/storage.js +425 -0
- package/dist/lib/execution/types.d.ts +145 -0
- package/dist/lib/execution/types.js +157 -0
- package/dist/lib/init/index.d.ts +75 -0
- package/dist/lib/init/index.js +355 -0
- package/dist/lib/machine-config.d.ts +170 -0
- package/dist/lib/machine-config.js +386 -0
- package/dist/lib/pmo/base-command.d.ts +195 -0
- package/dist/lib/pmo/base-command.js +319 -0
- package/dist/lib/pmo/create-spec-folders.d.ts +43 -0
- package/dist/lib/pmo/create-spec-folders.js +64 -0
- package/dist/lib/pmo/epic-files.d.ts +56 -0
- package/dist/lib/pmo/epic-files.js +195 -0
- package/dist/lib/pmo/find-pmo.d.ts +14 -0
- package/dist/lib/pmo/find-pmo.js +172 -0
- package/dist/lib/pmo/index.d.ts +109 -0
- package/dist/lib/pmo/index.js +501 -0
- package/dist/lib/pmo/markdown.d.ts +31 -0
- package/dist/lib/pmo/markdown.js +245 -0
- package/dist/lib/pmo/pmo-context.d.ts +27 -0
- package/dist/lib/pmo/pmo-context.js +44 -0
- package/dist/lib/pmo/schema.d.ts +82 -0
- package/dist/lib/pmo/schema.js +531 -0
- package/dist/lib/pmo/spec-parser.d.ts +25 -0
- package/dist/lib/pmo/spec-parser.js +205 -0
- package/dist/lib/pmo/spec-types.d.ts +43 -0
- package/dist/lib/pmo/spec-types.js +7 -0
- package/dist/lib/pmo/storage/actions.d.ts +34 -0
- package/dist/lib/pmo/storage/actions.js +177 -0
- package/dist/lib/pmo/storage/base.d.ts +47 -0
- package/dist/lib/pmo/storage/base.js +858 -0
- package/dist/lib/pmo/storage/dependencies.d.ts +61 -0
- package/dist/lib/pmo/storage/dependencies.js +267 -0
- package/dist/lib/pmo/storage/epics.d.ts +46 -0
- package/dist/lib/pmo/storage/epics.js +243 -0
- package/dist/lib/pmo/storage/helpers.d.ts +33 -0
- package/dist/lib/pmo/storage/helpers.js +148 -0
- package/dist/lib/pmo/storage/index.d.ts +186 -0
- package/dist/lib/pmo/storage/index.js +689 -0
- package/dist/lib/pmo/storage/phases.d.ts +65 -0
- package/dist/lib/pmo/storage/phases.js +392 -0
- package/dist/lib/pmo/storage/projects.d.ts +79 -0
- package/dist/lib/pmo/storage/projects.js +303 -0
- package/dist/lib/pmo/storage/specs.d.ts +77 -0
- package/dist/lib/pmo/storage/specs.js +389 -0
- package/dist/lib/pmo/storage/statuses.d.ts +63 -0
- package/dist/lib/pmo/storage/statuses.js +404 -0
- package/dist/lib/pmo/storage/subtasks.d.ts +37 -0
- package/dist/lib/pmo/storage/subtasks.js +184 -0
- package/dist/lib/pmo/storage/templates.d.ts +40 -0
- package/dist/lib/pmo/storage/templates.js +210 -0
- package/dist/lib/pmo/storage/tickets.d.ts +57 -0
- package/dist/lib/pmo/storage/tickets.js +453 -0
- package/dist/lib/pmo/storage/types.d.ts +200 -0
- package/dist/lib/pmo/storage/types.js +5 -0
- package/dist/lib/pmo/storage/views.d.ts +44 -0
- package/dist/lib/pmo/storage/views.js +355 -0
- package/dist/lib/pmo/storage-sqlite.d.ts +7 -0
- package/dist/lib/pmo/storage-sqlite.js +7 -0
- package/dist/lib/pmo/sync-manager.d.ts +92 -0
- package/dist/lib/pmo/sync-manager.js +229 -0
- package/dist/lib/pmo/types.d.ts +710 -0
- package/dist/lib/pmo/types.js +108 -0
- package/dist/lib/pmo/utils.d.ts +122 -0
- package/dist/lib/pmo/utils.js +174 -0
- package/dist/lib/pmo/watcher.d.ts +43 -0
- package/dist/lib/pmo/watcher.js +208 -0
- package/dist/lib/pr/index.d.ts +150 -0
- package/dist/lib/pr/index.js +483 -0
- package/dist/lib/prompt-json.d.ts +231 -0
- package/dist/lib/prompt-json.js +213 -0
- package/dist/lib/repos/index.d.ts +81 -0
- package/dist/lib/repos/index.js +679 -0
- package/dist/lib/styles.d.ts +98 -0
- package/dist/lib/styles.js +195 -0
- package/dist/lib/themes.d.ts +128 -0
- package/dist/lib/themes.js +301 -0
- package/dist/lib/ui/BoardUI.d.ts +21 -0
- package/dist/lib/ui/BoardUI.js +85 -0
- package/dist/lib/ui/ClaimTicketUI.d.ts +17 -0
- package/dist/lib/ui/ClaimTicketUI.js +64 -0
- package/dist/lib/ui/CreateTicketUI.d.ts +13 -0
- package/dist/lib/ui/CreateTicketUI.js +101 -0
- package/dist/lib/workspace.d.ts +66 -0
- package/dist/lib/workspace.js +204 -0
- package/oclif.manifest.json +10593 -0
- package/package.json +103 -56
- package/LICENSE +0 -21
- package/dist/bin/prlt.d.ts +0 -11
- package/dist/bin/prlt.d.ts.map +0 -1
- package/dist/bin/prlt.js +0 -144
- package/dist/bin/prlt.js.map +0 -1
- package/dist/lib/config/index.d.ts +0 -14
- package/dist/lib/config/index.d.ts.map +0 -1
- package/dist/lib/config/index.js +0 -142
- package/dist/lib/config/index.js.map +0 -1
- package/dist/lib/config/upgrade.d.ts +0 -2
- package/dist/lib/config/upgrade.d.ts.map +0 -1
- package/dist/lib/config/upgrade.js +0 -248
- package/dist/lib/config/upgrade.js.map +0 -1
- package/dist/lib/themes/index.d.ts +0 -8
- package/dist/lib/themes/index.d.ts.map +0 -1
- package/dist/lib/themes/index.js +0 -80
- package/dist/lib/themes/index.js.map +0 -1
- package/dist/lib/utils/helpers.d.ts +0 -4
- package/dist/lib/utils/helpers.d.ts.map +0 -1
- package/dist/lib/utils/helpers.js +0 -39
- package/dist/lib/utils/helpers.js.map +0 -1
- package/dist/lib/utils/logger.d.ts +0 -4
- package/dist/lib/utils/logger.d.ts.map +0 -1
- package/dist/lib/utils/logger.js +0 -28
- package/dist/lib/utils/logger.js.map +0 -1
- package/dist/lib/workspace/index.d.ts +0 -13
- package/dist/lib/workspace/index.d.ts.map +0 -1
- package/dist/lib/workspace/index.js +0 -116
- package/dist/lib/workspace/index.js.map +0 -1
- package/dist/lib/worktree/index.d.ts +0 -7
- package/dist/lib/worktree/index.d.ts.map +0 -1
- package/dist/lib/worktree/index.js +0 -362
- package/dist/lib/worktree/index.js.map +0 -1
- package/dist/lib/worktree/migrate.d.ts +0 -2
- package/dist/lib/worktree/migrate.d.ts.map +0 -1
- package/dist/lib/worktree/migrate.js +0 -214
- package/dist/lib/worktree/migrate.js.map +0 -1
- package/dist/lib/worktree/repair.d.ts +0 -3
- package/dist/lib/worktree/repair.d.ts.map +0 -1
- package/dist/lib/worktree/repair.js +0 -320
- package/dist/lib/worktree/repair.js.map +0 -1
- package/dist/types/index.d.ts +0 -57
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for agent commands - implementing DRY principles
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { execSync } from 'node:child_process';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import { getWorkspaceConfig, getWorkspaceAgents, getWorkspaceRepositories, getAgentWorktrees, addAgentsToDatabase, removeAgentsFromDatabase, addEphemeralAgentToDatabase, getEphemeralAgentNames, getActiveTheme, markAgentCleaned, syncAgentsWithDisk } from '../database/index.js';
|
|
9
|
+
import { isValidAgentName, getSuggestedAgentNames, generateEphemeralAgentName, getThemePersistentDir, getThemeEphemeralDir, } from '../themes.js';
|
|
10
|
+
import { createDevcontainerConfig } from '../execution/devcontainer.js';
|
|
11
|
+
import { getPMOContext } from '../pmo/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Find workspace root and return workspace information.
|
|
14
|
+
*
|
|
15
|
+
* Search priority:
|
|
16
|
+
* 1. PRLT_HQ_PATH environment variable (ONLY when DEVCONTAINER=true - for devcontainer mounts)
|
|
17
|
+
* 2. Current directory tree for HQ with workspace.db
|
|
18
|
+
*
|
|
19
|
+
* NOTE: PRLT_HQ_PATH is ignored on host machines to support multiple agents
|
|
20
|
+
* working in different workspaces simultaneously.
|
|
21
|
+
*/
|
|
22
|
+
export function getWorkspaceInfo() {
|
|
23
|
+
// Check PRLT_HQ_PATH environment variable (only in devcontainers)
|
|
24
|
+
const hqPath = process.env.PRLT_HQ_PATH;
|
|
25
|
+
const isDevcontainer = process.env.DEVCONTAINER === 'true';
|
|
26
|
+
if (hqPath && isDevcontainer) {
|
|
27
|
+
const dbPath = path.join(hqPath, '.proletariat', 'workspace.db');
|
|
28
|
+
if (fs.existsSync(dbPath)) {
|
|
29
|
+
try {
|
|
30
|
+
const config = getWorkspaceConfig(hqPath);
|
|
31
|
+
if (config) {
|
|
32
|
+
// Sync agents with disk - mark missing ones as cleaned
|
|
33
|
+
syncAgentsWithDisk(hqPath);
|
|
34
|
+
const agents = getWorkspaceAgents(hqPath);
|
|
35
|
+
const repositories = getWorkspaceRepositories(hqPath);
|
|
36
|
+
const activeTheme = getActiveTheme(hqPath);
|
|
37
|
+
const persistentAgentsDir = getThemePersistentDir(activeTheme?.id);
|
|
38
|
+
const ephemeralAgentsDir = getThemeEphemeralDir(activeTheme?.id);
|
|
39
|
+
const agentsPath = config.type === 'hq'
|
|
40
|
+
? path.join(hqPath, 'agents', persistentAgentsDir)
|
|
41
|
+
: hqPath;
|
|
42
|
+
return {
|
|
43
|
+
path: hqPath,
|
|
44
|
+
type: config.type,
|
|
45
|
+
workspaceName: config.workspace_name,
|
|
46
|
+
hasPMO: config.has_pmo,
|
|
47
|
+
agents,
|
|
48
|
+
repositories,
|
|
49
|
+
agentsPath,
|
|
50
|
+
activeThemeId: activeTheme?.id ?? null,
|
|
51
|
+
persistentAgentsDir,
|
|
52
|
+
ephemeralAgentsDir,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Continue to directory tree search if PRLT_HQ_PATH is invalid
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Search up the directory tree
|
|
62
|
+
let currentDir = process.cwd();
|
|
63
|
+
while (currentDir !== '/') {
|
|
64
|
+
const dbPath = path.join(currentDir, '.proletariat', 'workspace.db');
|
|
65
|
+
if (fs.existsSync(dbPath)) {
|
|
66
|
+
try {
|
|
67
|
+
const config = getWorkspaceConfig(currentDir);
|
|
68
|
+
if (config) {
|
|
69
|
+
// Sync agents with disk - mark missing ones as cleaned
|
|
70
|
+
syncAgentsWithDisk(currentDir);
|
|
71
|
+
const agents = getWorkspaceAgents(currentDir);
|
|
72
|
+
const repositories = getWorkspaceRepositories(currentDir);
|
|
73
|
+
const activeTheme = getActiveTheme(currentDir);
|
|
74
|
+
const persistentAgentsDir = getThemePersistentDir(activeTheme?.id);
|
|
75
|
+
const ephemeralAgentsDir = getThemeEphemeralDir(activeTheme?.id);
|
|
76
|
+
const agentsPath = config.type === 'hq'
|
|
77
|
+
? path.join(currentDir, 'agents', persistentAgentsDir)
|
|
78
|
+
: currentDir;
|
|
79
|
+
return {
|
|
80
|
+
path: currentDir,
|
|
81
|
+
type: config.type,
|
|
82
|
+
workspaceName: config.workspace_name,
|
|
83
|
+
hasPMO: config.has_pmo,
|
|
84
|
+
agents,
|
|
85
|
+
repositories,
|
|
86
|
+
agentsPath,
|
|
87
|
+
activeThemeId: activeTheme?.id ?? null,
|
|
88
|
+
persistentAgentsDir,
|
|
89
|
+
ephemeralAgentsDir,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Continue searching if database is corrupted
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
currentDir = path.dirname(currentDir);
|
|
98
|
+
}
|
|
99
|
+
throw new Error('Not in an HQ or workspace directory. Run "prlt init" first.');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Validate agent name
|
|
103
|
+
*/
|
|
104
|
+
export { isValidAgentName } from '../themes.js';
|
|
105
|
+
/**
|
|
106
|
+
* Get suggested agent names (not yet added to workspace)
|
|
107
|
+
*/
|
|
108
|
+
export function getAvailableAgentSuggestions(workspaceInfo) {
|
|
109
|
+
const existingAgentNames = new Set(workspaceInfo.agents.map(a => a.name));
|
|
110
|
+
return getSuggestedAgentNames().filter(name => !existingAgentNames.has(name));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Interactive agent selection - prompts user for agent names
|
|
114
|
+
*/
|
|
115
|
+
export async function selectAgentsInteractively(workspaceInfo, message = 'Enter agent names:') {
|
|
116
|
+
const suggestions = getAvailableAgentSuggestions(workspaceInfo);
|
|
117
|
+
const { agentNames } = await inquirer.prompt([{
|
|
118
|
+
type: 'input',
|
|
119
|
+
name: 'agentNames',
|
|
120
|
+
message: `${message} (space-separated, e.g., "${suggestions.slice(0, 2).join(' ')}"):`,
|
|
121
|
+
validate: (input) => {
|
|
122
|
+
if (!input.trim()) {
|
|
123
|
+
return 'Please enter at least one agent name';
|
|
124
|
+
}
|
|
125
|
+
const names = input.trim().split(/\s+/);
|
|
126
|
+
const invalid = names.filter(n => !isValidAgentName(n));
|
|
127
|
+
if (invalid.length > 0) {
|
|
128
|
+
return `Invalid agent names: ${invalid.join(', ')}. Names must be lowercase alphanumeric with optional hyphens/underscores.`;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
},
|
|
132
|
+
}]);
|
|
133
|
+
return agentNames.trim().split(/\s+/).filter(Boolean);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Interactive selection from existing agents
|
|
137
|
+
*/
|
|
138
|
+
export async function selectExistingAgentsInteractively(workspaceInfo, message = 'Select agents:') {
|
|
139
|
+
if (workspaceInfo.agents.length === 0) {
|
|
140
|
+
throw new Error('No agents found in workspace.');
|
|
141
|
+
}
|
|
142
|
+
const { selected } = await inquirer.prompt([{
|
|
143
|
+
type: 'checkbox',
|
|
144
|
+
name: 'selected',
|
|
145
|
+
message,
|
|
146
|
+
choices: workspaceInfo.agents.map(agent => ({ name: agent.name, value: agent.name })),
|
|
147
|
+
validate: (input) => input.length > 0 || 'Please select at least one agent'
|
|
148
|
+
}]);
|
|
149
|
+
return selected;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get detailed status for a specific agent
|
|
153
|
+
*/
|
|
154
|
+
export function getAgentStatus(workspaceInfo, agentName) {
|
|
155
|
+
// Agent exists if it's in the database - the source of truth
|
|
156
|
+
const agentRecord = workspaceInfo.agents.find(a => a.name === agentName);
|
|
157
|
+
const exists = !!agentRecord;
|
|
158
|
+
// Get worktrees from database to find actual agent location
|
|
159
|
+
const worktrees = getAgentWorktrees(workspaceInfo.path, agentName);
|
|
160
|
+
// Derive agent directory from worktree path, or fall back to default
|
|
161
|
+
let agentDir = path.join(workspaceInfo.agentsPath, agentName);
|
|
162
|
+
if (worktrees.length > 0) {
|
|
163
|
+
// worktree_path is like "agents/staff/altman/proletariat-altman"
|
|
164
|
+
// Agent dir is the parent: "agents/staff/altman"
|
|
165
|
+
const worktreePath = worktrees[0].worktree_path;
|
|
166
|
+
const agentDirRelative = path.dirname(worktreePath);
|
|
167
|
+
agentDir = path.join(workspaceInfo.path, agentDirRelative);
|
|
168
|
+
}
|
|
169
|
+
const dirExists = fs.existsSync(agentDir);
|
|
170
|
+
const status = {
|
|
171
|
+
name: agentName,
|
|
172
|
+
exists,
|
|
173
|
+
assignedTickets: [],
|
|
174
|
+
completedTickets: [],
|
|
175
|
+
repositories: []
|
|
176
|
+
};
|
|
177
|
+
if (!dirExists) {
|
|
178
|
+
return status;
|
|
179
|
+
}
|
|
180
|
+
// Get git branch info
|
|
181
|
+
try {
|
|
182
|
+
const gitDir = path.join(agentDir, '.git');
|
|
183
|
+
if (fs.existsSync(gitDir)) {
|
|
184
|
+
const gitContent = fs.readFileSync(gitDir, 'utf-8');
|
|
185
|
+
const branchMatch = gitContent.match(/gitdir: (.+)/);
|
|
186
|
+
if (branchMatch) {
|
|
187
|
+
status.branch = branchMatch[1].split('/').pop()?.replace('.git', '');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Ignore git reading errors
|
|
193
|
+
}
|
|
194
|
+
// Get repository status from database worktrees
|
|
195
|
+
status.repositories = worktrees.map(worktree => {
|
|
196
|
+
const repoPath = path.join(workspaceInfo.path, worktree.worktree_path);
|
|
197
|
+
const repoExists = fs.existsSync(repoPath);
|
|
198
|
+
let repoStatus = 'missing';
|
|
199
|
+
let commitsAhead = 0;
|
|
200
|
+
if (repoExists) {
|
|
201
|
+
try {
|
|
202
|
+
// Check if clean
|
|
203
|
+
const gitStatus = execSync('git status --porcelain', {
|
|
204
|
+
cwd: repoPath,
|
|
205
|
+
encoding: 'utf-8',
|
|
206
|
+
stdio: 'pipe'
|
|
207
|
+
});
|
|
208
|
+
repoStatus = gitStatus.trim() === '' ? 'clean' : 'dirty';
|
|
209
|
+
// Check commits ahead
|
|
210
|
+
try {
|
|
211
|
+
const ahead = execSync('git rev-list --count HEAD ^origin/main', {
|
|
212
|
+
cwd: repoPath,
|
|
213
|
+
encoding: 'utf-8',
|
|
214
|
+
stdio: 'pipe'
|
|
215
|
+
}).trim();
|
|
216
|
+
commitsAhead = parseInt(ahead) || 0;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Ignore if can't determine commits ahead
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
repoStatus = 'error';
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
name: worktree.repo_name,
|
|
228
|
+
status: repoStatus,
|
|
229
|
+
commitsAhead
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
// Get ticket assignments (if PMO enabled)
|
|
233
|
+
if (workspaceInfo.hasPMO) {
|
|
234
|
+
try {
|
|
235
|
+
const ticketsFile = path.join(workspaceInfo.path, 'pmo', 'tickets.json');
|
|
236
|
+
if (fs.existsSync(ticketsFile)) {
|
|
237
|
+
const tickets = JSON.parse(fs.readFileSync(ticketsFile, 'utf-8'));
|
|
238
|
+
status.assignedTickets = tickets
|
|
239
|
+
.filter((t) => t.assignee === agentName && t.status !== 'done')
|
|
240
|
+
.map((t) => t.id);
|
|
241
|
+
status.completedTickets = tickets
|
|
242
|
+
.filter((t) => t.assignee === agentName && t.status === 'done')
|
|
243
|
+
.map((t) => t.id);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Ignore ticket loading errors
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return status;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get status for all agents
|
|
254
|
+
*/
|
|
255
|
+
export function getAllAgentsStatus(workspaceInfo) {
|
|
256
|
+
return workspaceInfo.agents.map(agent => getAgentStatus(workspaceInfo, agent.name));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Validate agent names (must be valid format)
|
|
260
|
+
*/
|
|
261
|
+
export function validateAgentNames(agentNames) {
|
|
262
|
+
const valid = [];
|
|
263
|
+
const invalid = [];
|
|
264
|
+
for (const name of agentNames) {
|
|
265
|
+
if (isValidAgentName(name)) {
|
|
266
|
+
valid.push(name);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
invalid.push(name);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { valid, invalid };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Create agent worktrees and update database
|
|
276
|
+
*/
|
|
277
|
+
export async function addAgentsToWorkspace(workspaceInfo, agentNames, options) {
|
|
278
|
+
// Import dynamically to avoid circular dependency
|
|
279
|
+
const { createAgentWorktrees } = await import('./index.js');
|
|
280
|
+
// Filter out existing agents
|
|
281
|
+
const existingNames = new Set(workspaceInfo.agents.map(a => a.name));
|
|
282
|
+
const newAgents = agentNames.filter(name => !existingNames.has(name));
|
|
283
|
+
if (newAgents.length === 0) {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
// Create worktrees
|
|
287
|
+
if (workspaceInfo.type === 'hq') {
|
|
288
|
+
await createAgentWorktrees(workspaceInfo.agentsPath, newAgents, workspaceInfo.path, options);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
await createAgentWorktrees(workspaceInfo.agentsPath, newAgents, undefined, options);
|
|
292
|
+
}
|
|
293
|
+
// Add to database (with optional theme ID)
|
|
294
|
+
addAgentsToDatabase(workspaceInfo.path, newAgents, options?.themeId);
|
|
295
|
+
return newAgents;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Remove agents and clean up worktrees
|
|
299
|
+
*/
|
|
300
|
+
export async function removeAgentsFromWorkspace(workspaceInfo, agentNames) {
|
|
301
|
+
const removed = [];
|
|
302
|
+
const failed = [];
|
|
303
|
+
for (const agentName of agentNames) {
|
|
304
|
+
try {
|
|
305
|
+
const agentDir = path.join(workspaceInfo.agentsPath, agentName);
|
|
306
|
+
// Stop and remove Docker container if it exists
|
|
307
|
+
try {
|
|
308
|
+
const containerId = execSync(`docker ps -aq --filter "label=devcontainer.local_folder=${agentDir}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
309
|
+
if (containerId) {
|
|
310
|
+
execSync(`docker stop ${containerId}`, { stdio: 'pipe' });
|
|
311
|
+
execSync(`docker rm ${containerId}`, { stdio: 'pipe' });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Container might not exist, ignore errors
|
|
316
|
+
}
|
|
317
|
+
if (fs.existsSync(agentDir)) {
|
|
318
|
+
// Remove worktrees for each repository
|
|
319
|
+
for (const repo of workspaceInfo.repositories) {
|
|
320
|
+
const repoWorktreePath = path.join(agentDir, repo.name);
|
|
321
|
+
const sourceRepoPath = workspaceInfo.type === 'hq'
|
|
322
|
+
? path.join(workspaceInfo.path, 'repos', repo.name)
|
|
323
|
+
: process.cwd(); // For workspace-only, source is current directory
|
|
324
|
+
if (fs.existsSync(repoWorktreePath)) {
|
|
325
|
+
try {
|
|
326
|
+
execSync(`git worktree remove ${path.relative(sourceRepoPath, repoWorktreePath)} --force`, {
|
|
327
|
+
cwd: sourceRepoPath,
|
|
328
|
+
stdio: 'pipe'
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// If git worktree remove fails, remove directory manually
|
|
333
|
+
fs.rmSync(repoWorktreePath, { recursive: true, force: true });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Remove agent directory
|
|
338
|
+
if (fs.existsSync(agentDir)) {
|
|
339
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
340
|
+
}
|
|
341
|
+
// Clean up git worktree list
|
|
342
|
+
for (const repo of workspaceInfo.repositories) {
|
|
343
|
+
const sourceRepoPath = workspaceInfo.type === 'hq'
|
|
344
|
+
? path.join(workspaceInfo.path, 'repos', repo.name)
|
|
345
|
+
: process.cwd();
|
|
346
|
+
try {
|
|
347
|
+
execSync('git worktree prune', {
|
|
348
|
+
cwd: sourceRepoPath,
|
|
349
|
+
stdio: 'pipe'
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
// Ignore prune errors
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
removed.push(agentName);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
failed.push(agentName);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Remove from database
|
|
364
|
+
if (removed.length > 0) {
|
|
365
|
+
removeAgentsFromDatabase(workspaceInfo.path, removed);
|
|
366
|
+
// Clear ticket assignees for removed agents
|
|
367
|
+
try {
|
|
368
|
+
const { storage } = await getPMOContext();
|
|
369
|
+
const allTickets = await storage.listTickets(undefined);
|
|
370
|
+
for (const ticket of allTickets) {
|
|
371
|
+
if (ticket.assignee && removed.includes(ticket.assignee)) {
|
|
372
|
+
// Pass null to clear the assignee in the database
|
|
373
|
+
await storage.updateTicket(ticket.id, { assignee: null });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
await storage.close();
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
// PMO might not exist, ignore errors
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return { removed, failed };
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Create an ephemeral agent on-demand for a spawn operation.
|
|
386
|
+
* Creates worktree in agents/temp/{name}/
|
|
387
|
+
*/
|
|
388
|
+
export async function createEphemeralAgent(workspaceInfo, options) {
|
|
389
|
+
// Get existing agent names for uniqueness check
|
|
390
|
+
const existingNames = new Set([
|
|
391
|
+
...workspaceInfo.agents.map(a => a.name.toLowerCase()),
|
|
392
|
+
...Array.from(getEphemeralAgentNames(workspaceInfo.path))
|
|
393
|
+
]);
|
|
394
|
+
const log = options?.log;
|
|
395
|
+
// Get theme: use provided themeId, or fall back to workspace's active theme
|
|
396
|
+
let themeId = options?.themeId;
|
|
397
|
+
if (!themeId) {
|
|
398
|
+
themeId = workspaceInfo.activeThemeId ?? undefined;
|
|
399
|
+
}
|
|
400
|
+
// Use theme-specific ephemeral directory
|
|
401
|
+
const ephemeralDir = themeId ? getThemeEphemeralDir(themeId) : workspaceInfo.ephemeralAgentsDir;
|
|
402
|
+
const tempAgentsBasePath = path.join(workspaceInfo.path, 'agents', ephemeralDir);
|
|
403
|
+
// Create a conflict checker for external resources (tmux sessions, directories)
|
|
404
|
+
const checkExternalConflict = (candidateName) => {
|
|
405
|
+
// Check if a tmux session with this name already exists (could be from manual creation)
|
|
406
|
+
if (tmuxSessionExists(candidateName)) {
|
|
407
|
+
return { conflict: true, reason: `tmux session "${candidateName}" already exists` };
|
|
408
|
+
}
|
|
409
|
+
// Check if the directory already exists in agents/temp/
|
|
410
|
+
const candidateDir = path.join(tempAgentsBasePath, candidateName);
|
|
411
|
+
if (fs.existsSync(candidateDir)) {
|
|
412
|
+
return { conflict: true, reason: `directory "${candidateDir}" already exists` };
|
|
413
|
+
}
|
|
414
|
+
return { conflict: false };
|
|
415
|
+
};
|
|
416
|
+
// Log when conflicts are skipped during name generation
|
|
417
|
+
const onConflictSkipped = (name, reason) => {
|
|
418
|
+
log?.(`⚠️ Skipping name "${name}": ${reason}`);
|
|
419
|
+
};
|
|
420
|
+
// Generate unique ephemeral name using workspace theme
|
|
421
|
+
const nameOptions = {
|
|
422
|
+
themeId,
|
|
423
|
+
checkExternalConflict,
|
|
424
|
+
onConflictSkipped
|
|
425
|
+
};
|
|
426
|
+
const agentName = generateEphemeralAgentName(existingNames, nameOptions);
|
|
427
|
+
// Extract base name from the generated name (e.g., "bezos" from "bold-bezos-1")
|
|
428
|
+
const parts = agentName.split('-');
|
|
429
|
+
const baseName = parts.length >= 3 ? parts.slice(1, -1).join('-') : agentName;
|
|
430
|
+
// Create temp agents directory if it doesn't exist
|
|
431
|
+
if (!fs.existsSync(tempAgentsBasePath)) {
|
|
432
|
+
fs.mkdirSync(tempAgentsBasePath, { recursive: true });
|
|
433
|
+
}
|
|
434
|
+
const agentDir = path.join(tempAgentsBasePath, agentName);
|
|
435
|
+
// Create agent directory
|
|
436
|
+
if (!fs.existsSync(agentDir)) {
|
|
437
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
438
|
+
}
|
|
439
|
+
// Create worktrees for each repository
|
|
440
|
+
const reposPath = path.join(workspaceInfo.path, 'repos');
|
|
441
|
+
if (fs.existsSync(reposPath) && workspaceInfo.repositories.length > 0) {
|
|
442
|
+
for (const repo of workspaceInfo.repositories) {
|
|
443
|
+
const sourceRepoPath = path.join(reposPath, repo.name);
|
|
444
|
+
const worktreePath = path.join(agentDir, repo.name);
|
|
445
|
+
if (fs.existsSync(sourceRepoPath) && !fs.existsSync(worktreePath)) {
|
|
446
|
+
try {
|
|
447
|
+
// Create git worktree for the repository
|
|
448
|
+
// Don't create a branch yet - that happens in work:start
|
|
449
|
+
// Use --detach to create without a branch reference
|
|
450
|
+
execSync(`git worktree add --detach "${worktreePath}"`, {
|
|
451
|
+
cwd: sourceRepoPath,
|
|
452
|
+
stdio: 'pipe'
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
// If worktree creation fails, try to just create the directory
|
|
457
|
+
// The agent can still work without a worktree (e.g., for non-git projects)
|
|
458
|
+
if (!fs.existsSync(worktreePath)) {
|
|
459
|
+
fs.mkdirSync(worktreePath, { recursive: true });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Create devcontainer config if not skipped (uses shared devcontainer generator)
|
|
466
|
+
if (!options?.skipDevcontainer) {
|
|
467
|
+
const devcontainerDir = path.join(agentDir, '.devcontainer');
|
|
468
|
+
if (!fs.existsSync(devcontainerDir)) {
|
|
469
|
+
createDevcontainerConfig({
|
|
470
|
+
agentName,
|
|
471
|
+
agentDir,
|
|
472
|
+
repoWorktrees: workspaceInfo.repositories.map(r => r.name)
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// Add to database
|
|
477
|
+
const agent = addEphemeralAgentToDatabase(workspaceInfo.path, agentName, baseName, options?.themeId);
|
|
478
|
+
return {
|
|
479
|
+
name: agentName,
|
|
480
|
+
baseName,
|
|
481
|
+
worktreePath: agentDir,
|
|
482
|
+
agent
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Check if a tmux session exists for a given name
|
|
487
|
+
*/
|
|
488
|
+
export function tmuxSessionExists(sessionName) {
|
|
489
|
+
try {
|
|
490
|
+
execSync(`tmux has-session -t "${sessionName}" 2>/dev/null`, { stdio: 'pipe' });
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Get list of active tmux sessions that match our pattern
|
|
499
|
+
* Pattern: {ticketId}-{action}-{agent}
|
|
500
|
+
*/
|
|
501
|
+
export function getActiveTmuxSessions() {
|
|
502
|
+
try {
|
|
503
|
+
const output = execSync('tmux list-sessions -F "#{session_name}"', {
|
|
504
|
+
encoding: 'utf-8',
|
|
505
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
506
|
+
});
|
|
507
|
+
return output
|
|
508
|
+
.trim()
|
|
509
|
+
.split('\n')
|
|
510
|
+
.filter(Boolean)
|
|
511
|
+
.map(name => {
|
|
512
|
+
// Parse session name: {ticketId}-{action}-{agent}
|
|
513
|
+
const parts = name.split('-');
|
|
514
|
+
if (parts.length >= 3) {
|
|
515
|
+
// TKT-123-implement-bold-bezos-1 -> ticketId: TKT-123, agent: bold-bezos-1
|
|
516
|
+
const ticketId = parts.slice(0, 2).join('-');
|
|
517
|
+
const agent = parts.slice(3).join('-');
|
|
518
|
+
return { name, ticketId, agent };
|
|
519
|
+
}
|
|
520
|
+
return { name, ticketId: '', agent: '' };
|
|
521
|
+
})
|
|
522
|
+
.filter(s => s.ticketId.startsWith('TKT-'));
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Check if there's an active tmux session for a specific ticket
|
|
530
|
+
*/
|
|
531
|
+
export function getTicketTmuxSession(ticketId) {
|
|
532
|
+
const sessions = getActiveTmuxSessions();
|
|
533
|
+
const session = sessions.find(s => s.ticketId === ticketId);
|
|
534
|
+
if (session) {
|
|
535
|
+
return { sessionName: session.name, agent: session.agent };
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Kill a tmux session by name
|
|
541
|
+
*/
|
|
542
|
+
export function killTmuxSession(sessionName) {
|
|
543
|
+
try {
|
|
544
|
+
execSync(`tmux kill-session -t "${sessionName}"`, { stdio: 'pipe' });
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Get tmux sessions associated with an agent
|
|
553
|
+
*/
|
|
554
|
+
export function getAgentTmuxSessions(agentName) {
|
|
555
|
+
const sessions = getActiveTmuxSessions();
|
|
556
|
+
return sessions
|
|
557
|
+
.filter(s => s.agent === agentName)
|
|
558
|
+
.map(s => s.name);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Get docker containers associated with an agent directory
|
|
562
|
+
*/
|
|
563
|
+
function getAgentContainers(agentDir) {
|
|
564
|
+
try {
|
|
565
|
+
const output = execSync(`docker ps -aq --filter "label=devcontainer.local_folder=${agentDir}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
566
|
+
return output.trim().split('\n').filter(Boolean);
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
return [];
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Check git status for all worktrees in an agent directory.
|
|
574
|
+
* Returns info about uncommitted changes and unpushed commits.
|
|
575
|
+
*/
|
|
576
|
+
export function getAgentGitStatus(workspaceInfo, agentName) {
|
|
577
|
+
const agent = workspaceInfo.agents.find(a => a.name === agentName);
|
|
578
|
+
const agentDir = agent?.type === 'ephemeral'
|
|
579
|
+
? path.join(workspaceInfo.path, 'agents', workspaceInfo.ephemeralAgentsDir, agentName)
|
|
580
|
+
: path.join(workspaceInfo.path, 'agents', workspaceInfo.persistentAgentsDir, agentName);
|
|
581
|
+
const result = {
|
|
582
|
+
agentName,
|
|
583
|
+
worktrees: [],
|
|
584
|
+
hasUnsavedWork: false
|
|
585
|
+
};
|
|
586
|
+
// Check each repository worktree
|
|
587
|
+
for (const repo of workspaceInfo.repositories) {
|
|
588
|
+
const worktreePath = path.join(agentDir, repo.name);
|
|
589
|
+
if (!fs.existsSync(worktreePath)) {
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
const status = {
|
|
593
|
+
worktreePath,
|
|
594
|
+
repoName: repo.name,
|
|
595
|
+
branch: '',
|
|
596
|
+
hasUncommittedChanges: false,
|
|
597
|
+
uncommittedFiles: [],
|
|
598
|
+
hasUnpushedCommits: false,
|
|
599
|
+
unpushedCount: 0
|
|
600
|
+
};
|
|
601
|
+
try {
|
|
602
|
+
// Get current branch
|
|
603
|
+
status.branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
604
|
+
cwd: worktreePath,
|
|
605
|
+
encoding: 'utf-8',
|
|
606
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
607
|
+
}).trim();
|
|
608
|
+
// Check for uncommitted changes (staged + unstaged + untracked)
|
|
609
|
+
const gitStatus = execSync('git status --porcelain', {
|
|
610
|
+
cwd: worktreePath,
|
|
611
|
+
encoding: 'utf-8',
|
|
612
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
613
|
+
}).trim();
|
|
614
|
+
if (gitStatus) {
|
|
615
|
+
status.hasUncommittedChanges = true;
|
|
616
|
+
status.uncommittedFiles = gitStatus.split('\n').filter(line => line.trim());
|
|
617
|
+
result.hasUnsavedWork = true;
|
|
618
|
+
}
|
|
619
|
+
// Check for unpushed commits
|
|
620
|
+
try {
|
|
621
|
+
const unpushed = execSync(`git log @{u}..HEAD --oneline 2>/dev/null || echo ""`, {
|
|
622
|
+
cwd: worktreePath,
|
|
623
|
+
encoding: 'utf-8',
|
|
624
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
625
|
+
}).trim();
|
|
626
|
+
if (unpushed) {
|
|
627
|
+
status.hasUnpushedCommits = true;
|
|
628
|
+
status.unpushedCount = unpushed.split('\n').filter(line => line.trim()).length;
|
|
629
|
+
result.hasUnsavedWork = true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// No upstream tracking branch - check if there are any commits at all
|
|
634
|
+
try {
|
|
635
|
+
const hasCommits = execSync('git log --oneline -1', {
|
|
636
|
+
cwd: worktreePath,
|
|
637
|
+
encoding: 'utf-8',
|
|
638
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
639
|
+
}).trim();
|
|
640
|
+
if (hasCommits) {
|
|
641
|
+
// Has commits but no upstream - consider as unpushed
|
|
642
|
+
status.hasUnpushedCommits = true;
|
|
643
|
+
status.unpushedCount = 1; // At least one
|
|
644
|
+
result.hasUnsavedWork = true;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
// No commits at all
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
// Git commands failed - worktree might be corrupted
|
|
654
|
+
}
|
|
655
|
+
result.worktrees.push(status);
|
|
656
|
+
}
|
|
657
|
+
return result;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Commit and push all work in an agent's worktrees.
|
|
661
|
+
* - Stages all uncommitted changes (git add -A)
|
|
662
|
+
* - Commits with a WIP message if there are staged changes
|
|
663
|
+
* - Pushes all commits to remote
|
|
664
|
+
* Returns true if all operations succeeded.
|
|
665
|
+
*/
|
|
666
|
+
export function pushAgentWork(workspaceInfo, agentName, log) {
|
|
667
|
+
const gitStatus = getAgentGitStatus(workspaceInfo, agentName);
|
|
668
|
+
let allSuccess = true;
|
|
669
|
+
for (const worktree of gitStatus.worktrees) {
|
|
670
|
+
const { worktreePath, repoName, hasUncommittedChanges, uncommittedFiles, hasUnpushedCommits, unpushedCount, branch } = worktree;
|
|
671
|
+
// First, commit any uncommitted changes
|
|
672
|
+
if (hasUncommittedChanges) {
|
|
673
|
+
try {
|
|
674
|
+
log?.(`Committing ${uncommittedFiles.length} file(s) in ${repoName}...`);
|
|
675
|
+
// Stage all changes
|
|
676
|
+
execSync('git add -A', {
|
|
677
|
+
cwd: worktreePath,
|
|
678
|
+
stdio: 'pipe'
|
|
679
|
+
});
|
|
680
|
+
// Commit with WIP message
|
|
681
|
+
const commitMessage = `WIP: Auto-commit before cleanup\n\nAgent: ${agentName}\nFiles: ${uncommittedFiles.length}`;
|
|
682
|
+
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
683
|
+
cwd: worktreePath,
|
|
684
|
+
stdio: 'pipe'
|
|
685
|
+
});
|
|
686
|
+
log?.(`✓ Committed changes in ${repoName}`);
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
log?.(`✗ Failed to commit ${repoName}: ${error}`);
|
|
690
|
+
allSuccess = false;
|
|
691
|
+
continue; // Skip push if commit failed
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// Then push (either existing unpushed commits or the one we just made)
|
|
695
|
+
if (hasUnpushedCommits || hasUncommittedChanges) {
|
|
696
|
+
try {
|
|
697
|
+
const commitCount = hasUncommittedChanges ? (unpushedCount + 1) : unpushedCount;
|
|
698
|
+
log?.(`Pushing ${commitCount} commit(s) from ${repoName} on ${branch}...`);
|
|
699
|
+
// Set upstream if needed and push
|
|
700
|
+
execSync(`git push -u origin ${branch}`, {
|
|
701
|
+
cwd: worktreePath,
|
|
702
|
+
stdio: 'pipe'
|
|
703
|
+
});
|
|
704
|
+
log?.(`✓ Pushed ${repoName}`);
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
log?.(`✗ Failed to push ${repoName}: ${error}`);
|
|
708
|
+
allSuccess = false;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return allSuccess;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Clean up a single agent - removes resources but keeps DB record (marked as cleaned)
|
|
716
|
+
*/
|
|
717
|
+
export async function cleanupAgent(workspaceInfo, agentName, options) {
|
|
718
|
+
const log = options?.log ?? (() => { });
|
|
719
|
+
const dryRun = options?.dryRun ?? false;
|
|
720
|
+
const force = options?.force ?? false;
|
|
721
|
+
const pushFirst = options?.pushFirst ?? false;
|
|
722
|
+
const result = {
|
|
723
|
+
agent: agentName,
|
|
724
|
+
success: true,
|
|
725
|
+
tmuxSessionsKilled: [],
|
|
726
|
+
containersRemoved: [],
|
|
727
|
+
directoriesRemoved: [],
|
|
728
|
+
errors: []
|
|
729
|
+
};
|
|
730
|
+
// Find the agent
|
|
731
|
+
const agent = workspaceInfo.agents.find(a => a.name === agentName);
|
|
732
|
+
if (!agent) {
|
|
733
|
+
result.success = false;
|
|
734
|
+
result.errors.push(`Agent "${agentName}" not found`);
|
|
735
|
+
return result;
|
|
736
|
+
}
|
|
737
|
+
// Check for unsaved work (uncommitted changes or unpushed commits)
|
|
738
|
+
if (!force) {
|
|
739
|
+
const gitStatus = getAgentGitStatus(workspaceInfo, agentName);
|
|
740
|
+
if (gitStatus.hasUnsavedWork) {
|
|
741
|
+
// If pushFirst is set, try to push before cleanup
|
|
742
|
+
if (pushFirst) {
|
|
743
|
+
log('Pushing unpushed work before cleanup...');
|
|
744
|
+
const pushed = pushAgentWork(workspaceInfo, agentName, log);
|
|
745
|
+
if (!pushed) {
|
|
746
|
+
result.success = false;
|
|
747
|
+
result.blockedByGit = true;
|
|
748
|
+
result.gitStatus = gitStatus;
|
|
749
|
+
result.errors.push('Failed to push some work. Use --force to cleanup anyway.');
|
|
750
|
+
return result;
|
|
751
|
+
}
|
|
752
|
+
// Re-check git status after push
|
|
753
|
+
const newStatus = getAgentGitStatus(workspaceInfo, agentName);
|
|
754
|
+
if (newStatus.hasUnsavedWork) {
|
|
755
|
+
result.success = false;
|
|
756
|
+
result.blockedByGit = true;
|
|
757
|
+
result.gitStatus = newStatus;
|
|
758
|
+
result.errors.push('Agent still has uncommitted changes after push. Commit changes or use --force.');
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// Block cleanup - has unsaved work
|
|
764
|
+
result.success = false;
|
|
765
|
+
result.blockedByGit = true;
|
|
766
|
+
result.gitStatus = gitStatus;
|
|
767
|
+
const issues = [];
|
|
768
|
+
for (const wt of gitStatus.worktrees) {
|
|
769
|
+
if (wt.hasUncommittedChanges) {
|
|
770
|
+
issues.push(`${wt.repoName}: ${wt.uncommittedFiles.length} uncommitted files`);
|
|
771
|
+
}
|
|
772
|
+
if (wt.hasUnpushedCommits) {
|
|
773
|
+
issues.push(`${wt.repoName}: ${wt.unpushedCount} unpushed commits on ${wt.branch}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
result.errors.push(`Agent has unsaved work: ${issues.join(', ')}. Use --push to push first or --force to cleanup anyway.`);
|
|
777
|
+
return result;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// Determine agent directory
|
|
782
|
+
const agentDir = agent.type === 'ephemeral'
|
|
783
|
+
? path.join(workspaceInfo.path, 'agents', workspaceInfo.ephemeralAgentsDir, agentName)
|
|
784
|
+
: path.join(workspaceInfo.path, 'agents', workspaceInfo.persistentAgentsDir, agentName);
|
|
785
|
+
// 1. Kill tmux sessions for this agent
|
|
786
|
+
const tmuxSessions = getAgentTmuxSessions(agentName);
|
|
787
|
+
for (const session of tmuxSessions) {
|
|
788
|
+
if (dryRun) {
|
|
789
|
+
log(`[dry-run] Would kill tmux session: ${session}`);
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
log(`Killing tmux session: ${session}`);
|
|
793
|
+
if (killTmuxSession(session)) {
|
|
794
|
+
result.tmuxSessionsKilled.push(session);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
result.errors.push(`Failed to kill tmux session: ${session}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// 2. Stop and remove docker containers
|
|
802
|
+
const containers = getAgentContainers(agentDir);
|
|
803
|
+
for (const containerId of containers) {
|
|
804
|
+
if (dryRun) {
|
|
805
|
+
log(`[dry-run] Would remove container: ${containerId}`);
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
log(`Removing container: ${containerId}`);
|
|
809
|
+
try {
|
|
810
|
+
execSync(`docker rm -f ${containerId}`, { stdio: 'pipe' });
|
|
811
|
+
result.containersRemoved.push(containerId);
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
result.errors.push(`Failed to remove container ${containerId}: ${error}`);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// 3. Remove git worktrees for each repository
|
|
819
|
+
for (const repo of workspaceInfo.repositories) {
|
|
820
|
+
const worktreePath = path.join(agentDir, repo.name);
|
|
821
|
+
const sourceRepoPath = path.join(workspaceInfo.path, 'repos', repo.name);
|
|
822
|
+
if (fs.existsSync(worktreePath) && fs.existsSync(sourceRepoPath)) {
|
|
823
|
+
if (dryRun) {
|
|
824
|
+
log(`[dry-run] Would remove worktree: ${worktreePath}`);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
log(`Removing worktree: ${worktreePath}`);
|
|
828
|
+
try {
|
|
829
|
+
execSync(`git worktree remove "${worktreePath}" --force`, {
|
|
830
|
+
cwd: sourceRepoPath,
|
|
831
|
+
stdio: 'pipe'
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
// If git worktree remove fails, we'll still try to remove the directory
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// 4. Remove agent directory
|
|
841
|
+
if (fs.existsSync(agentDir)) {
|
|
842
|
+
if (dryRun) {
|
|
843
|
+
log(`[dry-run] Would remove directory: ${agentDir}`);
|
|
844
|
+
result.directoriesRemoved.push(agentDir);
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
log(`Removing directory: ${agentDir}`);
|
|
848
|
+
try {
|
|
849
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
850
|
+
result.directoriesRemoved.push(agentDir);
|
|
851
|
+
}
|
|
852
|
+
catch (error) {
|
|
853
|
+
result.errors.push(`Failed to remove directory ${agentDir}: ${error}`);
|
|
854
|
+
result.success = false;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
// 5. Prune worktrees
|
|
859
|
+
if (!dryRun) {
|
|
860
|
+
for (const repo of workspaceInfo.repositories) {
|
|
861
|
+
const sourceRepoPath = path.join(workspaceInfo.path, 'repos', repo.name);
|
|
862
|
+
if (fs.existsSync(sourceRepoPath)) {
|
|
863
|
+
try {
|
|
864
|
+
execSync('git worktree prune', { cwd: sourceRepoPath, stdio: 'pipe' });
|
|
865
|
+
}
|
|
866
|
+
catch {
|
|
867
|
+
// Ignore prune errors
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// 6. Mark agent as cleaned in database (not delete)
|
|
873
|
+
if (!dryRun && result.success) {
|
|
874
|
+
log(`Marking agent "${agentName}" as cleaned`);
|
|
875
|
+
markAgentCleaned(workspaceInfo.path, agentName);
|
|
876
|
+
}
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Get agents that can be cleaned up (active ephemeral agents with no running work)
|
|
881
|
+
*/
|
|
882
|
+
export function getCleanableAgents(workspaceInfo, checkRunning = true) {
|
|
883
|
+
// Get active ephemeral agents
|
|
884
|
+
const ephemeralAgents = workspaceInfo.agents.filter(a => a.type === 'ephemeral' && a.status === 'active');
|
|
885
|
+
if (!checkRunning) {
|
|
886
|
+
return ephemeralAgents;
|
|
887
|
+
}
|
|
888
|
+
// Filter out agents with active tmux sessions
|
|
889
|
+
return ephemeralAgents.filter(agent => {
|
|
890
|
+
const sessions = getAgentTmuxSessions(agent.name);
|
|
891
|
+
return sessions.length === 0;
|
|
892
|
+
});
|
|
893
|
+
}
|