Skip to content

Commit 5724107

Browse files
committed
feat(prm): make the dashboard leg part of the close-out
Shipped and half-shipped tasks keep showing up on the PyAutoMind dashboard as pickable backlog. The render was never the problem: `intake dashboard --check` on 2026-08-25 reported the page current, while `intake reconcile` over the same tree ranked 24 suspects of 138 scanned. dashboard_refresh.yml self-heals a stale *render* and nothing else — a prompt the merge finished and nobody retired renders faithfully, and no workflow can tell the difference. So the gap is at close-out, and /prm is the door that owns close-out. Step 5 gains a Mind sub-step 4, "leave the page true": - sweep the slug and prompt filename across draft/, active/, epics.md and the registry files, repointing what the merge falsified (a blocked-by now unblocked, an epic phase now done, a superseded-by chain that now ends in a record); - reconcile the neighbourhood with `intake reconcile draft/<work-type>/<target>` — folder-scoped, since both runs are fast but the bare one hands back 24 suspects spanning work this task never touched, which /prm has no proof for and cannot adjudicate without a second question. Proof retires, resemblance reports: a sibling whose issue this PR closed or whose scope sits inside the record gets its own record and a git rm; one that merely looks alike gets a ledger line and the /intake reconcile prefix; - regenerate, and commit the pages *with* the record. The workflow fallback is strictly worse — its heal commit is made with GITHUB_TOKEN, so it triggers no other workflow and has to dispatch pages_dashboard.yml itself. Sub-step 3 also gains the half-shipped rule: record the scope you merged, not the scope you filed. `lifecycle.py check` compares active.md slugs against complete/ records and knows nothing about how much of a prompt's scope the merge covered, so a campaign prompt recorded whole on a phase-1 merge is drift no guard catches. Record what shipped; re-file the remainder as a fresh draft/ prompt pointing back at the record. Why this is a skill step rather than a `lifecycle.py record` step (reference.md says so where the question arises): complete/index.md and the active.md prune were both folded into `record` for exactly this "easy to forget" reason, but the dashboard cannot follow — the state is Mind's and the renderer is Brain's, and a Mind script importing a Brain module inverts the organ boundary. That is why dashboard_refresh.yml checks out both repos. Worktree/branches/ledger renumber to 5/6/7 in prm.md and reference.md. The matching PyAutoMind doc correction ships on the same branch there. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011QFJaycmUfaEhR4XoZajy9
1 parent af1a439 commit 5724107

5 files changed

Lines changed: 190 additions & 30 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ via `/route`, and it routes to the right agent; normal usage never says
171171
work-type fixed (no dedicated conductor — added only on demonstrated need, never
172172
for symmetry); `/prm` composes the
173173
end-of-task close-out (CI green → merge → issue closed → Mind `active/`
174-
`complete/` → worktree and branches removed); `/brain
174+
`complete/` → dashboard reconciled and regenerated → worktree and branches
175+
removed); `/brain
175176
<agent>` is the raw passthrough. Every command routes **through** the Brain;
176177
none replaces it.
177178

skills/COMMANDS.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,14 @@ call, so the Brain is not bypassed):
8989
checks until **every** workflow run and **every** matrix leg for the head sha is
9090
green, merges in library-first order, then closes the task out end to end:
9191
Shipped comment, issue closed, `lifecycle.py record` moving the prompt
92-
`active/``complete/` (claim released, Mind pushed), `worktree_remove`, merged
93-
branches deleted, and a ledger of what it did. Typing `/prm` authorizes all of
94-
it, so it does not stop to ask again. Owns no gate of its own — it refuses on
92+
`active/``complete/` (claim released, Mind pushed), the Mind's
93+
`dashboard.md` reconciled and regenerated in that same commit,
94+
`worktree_remove`, merged branches deleted, and a ledger of what it did.
95+
Typing `/prm` authorizes all of it, so it does not stop to ask again. The
96+
dashboard leg is the one thing here nothing else covers: `dashboard_refresh.yml`
97+
self-heals a stale *render*, never a prompt the merge finished and nobody
98+
retired, so a close-out that skips it is how shipped and half-shipped tasks
99+
pile up on the page as pickable backlog. Owns no gate of its own — it refuses on
95100
red/pending/conflicting checks, an unmerged upstream library PR, an unmerged
96101
sibling branch (the shipped-in-waves trap), or a `worktree_remove` refusal, and
97102
asks exactly once before deleting a worktree holding irreplaceable data

skills/prm/SKILL.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: prm
3-
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.
3+
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/, reconciles and regenerates the Mind dashboard, 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.
44
---
55

66
# /prm — PR, CI green, then merge, then close the task out
@@ -10,8 +10,9 @@ Follow [`prm.md`](prm.md) exactly; gh + close-out mechanics in
1010

1111
Composition door — it owns no agent and re-derives nothing: the merge gates come
1212
from `/ship_library` / `/ship_workspace`, the CI verdict from GitHub Actions, the
13-
lifecycle from PyAutoMind. Typing `/prm` authorizes the **whole** close-out —
14-
merge, issue close, `active/``complete/`, worktree and branch removal — so it
15-
runs to the end without asking again. It still refuses on red, pending,
13+
lifecycle from PyAutoMind, the dashboard render from `/intake`. Typing `/prm`
14+
authorizes the **whole** close-out — merge, issue close, `active/`
15+
`complete/`, the dashboard reconciled and regenerated, worktree and branch
16+
removal — so it runs to the end without asking again. It still refuses on red, pending,
1617
conflicting, an unmerged upstream library PR, or an unmerged sibling branch, and
1718
asks once before deleting a worktree that holds irreplaceable data products.

skills/prm/prm.md

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
The end-of-task shortcut for "PR CI green then merge" — and the **full task
44
close-out**: the last thing you type for a task. It watches the feature PR's
55
checks, merges the moment they are genuinely green, then closes the task out
6-
completely — issue closed, PyAutoMind moved `active/``complete/`, worktree
7-
removed, branches deleted — and hands back a ledger of what it did.
6+
completely — issue closed, PyAutoMind moved `active/``complete/`, the Mind's
7+
`dashboard.md` reconciled and regenerated, worktree removed, branches deleted —
8+
and hands back a ledger of what it did.
89

910
Shared routing context: `PyAutoBrain/skills/COMMANDS.md`.
1011
gh mechanics + snippets: [`reference.md`](reference.md).
@@ -15,8 +16,11 @@ Every rule here already has an owner. The merge gates belong to
1516
[`../ship_library/ship_library.md`](../ship_library/ship_library.md) and
1617
[`../ship_workspace/ship_workspace.md`](../ship_workspace/ship_workspace.md)
1718
(library-first gate, issue completion, Mind state); the verdict belongs to GitHub
18-
Actions. `/prm` only sequences them. It never re-runs the readiness gate, never
19-
edits code to make a check pass, and never opens a PR — that is `/ship_*`.
19+
Actions; the lifecycle moves belong to `PyAutoMind/scripts/lifecycle.py` and the
20+
dashboard render to [`../intake/intake.md`](../intake/intake.md). `/prm` only
21+
sequences them. It never re-runs the readiness gate, never edits code to make a
22+
check pass, never opens a PR — that is `/ship_*` — and never hand-writes a
23+
generated page.
2024

2125
## Usage
2226

@@ -122,27 +126,69 @@ step 6. Order is forced by the tooling, so do not reorder:
122126
`lifecycle.py record <slug> --date … --from-file … --prompt <bare-filename>
123127
--apply`. The `--prompt` argument is a **bare filename**; a path silently
124128
no-ops. Verify all three effects (record has `## Original prompt`, the
125-
`active/` prompt is gone, the `## <task>` entry left `active.md`), run
126-
`lifecycle.py check`, then commit and push Mind — on `main`, and note that
129+
`active/` prompt is gone, the `## <task>` entry left `active.md`).
130+
131+
**Record the scope you merged, not the scope you filed.** Where the merged
132+
PRs cover only part of what the prompt asked for — one phase of a campaign,
133+
three of five findings — neither recording the whole prompt complete nor
134+
leaving the whole prompt in `active/` is true. Record what shipped, and
135+
re-file the remainder as a fresh `draft/<work-type>/<target>/` prompt whose
136+
header points back at the record. A prompt recorded whole on a partial merge
137+
is exactly what puts a half-done task on the dashboard as pickable backlog.
138+
4. **Mind: leave the page true** — the close-out is not finished when the claim
139+
is released; it is finished when `dashboard.md` stops offering this work.
140+
`dashboard_refresh.yml` self-heals a stale *render*, never a stale *prompt*,
141+
so this leg belongs to `/prm` and to nothing else — skipping it is how
142+
shipped and half-shipped tasks accumulate on the page:
143+
144+
1. **Sweep what this task is named in.** Grep the slug and the prompt
145+
filename across `draft/`, `active/`, `epics.md` and the registry files.
146+
Repoint or remove every hit the merge falsified: a `blocked-by:` this PR
147+
unblocked, an epic phase now done, a `superseded-by:` chain that now ends
148+
in a record.
149+
2. **Reconcile the neighbourhood.** Run `pyauto-brain intake reconcile
150+
draft/<work-type>/<target>` over the folder the shipped prompt came from,
151+
plus any folder the merged diff lands in — folder-scoped it is a handful
152+
of prompts, not the 130-odd of the whole backlog. Then: **proof retires,
153+
resemblance reports.** A sibling the merge provably covers — its issue
154+
closed by this PR, its scope inside the record you just wrote, a merged PR
155+
named in its own body — gets its own record and `git rm` under the same
156+
`/prm` authorization. One that merely *looks* alike gets a ledger line
157+
naming it and the `/intake reconcile` door; never retire on resemblance,
158+
and never turn this sweep into a second question (step 6 owns the only one).
159+
3. **Regenerate.** `pyauto-brain intake --apply dashboard` — never hand-edit
160+
`dashboard.md` / `dashboard.html`. Commit the render **with** the record,
161+
in one commit: the workflow's heal commit is made with `GITHUB_TOKEN`,
162+
which triggers no other workflow and has to re-dispatch
163+
`pages_dashboard.yml` by hand, so a self-healed page reaches Pages later
164+
than a correctly-committed one — and heals nothing you retired in 1 and 2.
165+
166+
Then `lifecycle.py check` and push Mind — on `main`, and note that
127167
`prompt_sync_push` stages `-A`, so check for unrelated work first.
128-
4. **Worktree**`worktree_remove <task>` (source `bin/worktree.sh`, `PYAUTO_MAIN`
168+
5. **Worktree**`worktree_remove <task>` (source `bin/worktree.sh`, `PYAUTO_MAIN`
129169
set), never `rm -rf`. It refuses on a dirty repo and on a claim still
130170
registered in `active.md` — which is exactly why step 3 comes first.
131-
5. **Branches** — delete the remote `feature/<task>` per proven-merged repo, plus
171+
6. **Branches** — delete the remote `feature/<task>` per proven-merged repo, plus
132172
any local branch left in the canonical checkout. Never delete a branch whose
133173
merge you did not prove in sub-step 1. **Where the environment cannot delete
134174
remote refs** (proxied web session, above), this sub-step does not exist:
135175
attempt nothing, and report nothing about it. `/repo_cleanup` finds those
136176
branches on origin by itself, so silence here loses nothing.
137-
6. **Report the ledger** — PRs merged, issue closed, record path, `active.md`
138-
released, worktree removed, branches deleted, and anything skipped. Branches
139-
are simply absent from the ledger where sub-step 5 did not apply.
177+
7. **Report the ledger** — PRs merged, issue closed, record path, `active.md`
178+
released, **dashboard regenerated** (plus any sibling prompt retired, and any
179+
suspect left standing with its `/intake reconcile` prefix), worktree removed,
180+
branches deleted, and anything skipped. Branches are simply absent from the
181+
ledger where sub-step 6 did not apply.
140182

141183
**Remote (mobile/codex):** sub-steps 1 and 2 run over `gh`/`git ls-remote` as
142184
usual. Mind (3) works if PyAutoMind is checked out; otherwise say the record is
143-
pending. The worktree (4) is local-only — name it as outstanding rather than
144-
implying it ran. Branches (5) delete normally from mobile and Codex; a proxied
145-
web session drops the sub-step silently.
185+
pending. The dashboard leg (4) needs **both** checkouts — the state is Mind's,
186+
the renderer is Brain's; with only Mind, do its sweep and reconcile legs and say
187+
the render
188+
is left to `dashboard_refresh.yml`; with neither, say the whole leg is pending
189+
rather than implying the page is true. The worktree (5) is local-only — name it
190+
as outstanding rather than implying it ran. Branches (6) delete normally from
191+
mobile and Codex; a proxied web session drops the sub-step silently.
146192

147193
### 6. The only guards that stop you
148194

skills/prm/reference.md

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,96 @@ grep -n "^## <task>" active.md # want no match
152152
python3 scripts/lifecycle.py check # want clean exit
153153
```
154154

155+
Do **not** commit yet — the dashboard leg below belongs in the same commit.
156+
157+
`lifecycle.py check` will not catch the partial-ship case: it compares
158+
`active.md` slugs against `complete/` records and knows nothing about how much
159+
of a prompt's scope the merge actually covered. That judgement is yours. Where
160+
the merge covered part of it, write the record for the shipped part and re-file
161+
the rest before committing:
162+
163+
```bash
164+
# the remainder, as a fresh backlog prompt pointing back at the record
165+
cat > draft/<work-type>/<target>/<remainder>.md <<'EOF'
166+
# <remainder title>
167+
168+
- Status: split out of `<slug>` at close-out — phases 1-2 shipped in
169+
`complete/<YYYY>/<MM>/<slug>.md` (<PR links>); this is what remains.
170+
...
171+
EOF
172+
```
173+
174+
### 4. Mind: leave the page true
175+
176+
`dashboard.md` / `dashboard.html` are generated from `draft/`, `active/` and the
177+
registry files. `dashboard_refresh.yml` re-renders them on any push touching
178+
those paths, so the *render* self-heals — but a prompt the merge finished and
179+
nobody retired renders faithfully, as pickable backlog. That is the drift this
180+
step exists for, and `intake reconcile` measures it: the whole-backlog run on
181+
2026-08-25 ranked **24 suspects of 138 scanned** while the page itself was
182+
current.
183+
184+
**Sweep what this task is named in** — the merge falsifies more than its own
185+
prompt:
186+
187+
```bash
188+
cd $PYAUTO_MAIN/PyAutoMind
189+
grep -rn "<slug>\|<prompt-filename>" draft/ active/ epics.md \
190+
active.md planned.md parked.md condemned.md
191+
```
192+
193+
Repoint or remove each hit: a `blocked-by:` this PR unblocked, an epic phase now
194+
done, a `superseded-by:` chain that now ends in a record.
195+
196+
**Reconcile the neighbourhood** — folder-scoped, not whole-backlog. Both are
197+
fast (~5s bare, <1s scoped); the cost is *adjudication*, not runtime. The bare
198+
run hands you 24 suspects spanning work this task never touched, none of which
199+
`/prm` has proof for — a backlog chore, and incompatible with running to the end
200+
without asking. A folder is the handful this merge could plausibly have
201+
finished:
202+
203+
```bash
204+
BRAIN=$PYAUTO_MAIN/PyAutoBrain/agents/conductors/intake/_intake.py
205+
python3 "$BRAIN" --mind . reconcile draft/<work-type>/<target>
206+
```
207+
208+
It is **read-only by design**: `--apply` is ignored with *"intake reconcile is
209+
read-only — retiring prompts stays human"*, and the emit closes with the same
210+
rule. Under `/prm` that human authorization is the typed `/prm` — but it extends
211+
only as far as *proof*:
212+
213+
| Evidence | Action |
214+
|---|---|
215+
| The sibling's issue was closed by this PR (`Closes #N`), or its scope sits inside the record you just wrote, or its own body names a now-merged PR | Write its record (`lifecycle.py record …`), `git rm` the prompt, repoint references |
216+
| `shared-identifiers` / `rare-topic-overlap` / `stale-status` only | Leave it filed; name it in the ledger with the prefix to re-run |
217+
218+
`record` serves a `draft/` prompt too, with one catch: `--prompt` resolves under
219+
`active/` only, so the fold does not happen — append the draft's full text under
220+
a `## Original prompt` heading in the `--from-file` body yourself, then `git rm`
221+
the draft. Everything else (`complete/index.md`, the `active.md` prune) is
222+
folded into `record` already.
223+
224+
**Why this is a skill step and not a `record` step.** Two chores that used to sit
225+
here were folded into `lifecycle.py record` precisely because a separate step was
226+
easy to forget — `complete/index.md` and the `active.md` prune, both after their
227+
own drift-alarm email storms. The dashboard cannot follow them: the state is
228+
Mind's but the renderer is Brain's (`agents/conductors/intake/_intake.py`), and a
229+
Mind script importing a Brain module inverts the organ boundary — which is why
230+
`dashboard_refresh.yml` checks out both repos to do it. So the render stays a
231+
step someone has to take, and `/prm` is the door that takes it.
232+
233+
**Regenerate, and commit the render with the record:**
234+
235+
```bash
236+
python3 "$BRAIN" --mind . --apply dashboard # writes dashboard.md + dashboard.html
237+
python3 "$BRAIN" --mind . dashboard --check # "…are current"; exit 1 = drift
238+
python3 scripts/lifecycle.py check
239+
```
240+
241+
`--check` exits **1** for drift and anything higher for a renderer failure
242+
(Brain/Mind version skew) — a non-1 code is not a stale page, so read the
243+
message rather than re-rendering.
244+
155245
Then commit + push. `prompt_sync_push` runs `git add -A`, so check for unrelated
156246
work first and use explicit pathspecs if any exists:
157247

@@ -160,7 +250,13 @@ git status --short # unrelated work?
160250
source scripts/prompt_sync.sh && prompt_sync_push "complete: <task>"
161251
```
162252

163-
### 4. Worktree
253+
One commit carrying the record, the retirements and the regenerated pages is the
254+
goal. The workflow's fallback is strictly worse: its heal commit is made with
255+
`GITHUB_TOKEN`, which triggers no other workflow, so it must dispatch
256+
`pages_dashboard.yml` itself for the Pages site to catch up — and it can only
257+
heal the render, never the retirements from the sweep above.
258+
259+
### 5. Worktree
164260

165261
Removal deletes the whole task root — **including gitignored `output/`,
166262
`cache/`, and downloaded data** that only live there (a reduced dataset + a
@@ -187,7 +283,7 @@ needs it. A `PyAutoLabs-wt/<task>/` dir whose worktrees are already gone survive
187283
of symlinks + `activate.sh` and `git worktree list` will not name it — a
188284
directory listing is the only way to find it.
189285

190-
### 5. Branches
286+
### 6. Branches
191287

192288
Local branches go with the worktree; the remote ones do not. **First establish
193289
whether this environment may delete remote refs at all** — one probe, once per
@@ -237,11 +333,22 @@ to work, treat it as the proxied case from that point on: **stop after the first
237333
refusal** and drop the sub-step — do not repeat the push per repo, and do not
238334
narrate the failure.
239335

240-
### 6. The ledger
336+
### 7. The ledger
241337

242338
Report, per line: PR(s) merged (URL + `MERGED`), issue closed (number + state),
243-
record path under `complete/<YYYY>/<MM>/`, `active.md` claim released, worktree
244-
removed, branches deleted, and **anything skipped** with the reason. A close-out
245-
that quietly skipped a step reads exactly like one that finished — with one
246-
deliberate exception: a sub-step the environment makes impossible (step 5 behind
247-
the proxy) is omitted outright rather than reported as skipped.
339+
record path under `complete/<YYYY>/<MM>/`, `active.md` claim released, the
340+
dashboard regenerated (and any sibling prompt retired, and any suspect left
341+
standing with the prefix to re-run), worktree removed, branches deleted, and
342+
**anything skipped** with the reason. A close-out that quietly skipped a step
343+
reads exactly like one that finished — with one deliberate exception: a sub-step
344+
the environment makes impossible (step 6 behind the proxy) is omitted outright
345+
rather than reported as skipped.
346+
347+
The dashboard lines carry the drift, so give them numbers rather than a verb:
348+
349+
```
350+
dashboard: regenerated (dashboard.md + dashboard.html, same commit as the record)
351+
retired: draft/bug/priors/15_transformed_message_logpdf_jacobian.md — PyAutoFit#1498 merged, scope inside this record
352+
standing: draft/bug/priors/14_replace_transform_stack_with_bijectors.md — resemblance only
353+
→ /intake reconcile draft/bug/priors
354+
```

0 commit comments

Comments
 (0)