feat: phases + tasks — per-task execution with phase directories

- A phase is now a directory: 00_phase.md (overview + task index) plus
  small, quick NN_task.md task files; complete/ mirrors todo/
- Task is the unit of execution: run-task.sh (one unit), run-phase.sh
  (phase to completion incl. 00_phase.md final pass), auto-phase.sh
  (all units in order); validate.sh gate runs after every task
- PHASE_COMMIT commits at phase boundaries (00_phase.md / legacy file moves)
- Legacy flat todo/NN_name.md files still execute as a single unit;
  new migrate-phases-to-tasks.sh converts them to the directory layout
- phase-status.sh reports per-task state and the next unit
- New task-template.md; phase templates now carry a Tasks index
This commit is contained in:
2026-08-23 19:28:01 -04:00
parent b4eb89a677
commit 85ac0d61b9
17 changed files with 696 additions and 223 deletions
+32 -24
View File
@@ -10,16 +10,17 @@ adopting an *existing* project into the phased-execution strategy. You audit
the codebase, scaffold the `.agent/` planning structure, and write a phase
roadmap that takes the project from its current state to the agreed target
state. You **never implement code changes yourself** — every code change is
expressed as a phase file that the `phased-execution` skill executes. And you
expressed as a phase directory (a `00_phase.md` overview plus small task
files) that the `phased-execution` skill executes, task by task. And you
never add individual phases to an already-converted project — that is the
`phase-authoring` skill.
Phase state lives in files, not chat:
- `.agent/PLAN.md` — master plan; **LOCKED DECISIONS** are binding
- `.agent/phases/todo/NN_name.md` — pending phases (sort order = execution order)
- `.agent/phases/complete/` — finished phases (read-only history)
- `.agent/validate.sh` — the pass/fail gate for every phase (installed by the `phased-execution` skill on first run)
- `.agent/phases/todo/NN_name/` — a pending phase: `00_phase.md` overview + `NN_task.md` task files (task sort order = execution order)
- `.agent/phases/complete/` — finished phases, mirroring the todo/ layout (read-only history)
- `.agent/validate.sh` — the pass/fail gate, run after every task (installed by the `phased-execution` skill on first run)
## Choosing the mode
@@ -86,7 +87,7 @@ Create (or merge into what already exists) with file tools:
strategy, public-API principles, or UI/UX guidelines), and the phase roadmap.
- **`AGENTS.md`** — the five base rules below, plus the domain additions.
- **`.agent/phases/todo/`** — the roadmap from Phase 5.
- **`.agent/phases/complete/`** — create the directory, leave it empty.
- **`.agent/phases/complete/`** — create the directory, leave it empty (it mirrors `todo/` as phases complete).
- **`.gitignore`** — add `.agent/` and `.agent/phase-sessions/` if missing.
**AGENTS.md base rules:**
@@ -122,10 +123,12 @@ project's real checks (the foundation phase does that).
## Phase 5 — The Phase Roadmap
Write sequential phase files into `.agent/phases/todo/` using
`assets/phase-template.md` (mirrors the `phase-authoring` template; keep the
sections identical). Numbering starts at the audit's "next phase number",
counting `todo/` and `complete/` together. Never reuse or collide a number.
Write sequential phase **directories** into `.agent/phases/todo/` — each
`NN_name/` holds a `00_phase.md` overview (from `assets/phase-template.md`)
and its task files (from `assets/task-template.md`); both mirror the
`phase-authoring` templates, keep the sections identical. Numbering starts
at the audit's "next phase number", counting `todo/` and `complete/`
together. Never reuse or collide a number.
Roadmap shape:
@@ -138,19 +141,23 @@ Roadmap shape:
3. **One phase per feature file** (one workflow/capability/story, one
phase), in dependency order.
Every phase file must contain:
Every phase directory must contain:
- **Objective / Dependencies / Tasks** — file-level detail; tasks express the
*delta* from current state to the contract, not a from-scratch rebuild.
- **Testing & Quality (mandatory)** — unit and integration tests for all
new/modified logic; coverage **>90%** on new/modified code; plus the domain
block below.
- **Completion Criteria** — observable checks (commands to run, endpoints to
hit, artifacts to exist).
- **Feature linkage** — the header line references the corresponding feature
file (`.agent/workflows/…`, `.agent/features/…`, or `.agent/user_stories/…`).
- **`00_phase.md`** — **Objective / Dependencies / Tasks** (ordered index of
the task files) / **Testing & Quality (mandatory)** / **Completion
Criteria.** Unit and integration tests are required for all new/modified
logic, with coverage **>90%** on new/modified code, plus the domain block
below. Tasks express the *delta* from current state to the contract, not a
from-scratch rebuild. The header line references the corresponding feature
file (`.agent/workflows/…`, `.agent/features/…`, or
`.agent/user_stories/…`).
- **Task files `01_…`, `02_…`, …** — small, quick units (one focused change,
roughly ≤30 minutes of executor work; a phase typically holds 2–8):
Objective, Work (file-level steps), Testing & Quality, Completion
Criteria.
Domain blocks (include verbatim in the matching phase files):
Domain blocks (include verbatim in the matching `00_phase.md`; the phase's
final pass is where they are verified end-to-end):
- **CLI:** `## CLI Contract Execution Phase` — instruct the executing agent
to run ONLY the specific CliRunner contract test module for that workflow
@@ -175,8 +182,9 @@ Domain blocks (include verbatim in the matching phase files):
- **Independent Viability:** each phase leaves the project functional and launchable.
- **Architectural Anchors only:** no phase may use technology outside the LOCKED DECISIONS.
- **No Regressions:** a phase must not alter the behavior of completed phases.
- **Executable in isolation:** an agent that sees only the repository and this
phase file — no chat history, no follow-ups — must be able to finish it.
- **Executable in isolation:** an agent that sees only the repository, the phase
overview, the completed task files, and one task file — no chat history, no
follow-ups — must be able to finish that task.
## Phase 6 — Version Control & Hand-Off
@@ -192,8 +200,8 @@ Domain blocks (include verbatim in the matching phase files):
Finish by summarizing the Architectural Anchors, the feature decomposition,
and the phase list (number, name, one-line objective). Then hand off:
- **Execute:** the `phased-execution` skill — `run-phase.sh` for a single
phase, `auto-phase.sh` for the full pipeline.
- **Execute:** the `phased-execution` skill — `run-task.sh` for a single
task, `run-phase.sh` for a phase, `auto-phase.sh` for the full pipeline.
- **Extend:** the `phase-authoring` skill for individual additional phases.
## Strict Operational Rules
+2 -2
View File
@@ -11,8 +11,8 @@
{{or "— (none)"}}
## Tasks
1. `{{path/to/file}}` — {{specific change, file-level detail}}
2. `{{path/to/file}}` — {{specific change}}
1. `01_{{short_name}}.md` — {{one-line summary}}
2. `02_{{short_name}}.md` — {{one-line summary}}
## Testing & Quality
- Unit/integration: {{tests required for all new/modified logic — name the behaviors to cover}}
+19
View File
@@ -0,0 +1,19 @@
# Task {{NN}} — {{Short Title}}
**Phase:** `{{NN_phase}}` · **Feature:** `{{.agent/workflows/<name>.md | .agent/features/<name>.md | .agent/user_stories/<name>.md | "n/a"}}`
## Objective
{{1–2 sentences: what this task delivers (the delta from current state to the contract)}}
## Work
1. `{{path/to/file}}` — {{specific change, file-level detail}}
2. `{{path/to/file}}` — {{specific change}}
## Testing & Quality
- Unit/integration: {{tests required for this task's logic}}
- Coverage: **>90%** on this task's new/modified code
## Completion Criteria
- [ ] {{observable check: command to run / endpoint to hit / artifact to exist}}
- [ ] full test suite green
- [ ] no behavior change in completed work
+21 -17
View File
@@ -95,37 +95,41 @@ for d in workflows features user_stories; do
done
todo_dir="$root/.agent/phases/todo"
complete_dir="$root/.agent/phases/complete"
if [[ -d "$todo_dir" ]]; then
echo " .agent/phases/todo:"
any=0
for f in "$todo_dir"/*.md; do
if [[ -e "$f" ]]; then
echo " $(basename "$f")"
list_phases() {
local dir="$1" entry n t any=0
for entry in "$dir"/*; do
if [[ ! -e "$entry" ]]; then continue; fi
n="$(basename "$entry")"
if [[ "$n" =~ ^[0-9] ]]; then
if [[ -d "$entry" ]]; then
t="$(ls -1 "$entry" 2>/dev/null | grep -cE '\.md$' || true)"
echo " $n/ ($t file(s): 00_phase.md + tasks)"
else
echo " $n (legacy single-file phase)"
fi
any=1
fi
done
if (( ! any )); then echo " (empty)"; fi
}
if [[ -d "$todo_dir" ]]; then
echo " .agent/phases/todo:"
list_phases "$todo_dir"
a=1
fi
if [[ -d "$complete_dir" ]]; then
echo " .agent/phases/complete:"
any=0
for f in "$complete_dir"/*.md; do
if [[ -e "$f" ]]; then
echo " $(basename "$f")"
any=1
fi
done
if (( ! any )); then echo " (empty)"; fi
list_phases "$complete_dir"
a=1
fi
if (( ! a )); then echo " (no .agent/ artifacts — full conversion needed)"; fi
max=0
for d in "$todo_dir" "$complete_dir"; do
for f in "$d"/*.md; do
if [[ ! -e "$f" ]]; then continue; fi
n="$(basename "$f" .md)"
[[ -d "$d" ]] || continue
for entry in "$d"/*; do
if [[ ! -e "$entry" ]]; then continue; fi
n="$(basename "$entry" .md)"
if [[ "$n" =~ ^([0-9]+) ]]; then
n=$((10#${BASH_REMATCH[1]}))
if (( n > max )); then max=$n; fi
+55 -34
View File
@@ -1,25 +1,33 @@
---
name: phase-authoring
description: The required entry point for ANY new work on a .agent/phases/ project. Whenever the user requests a new feature, a bug fix, a refactor, or any other code change to a project that has the .agent/phases/ structure, call this skill FIRST to capture the work as a phase file — never implement the change directly in code. Also use it when the user explicitly asks to add, write, or draft a phase, extend the phase roadmap, or start a new phased-execution project (Protocol B scaffolds fresh projects). Uses the Phase Architect protocol (the /new-phase and /new-project prompts, as a skill); scopes the phase from the user's chat context first and only interviews for information that is genuinely missing. This skill writes phase files; the phased-execution skill runs them.
description: The required entry point for ANY new work on a .agent/phases/ project. Whenever the user requests a new feature, a bug fix, a refactor, or any other code change to a project that has the .agent/phases/ structure, call this skill FIRST to capture the work as a phase directory with task files — never implement the change directly in code. Also use it when the user explicitly asks to add, write, or draft a phase, extend the phase roadmap, or start a new phased-execution project (Protocol B scaffolds fresh projects). Uses the Phase Architect protocol (the /new-phase and /new-project prompts, as a skill); scopes the phase from the user's chat context first and only interviews for information that is genuinely missing. This skill writes phase directories (a 00_phase.md overview plus task files); the phased-execution skill runs them, task by task.
---
# Phase Authoring
You are the **Phase Architect** — a senior engineer responsible for
extending a phased-execution project with new, independently-executable
phases. You design and write phase files; the `phased-execution` skill
executes them. You never implement phase code yourself, and you never
modify the master plan.
phases. You design and write phase directories — a `00_phase.md` overview
plus small task files; the `phased-execution` skill executes them, task by
task. You never implement phase code yourself, and you never modify the
master plan.
Phase state lives in files, not chat:
- `.agent/PLAN.md` — master plan; **LOCKED DECISIONS** are binding
- `.agent/phases/todo/NN_name.md` — pending phases (sort order = execution order)
- `.agent/phases/complete/` — finished phases (read-only history)
- `.agent/phases/todo/NN_name/` — a pending phase: `00_phase.md` overview + `NN_task.md` task files (task sort order = execution order)
- `.agent/phases/todo/NN_name.md` — legacy single-file phase (still valid; migrate or split it)
- `.agent/phases/complete/` — finished phases, mirroring the todo/ layout (read-only history)
Legacy flat phases (`todo/NN_name.md`) are still executed as a single unit.
`bash scripts/migrate-phases-to-tasks.sh [project-root]` (resolve `scripts/`
against this skill's directory) converts them to the directory layout —
mechanical, safe mid-pipeline; splitting a wrapped phase's inline task list
into real task files is this skill's job.
## When to invoke this skill
This skill is the **first stop** for any request to do work on a phased project. If the user asks for a new feature, bug fix, improvement, refactor, or any other code change — regardless of phrasing ("add X", "fix Y", "update Z", "it's broken when…", "make it so that…") — do **not** start editing application code. Convert the request into a phase file with this skill; the `phased-execution` skill is the only path from a phase to code (it runs in a fresh subprocess behind the validation gate).
This skill is the **first stop** for any request to do work on a phased project. If the user asks for a new feature, bug fix, improvement, refactor, or any other code change — regardless of phrasing ("add X", "fix Y", "update Z", "it's broken when…", "make it so that…") — do **not** start editing application code. Convert the request into a phase directory (overview + task files) with this skill; the `phased-execution` skill is the only path from a phase to code (it runs each task in a fresh subprocess behind the validation gate).
- **Phased project** (`.agent/phases/todo/` exists) → **Protocol A**: create the phase for the requested work. This is the default path for feature, bug, and change requests — the user does not need to mention "phase" at all.
- **Fresh project** (no `.agent/` structure) and the user wants a phased project (asks for it explicitly, or the chat context makes clear the phased workflow is wanted) → **Protocol B**: scaffold the project and its phase roadmap.
@@ -53,8 +61,8 @@ Before writing anything, you must:
1. Read `.agent/PLAN.md` — project goals, architecture, and **LOCKED DECISIONS**.
2. Read `AGENTS.md` if present — project rules may add requirements (e.g. one user story per phase with a dedicated E2E suite per story, mandatory commit conventions, or a `validate.sh` gate).
3. Run `bash scripts/phase-status.sh` (resolve `scripts/` against this skill's directory) to list `todo/` and `complete/` and compute the next free number `NN`.
4. Read every file in `.agent/phases/complete/` to understand what has already been built, and review the remaining `todo/` files to avoid overlap. Read the most recent completed phase file(s) and match their **local formatting conventions** (section names, story-mapping lines, E2E/commit blocks) while keeping the required sections below.
3. Run `bash scripts/phase-status.sh` (resolve `scripts/` against this skill's directory) to list the phases and their per-task state, and compute the next free number `NN`.
4. Read the `00_phase.md` files in `.agent/phases/complete/*/` to understand what has already been built, and review the remaining `todo/` phase directories to avoid overlap. Read the most recent completed phase files and match their **local formatting conventions** (section names, story-mapping lines, E2E/commit blocks) while keeping the required sections below.
### Phase 2: Scope the New Phase (context first, interview only if needed)
@@ -68,43 +76,56 @@ Derive from the chat context (see "Scoping from chat context" above):
- If the chat context answers all four, **do not interview** — proceed
straight to Phase 3 and report the derived scope in the final summary.
- If some are missing or ambiguous, ask **only those** — in one message —
and **stop and wait** for the answers before writing the file. Never
and **stop and wait** for the answers before writing the phase. Never
re-ask what the chat already settled.
- New technology: if the user already proposed or approved it in chat, that
**is** explicit permission — record it in the phase file and note that
**is** explicit permission — record it in the phase overview (`00_phase.md`) and note that
`PLAN.md`'s anchor table needs their sign-off (this skill never edits
`PLAN.md`). If it was not discussed in chat, you must ask for — and
receive — explicit permission before proceeding.
### Phase 3: Design & Create the Phase File
### Phase 3: Design & Create the Phase
Create **exactly one** new file at `.agent/phases/todo/NN_name.md`, where
`NN` comes from Phase 1 (next free number, counting `todo/` and
`complete/` together) and `name` is a short `snake_case` description. Use
`assets/phase-template.md` as the skeleton. The file must contain:
Create **exactly one** new phase directory at `.agent/phases/todo/NN_name/`,
where `NN` comes from Phase 1 (next free number, counting `todo/` and
`complete/` together) and `name` is a short `snake_case` description. It
contains the phase overview and the phase's task files:
1. **Objective** — a 1–3 sentence statement of what the phase delivers.
2. **Dependencies** — the phases (by file name) that must be completed first.
3. **Tasks** — specific, granular, ordered steps with file-level detail where applicable.
4. **Testing & Quality (mandatory)** — requires unit and integration tests for all new logic, and states the success criterion: the phase is "Complete" only when the test suite runs successfully and achieves **>90% code coverage** on new/modified code.
5. **Completion Criteria** — observable checks (commands to run, endpoints to hit, artifacts to exist) that tell the next agent the phase is done.
1. **`00_phase.md`** — use `assets/phase-template.md` as the skeleton. It must contain:
- **Objective** — a 1–3 sentence statement of what the phase delivers.
- **Dependencies** — the phases (by directory name) that must be completed first.
- **Tasks** — the ordered index of this phase's task files (one line each).
- **Testing & Quality (mandatory)** — requires unit and integration tests for all new logic, and states the success criterion: the phase is "Complete" only when the test suite runs successfully and achieves **>90% code coverage** on new/modified code.
- **Completion Criteria** — observable checks (commands to run, endpoints to hit, artifacts to exist) that the phase's final pass verifies; include any phase-level verification blocks (e.g. a dedicated E2E or contract suite).
2. **Task files `01_…`, `02_…`, …** — use `assets/task-template.md` as the skeleton. Each must contain:
- **Objective** — 1–2 sentences on what the task delivers.
- **Work** — specific, ordered steps with file-level detail where applicable.
- **Testing & Quality** — the unit/integration tests this task's logic requires; coverage >90% on its new/modified code.
- **Completion Criteria** — observable checks that tell the next agent the task is done.
**Task sizing:** each task must be small and quick — one focused change, a
small file set, roughly ≤30 minutes of executor work; a phase typically holds
2–8 tasks. Tasks execute in file-name order, each in its own fresh subprocess
with the validation gate after every task, so later tasks may build on
earlier ones within the phase.
**Design Mandates:**
- **Independent Viability:** the phase must leave the project functional and launchable on its own once complete.
- **Architectural Anchors:** use only the technologies in the LOCKED DECISIONS of `.agent/PLAN.md`. Never introduce new technology without explicit permission.
- **No Regressions:** the phase must not alter the behavior of completed phases.
- **Executable in isolation:** an agent that sees only the repository and this file — no chat history, no follow-ups — must be able to finish the phase. No hidden assumptions.
- **Executable in isolation:** an agent that sees only the repository, the phase overview, the completed task files, and one task file — no chat history, no follow-ups — must be able to finish that task. No hidden assumptions.
### Final Output
Confirm the path and number of the created file, and summarize its
objective, dependencies, and completion criteria. If you skipped the
interview, open the summary with the scope you derived from the chat
context (intent, dependencies, boundaries, and any new technology with its
permission source) so the user can correct it. Remind the user it can be
executed with the `phased-execution` skill (`run-phase.sh NN_name.md` for a
single phase, `auto-phase.sh` for the full pipeline).
Confirm the path and number of the created phase directory and its task
list, and summarize the phase's objective, dependencies, and completion
criteria. If you skipped the interview, open the summary with the scope you
derived from the chat context (intent, dependencies, boundaries, and any new
technology with its permission source) so the user can correct it. Remind the
user it can be executed with the `phased-execution` skill (`run-task.sh
NN_name/01_…` for a single task, `run-phase.sh NN_name` for the phase,
`auto-phase.sh` for the full pipeline).
## Protocol B — New project with a phased roadmap
@@ -155,7 +176,7 @@ Use file tools to create (not just describe):
- **`.agent/PLAN.md`** — the master design from Phase 3 (architecture, locked decisions, high-level roadmap).
- **`AGENTS.md`** — initialized with: always read `.agent/PLAN.md` first; follow the phased protocol in `.agent/phases/`; never modify `.agent/PLAN.md` or anything in `.agent/phases/complete/`; ask the user before editing files in `.agent/phases/todo/`; strictly adhere to the **LOCKED DECISIONS**.
- **`.agent/phases/todo/`** — sequential, granular phase files (`01_…`, `02_…`, …) that each contain the sections and design mandates from Protocol A, Phase 3, and each leaves the project launchable on its own.
- **`.agent/phases/todo/`** — sequential phase directories (`01_…/`, `02_…/`, …), each with a `00_phase.md` overview and its task files per Protocol A, Phase 3, and each leaving the project launchable on its own.
- **`.agent/phases/complete/`** — create the directory, leave it empty.
Do **not** create `.agent/validate.sh` — the `phased-execution` skill
@@ -167,8 +188,8 @@ execution with `phased-execution` (`auto-phase.sh`).
## Strict Operational Rules
- **Work requests become phase files.** When this skill was invoked because of a feature, bug, or change request, the deliverable is the phase file — not code. Do not edit application code during this invocation; the `phased-execution` skill does that from the phase file.
- **Work requests become phase directories.** When this skill was invoked because of a feature, bug, or change request, the deliverable is the phase directory (overview + task files) — not code. Do not edit application code during this invocation; the `phased-execution` skill does that from the task files.
- **Never** modify `.agent/PLAN.md`, `AGENTS.md`, or any file in `.agent/phases/complete/`.
- **Never** modify existing files in `.agent/phases/todo/`; if one needs updating, ask the user for permission first.
- Create exactly **one** phase file per invocation in Protocol A. If the request covers multiple phases, propose the ordered split and ask the user to confirm it, then create only the first — the rest follow in later invocations (or a Protocol B roadmap pass if the project is new).
- Numbering: `NN` is the next free number after the highest existing file, counting `todo/` and `complete/` together. Never reuse or collide a number.
- **Never** modify existing phase directories or their files in `.agent/phases/todo/`; if one needs updating, ask the user for permission first.
- Create exactly **one** phase directory (overview + task files) per invocation in Protocol A. If the request covers multiple phases, propose the ordered split and ask the user to confirm it, then create only the first — the rest follow in later invocations (or a Protocol B roadmap pass if the project is new).
- Numbering: phase `NN` is the next free number after the highest existing entry (directory or legacy file), counting `todo/` and `complete/` together; task `NN` is per-phase (`01`…). Never reuse or collide a number.
+3 -3
View File
@@ -11,13 +11,13 @@
{{or "— (none)"}}
## Tasks
1. `{{path/to/file}}` — {{specific change, file-level detail}}
2. `{{path/to/file}}` — {{specific change}}
1. `01_{{short_name}}.md` — {{one-line summary}}
2. `02_{{short_name}}.md` — {{one-line summary}}
## Testing & Quality
- Unit/integration: {{tests required for all new logic — name the behaviors to cover}}
- Coverage: **>90%** on new/modified code
{{project additions, e.g. a dedicated Playwright E2E suite run in isolation}}
{{project additions, e.g. a dedicated Playwright E2E suite run in isolation by this phase's final pass}}
## Completion Criteria
- [ ] {{observable check: command to run / endpoint to hit / artifact to exist}}
+19
View File
@@ -0,0 +1,19 @@
# Task {{NN}} — {{Short Title}}
**Phase:** `{{NN_phase}}` · **Story:** `{{.agent/user_stories/<story>.md or "n/a"}}`
## Objective
{{1–2 sentences: what this task delivers}}
## Work
1. `{{path/to/file}}` — {{specific change, file-level detail}}
2. `{{path/to/file}}` — {{specific change}}
## Testing & Quality
- Unit/integration: {{tests required for this task's logic}}
- Coverage: **>90%** on this task's new/modified code
## Completion Criteria
- [ ] {{observable check: command to run / endpoint to hit / artifact to exist}}
- [ ] full test suite green
- [ ] no behavior change in completed work
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# migrate-phases-to-tasks.sh — convert the flat phase-file layout to the
# phase-directory + task-file layout.
#
# Each numeric-prefixed flat phase file in {todo,complete}/ is wrapped into a
# directory of the same name with the file renamed to 00_phase.md:
#
# todo/03_api.md → todo/03_api/00_phase.md
# complete/01_init.md → complete/01_init/00_phase.md
#
# The harness still executes wrapped phases: their inline task lists are
# picked up by the phase's 00_phase.md final pass, so this is safe
# mid-pipeline. Splitting a wrapped todo phase into real task files is the
# phase-authoring skill's job, not this script's. Already-migrated phases are
# left alone; a flat file colliding with an existing same-name directory is
# reported and skipped.
#
# Usage: bash migrate-phases-to-tasks.sh [project-root]
# project-root defaults to the nearest ancestor containing
# .agent/phases/todo or agent/phases/todo (older projects).
set -euo pipefail
root="${1:-}"
if [[ -n "$root" ]]; then
[[ -d "$root" ]] || { echo "✗ ERROR: $root is not a directory" >&2; exit 1; }
else
root="$(pwd)"
while :; do
if [[ -d "$root/.agent/phases/todo" || -d "$root/agent/phases/todo" ]]; then break; fi
[[ "$root" == "/" ]] && { echo "✗ ERROR: no phases/todo found at or above $(pwd)" >&2; exit 1; }
root="$(dirname "$root")"
done
fi
if [[ -d "$root/.agent/phases" ]]; then
phases="$root/.agent/phases"
elif [[ -d "$root/agent/phases" ]]; then
phases="$root/agent/phases"
else
echo "✗ ERROR: no phases directory under $root" >&2
exit 1
fi
echo "project root: $root"
echo "phases: $phases"
echo
moved=0 skipped=0
for d in todo complete; do
[[ -d "$phases/$d" ]] || continue
for f in "$phases/$d"/*.md; do
[[ -e "$f" ]] || continue
base="$(basename "$f" .md)"
[[ "$base" =~ ^[0-9] ]] || continue
if [[ -d "$phases/$d/$base" ]]; then
echo "⚠ skip: $d/$base.md — directory $d/$base/ already exists (conflict)" >&2
skipped=$((skipped + 1))
continue
fi
mkdir -p "$phases/$d/$base"
mv -f "$f" "$phases/$d/$base/00_phase.md"
echo " $d/$base.md → $d/$base/00_phase.md"
moved=$((moved + 1))
done
done
echo
echo "migrated: $moved phase file(s) → 00_phase.md directory layout; skipped: $skipped"
if (( moved > 0 )); then
echo "wrapped todo phases now run as a single final pass; use the phase-authoring"
echo "skill to split their inline task lists into real task files."
fi
+103 -23
View File
@@ -1,9 +1,13 @@
#!/usr/bin/env bash
# phase-status.sh — phase pipeline state for the Phase Architect.
# phase-status.sh — phase/task pipeline state for the Phase Architect.
#
# Finds the project root (nearest ancestor with .agent/phases/todo), prints
# the todo/ and complete/ phase listings, and computes the next free phase
# number NN (counting both directories together, zero-padded to 2 digits).
# the todo/ and complete/ phase listings with per-task state, the next free
# phase number NN (counting both directories together, zero-padded to 2
# digits), and the next pending unit in pipeline order.
#
# Phase layout: todo/NN_name/ holds 00_phase.md (overview) + NN_task.md task
# files; a legacy flat todo/NN_name.md is a single-unit phase.
#
# Usage: bash phase-status.sh # from anywhere in the project tree
@@ -16,31 +20,107 @@ while :; do
root="$(dirname "$root")"
done
list() {
local d="$root/.agent/phases/$1" f found=0
for f in "$d"/*.md; do
[[ -e "$f" ]] || continue
found=1
printf ' %s\n' "$(basename "$f")"
done
if (( ! found )); then printf ' (empty)\n'; fi
}
phases="$root/.agent/phases"
# --- todo: per-task state ------------------------------------------------------
echo "todo:"
found=0
for entry in "$phases/todo/"*; do
[[ -e "$entry" ]] || continue
name="$(basename "$entry")"
[[ "$name" =~ ^[0-9] ]] || continue
found=1
if [[ -d "$entry" ]]; then
# union of this phase's files across todo/ and complete/ (completed units
# move to complete/, so pending = present in todo/, done = in complete/)
comp_dir="$phases/complete/$name"
comp_files=""
if [[ -d "$comp_dir" ]]; then comp_files="$(ls -1 "$comp_dir" 2>/dev/null || true)"; fi
names="$( { ls -1 "$entry" 2>/dev/null || true; printf '%s\n' "$comp_files"; } | grep -E '\.md$' | sort -u || true)"
total=0 done_n=0
while IFS= read -r t; do
[[ -n "$t" ]] || continue
total=$((total + 1))
[[ -f "$phases/complete/$name/$t" ]] && done_n=$((done_n + 1))
done <<< "$names"
printf ' %s/ (%s of %s done)\n' "$name" "$done_n" "$total"
# tasks first (sorted), then 00_phase.md as the final pass
while IFS= read -r t; do
[[ -n "$t" ]] || continue
[[ "$t" == 00_phase.md ]] && continue
if [[ -f "$phases/complete/$name/$t" ]]; then
printf ' [x] %s\n' "$t"
else
printf ' [ ] %s\n' "$t"
fi
done <<< "$names"
if grep -qxF '00_phase.md' <<< "$names"; then
if [[ -f "$phases/complete/$name/00_phase.md" ]]; then
printf ' [x] 00_phase.md (final pass)\n'
else
printf ' [ ] 00_phase.md (final pass)\n'
fi
fi
else
printf ' %s (legacy single-file phase)\n' "$name"
fi
done
(( found )) || echo " (empty)"
# --- complete -------------------------------------------------------------------
echo "complete:"
found=0
for entry in "$phases/complete/"*; do
[[ -e "$entry" ]] || continue
name="$(basename "$entry")"
[[ "$name" =~ ^[0-9] ]] || continue
found=1
if [[ -d "$entry" ]]; then
n=0
for f in "$entry"/*.md; do [[ -e "$f" ]] && n=$((n + 1)); done
printf ' %s/ (%s files)\n' "$name" "$n"
else
printf ' %s (legacy single-file phase)\n' "$name"
fi
done
(( found )) || echo " (empty)"
# --- next free phase number -----------------------------------------------------
max=0
for d in todo complete; do
for f in "$root/.agent/phases/$d"/*.md; do
[[ -e "$f" ]] || continue
n="$(basename "$f" .md)"
if [[ "$n" =~ ^([0-9]+) ]]; then
for entry in "$phases/$d/"*; do
[[ -e "$entry" ]] || continue
name="$(basename "$entry" .md)"
if [[ "$name" =~ ^([0-9]+) ]]; then
n=$((10#${BASH_REMATCH[1]}))
if (( n > max )); then max=$n; fi
(( n > max )) && max=$n
fi
done
done
echo "next phase number: $(printf '%02d' $((max + 1)))"
echo "project root: $root"
echo "todo:"
list todo
echo "complete:"
list complete
echo "next number: $(printf '%02d' $((max + 1)))"
# --- next pending unit -----------------------------------------------------------
next=""
for entry in "$phases/todo/"*; do
[[ -e "$entry" ]] || continue
name="$(basename "$entry")"
[[ "$name" =~ ^[0-9] ]] || continue
if [[ -d "$entry" ]]; then
# first task present in todo/ and not yet in complete/ (mirrors the harness)
t=""
while IFS= read -r f; do
[[ -n "$f" ]] || continue
[[ "$f" =~ ^[0-9] && "$f" == *.md ]] || continue
[[ "$f" == 00_phase.md ]] && continue
[[ -f "$phases/complete/$name/$f" ]] && continue
t="$f"; break
done < <(ls -1 "$entry" 2>/dev/null || true)
if [[ -n "$t" ]]; then next="$name/$t"; break; fi
if [[ -f "$entry/00_phase.md" && ! -f "$phases/complete/$name/00_phase.md" ]]; then
next="$name/00_phase.md"; break
fi
elif [[ -f "$entry" ]]; then
next="$name"; break
fi
done
echo "next unit: ${next:-(none — pipeline complete)}"
+42 -24
View File
@@ -1,6 +1,6 @@
---
name: phased-execution
description: Runs the .agent/phases/ phased-execution pipeline (ported from opencode's next-phase/auto-phase commands). Use when the user asks to run the next phase, run all phases, run the phase pipeline, or check pipeline status. Each phase executes in a separate pi subprocess so this chat's context stays small.
description: Runs the .agent/phases/ phased-execution pipeline (ported from opencode's next-phase/auto-phase commands). Use when the user asks to run the next task, run the next phase, run all phases, run the phase pipeline, or check pipeline status. Each task executes in a separate pi subprocess so this chat's context stays small.
---
# Phased Execution
@@ -8,16 +8,21 @@ description: Runs the .agent/phases/ phased-execution pipeline (ported from open
Phase state lives in files, not chat:
- `.agent/PLAN.md` — master plan; LOCKED DECISIONS are binding
- `.agent/phases/todo/NN_name.md` — pending phases (alphanumerical sort = execution order)
- `.agent/phases/complete/` — finished phases
- `.agent/reports/` — per-phase executor reports, stderr, and validation logs
- `.agent/validate.sh` — the pass/fail gate for every phase
- `.agent/phases/todo/NN_name/` — a pending phase: `00_phase.md` (objective, dependencies, task index, testing & quality, completion criteria) plus `NN_task.md` task files (task sort order = execution order)
- `.agent/phases/todo/NN_name.md` — legacy single-file phase (still executable as one unit)
- `.agent/phases/complete/` — finished phases; mirrors the `todo/` layout (completed task files and the phase overview move here)
- `.agent/reports/<phase>/<task>.a<N>.{md,err,validate}` — per-task executor reports, stderr, and validation logs (legacy phases: `.agent/reports/<phase>.a<N>.*`)
- `.agent/validate.sh` — the pass/fail gate, run after **every task**
The scripts run each phase in a **separate pi process** (fresh context) with
bounded fixer retries. A phase only moves to `complete/` after the child exits
0, the child's stream ends with a clean final report, **and**
`.agent/validate.sh` passes. This chat only dispatches and relays results —
do not implement phase code yourself; that is what the subprocess is for.
The unit of execution is the **task**: each task runs in a **separate pi
process** (fresh context) with bounded fixer retries. A task only moves to
`complete/` after the child exits 0, the child's stream ends with a clean
final report, **and** `.agent/validate.sh` passes. When all of a phase's tasks
are done, `00_phase.md` runs as the phase's **final pass** (remaining inline
work + completion criteria + phase-level verification); moving it completes
the phase and is the `PHASE_COMMIT` commit point. This chat only dispatches
and relays results — do not implement task code yourself; that is what the
subprocesses are for.
## Live display
@@ -33,15 +38,23 @@ never lost to a truncated stream.
(Resolve `scripts/` against this skill's directory.)
Run the next phase, or a specific one:
Run the next task, or a specific one:
```bash
bash scripts/run-phase.sh # first pending phase
bash scripts/run-phase.sh 03_api.md # specific phase (warns if out of order)
bash scripts/run-task.sh # first pending task
bash scripts/run-task.sh 03_api/02_routes.md # specific task (warns if out of order)
```
Run the whole pipeline — every pending phase, in order, stopping at the first
phase that fails after all retries:
Run one phase to completion — all of its remaining tasks, then its
`00_phase.md` final pass:
```bash
bash scripts/run-phase.sh # next phase with pending work
bash scripts/run-phase.sh 03_api # specific phase (warns if out of order)
```
Run the whole pipeline — every pending task, in order (phase order, then task
order), stopping at the first task that fails after all retries:
```bash
bash scripts/auto-phase.sh
@@ -51,17 +64,18 @@ Re-running `auto-phase.sh` after a failure continues where it stopped.
## After a run
Exit codes: `0` = success (or nothing to run), `1` = phase failed after all
attempts / no pending phases error, `130`/`143` = interrupted (Ctrl+C / SIGTERM).
Exit codes: `0` = success (or nothing to run), `1` = task/phase failed after all
attempts, `130`/`143` = interrupted (Ctrl+C / SIGTERM).
Failures always print a `✗ ERROR:` line with the last error output — if the
script's output looks like it ended abruptly, re-run it; the failed executor's
session is resumed automatically (retries continue the child's own session,
keeping its work).
Relay to the user: the phase name, its executor report (printed at the end of
the script output), and the validation outcome. On failure, point the user at
`.agent/reports/<phase>.a*.{md,err,validate}` — the script also prints a ready
to run `pi --session … -c “…”` command to continue the failed session manually.
Relay to the user: the task (or phase) name, its executor report (printed at
the end of the script output), and the validation outcome. On failure, point
the user at `.agent/reports/<phase>/<task>.a*.{md,err,validate}` (legacy
phases: `.agent/reports/<phase>.a*.*`) — the script also prints a ready to run
`pi --session … -c “…”` command to continue the failed session manually.
## Configuration (environment variables)
@@ -70,7 +84,7 @@ to run `pi --session … -c “…”` command to continue the failed session ma
| `MAX_FIX_ATTEMPTS` | `3` | Fixer retries per phase |
| `PHASE_MODEL` | session default | `--model` for child executors (e.g. `anthropic/claude-sonnet-4-5`) |
| `PHASE_THINKING` | session default | `--thinking` level for child executors |
| `PHASE_COMMIT` | `0` | `1` = `git commit --no-gpg-sign` after each passing phase |
| `PHASE_COMMIT` | `0` | `1` = `git commit --no-gpg-sign` after each completed **phase** (when its `00_phase.md` final pass passes; legacy: when its file moves) |
| `PI_TRUST` | `0` | `1` = pass `--approve` (load project `.pi/` settings/skills into children) |
| `FRESH_FIX` | `0` | `1` = fixer retries start fresh instead of resuming the failed session |
| `QUIET` | `0` | `1` = suppress live progress display (reports are still written) |
@@ -80,8 +94,12 @@ to run `pi --session … -c “…”` command to continue the failed session ma
- First run creates `.agent/validate.sh` from `assets/validate.sh` if missing.
It must be adapted to the project's real checks — it is the authoritative
quality gate.
- Phase files are created by the `/to-phase`, `/audit-create`, `/new-project`,
and `/new-python-*` prompt templates.
- Phase directories are created by the `phase-authoring` skill or the
`/to-phase`, `/audit-create`, `/new-project`, and `/new-python-*` prompt
templates.
- Legacy flat phase files (`todo/NN_name.md`) are still executed as a single
unit; `phase-authoring`'s `migrate-phases-to-tasks.sh` converts them to the
directory layout (the phase's final pass then picks up any inline task list).
- Child executor sessions are kept in `.agent/phase-sessions/`; add it to
`.gitignore` if the project is versioned.
- If you keep non-skill markdown (e.g. a `README.md`) in a skills directory
+1 -1
View File
@@ -5,7 +5,7 @@ Target phase file: `.agent/phases/todo/{{PHASE}}`
## Steps
1. Read `.agent/PLAN.md` — project goals, architecture, and **LOCKED DECISIONS** (binding; never introduce technology outside them).
2. Read `AGENTS.md` if present.
3. Read every file in `.agent/phases/complete/` so your work stays architecturally consistent with what is already built.
3. Read the completed phase files so your work stays architecturally consistent with what is already built: every `00_phase.md` in `.agent/phases/complete/*/`, plus any legacy flat phase files directly in `.agent/phases/complete/`.
4. Read the target phase file and complete **every** task in it, in order.
5. Write the unit and integration tests required by the phase's Testing & Quality section. Do not omit parts of the code to inflate coverage.
6. Run the project's full test suite and linter. If anything fails — including the phase's coverage criterion — fix it and re-run until green.
@@ -0,0 +1,26 @@
You are the phase-completion executor in a phased build pipeline. You run in a fresh, isolated context; the harness manages phase and task files, retries, and final validation.
Phase overview: `.agent/phases/todo/{{PHASE}}/00_phase.md`
## Steps
1. Read `.agent/PLAN.md` — project goals, architecture, and **LOCKED DECISIONS** (binding; never introduce technology outside them).
2. Read `AGENTS.md` if present.
3. Read the phase overview `.agent/phases/todo/{{PHASE}}/00_phase.md`.
4. Check the phase's task index against `.agent/phases/complete/{{PHASE}}/`: if any task is missing from there, the overview itself still carries work (a legacy phase with an inline task list) — implement that remaining work now, in order, including its tests.
5. Otherwise every task is done: this is the final verification pass. Verify **each** completion criterion in the phase overview (commands to run, endpoints to hit, artifacts to exist), and execute any phase-level verification blocks it defines (e.g. a dedicated E2E or contract test suite).
6. Run the project's full test suite and linter. If anything fails — including the phase's coverage criterion or a completion criterion — fix it and re-run until green.
7. If you find defects in previously completed work (failing tests, lint errors, bugs), fix those as part of this pass.
## Rules
- Never start work from other files in `todo/`.
- Do **not** move, rename, or edit the phase overview, other files in `.agent/phases/todo/`, `.agent/PLAN.md`, or anything in `.agent/phases/complete/`. The harness moves the phase file on success.
- Do not assume the code is correct; fix any errors you find while testing.
- Leave the repository functional when you finish.
## Final response
When everything is green, reply with a report of **at most 15 lines**:
- What was implemented or verified (short bullet list)
- Test / lint / coverage results (exact commands and outcomes)
- Completion criteria: each one with its outcome
- Notable decisions or deviations
- The next pending phase, if any
@@ -0,0 +1,26 @@
You are a task executor in a phased build pipeline. You run in a fresh, isolated context; the harness manages phase and task files, retries, and final validation.
Target task file: `.agent/phases/todo/{{PHASE}}/{{TASK}}`
## Steps
1. Read `.agent/PLAN.md` — project goals, architecture, and **LOCKED DECISIONS** (binding; never introduce technology outside them).
2. Read `AGENTS.md` if present.
3. Read the phase overview `.agent/phases/todo/{{PHASE}}/00_phase.md` — objective, dependencies, testing & quality mandate, and completion criteria.
4. Read this phase's completed task files (`.agent/phases/complete/{{PHASE}}/*.md`, excluding `00_phase.md`) and the `00_phase.md` overviews of other completed phases (`.agent/phases/complete/*/00_phase.md`) so your work stays architecturally consistent with what is already built.
5. Read the target task file and complete it fully, in order.
6. Write the unit and integration tests required by the task. Do not omit parts of the code to inflate coverage.
7. Run the project's full test suite and linter. If anything fails — including the task's coverage criterion — fix it and re-run until green.
8. If you find defects in previously completed work (failing tests, lint errors, bugs), fix those as part of this task.
## Rules
- Work only on the target task; never start the next task or work from other files in `todo/`.
- Do **not** move, rename, or edit the task file, `00_phase.md`, other files in `.agent/phases/todo/`, `.agent/PLAN.md`, or anything in `.agent/phases/complete/`. The harness moves the task file on success.
- Do not assume the code is correct; fix any errors you find while testing.
- Leave the repository functional when you finish.
## Final response
When everything is green, reply with a report of **at most 15 lines**:
- What was implemented (short bullet list)
- Test / lint / coverage results (exact commands and outcomes)
- Notable decisions or deviations
- The next pending task, if any
+19 -16
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env bash
# auto-phase.sh — run the full phased pipeline, no LLM in the loop.
#
# Processes every file in .agent/phases/todo/ in alphanumerical order.
# Each phase runs in its own pi process (fresh context); on failure the
# executor's session is resumed for up to MAX_FIX_ATTEMPTS fixer rounds.
# A phase moves to .agent/phases/complete/ only after the child exits 0
# AND .agent/validate.sh passes. Stops at the first phase that cannot be
# completed — re-run this script to continue where it stopped.
# Processes every pending task in .agent/phases/todo/ in pipeline order
# (phase order, then task order within each phase). Each task runs in its
# own pi process (fresh context); .agent/validate.sh runs after EVERY task.
# On failure the executor's session is resumed for up to MAX_FIX_ATTEMPTS
# fixer rounds. A task moves to .agent/phases/complete/ only after the
# child exits 0 AND validation passes; a phase completes when its
# 00_phase.md final pass succeeds (legacy phases: when their file moves).
# Stops at the first task that cannot be completed — re-run this script to
# continue where it stopped.
#
# Env: see SKILL.md (MAX_FIX_ATTEMPTS, PHASE_MODEL, PHASE_THINKING,
# PHASE_COMMIT, PI_TRUST, FRESH_FIX).
@@ -16,24 +19,24 @@ source "$SCRIPT_DIR/lib.sh"
# Make interruptions visible: state stays in .agent/phases/todo, and the
# failed executor's session is still resumable on the next run.
trap 'echo; echo "✗ ERROR: interrupted (SIGINT) — ${phase:-the pipeline} is left in $PHASE_TODO/; re-run to continue where it stopped" >&2; exit 130' INT
trap 'echo; echo "✗ ERROR: interrupted (SIGTERM) — ${phase:-the pipeline} is left in $PHASE_TODO/; re-run to continue where it stopped" >&2; exit 143' TERM
trap 'echo; echo "✗ ERROR: interrupted (SIGINT) — ${unit:-the pipeline} is left in $PHASE_TODO/; re-run to continue where it stopped" >&2; exit 130' INT
trap 'echo; echo "✗ ERROR: interrupted (SIGTERM) — ${unit:-the pipeline} is left in $PHASE_TODO/; re-run to continue where it stopped" >&2; exit 143' TERM
cd "$(find_root)" || die "no .agent/phases/todo found in this or parent directories (run /to-phase or /audit-create first)"
build_pi_args
delivered=()
while phase="$(next_phase)"; do
[[ -n "$phase" ]] || break
if ! execute_phase "$phase"; then
echo "✗ ERROR: pipeline stopped — $phase FAILED after $MAX_FIX_ATTEMPTS attempts" >&2
while unit="$(next_unit)"; do
[[ -n "$unit" ]] || break
if ! execute_unit "$unit"; then
echo "✗ ERROR: pipeline stopped — $unit FAILED after $MAX_FIX_ATTEMPTS attempts" >&2
echo " fix the issues above, then re-run this script to continue where it stopped" >&2
exit 1
fi
delivered+=("$phase")
delivered+=("$unit")
done
echo
echo "✓ pipeline done — ${#delivered[@]} phase(s) delivered this run: ${delivered[*]:-none}"
remaining="$(ls -1 "$PHASE_TODO" 2>/dev/null | grep -cE '^[0-9]' || true)"
echo " remaining in $PHASE_TODO/: $remaining"
echo "✓ pipeline done — ${#delivered[@]} task(s) delivered this run: ${delivered[*]:-none}"
remaining="$(count_units)"
echo " remaining in $PHASE_TODO/: $remaining task(s)"
+176 -57
View File
@@ -1,19 +1,28 @@
#!/usr/bin/env bash
# lib.sh — shared logic for the phased-execution skill.
# Sourced by run-phase.sh and auto-phase.sh. Not meant to be run directly.
# Sourced by run-task.sh, run-phase.sh, and auto-phase.sh. Not meant to be run directly.
#
# Phase state lives in files, not chat context:
# .agent/PLAN.md master plan, LOCKED DECISIONS (binding)
# .agent/phases/todo/ pending phases, NN_name.md, sorted = execution order
# .agent/phases/complete/ finished phases
# .agent/reports/ per-phase executor reports, stderr, validation logs
# .agent/phases/todo/NN_name/ pending phase: 00_phase.md (overview) + NN_task.md task files
# .agent/phases/todo/NN_name.md legacy single-file phase (still executable)
# .agent/phases/complete/ finished phases — mirrors the todo/ layout
# .agent/reports/ per-task executor reports, stderr, validation logs
# .agent/phase-sessions/ child pi session files (resumable fixers)
#
# The pass/fail gate is .agent/validate.sh. A phase only moves to complete/
# after the child executor exits 0 AND validation passes.
# The unit of execution is the TASK: each task file runs in its own pi
# subprocess (fresh context) with bounded fixer retries, and
# .agent/validate.sh runs after EVERY task. A unit only moves to complete/
# after the child exits 0, the child's stream ends with a clean final
# report, and validation passes. When all of a phase's tasks are done,
# 00_phase.md runs as the phase's final pass (any remaining inline work +
# completion criteria + phase-level verification); moving it completes the
# phase and is the PHASE_COMMIT commit point.
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
EXECUTOR_PROMPT_FILE="$SKILL_DIR/assets/executor-prompt.md"
TASK_EXECUTOR_PROMPT_FILE="$SKILL_DIR/assets/task-executor-prompt.md"
PHASE_FINAL_PROMPT_FILE="$SKILL_DIR/assets/phase-final-prompt.md"
PHASE_TODO=".agent/phases/todo"
PHASE_DONE=".agent/phases/complete"
@@ -36,10 +45,100 @@ find_root() {
done
}
# --- phase selection ----------------------------------------------------------
# First pending phase (alphanumerical sort), or empty when none remain.
next_phase() {
( cd "$PHASE_TODO" 2>/dev/null && ls -1 | grep -E '^[0-9]' | sort | head -n1 ) || true
# --- unit selection -------------------------------------------------------------
# A unit is the smallest schedulable piece of work, referenced relative to
# .agent/phases/todo/:
# directory phase → each task file "NN_name/NN_task.md" (sort order), then
# the phase overview "NN_name/00_phase.md" as the final pass
# legacy flat → the phase file itself, "NN_name.md"
# Pending phase entries in todo/ (phase directories or legacy .md files), in execution order.
phase_entries() {
( cd "$PHASE_TODO" 2>/dev/null && ls -1 | grep -E '^[0-9]' | sort ) || true
}
# First pending unit of one phase entry, or empty when the phase is done.
phase_next_unit() {
local p="$1" t
if [[ -d "$PHASE_TODO/$p" ]]; then
t="$(ls -1 "$PHASE_TODO/$p" 2>/dev/null | grep -E '^[0-9].*\.md$' | grep -vE '^00_phase\.md$' | sort | head -n1)"
if [[ -n "$t" ]]; then printf '%s/%s\n' "$p" "$t"; return 0; fi
if [[ -f "$PHASE_TODO/$p/00_phase.md" ]]; then printf '%s/00_phase.md\n' "$p"; return 0; fi
elif [[ -f "$PHASE_TODO/$p" ]]; then
printf '%s\n' "$p"
fi
return 0
}
# Next pending unit in the whole pipeline, or empty when everything is done.
next_unit() {
local p u
for p in $(phase_entries); do
u="$(phase_next_unit "$p")"
if [[ -n "$u" ]]; then printf '%s\n' "$u"; return 0; fi
done
return 0
}
# Count of pending units across all phases (task files + 00_phase.md + legacy files).
count_units() {
local p n=0
for p in $(phase_entries); do
if [[ -d "$PHASE_TODO/$p" ]]; then
n=$(( n + $(ls -1 "$PHASE_TODO/$p" 2>/dev/null | grep -cE '\.md$') ))
elif [[ -f "$PHASE_TODO/$p" ]]; then
n=$(( n + 1 ))
fi
done
printf '%s\n' "$n"
}
# Name used in reports/sessions: "NN_name__NN_task" (dir phases) or "NN_name" (flat).
unit_base() {
local u="${1%.md}"
printf '%s\n' "${u//\//__}"
}
# Phase name for a unit (both forms).
unit_phase() {
local u="${1%.md}"
printf '%s\n' "${u%%/*}"
}
# Reports directory for a unit: per-phase subdir for dir phases, flat otherwise.
unit_report_dir() {
local u="$1"
if [[ "$u" == */* ]]; then
printf '%s/%s\n' "$PHASE_REPORTS" "$(unit_phase "$u")"
else
printf '%s\n' "$PHASE_REPORTS"
fi
}
# Report/log path for a unit attempt: <dir>/<base>.a<attempt>.<ext>.
unit_report() {
local u="$1" attempt="$2" ext="$3"
printf '%s/%s.a%s.%s\n' "$(unit_report_dir "$u")" "$(unit_base "$u")" "$attempt" "$ext"
}
# Phase-end unit (the commit point): 00_phase.md, or the whole file for legacy.
is_phase_end() {
local u="$1"
if [[ "$u" == */* ]]; then [[ "${u##*/}" == "00_phase.md" ]]; else return 0; fi
}
# Move a completed unit from todo/ to complete/ (same relative path).
move_unit() {
local u="$1" p t
if [[ "$u" == */* ]]; then
p="${u%%/*}"; t="${u##*/}"
mkdir -p "$PHASE_DONE/$p"
mv -f "$PHASE_TODO/$u" "$PHASE_DONE/$p/$t"
if [[ -z "$(ls -A "$PHASE_TODO/$p" 2>/dev/null)" ]]; then rmdir "$PHASE_TODO/$p"; fi
else
mkdir -p "$PHASE_DONE"
mv -f "$PHASE_TODO/$u" "$PHASE_DONE/$u"
fi
}
# --- child pi arguments -------------------------------------------------------
@@ -89,38 +188,52 @@ recover_report() {
}
# --- prompts ------------------------------------------------------------------
# First-attempt prompt for a unit. Directory tasks render the task executor
# prompt; the phase overview renders the phase-final prompt; legacy flat
# phase files render the original phase executor prompt.
first_prompt() {
local phase="$1"
local unit="$1" p t
if [[ "$unit" == */* ]]; then
p="${unit%%/*}"; t="${unit##*/}"
if [[ "$t" == "00_phase.md" ]]; then
[[ -f "$PHASE_FINAL_PROMPT_FILE" ]] || die "missing $PHASE_FINAL_PROMPT_FILE"
sed "s|{{PHASE}}|$p|g" "$PHASE_FINAL_PROMPT_FILE"
else
[[ -f "$TASK_EXECUTOR_PROMPT_FILE" ]] || die "missing $TASK_EXECUTOR_PROMPT_FILE"
sed "s|{{PHASE}}|$p|g; s|{{TASK}}|$t|g" "$TASK_EXECUTOR_PROMPT_FILE"
fi
else
[[ -f "$EXECUTOR_PROMPT_FILE" ]] || die "missing $EXECUTOR_PROMPT_FILE"
sed "s|{{PHASE}}|$phase|g" "$EXECUTOR_PROMPT_FILE"
sed "s|{{PHASE}}|$unit|g" "$EXECUTOR_PROMPT_FILE"
fi
}
fix_prompt() {
local errors="$1"
{
echo "Your previous attempt at this phase was rejected by the harness."
echo "The failures from the last attempt are below. Review them, fix the code, and re-run the full test suite and linter until everything is green. Do not start other phases' work."
echo "Your previous attempt at this task was rejected by the harness."
echo "The failures from the last attempt are below. Review them, fix the code, and re-run the full test suite and linter until everything is green. Do not start other tasks' work."
echo
echo "Failure output (may be truncated):"
echo '```'
printf '%s\n' "$errors" | tail -c 6000
echo '```'
echo
echo "When everything is green, reply with the same report as before (at most 15 lines: what was fixed, test/lint/coverage results, notable decisions, next pending phase)."
echo "When everything is green, reply with the same report as before (at most 15 lines: what was fixed, test/lint/coverage results, notable decisions, next pending task)."
}
}
# --- child executor -----------------------------------------------------------
# run_child <phase> <attempt> <prompt> [resume-session]
# run_child <unit> <attempt> <prompt> [resume-session]
# Attempt 1: fresh session in .agent/phase-sessions/.
# Attempt N>1: resumes the given session file — the failed executor's own
# session, tracked by execute_phase (so retries keep its work). When no
# session, tracked by execute_unit (so retries keep its work). When no
# session was captured (or FRESH_FIX=1) a fresh ephemeral session runs with
# the failure context instead.
# Progress (tool calls, assistant text, thinking, compaction) streams to the
# terminal live via scripts/progress.mjs, which also writes the final
# assistant message to: .agent/reports/<base>.a<attempt>.md
# Child stderr → .agent/reports/<base>.a<attempt>.err
# assistant message to: unit_report <unit> <attempt> md
# Child stderr → unit_report <unit> <attempt> err
# QUIET=1 suppresses the progress display (report file is still written).
# Sets CHILD_RC (pi's exit code) and PROGRESS_RC (progress.mjs's exit code;
# non-zero means the stream ended without a clean final report).
@@ -141,21 +254,23 @@ _run_pi_pipeline() {
# trap had fired, so the failure is always visible.
if (( CHILD_RC == 130 || PROGRESS_RC == 130 )); then
echo
echo "✗ ERROR: interrupted (SIGINT) — phase is left in $PHASE_TODO/; re-run to continue" >&2
echo "✗ ERROR: interrupted (SIGINT) — unit is left in $PHASE_TODO/; re-run to continue" >&2
exit 130
fi
if (( CHILD_RC == 143 || PROGRESS_RC == 143 )); then
echo
echo "✗ ERROR: interrupted (SIGTERM) — phase is left in $PHASE_TODO/; re-run to continue" >&2
echo "✗ ERROR: interrupted (SIGTERM) — unit is left in $PHASE_TODO/; re-run to continue" >&2
exit 143
fi
}
run_child() {
local phase="$1" attempt="$2" prompt="$3" resume_session="${4:-}"
local base="${phase%.md}"
local out="$PHASE_REPORTS/$base.a$attempt.md"
local errf="$PHASE_REPORTS/$base.a$attempt.err"
local unit="$1" attempt="$2" prompt="$3" resume_session="${4:-}"
local base out errf
base="$(unit_base "$unit")"
out="$(unit_report "$unit" "$attempt" md)"
errf="$(unit_report "$unit" "$attempt" err)"
mkdir -p "$(dirname "$out")"
local PROGRESS=(node "$SKILL_DIR/scripts/progress.mjs" "$out")
[[ "${QUIET:-0}" == "1" ]] && PROGRESS+=(--quiet)
@@ -176,7 +291,7 @@ ensure_validate() {
cp "$SKILL_DIR/assets/validate.sh" .agent/validate.sh
chmod +x .agent/validate.sh
warn "no .agent/validate.sh found — created it from the skill template."
warn "adapt it to this project's real test/lint/coverage commands; it is the pass/fail gate for every phase."
warn "adapt it to this project's real test/lint/coverage commands; it is the pass/fail gate after every task."
fi
}
@@ -185,39 +300,39 @@ run_validation() {
bash .agent/validate.sh >"$1" 2>&1
}
# --- one phase, with bounded fixer retries ------------------------------------
# execute_phase <phase-file>
# Returns 0 and moves the phase to complete/ on success; returns 1 after
# MAX_FIX_ATTEMPTS failed attempts (phase file is left in todo/).
execute_phase() {
local phase="$1"
local base="${phase%.md}"
local attempt=1 errors=""
# --- one unit (task, phase final pass, or legacy phase), with retries ---------
# execute_unit <unit>
# Returns 0 and moves the unit to complete/ on success; returns 1 after
# MAX_FIX_ATTEMPTS failed attempts (unit file is left in todo/).
execute_unit() {
local unit="$1"
local base attempt=1 errors=""
local last_session="" pre post
command -v node >/dev/null 2>&1 || die "node not found on PATH (needed to render phase progress)"
mkdir -p "$PHASE_DONE" "$PHASE_REPORTS" "$PHASE_SESSIONS"
base="$(unit_base "$unit")"
command -v node >/dev/null 2>&1 || die "node not found on PATH (needed to render task progress)"
mkdir -p "$PHASE_DONE" "$PHASE_REPORTS" "$PHASE_SESSIONS" "$(unit_report_dir "$unit")"
ensure_validate
while (( attempt <= MAX_FIX_ATTEMPTS )); do
echo "━━ $phase — attempt $attempt/$MAX_FIX_ATTEMPTS ━━"
echo "━━ $unit — attempt $attempt/$MAX_FIX_ATTEMPTS ━━"
pre="$(latest_child_session)"
if (( attempt == 1 )); then
run_child "$phase" 1 "$(first_prompt "$phase")"
run_child "$unit" 1 "$(first_prompt "$unit")"
else
run_child "$phase" "$attempt" "$(fix_prompt "$errors")" "$last_session"
run_child "$unit" "$attempt" "$(fix_prompt "$errors")" "$last_session"
fi
# Track which session file this attempt used, so the next attempt resumes
# exactly this phase's failed session (not just "latest in the directory").
# exactly this unit's failed session (not just "latest in the directory").
post="$(latest_child_session)"
if [[ -n "$post" && "$post" != "$pre" ]]; then
last_session="$post"
fi
# The child can be signaled mid-flush: the session file then holds a final
# report the stream lost. Recover it so a completed phase is not retried.
if (( CHILD_RC == 0 )) && [[ -f "$PHASE_REPORTS/$base.a$attempt.md" ]] \
&& grep -q "no final assistant message" "$PHASE_REPORTS/$base.a$attempt.md"; then
if [[ -n "$last_session" ]] && recover_report "$PHASE_REPORTS/$base.a$attempt.md" "$last_session"; then
# report the stream lost. Recover it so a completed unit is not retried.
if (( CHILD_RC == 0 )) && [[ -f "$(unit_report "$unit" "$attempt" md)" ]] \
&& grep -q "no final assistant message" "$(unit_report "$unit" "$attempt" md)"; then
if [[ -n "$last_session" ]] && recover_report "$(unit_report "$unit" "$attempt" md)" "$last_session"; then
warn "stream lost the final message — report recovered from ${last_session##*/}"
PROGRESS_RC=0
fi
@@ -225,40 +340,44 @@ execute_phase() {
errors=""
if (( CHILD_RC != 0 )); then
warn "child pi exited with code $CHILD_RC — see $PHASE_REPORTS/$base.a$attempt.err"
errors+="[child pi exited with code $CHILD_RC]"$'\n'"$(tail -c 4000 "$PHASE_REPORTS/$base.a$attempt.err" 2>/dev/null)"
warn "child pi exited with code $CHILD_RC — see $(unit_report "$unit" "$attempt" err)"
errors+="[child pi exited with code $CHILD_RC]"$'\n'"$(tail -c 4000 "$(unit_report "$unit" "$attempt" err)" 2>/dev/null)"
fi
if (( PROGRESS_RC != 0 )); then
warn "child run ended without a clean final report — see $PHASE_REPORTS/$base.a$attempt.md"
errors+="[child run ended without a clean final report]"$'\n'"$(tail -c 4000 "$PHASE_REPORTS/$base.a$attempt.err" 2>/dev/null)"
warn "child run ended without a clean final report — see $(unit_report "$unit" "$attempt" md)"
errors+="[child run ended without a clean final report]"$'\n'"$(tail -c 4000 "$(unit_report "$unit" "$attempt" err)" 2>/dev/null)"
fi
if ! run_validation "$PHASE_REPORTS/$base.a$attempt.validate"; then
warn ".agent/validate.sh FAILED — see $PHASE_REPORTS/$base.a$attempt.validate"
errors+="[.agent/validate.sh FAILED]"$'\n'"$(tail -n 120 "$PHASE_REPORTS/$base.a$attempt.validate" 2>/dev/null)"
if ! run_validation "$(unit_report "$unit" "$attempt" validate)"; then
warn ".agent/validate.sh FAILED — see $(unit_report "$unit" "$attempt" validate)"
errors+="[.agent/validate.sh FAILED]"$'\n'"$(tail -n 120 "$(unit_report "$unit" "$attempt" validate)" 2>/dev/null)"
fi
if [[ -z "$errors" ]]; then
mv -f "$PHASE_TODO/$phase" "$PHASE_DONE/$phase"
echo "✓ $phase → complete"
move_unit "$unit"
if is_phase_end "$unit"; then
echo "✓ $unit → complete (phase $(unit_phase "$unit") done)"
if [[ "${PHASE_COMMIT:-0}" == "1" ]]; then
if git add -A 2>/dev/null && git commit --no-gpg-sign -m "phase: $phase" >/dev/null 2>&1; then
if git add -A 2>/dev/null && git commit --no-gpg-sign -m "phase: $(unit_phase "$unit")" >/dev/null 2>&1; then
echo " (committed)"
else
warn "git commit failed (continuing)"
fi
fi
else
echo "✓ $unit → complete"
fi
echo "── executor report ──"
cat "$PHASE_REPORTS/$base.a$attempt.md"
cat "$(unit_report "$unit" "$attempt" md)"
return 0
fi
attempt=$(( attempt + 1 ))
done
{
echo "✗ $phase FAILED after $MAX_FIX_ATTEMPTS attempts — left in $PHASE_TODO/."
echo "✗ $unit FAILED after $MAX_FIX_ATTEMPTS attempts — left in $PHASE_TODO/."
echo " last errors:"
printf '%s\n' "$errors" | tail -n 40 | sed 's/^/ /'
echo " logs: $PHASE_REPORTS/$base.a*.{md,err,validate}"
echo " logs: $(unit_report_dir "$unit")/$(unit_base "$unit").a*.{md,err,validate}"
if [[ -n "${last_session:-}" && -f "${last_session:-}" ]]; then
echo " resume: re-run this script (it auto-resumes the failed session), or manually:"
echo " pi --session $last_session -c \"review the failures above, fix them, re-validate\""
+26 -14
View File
@@ -1,9 +1,14 @@
#!/usr/bin/env bash
# run-phase.sh — execute exactly one phase in a fresh pi context.
# run-phase.sh — run every remaining task of one phase to completion.
#
# Each task runs in its own pi process (fresh context) with the
# .agent/validate.sh gate after every task; the phase ends with its
# 00_phase.md final pass.
#
# Usage:
# run-phase.sh # first pending phase (alphanumerical order)
# run-phase.sh 03_api.md # a specific pending phase (warns if out of order)
# run-phase.sh # next phase with pending work, to completion
# run-phase.sh 03_api # a specific phase (warns if out of order)
# run-phase.sh 03_api.md # a legacy single-file phase
#
# Env: see SKILL.md (MAX_FIX_ATTEMPTS, PHASE_MODEL, PHASE_THINKING,
# PHASE_COMMIT, PI_TRUST, FRESH_FIX).
@@ -20,22 +25,29 @@ cd "$(find_root)" || die "no .agent/phases/todo found in this or parent director
phase="${1:-}"
if [[ -n "$phase" ]]; then
[[ "$phase" == *.md ]] || phase="$phase.md"
[[ -f "$PHASE_TODO/$phase" ]] || die "$phase not found in $PHASE_TODO/"
first="$(next_phase)"
if [[ -n "$first" && "$first" != "$phase" ]]; then
phase="${phase%/}"; phase="${phase%.md}"; phase="${phase%%/*}"
[[ -d "$PHASE_TODO/$phase" || -f "$PHASE_TODO/$phase.md" ]] || die "$phase not found in $PHASE_TODO/"
first="$(next_unit)"
if [[ -n "$first" && "${first%%/*}" != "$phase" ]]; then
warn "dependency order: $first is pending before $phase — running out of order"
fi
else
phase="$(next_phase)"
[[ -n "$phase" ]] || { echo "✓ no pending phases in $PHASE_TODO/ — project complete."; exit 0; }
unit="$(next_unit)"
[[ -n "$unit" ]] || { echo "✓ no pending tasks in $PHASE_TODO/ — project complete."; exit 0; }
phase="$(unit_phase "$unit")"
fi
build_pi_args
if execute_phase "$phase"; then
exit 0
fi
echo "✗ ERROR: phase $phase FAILED after $MAX_FIX_ATTEMPTS attempts" >&2
echo " reports: $PHASE_REPORTS/${phase%.md}.a*.{md,err,validate}" >&2
failed=0
while unit="$(phase_next_unit "$phase")"; do
[[ -n "$unit" ]] || break
execute_unit "$unit" || { failed=1; break; }
done
if (( failed )); then
echo "✗ ERROR: phase $phase FAILED — see error above" >&2
echo " reports: $(unit_report_dir "$unit")/$(unit_base "$unit").a*.{md,err,validate}" >&2
echo " resume: re-run this script — the failed executor session is resumed automatically" >&2
exit 1
fi
echo "✓ phase $phase complete"
exit 0
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# run-task.sh — execute exactly one task in a fresh pi context.
#
# A task is the smallest execution unit: a task file inside a phase
# directory (03_api/02_routes.md), a phase's 00_phase.md final pass, or a
# legacy single-file phase.
#
# Usage:
# run-task.sh # next pending task (pipeline order)
# run-task.sh 03_api/02_routes.md # a specific task (warns if out of order)
# run-task.sh 03_api/02_routes # ".md" is added when missing
#
# Env: see SKILL.md (MAX_FIX_ATTEMPTS, PHASE_MODEL, PHASE_THINKING,
# PHASE_COMMIT, PI_TRUST, FRESH_FIX).
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib.sh"
# Make interruptions visible: state stays in .agent/phases/todo, and the
# failed executor's session is still resumable on the next run.
trap 'echo; echo "✗ ERROR: interrupted (SIGINT) — ${unit:-this task} is left in $PHASE_TODO/; re-run to continue" >&2; exit 130' INT
trap 'echo; echo "✗ ERROR: interrupted (SIGTERM) — ${unit:-this task} is left in $PHASE_TODO/; re-run to continue" >&2; exit 143' TERM
cd "$(find_root)" || die "no .agent/phases/todo found in this or parent directories (run /to-phase or /audit-create first)"
unit="${1:-}"
if [[ -n "$unit" ]]; then
[[ "$unit" == *.md ]] || unit="$unit.md"
[[ -f "$PHASE_TODO/$unit" ]] || die "$unit not found in $PHASE_TODO/"
first="$(next_unit)"
if [[ -n "$first" && "$first" != "$unit" ]]; then
warn "execution order: $first is pending before $unit — running out of order"
fi
else
unit="$(next_unit)"
[[ -n "$unit" ]] || { echo "✓ no pending tasks in $PHASE_TODO/ — project complete."; exit 0; }
fi
build_pi_args
if execute_unit "$unit"; then
exit 0
fi
echo "✗ ERROR: task $unit FAILED after $MAX_FIX_ATTEMPTS attempts" >&2
echo " reports: $(unit_report_dir "$unit")/$(unit_base "$unit").a*.{md,err,validate}" >&2
echo " resume: re-run this script — the failed executor session is resumed automatically" >&2
exit 1