Repository navigation
plan-lifecycle: tear down what a plan created #83
Description
Goal
Make plan-manager and yolo tear down the resources that a plan created. After archive succeeds, the agent stops the processes and compose stacks that the plan started, removes the plan's linked worktree with git worktree remove and git worktree prune, and deletes the merged local branch with git branch -d. The merge question states this teardown, so a Merge now answer confirms the branch delete. On an exit without a merge, the agent stops processes and stacks, keeps the worktree and branch, and lists them under Kept resources in the report and the yolo run summary. Compose volumes are kept and reported, not removed. Local resources on the workstation are development, and teardown removes them without a question. Teardown cleans up a remote resource only when its target is non-production. A production cleanup needs its own in-session ask immediately before it; a yolo invocation does not answer that ask. The text stays tool-neutral: git and docker commands only, and plan scratch stays in the docks-review directory. Scope: the plan-lifecycle contract, plan-manager, yolo, and the yolo run summary template. Source request: DocksDocks/public#89.
Mode: plan-and-implement
Research
Request and owner decisions
- Request: omp: agent resource hygiene (scratch, tidy, docks-kit-omp plugin) public#89, sent by the docks-kit session. Gaps: plan-manager never removes the linked worktree after archive; no phase stops servers, watchers, test runners, or compose stacks; a yolo exit without
Merge nowsays nothing about these resources; the run summary has no place for kept resources. - Owner decision (in-session
ask, 2026-10-21): the merge question states the full teardown, includinggit branch -d, and aMerge nowanswer confirms it. Reason: the rootAGENTS.mdSecurity rule requires explicit user confirmation before a branch delete. - Owner decision (same
ask): tool-neutral text only. No docks-kit tool names (scratch_new,scratch_remove,tidy_report,workspace-hygiene) in the shipped plugin. - Owner decision (same
ask):docker compose downwithout--volumes; kept volumes go under Kept resources. - Owner clarification (relayed by the requester, 2026-10-21): use the contract's existing definition of a non-production target. Never stop, remove, undeploy, or delete a production resource automatically. A production cleanup needs an in-session
askimmediately before it, must be rare, and a yolo invocation does not answer it; it works like a step after the landing merge. In a subagent, or whenaskis unavailable, do not run it; list the resource under Kept resources with the reasonproduction. The same rule goes into the contract, plan-manager, and yolo. - Owner precision (relayed by the requester, 2026-10-21; it replaces the clarification's bullet about local resources): local resources on the workstation are always development: the linked worktree, local compose stacks, local servers and processes, and scratch files. Teardown removes them without a question. Production applies to remote resources that an agent reaches through SSH or a provider CLI, for example
wrangler, Vercel, Netlify, Fly, Supabase,kubectl, or a remote Docker host. Clean up such a resource only when its target is non-production by the contract definition. Owner example:wrangler deletewithout--envacts on the top-level Worker, which is usually production, so it needs the ask.
Request corrections
- Asked: use the docks-kit-omp tools
scratch_new,scratch_remove, andtidy_reportwhen present. Did: git and docker commands only. Reason: owner decision; docks-kit 0.21.0 is unreleased and omp-only;scratch_newputs scratch outsidegit rev-parse --git-path docks-review, which the contract (Helper commands and output) and the rootAGENTS.mdscratch rule require;scratch_removecannot remove a worktree underagent-worktrees. - Asked: run
git branch -d <branch>"when the branch is merged". Did: rungit branch -dafter archive, keep the branch when git refuses, never use-D. Reason: archive already proves the merge (contract, Archive). The git experiment below shows that-dsuccess does not prove a merge into the default branch, and that-drefuses after a squash or rebase merge once the remote branch is gone.
Repository facts
plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.mdis canonical (plugins/plan-lifecycle/skills/AGENTS.md, Plan-skill contract sync). Skill bodies point to it and do not copy its rules (plan-managerSKILL.mdintro: "Do not copy contract rules here.").- Contract
## Landing and archive proofholds### Linked branch,### Pull requests,### Checks and merge,### Steps after the landing merge, and### Archive. A new### Teardownsubsection goes after### Archive. The## Contentslist names only##sections, so it needs no entry. - Contract
### Checks and mergeasks withMerge nowandLeave pull request open. It does not state any cleanup. - The non-production target definition exists only in contract
## Yolo runs/### Effects before the landing merge. Teardown applies to plan-manager runs too, so the definition moves to## Steps, acceptance, and effects, and the yolo effects text points to it. - Contract
### Round ceiling: onLeave plan blocked, or without an answer, the plan goes toblockedand the agent reports. It names no cleanup. - yolo
SKILL.mdLanding step 9 is the only cleanup: afterMerge now, remove a worktree that the run made, or switch the user's clean checkout to the default branch andgit pull --ff-only; "Keep the merged local branch. Name it in the final report as a branch that the user can delete." Step 10 (noMerge now) reports only the two links. - yolo
references/run-summary.mdhas sections What changed, Decisions and corrections, Reviews, Verification, Limits and failures, After merge. After merge names "the merged local branch". - plan-manager
SKILL.mdLanding and reporting: merge onMerge now, archive after the landing merge; Done when names archive output or a reported open pull request. Phase 6 commits and pushes work before recording a blocker. No cleanup. - Linked branches: plan-manager uses
gh issue develop <n> --base <default> --checkoutin the user's clean checkout; a worktree only with an in-session approval for a dirty checkout. yolo uses a worktree at${XDG_DATA_HOME:-$HOME/.local/share}/agent-worktrees/<repository-name>/issue-<n>for a dirty checkout, andcheck-<short-sha>worktrees for commit checks, which it already removes. - No test pins skill prose (
plugins/plan-lifecycle/skills/AGENTS.md);scripts/tests/plan-cli.mjsandplugins/plan-lifecycle/test/selftest.mjsassert no phrase from the four target files..codex/agents/*.toml,plugins/plan-lifecycle/agents/*.md, and plan-workspace templates do not mention landing cleanup or run summary sections. - Validators for this plugin:
node scripts/ci.mjs --plugin plan-lifecycleruns skill guard,no-author-scripts,content-hash --check-only, agent guard and score,plugins/plan-lifecycle/test/selftest.mjs, andscripts/tests/plan-cli.mjs(scripts/lib/plugins.mjsdescriptor;scripts/ci.mjs). After a meaning change,node scripts/skills/content-hash.mjs --backfill plugins/plan-lifecycle/skillsresyncscontent_hashandmetadata.updated(plugins/plan-lifecycle/skills/AGENTS.md). - Size: plan-manager
SKILL.mdhas 164 lines and yoloSKILL.mdhas 336 lines. The hard cap is 500 lines; the productivity score floor is 8 (scripts/config/scoring.json,node scripts/config/read-floor.mjs skills productivity). - Shipped bodies must not name author scripts (
no-author-scripts). The docks-kit guards denyfind -delete,find -exec rm, andxargs rm; the new text uses none of them. - No CHANGELOG exists in the repository. Version bumps belong to the release flow (
scripts/AGENTS.md, Versioning), not to this plan.
Experiments (git 2.53.0)
Scratch: ${XDG_STATE_HOME:-$HOME/.local/state}/docks/plan-83-research/, throwaway repositories with a bare remote and default branch main, GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1.
| Case | Result |
|---|---|
git worktree remove -- <wt>, only ignored files (node_modules/) |
exit 0; the ignored files go with the worktree |
git worktree remove, untracked, modified, or staged file |
exit 128: contains modified or untracked files, use --force to delete it; worktree stays |
git worktree remove, locked worktree |
exit 128: cannot remove a locked working tree; worktree stays |
git worktree remove from inside the worktree |
exit 0, but the shell is left in a deleted directory and later git calls fail; run teardown from the main worktree |
directory deleted by hand, then git worktree prune |
stale record removed; a locked record stays |
git worktree remove of a worktree with docks-review scratch under git rev-parse --git-path docks-review |
exit 0; the worktree's scratch is removed with its admin directory |
git branch -d <b> while <b> is checked out in a linked worktree |
exit 1: cannot delete branch ... used by worktree; works after git worktree remove |
git branch -d with the upstream branch still present (merge, squash, or rebase landing) |
deletes, comparing against the upstream only, even when local HEAD lacks the work |
git branch -d with the upstream gone, merge commit, local main fast-forwarded |
deletes |
git branch -d with the upstream gone, squash or rebase landing |
exit 1: the branch '<b>' is not fully merged, with a hint to use -D; keep the branch |
Docs: git branch -d: "The branch must be fully merged in its upstream branch, or in HEAD if no upstream was set" (https://git-scm.com/docs/git-branch). git worktree remove: "Only clean worktrees (no untracked files and no modification in tracked files) can be removed." git worktree prune: "Remove worktree information in $GIT_DIR/worktrees for worktrees whose working trees are missing." (https://git-scm.com/docs/git-worktree).
Compose facts
docker compose downremoves by default only "Containers for services defined in the Compose file", networks in the networks section, and the default network. "Anonymous volumes are not removed by default."-v, --volumesremoves named and anonymous volumes. (https://docs.docker.com/reference/cli/docker/compose/down/)- The project name comes from
-p, thenCOMPOSE_PROJECT_NAME, then top-levelname:, then the base name of the project directory (https://docs.docker.com/compose/how-tos/project-name/). Two worktrees with the same directory name, such asissue-<n>in two repositories, get the same default project name, so teardown runsdownwith the same directory,-p, and-foptions that started the stack. - Compose labels named volumes with
com.docker.compose.project(https://docs.docker.com/reference/compose-file/volumes/). Anonymous volumes do not get that label, so a label query misses them (plan review round 1 finding, reproduced below). - Experiment (Docker Compose v5.6.0, Docker server 29.9.1; throwaway project
plan83expwith one anonymous volume/anonand one named volume, started withdocker compose -p plan83exp up -d):docker inspect --format '{{range .Mounts}}{{.Type}} {{.Name}} {{.Destination}}; {{end}}' $(docker compose -p plan83exp ps -a -q)printed both volumes (<64-hex> /anonandplan83exp_named /named).docker volume ls --filter label=com.docker.compose.project=plan83expanddocker compose -p plan83exp volumesprinted onlyplan83exp_named. The anonymous volume's labels were{"com.docker.volume.anonymous":""}. Afterdocker compose -p plan83exp down, both volumes stayed. The experiment volumes were then removed withdocker volume rm. - Therefore teardown records the volume names from the stack's container mounts before
docker compose down, becausedownremoves the containers that hold that record.
Decisions
- Placement: one
### Teardownsubsection in the contract owns the rule. plan-manager and yolo reference it at their landing and exit points, and repeat only the production-ask invariant in a constraint, as the contract-sync rule allows. Rejected: copying the full teardown into each skill (violates "Do not copy contract rules here"). - Order after archive: stop processes, stop stacks, leave the worktree, remove the worktree, prune, switch a clean user checkout to the default branch and fast-forward it, then
git branch -d. Reason:-drefuses while the branch is checked out in any worktree; removal from inside the worktree breaks the shell. - Branch delete:
git branch -donly. When it refuses, keep the branch and list it under Kept resources. Never-Dor--force, even when git's hint suggests it. Rejected:git branch --delete-merged(git 2.56.0 docs only; unknown option on 2.53.0). - Worktree delete:
git worktree remove -- <worktree>without--force. When it refuses (untracked or modified files, or a lock), keep the worktree and list it with git's reason. - Processes: track each server, watcher, test runner, and compose stack that the plan starts; stop each one when the step or check that needed it ends, and stop any that still runs at teardown. Never stop one that the plan did not start.
- Compose volumes: before
docker compose down, record the volume names from the stack's container mounts withdocker inspecton the IDs fromdocker compose ps -a -q, run with the same directory,-p, and-foptions. This record includes anonymous volumes, which carry no project label. Rundocker compose downwithout--volumes, and list the recorded volumes under Kept resources. Rejected:docker volume ls --filter label=com.docker.compose.project=<project>alone (misses anonymous volumes). - Exit without a merge (
Leave pull request open, a blocked plan, a stalled yolo run, or no answer): stop local processes and stacks, keep the worktree, the branch, and every remote resource, and report them under Kept resources. - Production: local resources on the workstation (worktree, branch, compose stacks, servers, processes, scratch) are development; teardown removes them without a question. A remote resource that the plan created through SSH or a provider CLI is cleaned up only when its target is non-production by the contract definition; the merge question names it, so
Merge nowconfirms that cleanup. A command that does not name a non-production target, such aswrangler deletewithout--env, acts on production. A remote production cleanup needs its own in-sessionaskimmediately before it, also in a yolo run, and must be rare. Withoutask, keep the resource and give the reasonproduction. - Kept resources naming: name a worktree by its branch and directory name, never by an absolute machine path (contract, Published text; run summary is the public pull request description).
- Run summary: add
### Kept resourcesfor resources that stay afterMerge now(volumes, production resources) and afterLeave pull request open(worktree and branch). The final report after the merge answer gives the actual list. After merge stops naming the merged local branch. - Scratch: unchanged. A worktree's
docks-reviewscratch goes with the worktree; a main checkout's scratch stays untracked and mode 0700. Rejected: plan-specific file deletion in the main checkout (owner decision: tool-neutral, scratch stays in docks-review).
Engineering standards
The change is skill prose only. Steps follow the plan-lifecycle authoring node (plugins/plan-lifecycle/skills/AGENTS.md) and the global Simplified Technical English output standard. No code, test, or error message changes, so code-clarity and tdd-workflow do not apply.
Steps
| # | Id | Task | Files | Depends | Effect | Status | Done when |
|---|---|---|---|---|---|---|---|
| 1 | contract_teardown | Move the non-production target definition from Yolo runs to Steps, acceptance, and effects and point the yolo effects text to it. Make the Merge now option in Checks and merge state the teardown, including git branch -d and each remote non-production cleanup. Add ### Teardown after ### Archive: process and stack tracking, recording each stack's volume names from its container mounts before docker compose down, the after-archive order, git worktree remove and git worktree prune without --force, the clean-checkout switch, git branch -d without -D, the exit-without-merge rule, the production rule, and Kept resources naming. Add the production-cleanup row to the Yolo runs table and the teardown exit to the Round ceiling Leave plan blocked path. |
plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md |
- | local | done | The contract has one ### Teardown subsection with every rule in the Decisions list, the Merge now text names the teardown, and the non-production definition is outside Yolo runs |
| 2 | manager_teardown | In plan-manager, reference the contract's Teardown in Landing and reporting and in the phase 6 blocker path; add the production-cleanup ask to the effect constraint; make Done when require the Kept resources report. | plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md |
1 | local | done | plan-manager points to Teardown at archive and at every exit without a merge, and states that a production cleanup needs its own question |
| 3 | yolo_teardown | In yolo, make Landing step 8's merge question use the contract's Checks and merge options, whose Merge now text states the teardown, including git branch -d. Replace Landing step 9's cleanup bullets and the "Keep the merged local branch" rule with the contract's Teardown; give step 10 and the stall exit the exit-without-merge rule; state in a constraint that a production cleanup needs its own question and that the invocation does not answer it. Add ### Kept resources to the run summary template and drop the merged local branch from After merge. |
plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md, plugins/plan-lifecycle/skills/productivity/yolo/references/run-summary.md |
1 | local | done | yolo's merge question states the teardown, yolo and the run summary reference Teardown and Kept resources, and no text keeps the merged local branch by rule |
| 4 | refresh_hashes | Run node scripts/skills/content-hash.mjs --backfill plugins/plan-lifecycle/skills after steps 1 to 3. |
plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md, plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md |
2, 3 | local | done | node scripts/skills/content-hash.mjs --check-only plugins/plan-lifecycle/skills exits 0 |
Acceptance
| ID | Command | Expected |
|---|---|---|
| A1 | node scripts/ci.mjs --plugin plan-lifecycle |
Exit 0 |
| A2 | node scripts/skills/content-hash.mjs --check-only plugins/plan-lifecycle/skills |
Exit 0 |
| A3 | grep -c '^### Teardown' plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md |
1 |
| A4 | grep -l 'Teardown' plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md |
Both paths print |
| A5 | grep -l 'Kept resources' plugins/plan-lifecycle/skills/productivity/plan-manager/references/plan-contract.md plugins/plan-lifecycle/skills/productivity/plan-manager/SKILL.md plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md plugins/plan-lifecycle/skills/productivity/yolo/references/run-summary.md |
All four paths print |
| A6 | grep -rn 'Keep the merged local branch' plugins/plan-lifecycle |
No output, exit 1 |
| A7 | grep -rnE 'find .*-delete|-exec rm|xargs rm' plugins/plan-lifecycle/skills |
No output, exit 1 |
| A8 | grep -rnE '/(home|Users)/' plugins/plan-lifecycle/skills |
No output, exit 1 |
Do not touch
plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs,plugins/plan-lifecycle/test/selftest.mjs,scripts/tests/plan-cli.mjs: the helper and its tests do not change.- Plugin manifests, marketplace catalogs,
plugins/plan-lifecycle/compatibility.json, versions, tags, and releases: the release flow owns them. - DocksDocks/public and docks-kit: the requester owns them.
- Remote branch deletion and
gh pr merge --delete-branch: out of scope. - Main-checkout scratch in the docks-review directory: owner decision.
plugins/docks/skills/engineering/refactor/references/executor-dispatch.md: its Claude-managed isolation worktree is outside this lifecycle's teardown.
Open questions
Verification Results
Run on branch 83-plan-lifecycle-tear-down-what-a-plan-created, uncommitted candidate, 2026-10-21.
| ID | Result |
|---|---|
| A1 | node scripts/ci.mjs --plugin plan-lifecycle: exit 0, "All ci.mjs checks passed". The first run failed one check, claude CLI could not run, because this host had no claude binary on PATH. After node node_modules/@anthropic-ai/claude-code/install.cjs (the lockfile-pinned CLI that CI also materializes) and node_modules/.bin on PATH, claude plugin validate ./plugins/plan-lifecycle passed and the gate passed. Skill score 56 (floor 32), agents score 30 (floor 28), selftest and plan CLI contract passed. |
| A2 | content-hash --check-only: exit 0, after --backfill wrote productivity/plan-manager and productivity/yolo |
| A3 | 1 |
| A4 | Both paths printed |
| A5 | All four paths printed |
| A6 | No output, exit 1 |
| A7 | No output, exit 1 |
| A8 | No output, exit 1 |
Limitation: the teardown rules are prose for agents. The git and compose behaviors they depend on were proven by the Research experiments, not by an end-to-end plan run.
Activity
DocksDocks commentedon Oct 21, 2026
Plan review round 1
Plan-review: repair
yolo_teardown;plugins/plan-lifecycle/skills/productivity/yolo/SKILL.md— Landing step 8 - Step 3 changes cleanup but leaves yolo’s hardcoded merge-only question unchanged. That question does not disclose branch deletion, so its answer cannot provide the confirmation required by Goal andAGENTS.mdSecurity. - Include Landing step 8 in the cutover and reference the contract’s teardown-aware merge question, includinggit branch -d.contract_teardown- The project-label query does not identify every retained volume. The cited volume documentation covers named-volume labels; anonymous volumes can lack those labels and remain afterdocker compose down. Label-only discovery therefore leaves kept resources unreported. - Correct the research and require recording owned containers’ volume names/IDs before removing those containers, including anonymous volumes, for the Kept resources report.DocksDocks commentedon Oct 21, 2026
Plan review round 2
Plan-review: pass
None.
DocksDocks commentedon Oct 21, 2026
Code review round 1
Code-review: pass
Standards
Leave pull request openalso starts another yolo run, step 11 requiresarchivebefore Teardown. The helper rejects an open plan, so this sequence cannot complete as written. - Finish through step 9 or 10 as appropriate before starting the next run; archive only on the merged path.Spec
None.
docs(plan-lifecycle): tear down what a plan created
DocksDocks commentedon Oct 21, 2026
Code review round 2
Code-review: pass
Standards
Leave pull request openalso starts another yolo run, step 11 still requires “archive and run Teardown.” This retained path contradicts step 10:archivePlanrejects an open plan (plugins/plan-lifecycle/skills/productivity/plan-manager/scripts/plan.mjs:935), so the handoff attempts an invalid archive. Follow step 9 only forMerge now; otherwise complete step 10’s exit-without-merge teardown before starting the new run.Spec
None.