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 diff --git a/AGENTS.md b/AGENTS.md index 042cbe5..703acec 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); `/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 8dea0ad..4bcae7a 100644 --- a/skills/COMMANDS.md +++ b/skills/COMMANDS.md @@ -84,14 +84,20 @@ call, so the Brain is not bypassed): destructive cleanup for approval. 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..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* @@ -94,16 +99,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 +## The close-out -Completion is the ship skills' contract, unchanged: +### 1. Prove every branch merged (before recording anything) -- "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. +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.