Skip to content

Commit 79d503c

Browse files
committed
merge: v2.6.4 — Compound V audit trail commit-discipline fix (9 points, 4 Codex rounds)
2 parents 44d0372 + ffd4e45 commit 79d503c

8 files changed

Lines changed: 82 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ All notable changes to **superpowers-v (Compound V)** are documented here.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses semantic versioning.
66

7+
## [2.6.4] — 2026-07-10
8+
9+
### Fixed — Compound V's own audit trail could be silently deleted, and `/v:status` could mislead
10+
11+
Two real incidents **noticed by Oscar Salcedo**, which a requested Codex cross-model hunt for
12+
"similar/adjacent bugs" grew into a full sweep of the same bug class across the orchestrator:
13+
14+
- **Data loss: an uncommitted run directory vanishes on worktree cleanup.** `docs/superpowers/execution/<run-id>/**` is documented as "the committed run substrate" (`execution-manifest.md`) — but nothing in the pipeline actually committed it. `superpowers:finishing-a-development-branch`'s cleanup step runs `git worktree remove` on **both** its Merge and Discard paths, which **silently deletes any uncommitted files** in that worktree — taking Compound V's own audit trail with it. After a restart, `/v:status` would then honestly (but confusingly) report "no orchestrator runs" for a repo that demonstrably had one.
15+
- **Misleading status message for non-Compound-V work.** When a repo had real prior work done via plain Superpowers `subagent-driven-development` (evidenced by `.superpowers/sdd/` task-brief/report/review artifacts) rather than Compound V's own manifest-driven dispatch, `/v:status`'s "no orchestrator runs" message read as "nothing happened here" — it had no visibility into that different, upstream-owned execution path. **Fixed with a cheap presence-check** (not a parse — that directory's format belongs to the base Superpowers plugin, not Compound V) that disambiguates the message without trying to understand or summarize its contents.
16+
17+
**The commit-discipline fix, after four rounds of Codex review, landed nine explicit commit points across the pipeline** (each closing a path where state could be written but never survive a worktree cleanup):
18+
1. `/v:orchestrate` — commits `manifest.yaml` + the initial `state.json` right after materializing them.
19+
2. `parallel-dispatcher` Step 7 — commits the run directory + memory/scorecard files in one shot **before** handing off to `finishing-a-development-branch` (the one point that can trigger the destructive cleanup). **Round 1 of review caught a bug in this very fix**: `state.json`'s phase was flipped to `MERGED` *after* the commit, so the committed record permanently lagged one phase behind reality — fixed by writing `MERGED` first, then committing everything together.
20+
3–7. `commands/v-epic.md` — a **separate, epic-level `epic-state.json`** (the epic's *only* resume mechanism, one level up from any single feature's run directory) was never committed anywhere. Five commit points added: after init, at every checkpoint (the default `MAX_FEATURES=1` stopping point after *every* feature), at epic-complete, at epic-blocked, and — caught in round 3 — after crash-reconcile (the `--status failed` "abandon and stop" path is terminal and doesn't otherwise pass through the checkpoint's commit).
21+
8. `commands/v-resume.md` — its own completion path didn't reference committing the recovered run substrate; a resume completing this way could re-lose the very state it just recovered.
22+
9. `commands/v-collect.md` — standalone use (re-checking an already-dispatched run without re-dispatching) rewrote `results/*.json` + `state.json` with no commit step at all.
23+
24+
`state-machine.md` documents the general "written to disk ≠ durable" principle tying it all together. Docs-only; no code changed. **Codex cross-model verification, four rounds:** round 1 found the `MERGED`-ordering bug plus the v-epic/v-resume/v-collect gaps; round 2 (broad hunt) confirmed the fix and found nothing new to add; round 3 caught the crash-reconcile gap; round 4 (narrow re-check) confirmed all nine commit points present, correctly scoped, and non-contradictory.
25+
726
## [2.6.3] — 2026-07-10
827

928
### Changed — Codex defaults bumped to the GPT-5.6 family (Sol/Terra/Luna)

agents/parallel-dispatcher.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ After every task is approved and every worktree job has merged back, dispatch ON
231231
- Cross-task integration works (Task 0's types are used correctly by parallel tasks) and the build is green
232232
- The composite change matches the spec + all three audits' constraints **and the manifest's feature-level `acceptance_criteria`** (the AC-gate for the run)
233233

234-
On PASS, advance `state.json` to `MERGED` and hand off to `superpowers:finishing-a-development-branch`.
234+
On PASS, proceed to Step 6 (post-run memory), then Step 7 (commit + `MERGED` + hand off). Do
235+
**not** advance `state.json` to `MERGED` yet — per [`state-machine.md`](../skills/compound-v/state-machine.md),
236+
`MERGED` means the run's substrate is actually merged and handed off, not just reviewed.
235237

236238
### Step 6 — Post-run memory (outcomes → scorecard)
237239

@@ -257,6 +259,29 @@ static default.
257259
The scorecard is regenerated each run and never hand-edited (unlike the human-curated
258260
`routing-lessons.md`); it emits no cost/token metrics.
259261

262+
### Step 7 — Advance to `MERGED`, commit EVERYTHING in that one commit, THEN hand off
263+
264+
Everything Steps 5–6 wrote — the run directory **and** the memory/scorecard files — is sitting
265+
on disk, not yet in git. **Write `state.json`'s phase as `MERGED` FIRST**, then stage and commit
266+
it together with the rest — one commit, so the committed record and the phase agree the moment
267+
this returns (committing the substrate *before* flipping the phase, or flipping the phase without
268+
re-committing it, both leave the git-recorded phase permanently one step behind reality):
269+
270+
```bash
271+
git add docs/superpowers/execution/<run-id>/ docs/superpowers/memory/task-outcomes.jsonl \
272+
docs/superpowers/memory/worker-performance.jsonl
273+
git commit -m "chore(v-dispatch): run <run-id> reviewed and merged"
274+
```
275+
276+
**This is not optional.** `finishing-a-development-branch`'s cleanup step (Options 1/Merge and
277+
4/Discard) runs `git worktree remove` on the branch this run happened in — that command silently
278+
deletes any *uncommitted* files, including an uncommitted run directory or memory update.
279+
Skipping this step means Compound V's own audit trail — the thing `state-machine.md` calls "the
280+
record" — and the scorecard's routing signal can both vanish the moment the branch is merged, and
281+
`/v:status` will report "no orchestrator runs" afterward even though one demonstrably happened (a
282+
real incident — noticed by Oscar Salcedo). **Only after this commit succeeds**, hand off to
283+
`superpowers:finishing-a-development-branch`.
284+
260285
## Output
261286

262287
Return a structured summary at the end of execution:

commands/v-collect.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ The run-dir layout and per-job `status` semantics are in [`skills/compound-v/sta
3939
- **INTEGRATION** — cross-job seams build, and the composite change satisfies the feature-level `acceptance_criteria`.
4040
DONE is gated on all three. Unresolvable reviewer ISSUES ⇒ HALT (do not merge).
4141

42-
5. **Update state + report.** Write `state.json` after each transition (`COLLECTED``REVIEWED`). Report: per-job scope verdict, the three review-pass outcomes, and whether the run is clear to merge. If clear, point at the merge step (worktree diffs apply into the main tree, then `superpowers:finishing-a-development-branch`). If BLOCKED, point at [`/v:resume {{args}}`](v-resume.md).
42+
5. **Update state + report.** Write `state.json` after each transition (`COLLECTED``REVIEWED`). **Commit what this command rewrote**`state.json` and the refreshed `results/*.json` — the same commit discipline as [`parallel-dispatcher`](../agents/parallel-dispatcher.md)'s Step 7: uncommitted files in a worktree are silently deleted by `finishing-a-development-branch`'s cleanup step, and `/v:collect` is explicitly usable **standalone** (re-checking an already-dispatched run), so don't assume a later step will commit on your behalf. Report: per-job scope verdict, the three review-pass outcomes, and whether the run is clear to merge. If clear, point at the merge step (worktree diffs apply into the main tree, then `superpowers:finishing-a-development-branch`). If BLOCKED, point at [`/v:resume {{args}}`](v-resume.md).
4343

4444
## Safety
4545

commands/v-epic.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ The epic model, run-dir layout, the final integration review, and the honesty bo
2424
--out docs/superpowers/execution/epics/<epic-id>/epic-state.json
2525
```
2626
`--require-specs` **refuses to start unless every feature has an existing `spec_path`** — the deterministic enforcement that no feature enters the autonomous loop without an approved spec. It also validates ids/refs/cycles/dups. A non-zero exit ⇒ fix and re-init; never hand-edit the state.
27+
- **Commit the epic-level files right after init**: `features.json` and the freshly-created `epic-state.json` are new, uncommitted files — `git add docs/superpowers/execution/epics/<epic-id>/features.json docs/superpowers/execution/epics/<epic-id>/epic-state.json && git commit -m "chore(v-epic): init epic <epic-id>"`. (Per-feature spec files are committed by `superpowers:brainstorming` itself when each spec is approved, in step 2 — no separate action needed for those.)
2728
2829
4. **The autonomous loop** (bounded by `MAX_FEATURES`). Repeat until no feature is runnable **or this invocation's budget is spent**:
2930
- **Ask for the next runnable feature:**
@@ -52,14 +53,14 @@ The epic model, run-dir layout, the final integration review, and the honesty bo
5253
--run-id <run-id> --state docs/superpowers/execution/epics/<epic-id>/epic-state.json
5354
```
5455
then **stop the loop** and go to step 6 (the epic is now blocked but resumable).
55-
5. **Checkpoint (human-in-the-loop cadence).** Count each completed feature against `MAX_FEATURES`. When this invocation's budget is spent, **STOP and report** `python3 scripts/compound-v-epic-state.py --stats --state <epic-state.json>` (done / remaining) so the human reviews the accumulated diff and re-runs `/v:epic` to continue. This is a *driver-enforced cadence*, not a token ceiling; with the default `MAX_FEATURES=1` the epic checkpoints after **every** feature.
56+
5. **Checkpoint (human-in-the-loop cadence).** Count each completed feature against `MAX_FEATURES`. When this invocation's budget is spent: **first commit `epic-state.json`** — `git add docs/superpowers/execution/epics/<epic-id>/epic-state.json && git commit -m "chore(v-epic): checkpoint <epic-id> (<N> features done)"` — **then STOP and report** `python3 scripts/compound-v-epic-state.py --stats --state <epic-state.json>` (done / remaining) so the human reviews the accumulated diff and re-runs `/v:epic` to continue. **The commit is not optional**: each feature's own v1.0 run already commits *that feature's* run directory (parallel-dispatcher's Step 7), but epic-state.json itself (which `run_id`/`status` each feature is at — the epic's *only* resume mechanism) lives one level up and is never covered by that. A checkpoint is exactly the moment control returns to a human who might close the session or clean up the worktree — an uncommitted `epic-state.json` at that instant means a later `/v:epic <epic-id>` has no record of what's done, and a `finishing-a-development-branch` cleanup can erase it outright. This is a *driver-enforced cadence*, not a token ceiling; with the default `MAX_FEATURES=1` the epic checkpoints (and commits) after **every** feature.
5657
- **If `feature` is null**, branch on `reason` (step 5/6).
5758
58-
5. **Epic complete** (`reason == "epic complete"`). All features are `done`. Run a **final cross-feature integration review**: the *whole accumulated diff* on the branch against the **epic's** acceptance criteria — not the per-feature ACs (those already passed in each feature's own review), but the cross-feature contracts: do the features compose, do shared boundaries line up, is the product coherent end-to-end. On PASS, hand to `superpowers:finishing-a-development-branch` (merge / PR / cleanup options). On ISSUES, surface them and stay resumable.
59+
5. **Epic complete** (`reason == "epic complete"`). All features are `done`. Run a **final cross-feature integration review**: the *whole accumulated diff* on the branch against the **epic's** acceptance criteria — not the per-feature ACs (those already passed in each feature's own review), but the cross-feature contracts: do the features compose, do shared boundaries line up, is the product coherent end-to-end. On PASS, **commit `epic-state.json` (same as the checkpoint step, if it isn't already)**, then hand to `superpowers:finishing-a-development-branch` (merge / PR / cleanup options) — never hand off with an uncommitted `epic-state.json`. On ISSUES, surface them and stay resumable.
5960
60-
6. **Epic blocked** (`reason` starts with `epic blocked` — a feature `failed` or an unmet dependency). **Stop and surface it.** Print `compound-v-epic-state.py --summary --state <epic-state.json>` so the user sees exactly which feature failed and what it blocks. The epic stays **resumable**: after the user fixes the failed feature (or its spec/partition), retry it (`--update --feature <id> --status pending`) and re-run `/v:epic <epic-id>` (or the same brief) — step 3 detects the existing `epic-state.json` and continues; only `pending` features run, the `done` ones are skipped.
61+
6. **Epic blocked** (`reason` starts with `epic blocked` — a feature `failed` or an unmet dependency). **Commit `epic-state.json` (same as the checkpoint step, if it isn't already), then stop and surface it.** Print `compound-v-epic-state.py --summary --state <epic-state.json>` so the user sees exactly which feature failed and what it blocks. The epic stays **resumable** — but only if the `failed` status actually made it into git before anyone touches the worktree. After the user fixes the failed feature (or its spec/partition), retry it (`--update --feature <id> --status pending`) and re-run `/v:epic <epic-id>` (or the same brief) — step 3 detects the existing `epic-state.json` and continues; only `pending` features run, the `done` ones are skipped.
6162
62-
**Epic needs reconcile** (`reason` starts with `epic needs reconcile` — a feature is still `running`). Because epic mode is **sequential**, `--next` is only ever called between features, so a `running` feature on resume means that feature's run **crashed mid-pipeline**. Do not route around it. **Reconcile by resuming first — don't discard half-built work:** the crashed feature ran a *normal v1.0 run* with its own crash-resume, so run **[`/v:resume <run-id>`](v-resume.md)** to re-dispatch only that run's incomplete jobs; if it completes, mark the feature **`--status done`**. **If the feature's `run_id` is null** (the crash happened before the run-id was recorded — see step 4.1 — or it is an old state), there is nothing to resume → restart it with **`--status pending`**. Only if a resumed run cannot be recovered, fall back to **`--status pending`** (full restart from the spec) or **`--status failed`** (abandon and stop). Never leave a feature `running` across a resume — the epic will not advance until the stale run is reconciled.
63+
**Epic needs reconcile** (`reason` starts with `epic needs reconcile` — a feature is still `running`). Because epic mode is **sequential**, `--next` is only ever called between features, so a `running` feature on resume means that feature's run **crashed mid-pipeline**. Do not route around it. **Reconcile by resuming first — don't discard half-built work:** the crashed feature ran a *normal v1.0 run* with its own crash-resume, so run **[`/v:resume <run-id>`](v-resume.md)** to re-dispatch only that run's incomplete jobs; if it completes, mark the feature **`--status done`**. **If the feature's `run_id` is null** (the crash happened before the run-id was recorded — see step 4.1 — or it is an old state), there is nothing to resume → restart it with **`--status pending`**. Only if a resumed run cannot be recovered, fall back to **`--status pending`** (full restart from the spec) or **`--status failed`** (abandon and stop). Never leave a feature `running` across a resume — the epic will not advance until the stale run is reconciled. **Whichever status this settles on, commit `epic-state.json` right after** (`git add docs/superpowers/execution/epics/<epic-id>/epic-state.json && git commit -m "chore(v-epic): reconcile <feature-id> -> <status>"`) — the `--status failed` ("abandon and stop") path in particular is terminal and does not otherwise pass through the checkpoint step's commit.
6364
6465
7. **Report.** Print the epic summary (`--summary`), the per-feature run-ids, and the next step: the integration review + `finishing-a-development-branch` on complete, or the blocking feature + the resume hint on blocked.
6566

commands/v-orchestrate.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ The manifest schema and rules are defined in [`skills/compound-v/execution-manif
3232
```
3333
It enforces the invariants (disjoint `write_allowed`, `codex ⇒ worktree`, `reviewers ⇒ opus`, shared-in-Task-0). If it exits non-zero, **fix the manifest** and re-run — do not hand a manifest the validator rejects to dispatch.
3434

35-
8. **Report.** Print the run-id, the run-dir path, the job count by backend/model, and the next step: `/v:dispatch <run-id>` to execute, or edit `manifest.yaml` first. Point the user at [`/v:status {{args}}`](v-status.md) to inspect.
35+
8. **Commit the run directory.** `docs/superpowers/execution/<run-id>/{manifest.yaml,state.json}` are new files on disk, not yet in git — a plain **write** is not durable. Stage and commit them now:
36+
```bash
37+
git add docs/superpowers/execution/<run-id>/manifest.yaml docs/superpowers/execution/<run-id>/state.json
38+
git commit -m "chore(v-orchestrate): materialize run <run-id>"
39+
```
40+
This is not optional. If this run is happening inside a git worktree, an *uncommitted* run directory is silently deleted by `git worktree remove` — the cleanup step in `superpowers:finishing-a-development-branch` — the very moment the branch is merged or discarded, taking Compound V's own audit trail with it. See [`state-machine.md`](../skills/compound-v/state-machine.md)'s note on this.
41+
42+
9. **Report.** Print the run-id, the run-dir path, the job count by backend/model, and the next step: `/v:dispatch <run-id>` to execute, or edit `manifest.yaml` first. Point the user at [`/v:status {{args}}`](v-status.md) to inspect.
3643

3744
## Safety
3845

commands/v-resume.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Resume is **Engine-A-owned**: it does not rely on Workflows (whose resume is sam
2929
- For a Codex worktree job with a recorded `session_id`, the codex adapter may use `codex exec resume <session_id>` instead of a cold start. Either way, the **scope gate re-runs** on return.
3030
- Update each job's `status` and write `state.json` after every transition.
3131

32-
6. **Continue the pipeline** from the reconciled phase: re-collect results, run the scope gate on every job, then the three-pass Review Gate (AC-gated), then merge worktree diffs on PASS. Already-`done` jobs are not re-run.
32+
6. **Continue the pipeline** from the reconciled phase: re-collect results, run the scope gate on every job, then the three-pass Review Gate (AC-gated), then merge worktree diffs on PASS. Already-`done` jobs are not re-run. **On reaching `MERGED`, commit the run substrate exactly as [`parallel-dispatcher`](../agents/parallel-dispatcher.md)'s Step 7 does**`state.json` (phase written as `MERGED` first, then committed together with the rest), `results/*.json`, and the memory/scorecard files if this resume refreshed them — **before** handing off to `superpowers:finishing-a-development-branch`. This matters *especially* on the resume path: the whole point of resuming is recovering from a crash or interruption, so leaving the just-recovered state uncommitted means a subsequent worktree cleanup can silently erase the very state resume just fixed.
3333

3434
7. **Report.** Which jobs were skipped (already landed), which were re-dispatched, which stayed **blocked behind an open breaker** (and the exact unblock action — top up credits or re-auth via `/v:init`), and the resulting `phase`. Point the user at [`/v:status {{args}}`](v-status.md) to inspect.
3535

0 commit comments

Comments
 (0)