test-plan-directories.sh 4.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. #!/usr/bin/env bash
  2. # Tests for plan directories: task-brief assembles the header and one task
  3. # file; sdd-workspace names the workspace after the directory; plan-boundary
  4. # flags a name the next plan consumes that the code does not have, checks the
  5. # later plans' attributed names too, and reports clean once the plan is fixed.
  6. set -euo pipefail
  7. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
  8. REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
  9. SDD="$REPO_ROOT/skills/subagent-driven-development/scripts"
  10. EP="$REPO_ROOT/skills/executing-plans/scripts"
  11. FAILURES=0; TEST_ROOT=""
  12. pass() { echo " [PASS] $1"; }
  13. fail() { echo " [FAIL] $1"; FAILURES=$((FAILURES + 1)); }
  14. cleanup() { if [[ -n "$TEST_ROOT" && -d "$TEST_ROOT" ]]; then rm -r -- "$TEST_ROOT"; fi; }
  15. main() {
  16. echo "=== Test: plan directories ==="
  17. TEST_ROOT="$(mktemp -d)"; trap cleanup EXIT
  18. git init -q -b main "$TEST_ROOT/repo"; local repo; repo="$(cd "$TEST_ROOT/repo" && git rev-parse --show-toplevel)"
  19. local git_id=(-c user.email=t@example.com -c user.name=t -c commit.gpgsign=false)
  20. mkdir -p "$repo/plans/1-engine" "$repo/plans/2-terminal" "$repo/plans/3-effects" "$repo/pkg"
  21. cat > "$repo/plans/1-engine/00-header.md" <<'MD'
  22. # Engine Plan
  23. **Goal:** the engine.
  24. ## Plan Set
  25. 1. `plans/1-engine` — engine. Consumes nothing.
  26. 2. `plans/2-terminal` — terminal. Consumes from plan 1: `NewGame`, `Advance`.
  27. 3. `plans/3-effects` — effects. Consumes from plan 1: `Advance`; from plan 2: `Canvas`.
  28. MD
  29. cat > "$repo/plans/1-engine/01-core.md" <<'MD'
  30. ### Task 1: Core
  31. **Interfaces:**
  32. - Consumes: nothing.
  33. - Produces: `NewGame() *Game`, `(g *Game) Advance(dt)`.
  34. - [ ] **Step 1:** write it.
  35. MD
  36. for d in 2-terminal 3-effects; do cp "$repo/plans/1-engine/00-header.md" "$repo/plans/$d/00-header.md"; done
  37. cat > "$repo/plans/2-terminal/01-app.md" <<'MD'
  38. ### Task 1: App
  39. **Interfaces:**
  40. - Consumes: `NewGame`, `Advance` (plan 1).
  41. - Produces: `Canvas`.
  42. MD
  43. cat > "$repo/plans/3-effects/01-fx.md" <<'MD'
  44. ### Task 1: FX
  45. **Interfaces:**
  46. - Consumes: `Advance` (plan 1), `Canvas` (plan 2).
  47. - Produces: `World`.
  48. MD
  49. # the engine as built calls it Tick, not Advance
  50. printf 'package pkg\ntype Game struct{}\nfunc NewGame() *Game { return &Game{} }\nfunc (g *Game) Tick(dt int) {}\n' > "$repo/pkg/game.go"
  51. (cd "$repo" && git add -A && git "${git_id[@]}" commit -q -m "engine")
  52. local out
  53. out="$(cd "$repo" && "$SDD/task-brief" plans/2-terminal 1 "$TEST_ROOT/brief.md")"
  54. if grep -q '^# Engine Plan' "$TEST_ROOT/brief.md" && grep -q '^### Task 1: App' "$TEST_ROOT/brief.md" && ! grep -q 'Task 1: FX' "$TEST_ROOT/brief.md"; then
  55. pass "task-brief on a directory writes the header plus the one task file"
  56. else fail "task-brief on a directory writes the header plus the one task file: $out"; fi
  57. out="$(cd "$repo" && "$SDD/sdd-workspace" plans/2-terminal)"
  58. if [[ "$out" == */.superpowers/sdd/2-terminal ]]; then pass "sdd-workspace names the workspace after the plan directory"; else fail "sdd-workspace names the workspace after the plan directory: $out"; fi
  59. local rc=0
  60. out="$(cd "$repo" && "$EP/plan-boundary" plans/2-terminal 2>&1)" || rc=$?
  61. if [[ "$rc" -eq 1 && "$out" == *"missing in code: Advance (consumed by 2-terminal)"* ]]; then
  62. pass "plan-boundary flags a consumed name the code does not have"
  63. else fail "plan-boundary flags a consumed name the code does not have (rc=$rc): $out"; fi
  64. if [[ "$out" == *"missing in code: Advance (consumed by 3-effects (from plans before 2-terminal))"* && "$out" != *"Canvas"* ]]; then
  65. pass "plan-boundary checks later plans' names from completed plans only"
  66. else fail "plan-boundary checks later plans' names from completed plans only: $out"; fi
  67. if [[ "$out" != *"NewGame"* ]]; then pass "plan-boundary accepts a consumed name the code has"; else fail "plan-boundary accepts a consumed name the code has"; fi
  68. (cd "$repo" && perl -pi -e 's/Advance/Tick/g' plans/*/00-header.md plans/2-terminal/01-app.md plans/3-effects/01-fx.md)
  69. rc=0; out="$(cd "$repo" && "$EP/plan-boundary" plans/2-terminal 2>&1)" || rc=$?
  70. if [[ "$rc" -eq 0 && "$out" == boundary:\ clean* ]]; then pass "plan-boundary reports clean once the plans match the code"; else fail "plan-boundary reports clean once the plans match the code (rc=$rc): $out"; fi
  71. echo
  72. if [[ "$FAILURES" -eq 0 ]]; then echo "PASS"; else echo "FAIL ($FAILURES)"; exit 1; fi
  73. }
  74. main "$@"