Skip to content

scripts/task-brief

skills/subagent-driven-development/scripts/task-brief · 4.7 KB · View on GitHub at 22f0b46

scripts/task-brief
#!/usr/bin/env bash
# Extract one task's full text from an implementation plan into a file the
# implementer reads in one call, so the task text never has to be pasted
# through the controller's context.
#
# Usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]
# Default OUTFILE: <repo-root>/.superpowers/sdd/<plan-basename>/task-<N>-brief.md
# (per plan and per worktree; concurrent runs of the SAME plan in the same
# working tree share it). -C DIR runs as if started in DIR, as in sdd-workspace.
set -euo pipefail
here=$(cd "$(dirname "$0")" && pwd)
if [ "${1:-}" = "-C" ]; then
[ -d "${2:-}" ] || { echo "bad -C directory: ${2:-}" >&2; exit 2; }
cd "$2"
shift 2
fi
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "usage: task-brief [-C DIR] PLAN_FILE TASK_NUMBER [OUTFILE]" >&2
exit 2
fi
plan=$1
n=$2
[ -f "$plan" ] || { echo "no such plan file: $plan" >&2; exit 2; }
if [ $# -eq 3 ]; then
out=$3
else
# Invoke via bash rather than direct exec: some extractors (Python zipfile)
# strip Unix exec bits when unpacking marketplace packages (#2040).
dir=$("${BASH:-bash}" "$here/sdd-workspace" "$plan")
out="$dir/task-${n}-brief.md"
fi
# A task heading is `Task` followed by an ID: letters and digits, optionally
# dot-separated, containing at least one digit (3, 6b, A1, 2.1). The digit
# keeps headings like "## Task Overview" or "#### Task notes" from becoming
# task boundaries. The ID is compared to TASK_NUMBER as a whole string, so
# Task 6 never matches Task 6b, Task 1 never matches Task 1.1, and
# TASK_NUMBER never reaches awk as a regex (#2459). A deeper Task heading
# whose ID extends the requested one with a letter or dot is a sub-task and
# stays in its brief (### Task 7 keeps #### Task 7a), as it always has.
# Depth alone is not enough: plans drift from "## Task 1" to "### Task 2".
#
# The word stays the English literal "Task", whatever language the rest of
# the plan is in. A plan with no Task headings at all usually translated it
# ("## Tarea 1"), so list the headings shaped like tasks before the
# not-found error instead of implying the plan has no task 1 (#2496).
#
# A fence that never closes hides every Task heading after it (#2436). When
# it hides one while the requested task is being captured, that task's end
# can't be found and the brief would run on through every later task: refuse
# (exit 4) rather than hand an implementer several tasks as its requirements.
# When it hides the requested heading itself, say so before the not-found
# error. A stray fence that hides no Task heading (real plans end with a lone
# ``` line) leaves the brief complete, so brief it with a warning.
# The plan path goes through ENVIRON, not -v, which would mangle the
# backslashes of a Windows path.
rc=0
TASK_BRIEF_PLAN="$plan" awk -v n="$n" '
/^```/ { infence = !infence; if (infence) { fence_line = NR; hidden = 0 } }
infence && /^#+[ \t]+Task[ \t]/ { hidden = 1 }
!infence && /^#+[ \t]+Task[ \t]/ {
id = $0
sub(/^#+[ \t]+Task[ \t]+/, "", id)
if (match(id, /^[A-Za-z0-9]+(\.[A-Za-z0-9]+)*/) && substr(id, 1, RLENGTH) ~ /[0-9]/) {
tasks++
id = substr(id, 1, RLENGTH)
match($0, /^#+/)
subtask = intask && RLENGTH > level && substr(id, 1, length(n)) == n \
&& substr(id, length(n) + 1, 1) ~ /[A-Za-z.]/
if (!subtask) {
intask = (id == n)
level = RLENGTH
}
}
}
!infence && /^#+[ \t]+[^ \t]+[ \t]+[A-Za-z0-9.]*[0-9]/ && shaped < 5 { shaped_heading[++shaped] = $0 }
intask { print; found = 1 }
END {
where = "the code fence opened at line " fence_line " of " ENVIRON["TASK_BRIEF_PLAN"] " never closes"
if (infence && hidden && (intask || !found)) {
print "task-brief: " where ", so no Task heading after it can be seen; close that fence in the plan and rerun" | "cat >&2"
if (intask) exit 4
} else if (infence && intask) {
print "task-brief: warning: " where "; it hides no Task heading, so this brief is complete" | "cat >&2"
}
# An open fence that hid Task headings already explains why none were
# seen; the English-word hint would contradict it.
if (!tasks && shaped && !(infence && hidden)) {
print "task-brief: no task headings found. Task headings must use the English word \"Task\" (\"### Task 1: ...\"), even when the rest of the plan is in another language. Headings shaped like tasks:" | "cat >&2"
for (i = 1; i <= shaped; i++) print " " shaped_heading[i] | "cat >&2"
}
}
' "$plan" > "$out" || rc=$?
if [ "$rc" -ne 0 ]; then
if [ -f "$out" ]; then rm -f "$out"; fi
exit "$rc"
fi
if [ ! -s "$out" ]; then
echo "task ${n} not found in ${plan} (no heading matching 'Task ${n}')" >&2
exit 3
fi
echo "wrote ${out}: $(wc -l < "$out" | tr -d ' ') lines"