From d025ee340deee4aefbfccdd83869fdb4289b1c63 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Sun, 23 Aug 2026 13:20:41 -0400 Subject: [PATCH 1/3] feat(prm): make /prm the full task close-out, not just the merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /prm was stopping at the merge and handing back to the ship skills. It is now the last thing a human types for a task: merge, then close the task out end to end — Shipped comment, issue closed, PyAutoMind prompt moved active/ → complete/ (claim released, Mind pushed), worktree removed, merged branches deleted, and a ledger of what ran and what was skipped. Typing /prm authorizes all of it, so the run no longer stops to ask again. The close-out order is forced by the tooling and documented as such: prove every branch merged -> issue -> Mind record -> worktree_remove -> branches. worktree_remove refuses while active.md still claims the task, which is exactly why the Mind step precedes it. Guards that stop the run instead of pressing on: - an unmerged sibling branch with no open PR (the shipped-in-waves trap — a complete/ record is a write-up, not a merge receipt) - a worktree_remove refusal (dirty repo, stale claim); never PYAUTO_WT_FORCE=1 - a worktree holding gitignored data products — the one question /prm asks, and only when such files exist (2026-07-09: a reduced dataset + archive cache were destroyed by exactly this cleanup) reference.md gains the mechanics: per-branch merge proof, `gh api -X PATCH` for the close (gh issue close is broken in 2.4.0), lifecycle.py record with the BARE --prompt filename plus the three effects to verify, worktree_root_path and the data-product check, and remote branch deletion proven with ls-remote. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UrgmL8WMK82GxQJjPL8ZCs --- AGENTS.md | 3 +- skills/COMMANDS.md | 22 +++-- skills/prm/SKILL.md | 17 ++-- skills/prm/prm.md | 70 +++++++++++--- skills/prm/reference.md | 120 +++++++++++++++++++++--- skills/ship_library/ship_library.md | 7 +- skills/ship_workspace/ship_workspace.md | 4 +- 7 files changed, 194 insertions(+), 49 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index edded74..670c9bd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -170,7 +170,8 @@ via `/route`, and it routes to the right agent; normal usage never says `/docs` and `/research` route through the dev-flow with their PyAutoMind work-type fixed (no dedicated conductor — added only on demonstrated need, never for symmetry); `/wake_up` composes sync + `/health` + `/hygiene`; `/prm` composes the -end-of-task wrap-up (CI green → merge → `ship_*` completion); `/brain +end-of-task close-out (CI green → merge → issue closed → Mind `active/` → +`complete/` → worktree and branches removed); `/brain ` is the raw passthrough. Every command routes **through** the Brain; none replaces it. diff --git a/skills/COMMANDS.md b/skills/COMMANDS.md index f1847d6..e9a16cc 100644 --- a/skills/COMMANDS.md +++ b/skills/COMMANDS.md @@ -85,14 +85,20 @@ call, so the Brain is not bypassed): local-only steps. Interactive/terminal only (the automated morning webhooks are separate). -- **`/prm`** — the wrap-up shortcut: *"PR, CI green, then merge"*. Watches a - feature PR's checks until **every** workflow run and **every** matrix leg for - the head sha is green, merges in library-first order, then hands back to the - `ship_*` completion steps (Shipped comment, `lifecycle.py record`, cleanup). - Three keystrokes for the sentence the human types at the end of nearly every - task. Owns no gate of its own — it refuses on red/pending/conflicting and on an - unmerged upstream library PR, and asks before closing the issue. Pure `gh`, so - it runs on the CLI, mobile Claude Code chat and Codex alike. +- **`/prm`** — the wrap-up door and **full task close-out**: *"PR, CI green, then + merge"* — the last thing a human types for a task. Watches the feature PR's + checks until **every** workflow run and **every** matrix leg for the head sha is + green, merges in library-first order, then closes the task out end to end: + Shipped comment, issue closed, `lifecycle.py record` moving the prompt + `active/` → `complete/` (claim released, Mind pushed), `worktree_remove`, merged + branches deleted, and a ledger of what it did. Typing `/prm` authorizes all of + it, so it does not stop to ask again. Owns no gate of its own — it refuses on + red/pending/conflicting checks, an unmerged upstream library PR, an unmerged + sibling branch (the shipped-in-waves trap), or a `worktree_remove` refusal, and + asks exactly once before deleting a worktree holding irreplaceable data + products. Mostly `gh`, so the merge/issue half runs on the CLI, mobile Claude + Code chat and Codex alike; the worktree half is local-only and is reported as + outstanding elsewhere. **5. Maintenance doors** — periodic sweeps that reason about accumulated debris and then execute their own cleanup after per-bucket human confirmation. They own diff --git a/skills/prm/SKILL.md b/skills/prm/SKILL.md index c371e93..eb8d859 100644 --- a/skills/prm/SKILL.md +++ b/skills/prm/SKILL.md @@ -1,14 +1,17 @@ --- name: prm -description: The wrap-up shortcut — "PR, CI green, then merge". Watch a feature PR's checks until every workflow run and every matrix leg is green, then merge it (library-first gate honoured) and finish the ship — Mind record, issue comment, cleanup. Use when the user says merge this PR once CI is green, or types /prm. Runs anywhere gh is authenticated: CLI, mobile Claude Code chat, Codex. +description: The last thing you type for a task — "PR, CI green, then merge", then the full close-out. Watches a feature PR's checks until every workflow run and every matrix leg is green, merges (library-first gate honoured), closes the issue, moves the PyAutoMind prompt active/ → complete/, removes the task worktree and deletes the merged branches. Use when the user says merge this PR once CI is green, close this task out, or types /prm. Runs anywhere gh is authenticated: CLI, mobile Claude Code chat, Codex. --- -# /prm — PR, CI green, then merge +# /prm — PR, CI green, then merge, then close the task out -Follow [`prm.md`](prm.md) exactly; gh mechanics in [`reference.md`](reference.md). +Follow [`prm.md`](prm.md) exactly; gh + close-out mechanics in +[`reference.md`](reference.md). Composition door — it owns no agent and re-derives nothing: the merge gates come -from `/ship_library` / `/ship_workspace`, the CI verdict from GitHub Actions. -Typing `/prm` **is** the merge authorization; the skill still refuses to merge on -red, pending, conflicting, or an unmerged upstream library PR, and still asks -before closing the issue. +from `/ship_library` / `/ship_workspace`, the CI verdict from GitHub Actions, the +lifecycle from PyAutoMind. Typing `/prm` authorizes the **whole** close-out — +merge, issue close, `active/` → `complete/`, worktree and branch removal — so it +runs to the end without asking again. It still refuses on red, pending, +conflicting, an unmerged upstream library PR, or an unmerged sibling branch, and +asks once before deleting a worktree that holds irreplaceable data products. diff --git a/skills/prm/prm.md b/skills/prm/prm.md index 2cd91be..1201dc1 100644 --- a/skills/prm/prm.md +++ b/skills/prm/prm.md @@ -1,8 +1,10 @@ # /prm — PR, CI green, then merge -The end-of-task shortcut for "PR CI green then merge" — three keystrokes instead -of the sentence. It watches the feature PR's checks, merges the moment they are -genuinely green, and finishes the ship (Mind record, issue comment, cleanup). +The end-of-task shortcut for "PR CI green then merge" — and the **full task +close-out**: the last thing you type for a task. It watches the feature PR's +checks, merges the moment they are genuinely green, then closes the task out +completely — issue closed, PyAutoMind moved `active/` → `complete/`, worktree +removed, branches deleted — and hands back a ledger of what it did. Shared routing context: `PyAutoBrain/skills/COMMANDS.md`. gh mechanics + snippets: [`reference.md`](reference.md). @@ -90,18 +92,54 @@ Green on every leg → merge, in this order: Never force, never override a protection, never rewrite history. If a merge is refused by GitHub, report the reason verbatim and stop. -### 5. Finish the ship - -Hand back to the ship skills' completion steps — do not re-invent them: - -- Post the "Shipped" comment on the issue (templates: `../ship_library/reference.md` - → "Issue comments + Mind state"). -- Write the dated completion record — `PyAutoMind/scripts/lifecycle.py record` - (also refreshes the index and prunes the `active.md` entry) — and push Mind. -- **Ask before closing the issue.** Merging is what `/prm` was typed for; closing - is a separate decision the human makes. -- **Local only:** offer the post-merge cleanup (worktree removal, local branch - deletion) per the ship skills; on mobile/codex note it as still pending. +### 5. Close the task out + +Typing `/prm` authorizes the whole close-out — merge **and** issue close **and** +cleanup. Run all of it without asking again; the only questions are the guards in +step 6. Order is forced by the tooling, so do not reorder: + +1. **Prove every branch merged, per repo.** The task may have shipped in waves — + a completion record is a write-up, not a merge receipt. For each repo the task + claims, `merge-base --is-ancestor origin/feature/ origin/main` and + `rev-list --count origin/main..origin/feature/` (want 0). Any repo with + unmerged commits and no open PR → **stop the close-out** and report it; a + half-merged task must not be recorded complete. +2. **Issue** — post the "Shipped" comment (template: `../ship_library/reference.md` + → "Issue comments + Mind state"), then close it. `gh issue close` is broken in + this gh; use the REST path in [`reference.md`](reference.md). +3. **Mind: `active/` → `complete/`** — draft the completion body, then + `lifecycle.py record --date … --from-file … --prompt + --apply`. The `--prompt` argument is a **bare filename**; a path silently + no-ops. Verify all three effects (record has `## Original prompt`, the + `active/` prompt is gone, the `## ` entry left `active.md`), run + `lifecycle.py check`, then commit and push Mind — on `main`, and note that + `prompt_sync_push` stages `-A`, so check for unrelated work first. +4. **Worktree** — `worktree_remove ` (source `bin/worktree.sh`, `PYAUTO_MAIN` + set), never `rm -rf`. It refuses on a dirty repo and on a claim still + registered in `active.md` — which is exactly why step 3 comes first. +5. **Branches** — delete the remote `feature/` per proven-merged repo, plus + any local branch left in the canonical checkout. Never delete a branch whose + merge you did not prove in sub-step 1. +6. **Report the ledger** — PRs merged, issue closed, record path, `active.md` + released, worktree removed, branches deleted, and anything skipped. + +**Remote (mobile/codex):** sub-steps 1, 2 and 5 run over `gh`/`git ls-remote` as +usual. Mind (3) works if PyAutoMind is checked out; otherwise say the record is +pending. The worktree (4) is local-only — name it as outstanding rather than +implying it ran. + +### 6. The only guards that stop you + +Stop and report instead of pressing on when: + +- a branch in the task is **unmerged** with no open PR (step 5.1) — the waves trap; +- `worktree_remove` **refuses** (dirty repo, stale claim) — fix the cause, never + `PYAUTO_WT_FORCE=1` your way past it; +- the worktree holds **gitignored data products** (reduced datasets, caches, + `output/` fits) — removal destroys them and they may not be cheaply + re-derivable. List them with sizes and **ask once**: delete, or keep the + worktree and finish everything else. This is the one question `/prm` asks, and + only when such files exist. ## Notes @@ -111,3 +149,5 @@ Hand back to the ship skills' completion steps — do not re-invent them: red Heart is not something a merge shortcut may re-judge. - Under a `--auto` workflow run, merge stays human: `/prm` is a human-typed door and is never invoked by the autonomous queue. +- A task with no issue, no Mind prompt, or no worktree (a direct wiring change, + say) simply skips those sub-steps and says so — it is not an error. diff --git a/skills/prm/reference.md b/skills/prm/reference.md index 0647d20..dc59708 100644 --- a/skills/prm/reference.md +++ b/skills/prm/reference.md @@ -94,16 +94,110 @@ The library-first gate is `../ship_workspace/reference.md` → "Library-first me gate": a workspace PR linked to an upstream library PR may only merge once that PR reads `MERGED`. There is no workaround — not `--auto`, not `--admin`. -## Finishing - -Completion is the ship skills' contract, unchanged: - -- "Shipped" comment template → `../ship_library/reference.md` → "Issue comments + - Mind state". -- `python3 PyAutoMind/scripts/lifecycle.py record --prompt ` — - the argument is the **bare prompt filename**, not a path — then commit and push - Mind (on `main`, and check that first). -- Closing the issue is a separate human decision: ask, don't assume. -- Local post-merge cleanup (worktree removal, local + remote branch deletion) → - the `ship_library` / `ship_workspace` cleanup sections. On mobile/Codex, say - it is still pending rather than pretending it ran. +## The close-out + +### 1. Prove every branch merged (before recording anything) + +A `complete/` record is a write-up, not a merge receipt — a task that shipped in +waves gets its record on the first wave while later branches keep living on +origin. Prove it per repo, never from the record and never from a clean +`git status`: + +```bash +git -C fetch origin --quiet +git merge-base --is-ancestor origin/feature/ origin/main && echo MERGED || echo UNMERGED +git rev-list --count origin/main..origin/feature/ # want 0 +``` + +`UNMERGED` with no open PR → stop the close-out and report which repo. Squash +merges break `--is-ancestor`; fall back to the PR's `state=MERGED`. + +### 2. Issue: comment, then close + +`gh issue close` prints its usage string and exits non-zero in this gh (2.4.0) — +use the REST path: + +```bash +gh issue comment -R / --body "$(cat <<'EOF' +## Shipped + +EOF +)" +gh api -X PATCH repos///issues/ -f state=closed --jq .state # → "closed" +``` + +### 3. Mind: active/ → complete/ + +```bash +cd $PYAUTO_MAIN/PyAutoMind +git rev-parse --abbrev-ref HEAD # must be main — check BEFORE writing +python3 scripts/lifecycle.py record --date --from-file --prompt --apply +``` + +`--prompt` takes a **bare filename** (it resolves as `active/`); a path +like `active/foo.md` becomes `active/active/foo.md`, **exits 0 anyway**, and +leaves you with a record missing `## Original prompt` plus an orphan in +`active/`. Success prints `(+folds active/)`. Verify all three effects — +none of them is announced on failure: + +```bash +grep -c "## Original prompt" complete///.md # want 1 +ls active/.md # want "No such file" +grep -n "^## " active.md # want no match +python3 scripts/lifecycle.py check # want clean exit +``` + +Then commit + push. `prompt_sync_push` runs `git add -A`, so check for unrelated +work first and use explicit pathspecs if any exists: + +```bash +git status --short # unrelated work? +source scripts/prompt_sync.sh && prompt_sync_push "complete: " +``` + +### 4. Worktree + +Removal deletes the whole task root — **including gitignored `output/`, +`cache/`, and downloaded data** that only live there (a reduced dataset + a +55-frame archive cache were destroyed this way on 2026-07-09). Look before you +remove, and ask once if anything real is there: + +```bash +root=${PYAUTO_WT_ROOT:-$HOME/Code/PyAutoLabs-wt}/ # worktree_root_path +du -sh "$root"/*/output "$root"/*/cache 2>/dev/null +git -C "$root/" status --porcelain --ignored | grep '^!!' | head -20 +``` + +Then remove it properly — never `rm -rf`: + +```bash +export PYAUTO_MAIN=$HOME/Code/PyAutoLabs +source PyAutoBrain/bin/worktree.sh && worktree_remove +``` + +It **refuses** on a dirty repo, and on a merged task whose `active.md` claim is +still registered — that refusal means step 3 has not finished, so fix the cause. +`PYAUTO_WT_FORCE=1` exists for abandoned/unmerged work only; a close-out never +needs it. A `PyAutoLabs-wt//` dir whose worktrees are already gone survives as a shell +of symlinks + `activate.sh` and `git worktree list` will not name it — a +directory listing is the only way to find it. + +### 5. Branches + +Local branches go with the worktree; the remote ones do not: + +```bash +git -C push origin --delete feature/ +git -C branch -d feature/ # if one survives in the canonical checkout +git -C fetch --prune +``` + +Only branches proven merged in step 1. `git ls-remote --heads origin feature/` +is the ground truth that the delete landed. + +### 6. The ledger + +Report, per line: PR(s) merged (URL + `MERGED`), issue closed (number + state), +record path under `complete///`, `active.md` claim released, worktree +removed, branches deleted, and **anything skipped** with the reason. A close-out +that quietly skipped a step reads exactly like one that finished. diff --git a/skills/ship_library/ship_library.md b/skills/ship_library/ship_library.md index 84b5011..fac2184 100644 --- a/skills/ship_library/ship_library.md +++ b/skills/ship_library/ship_library.md @@ -112,7 +112,8 @@ next human (or queued) launch. - This skill ships **library source only** — workspace scripts/notebooks go through `/ship_workspace`. -- Waiting on CI before the merge is [`/prm`](../prm/prm.md) — the wrap-up door - that judges every run/leg, merges in library-first order, and returns here for - the completion steps. +- Waiting on CI before the merge is [`/prm`](../prm/prm.md) — the close-out door + that judges every run/leg, merges in library-first order, then runs this + skill's completion contract itself (Shipped comment, issue closed, Mind record, + worktree + branches removed). - Never skip the readiness gate; never `--no-verify`; fix the underlying issue. diff --git a/skills/ship_workspace/ship_workspace.md b/skills/ship_workspace/ship_workspace.md index 3ac92e2..a037b8f 100644 --- a/skills/ship_workspace/ship_workspace.md +++ b/skills/ship_workspace/ship_workspace.md @@ -100,7 +100,7 @@ and prunes the `active.md` entry), and push Mind. Templates in - Workspace scripts/notebooks/configs only — never library source. - If CI is still running when step 5 arrives, [`/prm`](../prm/prm.md) is the door - that waits for every run/leg, merges behind the library-first gate, and comes - back to step 6. + that waits for every run/leg, merges behind the library-first gate, and then + performs step 6 plus the post-merge cleanup itself. - Only edit `scripts/`; notebooks are regenerated. - Never skip the readiness gate or the library-first merge gate. From 97fafac4e315ca09f81db775a794e36d461b5371 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Sun, 23 Aug 2026 13:23:11 -0400 Subject: [PATCH 2/3] chore(discovery): commit the board skill's .claude/.codex links (drift from #253) `install.sh --check-project-discovery` flagged skills/board as present with no committed discovery links, so /board would not register in web/cloud sessions where the user-level symlinks do not travel. Regenerated. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UrgmL8WMK82GxQJjPL8ZCs --- .claude/commands/board.md | 1 + .claude/skills/board | 1 + .codex/skills/board | 1 + 3 files changed, 3 insertions(+) create mode 120000 .claude/commands/board.md create mode 120000 .claude/skills/board create mode 120000 .codex/skills/board diff --git a/.claude/commands/board.md b/.claude/commands/board.md new file mode 120000 index 0000000..1b7fccb --- /dev/null +++ b/.claude/commands/board.md @@ -0,0 +1 @@ +../../skills/board/board.md \ No newline at end of file diff --git a/.claude/skills/board b/.claude/skills/board new file mode 120000 index 0000000..f2f436a --- /dev/null +++ b/.claude/skills/board @@ -0,0 +1 @@ +../../skills/board \ No newline at end of file diff --git a/.codex/skills/board b/.codex/skills/board new file mode 120000 index 0000000..f2f436a --- /dev/null +++ b/.codex/skills/board @@ -0,0 +1 @@ +../../skills/board \ No newline at end of file From 9cb24d656027c90e92fe42cd2b34a54108da7656 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Sun, 23 Aug 2026 13:23:38 -0400 Subject: [PATCH 3/3] docs(prm): an empty run list usually means the PR is CONFLICTING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub cannot build a merge ref for a DIRTY pull request, so no pull_request run is created at all — the enumeration comes back empty and looks like 'no failures'. Observed on PR #255 itself: zero runs while conflicting, one run queued seconds after main was merged in. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UrgmL8WMK82GxQJjPL8ZCs --- skills/prm/reference.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/skills/prm/reference.md b/skills/prm/reference.md index dc59708..3a3e54d 100644 --- a/skills/prm/reference.md +++ b/skills/prm/reference.md @@ -60,8 +60,13 @@ gh pr view $pr -R $repo --json state,mergeable,mergeStateStatus,reviewDecision,s `DIRTY` → conflicts. Only `CLEAN` merges without a human decision. - `mergeable`: `CONFLICTING` stops the run regardless of check colour. -An empty run list is not green — it means no workflow fired for that sha (a -docs-only path filter, a skipped event, or Actions being down). Say which. +An empty run list is not green — it means no workflow fired for that sha. The +usual causes, in order of likelihood: **the PR is `CONFLICTING`/`DIRTY`**, so +GitHub cannot build the merge ref and no `pull_request` run is created (observed +2026-08-23 on this very skill's PR — merge `main` in, and the runs appear on the +new head); a path filter excluded the change; the branch's workflow only fires on +`main`; or Actions is degraded. Say which one it is rather than reporting +"no failures". ## A red leg: grab the log *now*