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
14 changes: 12 additions & 2 deletions .claude/skills/phax-planning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ field; the top-level heading is the sole source of the run's identity.

## Plan frontmatter block

Create a repo-tracked plan with `phax artifact new plan <slug> --spec <spec path>`
(omit `--spec` when there is no source spec): it names the file
Create a repo-tracked plan with
`phax artifact new plan <slug> --spec <spec path> --last|--not-last`
(omit all three when there is no source spec; both flags are refused without `--spec`): it names the file
`docs/plans/<YYMMDDHHMM>-<slug>-plan.md` from the clock — the slug must equal the
source spec's slug, which `phax plans lint` checks — and writes the frontmatter
block; you fill in the body. Never compute the stamp yourself, and refer to a plan
Expand All @@ -83,6 +84,7 @@ Every plan carries a YAML frontmatter block at offset 0, before the `# ` title:
---
status: Approved
source-spec: docs/specs/<YYMMDDHHMM>-<slug>.md
completes-spec: false
---
```

Expand All @@ -96,6 +98,12 @@ source-spec: docs/specs/<YYMMDDHHMM>-<slug>.md
- **`source-spec`** — the spec this plan implements
(`docs/specs/<YYMMDDHHMM>-<slug>.md`), or `null` when there is no source spec. It is the
lineage anchor for staleness tracking.
- **`completes-spec`** — `true` when this plan is its spec's last and its run completes
the spec, `false` when more plans of the spec follow. Required when `source-spec` names
a spec; absent (refused) when it is `null`. Every plan of a spec except the last says
`false`. The chain gate still applies on top of `true`. The value is fingerprinted, so
changing it on an Approved plan makes the plan stale. `--last` writes `true`,
`--not-last` writes `false`.
- **`approved`** — optional; a mapping with `date` and `baseline` written by
`phax artifact approve` when it stamps the approval. Absent on a plan that has
never been approved.
Expand Down Expand Up @@ -529,6 +537,8 @@ Top-level keys:
- `version`: `1`; `kind`: `"plan"`.
- `sourceSpec`: the `--spec` path given to phax, or `null` without one (the prompt says
which). phax sets it from `--spec` regardless, so frontmatter and sidecar agree.
- `completesSpec`: `true` or `false` beside a `sourceSpec` path, `null` without one. phax
sets it from `--last`/`--not-last` regardless.
- `run`: `{ shortName, title, requiredCommands }` — the extracted run fields
(`# <Title>`, `## Required commands`).
- `preamble`: `{ summary, requiredCommandsNote, technicalArbitrations }` — the prose under
Expand Down
4 changes: 1 addition & 3 deletions NEXT_STEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ the previous one changes. The first in the chain, `schemas-package`, is Complete
yet: `phax artifact new plan artifact-decide --spec docs/specs/2609250815-artifact-decide.md`.
Decide runs on Drafts only, `artifact reopen` moves an artifact from Approved back to Draft,
every artifact gets the approval lock, §9 is hand-authored, and `--by` names who decided.
If the spec ships in more than one plan, revert the run's spec completion on every plan
but the last (see *Small follow-ups*).
- [ ] **`headless-review`** — `docs/specs/2609250823-headless-review.md`. It reuses decide's
approver form, skill and escalation block (`docs/ideas/headless-code-review.md`).
- [ ] **`oracle-phases`** — `docs/specs/2609281159-oracle-phases.md`. Oracle-first phases
Expand Down Expand Up @@ -149,7 +147,7 @@ cross-field checks registered by id so the build writes the same list into the s

## Small follow-ups

- [ ] **A run completes its source spec even when more plans are to come.** Found
- [x] **A run completes its source spec even when more plans are to come.** (Shipped: the `completes-spec` spec.) Found
2026-09-29 on `schemas-package` plan 1/5 (PR #104): at run end phax completed the
plan (correct) and the spec (`f8d2366`, spec moved to `archive/`, its approval
record removed), although plans 2–5 remain. Reverted by hand on the PR branch
Expand Down
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Write a spec, then a plan, and approve each. `artifact new` creates the file wit
phax artifact new spec greet # docs/specs/2610041200-greet.md, Draft
phax artifact approve docs/specs/2610041200-greet.md

phax artifact new plan greet --spec docs/specs/2610041200-greet.md
phax artifact new plan greet --spec docs/specs/2610041200-greet.md --last
phax plans lint docs/plans/2610041201-greet-plan.md
phax artifact approve docs/plans/2610041201-greet-plan.md
```
Expand All @@ -36,7 +36,7 @@ Or let phax drive each authoring session from a short brief, and get a committed

```bash
phax artifact new spec greet --headless --brief spec-brief.md
phax artifact new plan greet --headless --brief plan-brief.md --spec docs/specs/2610041200-greet.md
phax artifact new plan greet --headless --brief plan-brief.md --spec docs/specs/2610041200-greet.md --last
```

Run the plan. Each phase runs, passes its gates and commits; the last one stays open for you:
Expand Down Expand Up @@ -111,7 +111,7 @@ exec zsh

**Spec and plan.** A spec says what to build and why: requirements, acceptance criteria, and the questions still open. A plan says how: an ordered list of phases, each with its instructions, the files it will create and edit, the gate it must pass and its commit message. Both are Markdown files with a status in their frontmatter, under `docs/specs/` and `docs/plans/`. phax calls them **artifacts**.

**Lifecycle.** An artifact moves through statuses: `Draft` → `Approved` → `Completed`, or `Abandoned` if the work is dropped. A plan can also be marked `Stale` when the ground it was approved on has changed (`phax plans status` tells you). `phax artifact` makes every transition and commits it; an approval records what the artifact was approved against, so phax can tell later whether it still holds. A run completes its plan, and its spec where it can, on the run's own branch, so the merge lands the code and the completion together.
**Lifecycle.** An artifact moves through statuses: `Draft` → `Approved` → `Completed`, or `Abandoned` if the work is dropped. A plan can also be marked `Stale` when the ground it was approved on has changed (`phax plans status` tells you). `phax artifact` makes every transition and commits it; an approval records what the artifact was approved against, so phax can tell later whether it still holds. A run completes its plan on the run's own branch, and its spec only when the plan says it is the spec's last (`completes-spec: true`) and no other live plan still needs it, so the merge lands the code and the completion together.

**Run and phase.** `phax run` turns an approved plan into a **run**, named from the plan's title as `<namespace>.<name>`, where the namespace is your project's `name` in `phax.json`. Each **phase** runs in its own Git worktree on its own branch, `phax/<name>--phase-NN`, branched from the previous phase, so the last phase's branch carries the whole change.

Expand Down Expand Up @@ -181,18 +181,36 @@ phax schema upgrade # after upgrading phax: regenerate the ed

```bash
phax artifact new spec <slug> # docs/specs/<YYMMDDHHMM>-<slug>.md
phax artifact new plan <slug> --spec <spec path> # docs/plans/<YYMMDDHHMM>-<slug>-plan.md
phax artifact new plan <slug> --spec <spec path> --last # docs/plans/<YYMMDDHHMM>-<slug>-plan.md
```

phax names the file from the current UTC minute and the slug, with a `Draft` status, and refuses any other name (exit 12). A plan carries its spec's slug and names it as its `source-spec`; `--spec` can be left out when a plan has no spec. Fill the file in with your agent: the `phax-spec` and `phax-planning` skills hold the formats, and point at the right sections — requirements and acceptance criteria for a spec; for each plan phase, its instructions, the files it creates and edits, its gate and its commit. [`examples/hello-world/plan.md`](examples/hello-world/plan.md) is a small worked plan.
phax names the file from the current UTC minute and the slug, with a `Draft` status, and refuses any other name (exit 12). A plan carries its spec's slug and names it as its `source-spec`; `--spec` can be left out when a plan has no spec. With `--spec`, say whether the plan is the spec's last: `--last` or `--not-last` (see below). Fill the file in with your agent: the `phax-spec` and `phax-planning` skills hold the formats, and point at the right sections — requirements and acceptance criteria for a spec; for each plan phase, its instructions, the files it creates and edits, its gate and its commit. [`examples/hello-world/plan.md`](examples/hello-world/plan.md) is a small worked plan.

**One spec, several plans.** `--last` writes `completes-spec: true`, so the plan's run completes the spec; `--not-last` writes `completes-spec: false`, so the run leaves the spec live. Every plan of a spec except the last says `--not-last`. Splitting a spec across three plans:

```bash
phax artifact new plan greet-core --spec docs/specs/2610041200-greet.md --not-last # plan 1 of 3
phax artifact new plan greet-cli --spec docs/specs/2610041200-greet.md --not-last # plan 2 of 3
phax artifact new plan greet-docs --spec docs/specs/2610041200-greet.md --last # plan 3 of 3
```

Plan 1's frontmatter reads:

```markdown
---
status: Draft
source-spec: docs/specs/2610041200-greet.md
completes-spec: false
---
```

### Let phax write them

With `--headless`, phax runs the authoring session itself from a brief: it gives the agent the skill and the document's JSON Schema, accepts a valid document as the session's only output, renders it to Markdown, keeps the document as a `.json` sidecar beside it, and commits both. A plan written this way is already extracted, so `phax run` never extracts it again.

```bash
phax artifact new spec greet --headless --brief brief.md
phax artifact new plan greet --headless --brief brief.md --spec docs/specs/2610041200-greet.md
phax artifact new plan greet --headless --brief brief.md --spec docs/specs/2610041200-greet.md --last
cat brief.md | phax artifact new spec greet --headless --brief -
```

Expand Down Expand Up @@ -227,7 +245,7 @@ A read-only check, with no model: the plan's structure, its planned files agains
| `phax artifact abandon <path>` | `Draft`, `Approved`; `Stale` (plan) | `Abandoned` |
| `phax artifact status <path>` | any | — prints the status and the legal transitions |

Each transition rewrites the status in the file's frontmatter and commits it on its own, and refuses when the files it writes have uncommitted changes. `Completed` and `Abandoned` move the file, and its sidecar, into the folder's `archive/`. Approving writes the artifact's own approval record file, `docs/specs/approvals/<spec>.json` or `docs/plans/approvals/<plan>.json`, with the commit it was made against; completing or abandoning an artifact, or reopening a plan, deletes that file in the same commit, and no transition touches another artifact's record; approving a plan is refused while its spec's approval is missing or the spec has changed since. A run completes its own plan, and its spec where it can, on the run's branch.
Each transition rewrites the status in the file's frontmatter and commits it on its own, and refuses when the files it writes have uncommitted changes. `Completed` and `Abandoned` move the file, and its sidecar, into the folder's `archive/`. Approving writes the artifact's own approval record file, `docs/specs/approvals/<spec>.json` or `docs/plans/approvals/<plan>.json`, with the commit it was made against; completing or abandoning an artifact, or reopening a plan, deletes that file in the same commit, and no transition touches another artifact's record; approving a plan is refused while its spec's approval is missing or the spec has changed since. A run completes its own plan, and its spec only when the plan says it is the spec's last (`completes-spec: true`) and no other live plan still needs it, on the run's branch.

### Keep several plans in step

Expand Down Expand Up @@ -268,7 +286,7 @@ Then, for each phase:
5. The agent writes the phase's handoff.
6. phax commits with the planned message, then compares the files changed with the files planned (`file-reconciliation.json` in the phase's folder).

A phase that changes nothing stops the run (exit 9). The last phase runs the gate's `terminal` steps too, then the run stops at `review_open`. Its plan, and its spec where it can, are completed on the run's branch. The run's folder is `~/.phax/runs/<namespace>.<name>/`; when the run ends, phax prints what happened and the next command to run. On a Mac, keep it awake for long runs: `caffeinate -ims phax run --plan <plan>`.
A phase that changes nothing stops the run (exit 9). The last phase runs the gate's `terminal` steps too, then the run stops at `review_open`. Its plan is completed on the run's branch, and its spec too when the plan says it is the spec's last (`completes-spec: true`) and no other live plan still needs it. The run's folder is `~/.phax/runs/<namespace>.<name>/`; when the run ends, phax prints what happened and the next command to run. On a Mac, keep it awake for long runs: `caffeinate -ims phax run --plan <plan>`.

### When a run stops

Expand Down Expand Up @@ -535,6 +553,7 @@ phax reads no environment variable for its configuration: everything is in `phax
- **Lock conflict.** Another phax is working on that run, or one died; `phax unlock <run>` clears a stale lock.
- **The handoff is missing.** The phase ended in `handoff_failed`: `phax enter <run>` takes you back into its session.
- **A rate or usage limit.** The run stopped at exit 8 and keeps its place: `phax resume <run>` when the limit resets.
- **A plan with a source spec is refused for lacking `completes-spec`.** A plan whose `source-spec` names a spec must carry `completes-spec: true` (its run completes the spec) or `false` (more plans follow); `plans lint`, `artifact approve` and `run` refuse it with exit 12. Add the key by hand, then re-approve an Approved plan, since changing the value makes it stale. A headless plan's older `.json` sidecar lacks `completesSpec` too: re-author or delete it.
- **Approval records are per-artifact files.** Approval records were one shared ledger per kind; each is now a file of its own under `docs/plans/approvals/` and `docs/specs/approvals/`. After upgrading, `phax artifact approve` and `phax run` refuse with exit 12 while `docs/plans/approvals.json` or `docs/specs/approvals.json` exists. Run the one-time migration, which splits both ledgers into record files in one commit:

```console
Expand Down Expand Up @@ -597,9 +616,9 @@ Full CLI reference: [`docs/cli/reference.md`](docs/cli/reference.md).
- `phax artifact abandon <path>` — Abandons an artifact — a terminal status distinct from Completed, for work dropped without execution. Legal from Draft or Approved (specs) or Draft, Approved, or Stale (plans).
- `phax artifact complete <path>` — Completes an artifact — a terminal status for work that ran to completion. Legal from Approved (specs) or Approved or Stale (plans).
- `phax artifact reopen <path>` — Reopens a Stale plan back to Draft, for when re-planning is needed before re-approval. Legal from Stale only. Rewrites the frontmatter status key in place.
- `phax artifact new <SUBCOMMAND>` — Parent command for creating a Draft spec or plan named from the current UTC minute: <YYMMDDHHMM>-<slug>.md for a spec, <YYMMDDHHMM>-<slug>-plan.md for a plan. The instant is captured when the command runs, never chosen or backdated. A bad slug, an existing target name, or (for a plan) a --spec that is missing or not a spec all refuse with exit code 12 before anything is written.
- `phax artifact new <SUBCOMMAND>` — Parent command for creating a Draft spec or plan named from the current UTC minute: <YYMMDDHHMM>-<slug>.md for a spec, <YYMMDDHHMM>-<slug>-plan.md for a plan. The instant is captured when the command runs, never chosen or backdated. A bad slug, an existing target name, or (for a plan) a --spec that is missing or not a spec, a --spec without exactly one of --last/--not-last, or either flag without --spec all refuse with exit code 12 before anything is written.
- `phax artifact new spec [FLAGS] <slug>` — Creates a Draft spec at docs/specs/<YYMMDDHHMM>-<slug>.md, with a frontmatter-only skeleton (status, date, audience, scope). The slug must match `[a-z0-9]+(-[a-z0-9]+)*`.
- `phax artifact new plan [FLAGS] <slug>` — Creates a Draft plan at docs/plans/<YYMMDDHHMM>-<slug>-plan.md, with a frontmatter-only skeleton (status, source-spec). Pass --spec <path> to bind an existing spec as the plan's source-spec; the path must classify as a spec (live or archived), exist, and pass artifact validation. Without --spec, source-spec is written as null. The slug must match `[a-z0-9]+(-[a-z0-9]+)*`.
- `phax artifact new plan [FLAGS] <slug>` — Creates a Draft plan at docs/plans/<YYMMDDHHMM>-<slug>-plan.md, with a frontmatter-only skeleton (status, source-spec, and completes-spec when a spec is bound). Pass --spec <path> to bind an existing spec as the plan's source-spec; the path must classify as a spec (live or archived), exist, and pass artifact validation. With --spec, pass exactly one of --last or --not-last: --last writes completes-spec: true (this plan is the spec's last, and its run completes the spec), --not-last writes completes-spec: false (more plans of the spec follow, and its run leaves the spec live). Every plan of a spec except the last says --not-last; no default is ever inferred. Without --spec, source-spec is written as null with no completes-spec, and both flags are refused. The slug must match `[a-z0-9]+(-[a-z0-9]+)*`.
- `phax artifact schema <kind>` — Prints the JSON Schema of the experimental spec document (kind spec) or plan document (kind plan), pretty-printed to stdout, so a consumer can read the contract a headless authoring session must satisfy without a model call.
- `phax artifact migrate-approvals` — Reads docs/plans/approvals.json and docs/specs/approvals.json in any released shape, writes one record file per entry under docs/plans/approvals/ and docs/specs/approvals/, deletes the ledgers, and commits exactly those paths in one commit. Exits 0 when migrated or when there is nothing to migrate; exits 12, writing nothing, on an unreadable ledger, an entry that is not a live artifact path, an uncommitted change to any path it would write, or an existing record file holding a different record.
- `phax plans <SUBCOMMAND>` — Parent command for reporting on plans: the mechanical defects of a single plan (lint), staleness of Approved plans against their recorded approval, and cross-plan file overlap.
Expand Down
Loading
Loading