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 the codebase, scaffold the `.agent/` planning structure, and write a phase
roadmap that takes the project from its current state to the agreed target roadmap that takes the project from its current state to the agreed target
state. You **never implement code changes yourself** — every code change is 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 never add individual phases to an already-converted project — that is the
`phase-authoring` skill. `phase-authoring` skill.
Phase state lives in files, not chat: Phase state lives in files, not chat:
- `.agent/PLAN.md` — master plan; **LOCKED DECISIONS** are binding - `.agent/PLAN.md` — master plan; **LOCKED DECISIONS** are binding
- `.agent/phases/todo/NN_name.md` — pending phases (sort order = execution order) - `.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 (read-only history) - `.agent/phases/complete/` — finished phases, mirroring the todo/ layout (read-only history)
- `.agent/validate.sh` — the pass/fail gate for every phase (installed by the `phased-execution` skill on first run) - `.agent/validate.sh` — the pass/fail gate, run after every task (installed by the `phased-execution` skill on first run)
## Choosing the mode ## 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. strategy, public-API principles, or UI/UX guidelines), and the phase roadmap.
- **`AGENTS.md`** — the five base rules below, plus the domain additions. - **`AGENTS.md`** — the five base rules below, plus the domain additions.
- **`.agent/phases/todo/`** — the roadmap from Phase 5. - **`.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. - **`.gitignore`** — add `.agent/` and `.agent/phase-sessions/` if missing.
**AGENTS.md base rules:** **AGENTS.md base rules:**
@@ -122,10 +123,12 @@ project's real checks (the foundation phase does that).
## Phase 5 — The Phase Roadmap ## Phase 5 — The Phase Roadmap
Write sequential phase files into `.agent/phases/todo/` using Write sequential phase **directories** into `.agent/phases/todo/` — each
`assets/phase-template.md` (mirrors the `phase-authoring` template; keep the `NN_name/` holds a `00_phase.md` overview (from `assets/phase-template.md`)
sections identical). Numbering starts at the audit's "next phase number", and its task files (from `assets/task-template.md`); both mirror the
counting `todo/` and `complete/` together. Never reuse or collide a number. `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: Roadmap shape:
@@ -138,19 +141,23 @@ Roadmap shape:
3. **One phase per feature file** (one workflow/capability/story, one 3. **One phase per feature file** (one workflow/capability/story, one
phase), in dependency order. phase), in dependency order.
Every phase file must contain: Every phase directory must contain:
- **Objective / Dependencies / Tasks** — file-level detail; tasks express the - **`00_phase.md`** — **Objective / Dependencies / Tasks** (ordered index of
*delta* from current state to the contract, not a from-scratch rebuild. the task files) / **Testing & Quality (mandatory)** / **Completion
- **Testing & Quality (mandatory)** — unit and integration tests for all Criteria.** Unit and integration tests are required for all new/modified
new/modified logic; coverage **>90%** on new/modified code; plus the domain logic, with coverage **>90%** on new/modified code, plus the domain block
block below. below. Tasks express the *delta* from current state to the contract, not a
- **Completion Criteria** — observable checks (commands to run, endpoints to from-scratch rebuild. The header line references the corresponding feature
hit, artifacts to exist). file (`.agent/workflows/…`, `.agent/features/…`, or
- **Feature linkage** — the header line references the corresponding feature `.agent/user_stories/…`).
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 - **CLI:** `## CLI Contract Execution Phase` — instruct the executing agent
to run ONLY the specific CliRunner contract test module for that workflow 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. - **Independent Viability:** each phase leaves the project functional and launchable.
- **Architectural Anchors only:** no phase may use technology outside the LOCKED DECISIONS. - **Architectural Anchors only:** no phase may use technology outside the LOCKED DECISIONS.
- **No Regressions:** a phase must not alter the behavior of completed phases. - **No Regressions:** a phase must not alter the behavior of completed phases.
- **Executable in isolation:** an agent that sees only the repository and this - **Executable in isolation:** an agent that sees only the repository, the phase
phase file — no chat history, no follow-ups — must be able to finish it. 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 ## 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, Finish by summarizing the Architectural Anchors, the feature decomposition,
and the phase list (number, name, one-line objective). Then hand off: and the phase list (number, name, one-line objective). Then hand off:
- **Execute:** the `phased-execution` skill — `run-phase.sh` for a single - **Execute:** the `phased-execution` skill — `run-task.sh` for a single
phase, `auto-phase.sh` for the full pipeline. task, `run-phase.sh` for a phase, `auto-phase.sh` for the full pipeline.
- **Extend:** the `phase-authoring` skill for individual additional phases. - **Extend:** the `phase-authoring` skill for individual additional phases.
## Strict Operational Rules ## Strict Operational Rules
+2 -2
View File
@@ -11,8 +11,8 @@
{{or "— (none)"}} {{or "— (none)"}}
## Tasks ## Tasks
1. `{{path/to/file}}` — {{specific change, file-level detail}} 1. `01_{{short_name}}.md` — {{one-line summary}}
2. `{{path/to/file}}` — {{specific change}} 2. `02_{{short_name}}.md` — {{one-line summary}}
## Testing & Quality ## Testing & Quality
- Unit/integration: {{tests required for all new/modified logic — name the behaviors to cover}} - 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 done
todo_dir="$root/.agent/phases/todo" todo_dir="$root/.agent/phases/todo"
complete_dir="$root/.agent/phases/complete" complete_dir="$root/.agent/phases/complete"
if [[ -d "$todo_dir" ]]; then list_phases() {
echo " .agent/phases/todo:" local dir="$1" entry n t any=0
any=0 for entry in "$dir"/*; do
for f in "$todo_dir"/*.md; do if [[ ! -e "$entry" ]]; then continue; fi
if [[ -e "$f" ]]; then n="$(basename "$entry")"
echo " $(basename "$f")" 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 any=1
fi fi
done done
if (( ! any )); then echo " (empty)"; fi if (( ! any )); then echo " (empty)"; fi
}
if [[ -d "$todo_dir" ]]; then
echo " .agent/phases/todo:"
list_phases "$todo_dir"
a=1 a=1
fi fi
if [[ -d "$complete_dir" ]]; then if [[ -d "$complete_dir" ]]; then
echo " .agent/phases/complete:" echo " .agent/phases/complete:"
any=0 list_phases "$complete_dir"
for f in "$complete_dir"/*.md; do
if [[ -e "$f" ]]; then
echo " $(basename "$f")"
any=1
fi
done
if (( ! any )); then echo " (empty)"; fi
a=1 a=1
fi fi
if (( ! a )); then echo " (no .agent/ artifacts — full conversion needed)"; fi if (( ! a )); then echo " (no .agent/ artifacts — full conversion needed)"; fi
max=0 max=0
for d in "$todo_dir" "$complete_dir"; do for d in "$todo_dir" "$complete_dir"; do
for f in "$d"/*.md; do [[ -d "$d" ]] || continue
if [[ ! -e "$f" ]]; then continue; fi for entry in "$d"/*; do
n="$(basename "$f" .md)" if [[ ! -e "$entry" ]]; then continue; fi
n="$(basename "$entry" .md)"
if [[ "$n" =~ ^([0-9]+) ]]; then if [[ "$n" =~ ^([0-9]+) ]]; then
n=$((10#${BASH_REMATCH[1]})) n=$((10#${BASH_REMATCH[1]}))
if (( n > max )); then max=$n; fi if (( n > max )); then max=$n; fi
+55 -34
View File
@@ -1,25 +1,33 @@
--- ---
name: phase-authoring 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 # Phase Authoring
You are the **Phase Architect** — a senior engineer responsible for You are the **Phase Architect** — a senior engineer responsible for
extending a phased-execution project with new, independently-executable extending a phased-execution project with new, independently-executable
phases. You design and write phase files; the `phased-execution` skill phases. You design and write phase directories — a `00_phase.md` overview
executes them. You never implement phase code yourself, and you never plus small task files; the `phased-execution` skill executes them, task by
modify the master plan. task. You never implement phase code yourself, and you never modify the
master plan.
Phase state lives in files, not chat: Phase state lives in files, not chat:
- `.agent/PLAN.md` — master plan; **LOCKED DECISIONS** are binding - `.agent/PLAN.md` — master plan; **LOCKED DECISIONS** are binding
- `.agent/phases/todo/NN_name.md` — pending phases (sort order = execution order) - `.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 (read-only history) - `.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 ## 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. - **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. - **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**. 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). 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`. 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 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. 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) ### 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 - If the chat context answers all four, **do not interview** — proceed
straight to Phase 3 and report the derived scope in the final summary. 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 — - 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. re-ask what the chat already settled.
- New technology: if the user already proposed or approved it in chat, that - 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`'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 `PLAN.md`). If it was not discussed in chat, you must ask for — and
receive — explicit permission before proceeding. 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 Create **exactly one** new phase directory at `.agent/phases/todo/NN_name/`,
`NN` comes from Phase 1 (next free number, counting `todo/` and where `NN` comes from Phase 1 (next free number, counting `todo/` and
`complete/` together) and `name` is a short `snake_case` description. Use `complete/` together) and `name` is a short `snake_case` description. It
`assets/phase-template.md` as the skeleton. The file must contain: contains the phase overview and the phase's task files:
1. **Objective** — a 1–3 sentence statement of what the phase delivers. 1. **`00_phase.md`** — use `assets/phase-template.md` as the skeleton. It must contain:
2. **Dependencies** — the phases (by file name) that must be completed first. - **Objective** — a 1–3 sentence statement of what the phase delivers.
3. **Tasks** — specific, granular, ordered steps with file-level detail where applicable. - **Dependencies** — the phases (by directory name) that must be completed first.
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. - **Tasks** — the ordered index of this phase's task files (one line each).
5. **Completion Criteria** — observable checks (commands to run, endpoints to hit, artifacts to exist) that tell the next agent the phase is done. - **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:** **Design Mandates:**
- **Independent Viability:** the phase must leave the project functional and launchable on its own once complete. - **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. - **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. - **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 ### Final Output
Confirm the path and number of the created file, and summarize its Confirm the path and number of the created phase directory and its task
objective, dependencies, and completion criteria. If you skipped the list, and summarize the phase's objective, dependencies, and completion
interview, open the summary with the scope you derived from the chat criteria. If you skipped the interview, open the summary with the scope you
context (intent, dependencies, boundaries, and any new technology with its derived from the chat context (intent, dependencies, boundaries, and any new
permission source) so the user can correct it. Remind the user it can be technology with its permission source) so the user can correct it. Remind the
executed with the `phased-execution` skill (`run-phase.sh NN_name.md` for a user it can be executed with the `phased-execution` skill (`run-task.sh
single phase, `auto-phase.sh` for the full pipeline). 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 ## 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). - **`.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**. - **`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. - **`.agent/phases/complete/`** — create the directory, leave it empty.
Do **not** create `.agent/validate.sh` — the `phased-execution` skill Do **not** create `.agent/validate.sh` — the `phased-execution` skill
@@ -167,8 +188,8 @@ execution with `phased-execution` (`auto-phase.sh`).
## Strict Operational Rules ## 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 `.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. - **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 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). - 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: `NN` is the next free number after the highest existing file, counting `todo/` and `complete/` together. Never reuse or collide a number. - 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)"}} {{or "— (none)"}}
## Tasks ## Tasks
1. `{{path/to/file}}` — {{specific change, file-level detail}} 1. `01_{{short_name}}.md` — {{one-line summary}}
2. `{{path/to/file}}` — {{specific change}} 2. `02_{{short_name}}.md` — {{one-line summary}}
## Testing & Quality ## Testing & Quality
- Unit/integration: {{tests required for all new logic — name the behaviors to cover}} - Unit/integration: {{tests required for all new logic — name the behaviors to cover}}
- Coverage: **>90%** on new/modified code - 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 ## Completion Criteria
- [ ] {{observable check: command to run / endpoint to hit / artifact to exist}} - [ ] {{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 #!/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 # Finds the project root (nearest ancestor with .agent/phases/todo), prints
# the todo/ and complete/ phase listings, and computes the next free phase # the todo/ and complete/ phase listings with per-task state, the next free
# number NN (counting both directories together, zero-padded to 2 digits). # 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 # Usage: bash phase-status.sh # from anywhere in the project tree
@@ -16,31 +20,107 @@ while :; do
root="$(dirname "$root")" root="$(dirname "$root")"
done done
list() { phases="$root/.agent/phases"
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
}
# --- 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 max=0
for d in todo complete; do for d in todo complete; do
for f in "$root/.agent/phases/$d"/*.md; do for entry in "$phases/$d/"*; do
[[ -e "$f" ]] || continue [[ -e "$entry" ]] || continue
n="$(basename "$f" .md)" name="$(basename "$entry" .md)"
if [[ "$n" =~ ^([0-9]+) ]]; then if [[ "$name" =~ ^([0-9]+) ]]; then
n=$((10#${BASH_REMATCH[1]})) n=$((10#${BASH_REMATCH[1]}))
if (( n > max )); then max=$n; fi (( n > max )) && max=$n
fi fi
done done
done done
echo "next phase number: $(printf '%02d' $((max + 1)))"
echo "project root: $root" # --- next pending unit -----------------------------------------------------------
echo "todo:" next=""
list todo for entry in "$phases/todo/"*; do
echo "complete:" [[ -e "$entry" ]] || continue
list complete name="$(basename "$entry")"
echo "next number: $(printf '%02d' $((max + 1)))" [[ "$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 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 # Phased Execution
@@ -8,16 +8,21 @@ description: Runs the .agent/phases/ phased-execution pipeline (ported from open
Phase state lives in files, not chat: Phase state lives in files, not chat:
- `.agent/PLAN.md` — master plan; LOCKED DECISIONS are binding - `.agent/PLAN.md` — master plan; LOCKED DECISIONS are binding
- `.agent/phases/todo/NN_name.md` — pending phases (alphanumerical sort = execution order) - `.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/complete/` — finished phases - `.agent/phases/todo/NN_name.md` — legacy single-file phase (still executable as one unit)
- `.agent/reports/` — per-phase executor reports, stderr, and validation logs - `.agent/phases/complete/` — finished phases; mirrors the `todo/` layout (completed task files and the phase overview move here)
- `.agent/validate.sh` — the pass/fail gate for every phase - `.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 The unit of execution is the **task**: each task runs in a **separate pi
bounded fixer retries. A phase only moves to `complete/` after the child exits process** (fresh context) with bounded fixer retries. A task only moves to
0, the child's stream ends with a clean final report, **and** `complete/` after the child exits 0, the child's stream ends with a clean
`.agent/validate.sh` passes. This chat only dispatches and relays results — final report, **and** `.agent/validate.sh` passes. When all of a phase's tasks
do not implement phase code yourself; that is what the subprocess is for. 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 ## Live display
@@ -33,15 +38,23 @@ never lost to a truncated stream.
(Resolve `scripts/` against this skill's directory.) (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
bash scripts/run-phase.sh # first pending phase bash scripts/run-task.sh # first pending task
bash scripts/run-phase.sh 03_api.md # specific phase (warns if out of order) 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 Run one phase to completion — all of its remaining tasks, then its
phase that fails after all retries: `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
bash scripts/auto-phase.sh bash scripts/auto-phase.sh
@@ -51,17 +64,18 @@ Re-running `auto-phase.sh` after a failure continues where it stopped.
## After a run ## After a run
Exit codes: `0` = success (or nothing to run), `1` = phase failed after all Exit codes: `0` = success (or nothing to run), `1` = task/phase failed after all
attempts / no pending phases error, `130`/`143` = interrupted (Ctrl+C / SIGTERM). attempts, `130`/`143` = interrupted (Ctrl+C / SIGTERM).
Failures always print a `✗ ERROR:` line with the last error output — if the 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 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, session is resumed automatically (retries continue the child's own session,
keeping its work). keeping its work).
Relay to the user: the phase name, its executor report (printed at the end of Relay to the user: the task (or phase) name, its executor report (printed at
the script output), and the validation outcome. On failure, point the user at the end of the script output), and the validation outcome. On failure, point
`.agent/reports/<phase>.a*.{md,err,validate}` — the script also prints a ready the user at `.agent/reports/<phase>/<task>.a*.{md,err,validate}` (legacy
to run `pi --session … -c “…”` command to continue the failed session manually. 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) ## 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 | | `MAX_FIX_ATTEMPTS` | `3` | Fixer retries per phase |
| `PHASE_MODEL` | session default | `--model` for child executors (e.g. `anthropic/claude-sonnet-4-5`) | | `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_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) | | `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 | | `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) | | `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. - 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 It must be adapted to the project's real checks — it is the authoritative
quality gate. quality gate.
- Phase files are created by the `/to-phase`, `/audit-create`, `/new-project`, - Phase directories are created by the `phase-authoring` skill or the
and `/new-python-*` prompt templates. `/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 - Child executor sessions are kept in `.agent/phase-sessions/`; add it to
`.gitignore` if the project is versioned. `.gitignore` if the project is versioned.
- If you keep non-skill markdown (e.g. a `README.md`) in a skills directory - 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 ## Steps
1. Read `.agent/PLAN.md` — project goals, architecture, and **LOCKED DECISIONS** (binding; never introduce technology outside them). 1. Read `.agent/PLAN.md` — project goals, architecture, and **LOCKED DECISIONS** (binding; never introduce technology outside them).
2. Read `AGENTS.md` if present. 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. 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. 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. 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 #!/usr/bin/env bash
# auto-phase.sh — run the full phased pipeline, no LLM in the loop. # auto-phase.sh — run the full phased pipeline, no LLM in the loop.
# #
# Processes every file in .agent/phases/todo/ in alphanumerical order. # Processes every pending task in .agent/phases/todo/ in pipeline order
# Each phase runs in its own pi process (fresh context); on failure the # (phase order, then task order within each phase). Each task runs in its
# executor's session is resumed for up to MAX_FIX_ATTEMPTS fixer rounds. # own pi process (fresh context); .agent/validate.sh runs after EVERY task.
# A phase moves to .agent/phases/complete/ only after the child exits 0 # On failure the executor's session is resumed for up to MAX_FIX_ATTEMPTS
# AND .agent/validate.sh passes. Stops at the first phase that cannot be # fixer rounds. A task moves to .agent/phases/complete/ only after the
# completed — re-run this script to continue where it stopped. # 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, # Env: see SKILL.md (MAX_FIX_ATTEMPTS, PHASE_MODEL, PHASE_THINKING,
# PHASE_COMMIT, PI_TRUST, FRESH_FIX). # 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 # Make interruptions visible: state stays in .agent/phases/todo, and the
# failed executor's session is still resumable on the next run. # 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 (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) — ${phase:-the pipeline} is left in $PHASE_TODO/; re-run to continue where it stopped" >&2; exit 143' TERM 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)" cd "$(find_root)" || die "no .agent/phases/todo found in this or parent directories (run /to-phase or /audit-create first)"
build_pi_args build_pi_args
delivered=() delivered=()
while phase="$(next_phase)"; do while unit="$(next_unit)"; do
[[ -n "$phase" ]] || break [[ -n "$unit" ]] || break
if ! execute_phase "$phase"; then if ! execute_unit "$unit"; then
echo "✗ ERROR: pipeline stopped — $phase FAILED after $MAX_FIX_ATTEMPTS attempts" >&2 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 echo " fix the issues above, then re-run this script to continue where it stopped" >&2
exit 1 exit 1
fi fi
delivered+=("$phase") delivered+=("$unit")
done done
echo echo
echo "✓ pipeline done — ${#delivered[@]} phase(s) delivered this run: ${delivered[*]:-none}" echo "✓ pipeline done — ${#delivered[@]} task(s) delivered this run: ${delivered[*]:-none}"
remaining="$(ls -1 "$PHASE_TODO" 2>/dev/null | grep -cE '^[0-9]' || true)" remaining="$(count_units)"
echo " remaining in $PHASE_TODO/: $remaining" echo " remaining in $PHASE_TODO/: $remaining task(s)"
+176 -57
View File
@@ -1,19 +1,28 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# lib.sh — shared logic for the phased-execution skill. # 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: # Phase state lives in files, not chat context:
# .agent/PLAN.md master plan, LOCKED DECISIONS (binding) # .agent/PLAN.md master plan, LOCKED DECISIONS (binding)
# .agent/phases/todo/ pending phases, NN_name.md, sorted = execution order # .agent/phases/todo/NN_name/ pending phase: 00_phase.md (overview) + NN_task.md task files
# .agent/phases/complete/ finished phases # .agent/phases/todo/NN_name.md legacy single-file phase (still executable)
# .agent/reports/ per-phase executor reports, stderr, validation logs # .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) # .agent/phase-sessions/ child pi session files (resumable fixers)
# #
# The pass/fail gate is .agent/validate.sh. A phase only moves to complete/ # The unit of execution is the TASK: each task file runs in its own pi
# after the child executor exits 0 AND validation passes. # 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)" SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
EXECUTOR_PROMPT_FILE="$SKILL_DIR/assets/executor-prompt.md" 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_TODO=".agent/phases/todo"
PHASE_DONE=".agent/phases/complete" PHASE_DONE=".agent/phases/complete"
@@ -36,10 +45,100 @@ find_root() {
done done
} }
# --- phase selection ---------------------------------------------------------- # --- unit selection -------------------------------------------------------------
# First pending phase (alphanumerical sort), or empty when none remain. # A unit is the smallest schedulable piece of work, referenced relative to
next_phase() { # .agent/phases/todo/:
( cd "$PHASE_TODO" 2>/dev/null && ls -1 | grep -E '^[0-9]' | sort | head -n1 ) || true # 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 ------------------------------------------------------- # --- child pi arguments -------------------------------------------------------
@@ -89,38 +188,52 @@ recover_report() {
} }
# --- prompts ------------------------------------------------------------------ # --- 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() { 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" [[ -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() { fix_prompt() {
local errors="$1" local errors="$1"
{ {
echo "Your previous attempt at this phase was rejected by the harness." 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 phases' work." 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
echo "Failure output (may be truncated):" echo "Failure output (may be truncated):"
echo '```' echo '```'
printf '%s\n' "$errors" | tail -c 6000 printf '%s\n' "$errors" | tail -c 6000
echo '```' echo '```'
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 ----------------------------------------------------------- # --- 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 1: fresh session in .agent/phase-sessions/.
# Attempt N>1: resumes the given session file — the failed executor's own # 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 # session was captured (or FRESH_FIX=1) a fresh ephemeral session runs with
# the failure context instead. # the failure context instead.
# Progress (tool calls, assistant text, thinking, compaction) streams to the # Progress (tool calls, assistant text, thinking, compaction) streams to the
# terminal live via scripts/progress.mjs, which also writes the final # terminal live via scripts/progress.mjs, which also writes the final
# assistant message to: .agent/reports/<base>.a<attempt>.md # assistant message to: unit_report <unit> <attempt> md
# Child stderr → .agent/reports/<base>.a<attempt>.err # Child stderr → unit_report <unit> <attempt> err
# QUIET=1 suppresses the progress display (report file is still written). # 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; # 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). # 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. # trap had fired, so the failure is always visible.
if (( CHILD_RC == 130 || PROGRESS_RC == 130 )); then if (( CHILD_RC == 130 || PROGRESS_RC == 130 )); then
echo 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 exit 130
fi fi
if (( CHILD_RC == 143 || PROGRESS_RC == 143 )); then if (( CHILD_RC == 143 || PROGRESS_RC == 143 )); then
echo 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 exit 143
fi fi
} }
run_child() { run_child() {
local phase="$1" attempt="$2" prompt="$3" resume_session="${4:-}" local unit="$1" attempt="$2" prompt="$3" resume_session="${4:-}"
local base="${phase%.md}" local base out errf
local out="$PHASE_REPORTS/$base.a$attempt.md" base="$(unit_base "$unit")"
local errf="$PHASE_REPORTS/$base.a$attempt.err" 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") local PROGRESS=(node "$SKILL_DIR/scripts/progress.mjs" "$out")
[[ "${QUIET:-0}" == "1" ]] && PROGRESS+=(--quiet) [[ "${QUIET:-0}" == "1" ]] && PROGRESS+=(--quiet)
@@ -176,7 +291,7 @@ ensure_validate() {
cp "$SKILL_DIR/assets/validate.sh" .agent/validate.sh cp "$SKILL_DIR/assets/validate.sh" .agent/validate.sh
chmod +x .agent/validate.sh chmod +x .agent/validate.sh
warn "no .agent/validate.sh found — created it from the skill template." 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 fi
} }
@@ -185,39 +300,39 @@ run_validation() {
bash .agent/validate.sh >"$1" 2>&1 bash .agent/validate.sh >"$1" 2>&1
} }
# --- one phase, with bounded fixer retries ------------------------------------ # --- one unit (task, phase final pass, or legacy phase), with retries ---------
# execute_phase <phase-file> # execute_unit <unit>
# Returns 0 and moves the phase to complete/ on success; returns 1 after # Returns 0 and moves the unit to complete/ on success; returns 1 after
# MAX_FIX_ATTEMPTS failed attempts (phase file is left in todo/). # MAX_FIX_ATTEMPTS failed attempts (unit file is left in todo/).
execute_phase() { execute_unit() {
local phase="$1" local unit="$1"
local base="${phase%.md}" local base attempt=1 errors=""
local attempt=1 errors=""
local last_session="" pre post local last_session="" pre post
command -v node >/dev/null 2>&1 || die "node not found on PATH (needed to render phase progress)" base="$(unit_base "$unit")"
mkdir -p "$PHASE_DONE" "$PHASE_REPORTS" "$PHASE_SESSIONS" 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 ensure_validate
while (( attempt <= MAX_FIX_ATTEMPTS )); do while (( attempt <= MAX_FIX_ATTEMPTS )); do
echo "━━ $phase — attempt $attempt/$MAX_FIX_ATTEMPTS ━━" echo "━━ $unit — attempt $attempt/$MAX_FIX_ATTEMPTS ━━"
pre="$(latest_child_session)" pre="$(latest_child_session)"
if (( attempt == 1 )); then if (( attempt == 1 )); then
run_child "$phase" 1 "$(first_prompt "$phase")" run_child "$unit" 1 "$(first_prompt "$unit")"
else else
run_child "$phase" "$attempt" "$(fix_prompt "$errors")" "$last_session" run_child "$unit" "$attempt" "$(fix_prompt "$errors")" "$last_session"
fi fi
# Track which session file this attempt used, so the next attempt resumes # 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)" post="$(latest_child_session)"
if [[ -n "$post" && "$post" != "$pre" ]]; then if [[ -n "$post" && "$post" != "$pre" ]]; then
last_session="$post" last_session="$post"
fi fi
# The child can be signaled mid-flush: the session file then holds a final # 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. # report the stream lost. Recover it so a completed unit is not retried.
if (( CHILD_RC == 0 )) && [[ -f "$PHASE_REPORTS/$base.a$attempt.md" ]] \ if (( CHILD_RC == 0 )) && [[ -f "$(unit_report "$unit" "$attempt" md)" ]] \
&& grep -q "no final assistant message" "$PHASE_REPORTS/$base.a$attempt.md"; then && grep -q "no final assistant message" "$(unit_report "$unit" "$attempt" md)"; then
if [[ -n "$last_session" ]] && recover_report "$PHASE_REPORTS/$base.a$attempt.md" "$last_session"; 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##*/}" warn "stream lost the final message — report recovered from ${last_session##*/}"
PROGRESS_RC=0 PROGRESS_RC=0
fi fi
@@ -225,40 +340,44 @@ execute_phase() {
errors="" errors=""
if (( CHILD_RC != 0 )); then if (( CHILD_RC != 0 )); then
warn "child pi exited with code $CHILD_RC — see $PHASE_REPORTS/$base.a$attempt.err" 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 "$PHASE_REPORTS/$base.a$attempt.err" 2>/dev/null)" errors+="[child pi exited with code $CHILD_RC]"$'\n'"$(tail -c 4000 "$(unit_report "$unit" "$attempt" err)" 2>/dev/null)"
fi fi
if (( PROGRESS_RC != 0 )); then if (( PROGRESS_RC != 0 )); then
warn "child run ended without a clean final report — see $PHASE_REPORTS/$base.a$attempt.md" 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 "$PHASE_REPORTS/$base.a$attempt.err" 2>/dev/null)" errors+="[child run ended without a clean final report]"$'\n'"$(tail -c 4000 "$(unit_report "$unit" "$attempt" err)" 2>/dev/null)"
fi fi
if ! run_validation "$PHASE_REPORTS/$base.a$attempt.validate"; then if ! run_validation "$(unit_report "$unit" "$attempt" validate)"; then
warn ".agent/validate.sh FAILED — see $PHASE_REPORTS/$base.a$attempt.validate" warn ".agent/validate.sh FAILED — see $(unit_report "$unit" "$attempt" validate)"
errors+="[.agent/validate.sh FAILED]"$'\n'"$(tail -n 120 "$PHASE_REPORTS/$base.a$attempt.validate" 2>/dev/null)" errors+="[.agent/validate.sh FAILED]"$'\n'"$(tail -n 120 "$(unit_report "$unit" "$attempt" validate)" 2>/dev/null)"
fi fi
if [[ -z "$errors" ]]; then if [[ -z "$errors" ]]; then
mv -f "$PHASE_TODO/$phase" "$PHASE_DONE/$phase" move_unit "$unit"
echo "✓ $phase → complete" if is_phase_end "$unit"; then
echo "✓ $unit → complete (phase $(unit_phase "$unit") done)"
if [[ "${PHASE_COMMIT:-0}" == "1" ]]; then 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)" echo " (committed)"
else else
warn "git commit failed (continuing)" warn "git commit failed (continuing)"
fi fi
fi fi
else
echo "✓ $unit → complete"
fi
echo "── executor report ──" echo "── executor report ──"
cat "$PHASE_REPORTS/$base.a$attempt.md" cat "$(unit_report "$unit" "$attempt" md)"
return 0 return 0
fi fi
attempt=$(( attempt + 1 )) attempt=$(( attempt + 1 ))
done 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:" echo " last errors:"
printf '%s\n' "$errors" | tail -n 40 | sed 's/^/ /' 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 if [[ -n "${last_session:-}" && -f "${last_session:-}" ]]; then
echo " resume: re-run this script (it auto-resumes the failed session), or manually:" 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\"" echo " pi --session $last_session -c \"review the failures above, fix them, re-validate\""
+27 -15
View File
@@ -1,9 +1,14 @@
#!/usr/bin/env bash #!/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: # Usage:
# run-phase.sh # first pending phase (alphanumerical order) # run-phase.sh # next phase with pending work, to completion
# run-phase.sh 03_api.md # a specific pending phase (warns if out of order) # 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, # Env: see SKILL.md (MAX_FIX_ATTEMPTS, PHASE_MODEL, PHASE_THINKING,
# PHASE_COMMIT, PI_TRUST, FRESH_FIX). # 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:-}" phase="${1:-}"
if [[ -n "$phase" ]]; then if [[ -n "$phase" ]]; then
[[ "$phase" == *.md ]] || phase="$phase.md" phase="${phase%/}"; phase="${phase%.md}"; phase="${phase%%/*}"
[[ -f "$PHASE_TODO/$phase" ]] || die "$phase not found in $PHASE_TODO/" [[ -d "$PHASE_TODO/$phase" || -f "$PHASE_TODO/$phase.md" ]] || die "$phase not found in $PHASE_TODO/"
first="$(next_phase)" first="$(next_unit)"
if [[ -n "$first" && "$first" != "$phase" ]]; then if [[ -n "$first" && "${first%%/*}" != "$phase" ]]; then
warn "dependency order: $first is pending before $phase — running out of order" warn "dependency order: $first is pending before $phase — running out of order"
fi fi
else else
phase="$(next_phase)" unit="$(next_unit)"
[[ -n "$phase" ]] || { echo "✓ no pending phases in $PHASE_TODO/ — project complete."; exit 0; } [[ -n "$unit" ]] || { echo "✓ no pending tasks in $PHASE_TODO/ — project complete."; exit 0; }
phase="$(unit_phase "$unit")"
fi fi
build_pi_args build_pi_args
if execute_phase "$phase"; then failed=0
exit 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 fi
echo "✗ ERROR: phase $phase FAILED after $MAX_FIX_ATTEMPTS attempts" >&2 echo "✓ phase $phase complete"
echo " reports: $PHASE_REPORTS/${phase%.md}.a*.{md,err,validate}" >&2 exit 0
echo " resume: re-run this script — the failed executor session is resumed automatically" >&2
exit 1
+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