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
+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)}"