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:
@@ -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
|
||||
@@ -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)}"
|
||||
|
||||
Reference in New Issue
Block a user