Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/commands/board.md
1 change: 1 addition & 0 deletions .claude/skills/board
1 change: 1 addition & 0 deletions .codex/skills/board
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<agent>` is the raw passthrough. Every command routes **through** the Brain;
none replaces it.

Expand Down
22 changes: 14 additions & 8 deletions skills/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 10 additions & 7 deletions skills/prm/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
70 changes: 55 additions & 15 deletions skills/prm/prm.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down Expand Up @@ -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/<task> origin/main` and
`rev-list --count origin/main..origin/feature/<task>` (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 <slug> --date … --from-file … --prompt <bare-filename>
--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 `## <task>` 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 <task>` (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/<task>` 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

Expand All @@ -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.
125 changes: 112 additions & 13 deletions skills/prm/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*

Expand Down Expand Up @@ -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 <bare-filename>` —
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 <repo> fetch origin --quiet
git merge-base --is-ancestor origin/feature/<task> origin/main && echo MERGED || echo UNMERGED
git rev-list --count origin/main..origin/feature/<task> # 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 <n> -R <owner>/<repo> --body "$(cat <<'EOF'
## Shipped
<summary, PR links, what changed>
EOF
)"
gh api -X PATCH repos/<owner>/<repo>/issues/<n> -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 <slug> --date <YYYY-MM-DD> --from-file <body.md> --prompt <prompt.md> --apply
```

`--prompt` takes a **bare filename** (it resolves as `active/<prompt>`); 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/<name>)`. Verify all three effects —
none of them is announced on failure:

```bash
grep -c "## Original prompt" complete/<YYYY>/<MM>/<slug>.md # want 1
ls active/<prompt>.md # want "No such file"
grep -n "^## <task>" 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: <task>"
```

### 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}/<task> # worktree_root_path
du -sh "$root"/*/output "$root"/*/cache 2>/dev/null
git -C "$root/<repo>" 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 <task>
```

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/<task>/` 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 <repo> push origin --delete feature/<task>
git -C <repo> branch -d feature/<task> # if one survives in the canonical checkout
git -C <repo> fetch --prune
```

Only branches proven merged in step 1. `git ls-remote --heads origin feature/<task>`
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/<YYYY>/<MM>/`, `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.
7 changes: 4 additions & 3 deletions skills/ship_library/ship_library.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions skills/ship_workspace/ship_workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading