gru-ai 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.claude/hooks/detect-stale-docs.sh +132 -0
  2. package/.claude/hooks/tests/gate/01-valid-lightweight-skip/approve.skip +1 -0
  3. package/.claude/hooks/tests/gate/01-valid-lightweight-skip/audit.skip +1 -0
  4. package/.claude/hooks/tests/gate/01-valid-lightweight-skip/brainstorm.skip +1 -0
  5. package/.claude/hooks/tests/gate/01-valid-lightweight-skip/directive.json +12 -0
  6. package/.claude/hooks/tests/gate/01-valid-lightweight-skip/project-brainstorm.skip +1 -0
  7. package/.claude/hooks/tests/gate/02-valid-heavyweight/audit.md +2 -0
  8. package/.claude/hooks/tests/gate/02-valid-heavyweight/brainstorm.md +2 -0
  9. package/.claude/hooks/tests/gate/02-valid-heavyweight/digest.md +2 -0
  10. package/.claude/hooks/tests/gate/02-valid-heavyweight/directive.json +12 -0
  11. package/.claude/hooks/tests/gate/02-valid-heavyweight/plan.json +14 -0
  12. package/.claude/hooks/tests/gate/02-valid-heavyweight/projects/test-project/build-task-1.md +2 -0
  13. package/.claude/hooks/tests/gate/02-valid-heavyweight/projects/test-project/build-task-2.md +2 -0
  14. package/.claude/hooks/tests/gate/02-valid-heavyweight/projects/test-project/project.json +9 -0
  15. package/.claude/hooks/tests/gate/02-valid-heavyweight/projects/test-project/review-task-1.md +2 -0
  16. package/.claude/hooks/tests/gate/02-valid-heavyweight/projects/test-project/review-task-2.md +2 -0
  17. package/.claude/hooks/tests/gate/03-fail-missing-brainstorm/directive.json +11 -0
  18. package/.claude/hooks/tests/gate/04-fail-missing-review/directive.json +12 -0
  19. package/.claude/hooks/tests/gate/04-fail-missing-review/projects/test-project/build-task-a.md +2 -0
  20. package/.claude/hooks/tests/gate/04-fail-missing-review/projects/test-project/build-task-b.md +2 -0
  21. package/.claude/hooks/tests/gate/04-fail-missing-review/projects/test-project/project.json +9 -0
  22. package/.claude/hooks/tests/gate/04-fail-missing-review/projects/test-project/review-task-a.md +2 -0
  23. package/.claude/hooks/tests/gate/05-valid-completion/digest.md +2 -0
  24. package/.claude/hooks/tests/gate/05-valid-completion/directive.json +17 -0
  25. package/.claude/hooks/tests/gate/06-fail-missing-build/directive.json +10 -0
  26. package/.claude/hooks/tests/gate/06-fail-missing-build/projects/test-project/project.json +8 -0
  27. package/.claude/hooks/tests/gate/07-valid-medium-full/audit.md +2 -0
  28. package/.claude/hooks/tests/gate/07-valid-medium-full/brainstorm.md +2 -0
  29. package/.claude/hooks/tests/gate/07-valid-medium-full/directive.json +12 -0
  30. package/.claude/hooks/tests/gate/07-valid-medium-full/plan.json +10 -0
  31. package/.claude/hooks/tests/gate/07-valid-medium-full/projects/test-project/project.json +8 -0
  32. package/.claude/hooks/tests/gate/08-fail-invalid-json/brainstorm.md +2 -0
  33. package/.claude/hooks/tests/gate/08-fail-invalid-json/directive.json +10 -0
  34. package/.claude/hooks/tests/gate/08-fail-invalid-json/plan.json +1 -0
  35. package/.claude/hooks/tests/gate/09-malformed-directive-json/directive.json +1 -0
  36. package/.claude/hooks/tests/gate/run-tests.sh +283 -0
  37. package/.claude/hooks/validate-cast.sh +168 -0
  38. package/.claude/hooks/validate-gate.sh +478 -0
  39. package/.claude/hooks/validate-project-json.sh +109 -0
  40. package/.claude/hooks/validate-reviews.sh +89 -0
  41. package/.claude/skills/directive/docs/pipeline/00-delegation-and-triage.md +2 -0
  42. package/.claude/skills/directive/docs/pipeline/02-read-directive.md +0 -6
  43. package/.claude/skills/directive/docs/pipeline/03-read-context.md +1 -3
  44. package/.claude/skills/directive/docs/pipeline/05-planning.md +1 -1
  45. package/.claude/skills/directive/docs/pipeline/06-technical-audit.md +0 -23
  46. package/.claude/skills/directive/docs/pipeline/07-plan-approval.md +0 -1
  47. package/.claude/skills/directive/docs/pipeline/09-execute-projects.md +12 -7
  48. package/.claude/skills/directive/docs/pipeline/10-wrapup.md +0 -4
  49. package/.claude/skills/directive/docs/reference/rules/casting-rules.md +2 -2
  50. package/.claude/skills/directive/docs/reference/rules/phase-definitions.md +3 -0
  51. package/.claude/skills/directive/docs/reference/rules/scope-and-dod.md +1 -1
  52. package/.claude/skills/directive/docs/reference/schemas/directive-json.md +0 -7
  53. package/.claude/skills/directive/docs/reference/schemas/plan-schema.md +0 -1
  54. package/.claude/skills/directive/docs/reference/templates/investigator-prompt.md +0 -3
  55. package/.claude/skills/directive/docs/reference/templates/planner-prompt.md +0 -1
  56. package/.claude/skills/gruai-agents/SKILL.md +114 -68
  57. package/.claude/skills/gruai-config/SKILL.md +33 -6
  58. package/.claude/skills/scout/SKILL.md +1 -1
  59. package/cli/resolve-pkg-root.sh +49 -0
  60. package/cli/templates/CLAUDE.md.template +1 -1
  61. package/cli/templates/directive.json.template +0 -1
  62. package/cli/templates/welcome-directive/directive.json +0 -1
  63. package/dist/00_Modern_Office_Singles.tsx +4 -0
  64. package/dist/Game.tiled-project +14 -0
  65. package/dist/Game.tiled-session +90 -0
  66. package/dist/Interiors.tsx +4 -0
  67. package/dist/Interiors_32x32.tsx +4 -0
  68. package/dist/Office_Design_1.tsx +4 -0
  69. package/dist/Office_Design_2.tsx +4 -0
  70. package/dist/assets/GamePage-B2OsBjXm.js +49 -0
  71. package/dist/assets/index-Bh01am7W.js +12 -0
  72. package/dist/assets/index-DCNBE1pw.css +1 -0
  73. package/dist/assets/office/Interiors.png +0 -0
  74. package/dist/assets/office/classroom.png +0 -0
  75. package/dist/assets/office/conference.png +0 -0
  76. package/dist/assets/office/generic.png +0 -0
  77. package/dist/assets/office/kitchen.png +0 -0
  78. package/dist/assets/office/livingroom.png +0 -0
  79. package/dist/assets/office/music-sport.png +0 -0
  80. package/dist/classroom.tsx +4 -0
  81. package/dist/conference.tsx +4 -0
  82. package/dist/furniture.tsx +4 -0
  83. package/dist/generic.tsx +4 -0
  84. package/dist/index.html +2 -2
  85. package/dist/kitchen.tsx +4 -0
  86. package/dist/livingroom.tsx +4 -0
  87. package/dist/music-sport.tsx +4 -0
  88. package/dist/office.tmx +398 -0
  89. package/dist/room-builder.tsx +4 -0
  90. package/dist-cli/commands/init.d.ts +9 -0
  91. package/dist-cli/commands/init.js +239 -0
  92. package/dist-cli/commands/scaffold.d.ts +15 -0
  93. package/dist-cli/commands/scaffold.js +338 -0
  94. package/dist-cli/commands/start.d.ts +7 -0
  95. package/dist-cli/commands/start.js +84 -0
  96. package/dist-cli/commands/update.d.ts +9 -0
  97. package/dist-cli/commands/update.js +189 -0
  98. package/dist-cli/index.d.ts +2 -0
  99. package/dist-cli/index.js +75 -0
  100. package/dist-cli/lib/color.d.ts +15 -0
  101. package/dist-cli/lib/color.js +21 -0
  102. package/dist-cli/lib/paths.d.ts +13 -0
  103. package/dist-cli/lib/paths.js +41 -0
  104. package/dist-cli/lib/roles.d.ts +22 -0
  105. package/dist-cli/lib/roles.js +201 -0
  106. package/dist-cli/lib/types.d.ts +42 -0
  107. package/dist-cli/lib/types.js +4 -0
  108. package/dist-server/scripts/personality-compiler.d.ts +13 -0
  109. package/dist-server/scripts/personality-compiler.js +172 -0
  110. package/dist-server/scripts/spawn-agent.d.ts +15 -0
  111. package/dist-server/scripts/spawn-agent.js +144 -0
  112. package/dist-server/server/actions/send-input.js +1 -3
  113. package/dist-server/server/config.d.ts +0 -1
  114. package/dist-server/server/config.js +0 -4
  115. package/dist-server/server/db.d.ts +0 -1
  116. package/dist-server/server/db.js +0 -15
  117. package/dist-server/server/index.js +17 -27
  118. package/dist-server/server/notifications/notifier.js +3 -2
  119. package/dist-server/server/parsers/session-scanner.js +15 -8
  120. package/dist-server/server/parsers/task-parser.d.ts +2 -5
  121. package/dist-server/server/parsers/task-parser.js +10 -25
  122. package/dist-server/server/paths.d.ts +34 -0
  123. package/dist-server/server/paths.js +110 -0
  124. package/dist-server/server/platform/__tests__/claude-code.test.js +0 -2
  125. package/dist-server/server/platform/__tests__/codex-cli-spawn.test.d.ts +1 -0
  126. package/dist-server/server/platform/__tests__/codex-cli-spawn.test.js +124 -0
  127. package/dist-server/server/platform/aider-spawn.d.ts +77 -0
  128. package/dist-server/server/platform/aider-spawn.js +189 -0
  129. package/dist-server/server/platform/claude-code-spawn.d.ts +59 -0
  130. package/dist-server/server/platform/claude-code-spawn.js +174 -0
  131. package/dist-server/server/platform/claude-code.d.ts +1 -1
  132. package/dist-server/server/platform/claude-code.js +2 -3
  133. package/dist-server/server/platform/codex-cli-spawn.d.ts +81 -0
  134. package/dist-server/server/platform/codex-cli-spawn.js +204 -0
  135. package/dist-server/server/platform/gemini-cli-spawn.d.ts +75 -0
  136. package/dist-server/server/platform/gemini-cli-spawn.js +188 -0
  137. package/dist-server/server/platform/index.d.ts +26 -1
  138. package/dist-server/server/platform/index.js +37 -0
  139. package/dist-server/server/platform/spawn-adapter.d.ts +115 -0
  140. package/dist-server/server/platform/spawn-adapter.js +12 -0
  141. package/dist-server/server/platform/types.d.ts +0 -4
  142. package/dist-server/server/state/aggregator.d.ts +0 -2
  143. package/dist-server/server/state/aggregator.js +9 -107
  144. package/dist-server/server/state/work-item-types.d.ts +953 -291
  145. package/dist-server/server/state/work-item-types.js +2 -6
  146. package/dist-server/server/types.d.ts +3 -79
  147. package/dist-server/server/watchers/directive-watcher.js +7 -3
  148. package/dist-server/server/watchers/state-watcher.js +0 -3
  149. package/package.json +10 -3
  150. package/dist/assets/GamePage-C5XQQOQH.js +0 -49
  151. package/dist/assets/index-CnTPDqpP.js +0 -12
  152. package/dist/assets/index-gR5q7ikB.css +0 -1
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env bash
2
+ # detect-stale-docs.sh — Post-directive hook to detect potentially stale documentation
3
+ #
4
+ # After a directive completes, scans .context/ and .claude/ docs for references
5
+ # to files that were modified in the directive. If the doc itself was NOT modified,
6
+ # it is flagged as potentially stale.
7
+ #
8
+ # Staleness heuristic:
9
+ # "References a file" = doc contains a literal file path substring (grep -F)
10
+ # "Stale" = doc references a file modified in the directive, but the doc
11
+ # itself was NOT modified in the same directive
12
+ #
13
+ # Usage:
14
+ # ./detect-stale-docs.sh file1.ts file2.tsx ...
15
+ # ./detect-stale-docs.sh --from-diff <base-branch>
16
+ # echo "file1.ts\nfile2.ts" | ./detect-stale-docs.sh --stdin
17
+ #
18
+ # Output: list of potentially stale docs with the files they reference
19
+ # Exit 0 always (output is informational, not blocking)
20
+
21
+ set -euo pipefail
22
+
23
+ # Anchor to repo root
24
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || {
25
+ echo "Error: not in a git repo" >&2
26
+ exit 1
27
+ }
28
+ cd "$REPO_ROOT"
29
+
30
+ # --- Collect modified files ---
31
+ MODIFIED_FILES=()
32
+
33
+ if [[ "${1:-}" == "--from-diff" ]]; then
34
+ # Get modified files from git diff against a base branch
35
+ BASE_BRANCH="${2:-main}"
36
+ while IFS= read -r f; do
37
+ [[ -n "$f" ]] && MODIFIED_FILES+=("$f")
38
+ done < <(git diff --name-only "$BASE_BRANCH" 2>/dev/null)
39
+ elif [[ "${1:-}" == "--stdin" ]]; then
40
+ # Read file list from stdin
41
+ while IFS= read -r f; do
42
+ [[ -n "$f" ]] && MODIFIED_FILES+=("$f")
43
+ done
44
+ else
45
+ # Read file list from arguments
46
+ for f in "$@"; do
47
+ [[ -n "$f" ]] && MODIFIED_FILES+=("$f")
48
+ done
49
+ fi
50
+
51
+ if [[ ${#MODIFIED_FILES[@]} -eq 0 ]]; then
52
+ exit 0 # No modified files — nothing to check
53
+ fi
54
+
55
+ # --- Build newline-separated list of modified docs (for exclusion) ---
56
+ # Compatible with bash 3.x (no associative arrays)
57
+ MODIFIED_DOCS=""
58
+ for f in "${MODIFIED_FILES[@]}"; do
59
+ case "$f" in
60
+ .context/*.md|.claude/*.md)
61
+ MODIFIED_DOCS="${MODIFIED_DOCS}${f}"$'\n'
62
+ ;;
63
+ esac
64
+ done
65
+
66
+ # --- Create temp file with patterns (one per line) for grep -f ---
67
+ PATTERNS_FILE=$(mktemp)
68
+ trap 'rm -f "$PATTERNS_FILE"' EXIT
69
+ printf '%s\n' "${MODIFIED_FILES[@]}" > "$PATTERNS_FILE"
70
+
71
+ # --- Phase 1: Find all candidate docs in a single grep pass ---
72
+ # Use grep -Fl -f to find all .md files that contain ANY modified file path.
73
+ # This is much faster than iterating: one grep invocation covers all docs + patterns.
74
+ CANDIDATE_DOCS=()
75
+ while IFS= read -r doc; do
76
+ [[ -n "$doc" ]] && CANDIDATE_DOCS+=("$doc")
77
+ done < <(
78
+ find .context/ .claude/ -name '*.md' -type f 2>/dev/null \
79
+ | grep -v '/worktrees/' \
80
+ | grep -v 'node_modules' \
81
+ | xargs grep -Fl -f "$PATTERNS_FILE" 2>/dev/null || true
82
+ )
83
+
84
+ if [[ ${#CANDIDATE_DOCS[@]} -eq 0 ]]; then
85
+ echo "No potentially stale docs detected."
86
+ exit 0
87
+ fi
88
+
89
+ # --- Phase 2: For each candidate, determine which files it references ---
90
+ # Only runs on the (small) set of docs that matched in Phase 1.
91
+ # Also filters out docs that were themselves modified (zero false positives).
92
+
93
+ STALE_RESULTS=()
94
+
95
+ for doc in "${CANDIDATE_DOCS[@]}"; do
96
+ # Skip docs that were also modified in this directive
97
+ if echo "$MODIFIED_DOCS" | grep -qxF "$doc" 2>/dev/null; then
98
+ continue
99
+ fi
100
+
101
+ # Determine which specific modified files this doc references
102
+ REFERENCED=()
103
+ for modified in "${MODIFIED_FILES[@]}"; do
104
+ if grep -qF "$modified" "$doc" 2>/dev/null; then
105
+ REFERENCED+=("$modified")
106
+ fi
107
+ done
108
+
109
+ if [[ ${#REFERENCED[@]} -gt 0 ]]; then
110
+ REFS_STR=$(printf '%s, ' "${REFERENCED[@]}")
111
+ REFS_STR="${REFS_STR%, }" # trim trailing comma+space
112
+ STALE_RESULTS+=("$doc -> references modified: $REFS_STR")
113
+ fi
114
+ done
115
+
116
+ # --- Output ---
117
+ if [[ ${#STALE_RESULTS[@]} -eq 0 ]]; then
118
+ echo "No potentially stale docs detected."
119
+ exit 0
120
+ fi
121
+
122
+ echo "## Potentially Stale Docs"
123
+ echo ""
124
+ echo "The following docs reference files that were modified but were not themselves updated:"
125
+ echo ""
126
+ for result in "${STALE_RESULTS[@]}"; do
127
+ echo "- $result"
128
+ done
129
+ echo ""
130
+ echo "Consider reviewing these docs for accuracy."
131
+
132
+ exit 0
@@ -0,0 +1 @@
1
+ Skipped: lightweight directive does not require approval
@@ -0,0 +1 @@
1
+ Skipped: lightweight directive does not require audit
@@ -0,0 +1 @@
1
+ Skipped: lightweight directive does not require brainstorm
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "test-lightweight",
3
+ "title": "Test lightweight directive",
4
+ "status": "in_progress",
5
+ "weight": "lightweight",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "read": { "status": "completed" },
9
+ "context": { "status": "completed" },
10
+ "approve": { "status": "completed" }
11
+ }
12
+ }
@@ -0,0 +1 @@
1
+ Skipped: lightweight directive does not require project brainstorm
@@ -0,0 +1,2 @@
1
+ # Audit Results
2
+ Technical audit for heavyweight directive test.
@@ -0,0 +1,2 @@
1
+ # Brainstorm Output
2
+ This is a test brainstorm document for the heavyweight directive.
@@ -0,0 +1,2 @@
1
+ # Digest
2
+ Test heavyweight directive completed.
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "test-heavyweight",
3
+ "title": "Test heavyweight directive",
4
+ "status": "in_progress",
5
+ "weight": "heavyweight",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "read": { "status": "completed" },
9
+ "context": { "status": "completed" },
10
+ "approve": { "status": "completed" }
11
+ }
12
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "goal": "Test heavyweight",
3
+ "projects": [
4
+ {
5
+ "id": "test-project",
6
+ "title": "Test project",
7
+ "cast": { "builder": "riley", "auditor": "sarah", "reviewers": ["sarah"] },
8
+ "tasks": [
9
+ { "id": "task-1", "title": "First task" },
10
+ { "id": "task-2", "title": "Second task" }
11
+ ]
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,2 @@
1
+ # Build Report: task-1
2
+ Build completed successfully.
@@ -0,0 +1,2 @@
1
+ # Build Report: task-2
2
+ Build completed successfully.
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "test-project",
3
+ "title": "Test project",
4
+ "status": "in_progress",
5
+ "tasks": [
6
+ { "id": "task-1", "title": "First task", "status": "completed" },
7
+ { "id": "task-2", "title": "Second task", "status": "completed" }
8
+ ]
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "test-no-brainstorm",
3
+ "title": "Test missing brainstorm for heavyweight",
4
+ "status": "in_progress",
5
+ "weight": "heavyweight",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "read": { "status": "completed" },
9
+ "context": { "status": "completed" }
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "test-no-review",
3
+ "title": "Test missing review artifacts",
4
+ "status": "in_progress",
5
+ "weight": "medium",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "read": { "status": "completed" },
9
+ "context": { "status": "completed" },
10
+ "approve": { "status": "completed" }
11
+ }
12
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "test-project",
3
+ "title": "Test project",
4
+ "status": "in_progress",
5
+ "tasks": [
6
+ { "id": "task-a", "title": "Task A", "status": "completed" },
7
+ { "id": "task-b", "title": "Task B", "status": "completed" }
8
+ ]
9
+ }
@@ -0,0 +1,2 @@
1
+ # Digest
2
+ Strategic directive completed successfully.
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "test-completion",
3
+ "title": "Test completion gate",
4
+ "status": "awaiting_completion",
5
+ "weight": "strategic",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "read": { "status": "completed" },
9
+ "context": { "status": "completed" },
10
+ "brainstorm": { "status": "completed" },
11
+ "plan": { "status": "completed" },
12
+ "audit": { "status": "completed" },
13
+ "approve": { "status": "completed" },
14
+ "execute": { "status": "completed" },
15
+ "wrapup": { "status": "completed" }
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "test-no-build",
3
+ "title": "Test missing build artifact for review gate",
4
+ "status": "in_progress",
5
+ "weight": "medium",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "approve": { "status": "completed" }
9
+ }
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "test-project",
3
+ "title": "Test project",
4
+ "status": "in_progress",
5
+ "tasks": [
6
+ { "id": "widget", "title": "Build widget", "status": "in_progress" }
7
+ ]
8
+ }
@@ -0,0 +1,2 @@
1
+ # Audit
2
+ Medium directive audit.
@@ -0,0 +1,2 @@
1
+ # Brainstorm
2
+ Medium directive brainstorm.
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "test-medium-full",
3
+ "title": "Test medium with all artifacts",
4
+ "status": "in_progress",
5
+ "weight": "medium",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "read": { "status": "completed" },
9
+ "context": { "status": "completed" },
10
+ "approve": { "status": "completed" }
11
+ }
12
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "goal": "Test medium",
3
+ "projects": [
4
+ {
5
+ "id": "test-project",
6
+ "title": "Test project",
7
+ "tasks": [{ "id": "t1", "title": "Task 1" }]
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "test-project",
3
+ "title": "Test project",
4
+ "status": "in_progress",
5
+ "tasks": [
6
+ { "id": "t1", "title": "Task 1", "status": "pending" }
7
+ ]
8
+ }
@@ -0,0 +1,2 @@
1
+ # Brainstorm
2
+ Test brainstorm.
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "test-invalid-json",
3
+ "title": "Test invalid JSON in plan",
4
+ "status": "in_progress",
5
+ "weight": "medium",
6
+ "pipeline": {
7
+ "triage": { "status": "completed" },
8
+ "context": { "status": "completed" }
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ { this is not valid json !!!
@@ -0,0 +1,283 @@
1
+ #!/usr/bin/env bash
2
+ # run-tests.sh — Test runner for validate-gate.sh
3
+ #
4
+ # Exercises all fixture directories and reports pass/fail.
5
+ # Exit 0 when all tests pass, exit 1 with details on failure.
6
+ #
7
+ # Usage: ./run-tests.sh
8
+
9
+ set -euo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ GATE_SCRIPT="$(cd "$SCRIPT_DIR/../.." && pwd)/validate-gate.sh"
13
+ PASS_COUNT=0
14
+ FAIL_COUNT=0
15
+ FAILURES=()
16
+
17
+ # Colors (if terminal supports them)
18
+ RED='\033[0;31m'
19
+ GREEN='\033[0;32m'
20
+ YELLOW='\033[0;33m'
21
+ NC='\033[0m' # No Color
22
+
23
+ run_test() {
24
+ local name="$1"
25
+ local fixture_dir="$2"
26
+ local target_step="$3"
27
+ local expect_valid="$4" # "true" or "false"
28
+ local extra_args="${5:-}"
29
+ local expect_violation_substr="${6:-}"
30
+
31
+ # Make a working copy so directive.json writes don't pollute fixtures
32
+ local tmpdir
33
+ tmpdir=$(mktemp -d)
34
+ cp -R "$fixture_dir/" "$tmpdir/"
35
+
36
+ local output
37
+ if [[ -n "$extra_args" ]]; then
38
+ output=$("$GATE_SCRIPT" "$tmpdir" "$target_step" "$extra_args" 2>&1) || true
39
+ else
40
+ output=$("$GATE_SCRIPT" "$tmpdir" "$target_step" 2>&1) || true
41
+ fi
42
+
43
+ local actual_valid
44
+ actual_valid=$(echo "$output" | jq -r '.valid' 2>/dev/null || echo "parse_error")
45
+
46
+ local test_passed=true
47
+
48
+ if [[ "$actual_valid" != "$expect_valid" ]]; then
49
+ test_passed=false
50
+ fi
51
+
52
+ # If we expect a specific violation substring, check for it
53
+ if [[ -n "$expect_violation_substr" && "$expect_valid" == "false" ]]; then
54
+ if ! echo "$output" | jq -r '.violations[].message' 2>/dev/null | grep -q "$expect_violation_substr"; then
55
+ test_passed=false
56
+ fi
57
+ fi
58
+
59
+ # If gate passed (valid=true), verify it wrote to directive.json
60
+ if [[ "$expect_valid" == "true" && "$actual_valid" == "true" ]]; then
61
+ local gates_entry
62
+ gates_entry=$(jq -r ".gates.\"$target_step\".passed_at // empty" "$tmpdir/directive.json" 2>/dev/null)
63
+ if [[ -z "$gates_entry" ]]; then
64
+ test_passed=false
65
+ echo -e " ${RED}FAIL${NC} $name — gate passed but did not write gates.$target_step to directive.json"
66
+ FAIL_COUNT=$((FAIL_COUNT + 1))
67
+ FAILURES+=("$name (gates write missing)")
68
+ rm -rf "$tmpdir"
69
+ return
70
+ fi
71
+ fi
72
+
73
+ if [[ "$test_passed" == "true" ]]; then
74
+ echo -e " ${GREEN}PASS${NC} $name"
75
+ PASS_COUNT=$((PASS_COUNT + 1))
76
+ else
77
+ echo -e " ${RED}FAIL${NC} $name"
78
+ echo " Expected valid=$expect_valid, got valid=$actual_valid"
79
+ echo " Output: $(echo "$output" | head -5)"
80
+ FAIL_COUNT=$((FAIL_COUNT + 1))
81
+ FAILURES+=("$name")
82
+ fi
83
+
84
+ rm -rf "$tmpdir"
85
+ }
86
+
87
+ echo ""
88
+ echo "validate-gate.sh Test Suite"
89
+ echo "==========================="
90
+ echo ""
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Test 01: Valid lightweight with .skip markers
94
+ # ---------------------------------------------------------------------------
95
+ echo "01: Lightweight directive with .skip markers"
96
+
97
+ run_test \
98
+ "plan gate passes with brainstorm.skip (lightweight)" \
99
+ "$SCRIPT_DIR/01-valid-lightweight-skip" \
100
+ "plan" \
101
+ "true"
102
+
103
+ run_test \
104
+ "execute gate passes with approve.skip (lightweight)" \
105
+ "$SCRIPT_DIR/01-valid-lightweight-skip" \
106
+ "execute" \
107
+ "true"
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # Test 02: Valid heavyweight — full artifact chain
111
+ # ---------------------------------------------------------------------------
112
+ echo ""
113
+ echo "02: Heavyweight directive with all artifacts"
114
+
115
+ run_test \
116
+ "plan gate passes (brainstorm.md exists)" \
117
+ "$SCRIPT_DIR/02-valid-heavyweight" \
118
+ "plan" \
119
+ "true"
120
+
121
+ run_test \
122
+ "audit gate passes (plan.json exists with .projects)" \
123
+ "$SCRIPT_DIR/02-valid-heavyweight" \
124
+ "audit" \
125
+ "true"
126
+
127
+ run_test \
128
+ "execute gate passes (approval in directive.json)" \
129
+ "$SCRIPT_DIR/02-valid-heavyweight" \
130
+ "execute" \
131
+ "true"
132
+
133
+ run_test \
134
+ "review-gate passes for task-1 (build-task-1.md exists)" \
135
+ "$SCRIPT_DIR/02-valid-heavyweight" \
136
+ "review-gate" \
137
+ "true" \
138
+ "task-1"
139
+
140
+ run_test \
141
+ "wrapup gate passes (all reviews exist)" \
142
+ "$SCRIPT_DIR/02-valid-heavyweight" \
143
+ "wrapup" \
144
+ "true"
145
+
146
+ run_test \
147
+ "completion gate passes (digest.md exists)" \
148
+ "$SCRIPT_DIR/02-valid-heavyweight" \
149
+ "completion" \
150
+ "true"
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # Test 03: Fail — missing brainstorm for heavyweight
154
+ # ---------------------------------------------------------------------------
155
+ echo ""
156
+ echo "03: Missing brainstorm for heavyweight"
157
+
158
+ run_test \
159
+ "plan gate fails (no brainstorm.md, heavyweight)" \
160
+ "$SCRIPT_DIR/03-fail-missing-brainstorm" \
161
+ "plan" \
162
+ "false" \
163
+ "" \
164
+ "Missing brainstorm artifact"
165
+
166
+ # ---------------------------------------------------------------------------
167
+ # Test 04: Fail — missing review artifact
168
+ # ---------------------------------------------------------------------------
169
+ echo ""
170
+ echo "04: Missing review artifact blocks wrapup"
171
+
172
+ run_test \
173
+ "wrapup gate fails (review-task-b.md missing)" \
174
+ "$SCRIPT_DIR/04-fail-missing-review" \
175
+ "wrapup" \
176
+ "false" \
177
+ "" \
178
+ "Missing review artifact"
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Test 05: Valid completion gate
182
+ # ---------------------------------------------------------------------------
183
+ echo ""
184
+ echo "05: Valid completion gate"
185
+
186
+ run_test \
187
+ "completion gate passes (digest.md exists, strategic)" \
188
+ "$SCRIPT_DIR/05-valid-completion" \
189
+ "completion" \
190
+ "true"
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # Test 06: Fail — missing build artifact blocks review
194
+ # ---------------------------------------------------------------------------
195
+ echo ""
196
+ echo "06: Missing build artifact blocks review"
197
+
198
+ run_test \
199
+ "review-gate fails for task 'widget' (no build-widget.md)" \
200
+ "$SCRIPT_DIR/06-fail-missing-build" \
201
+ "review-gate" \
202
+ "false" \
203
+ "widget" \
204
+ "Missing build artifact"
205
+
206
+ # ---------------------------------------------------------------------------
207
+ # Test 07: Valid medium — full chain through execute
208
+ # ---------------------------------------------------------------------------
209
+ echo ""
210
+ echo "07: Medium directive with full chain"
211
+
212
+ run_test \
213
+ "plan gate passes (brainstorm.md exists, medium)" \
214
+ "$SCRIPT_DIR/07-valid-medium-full" \
215
+ "plan" \
216
+ "true"
217
+
218
+ run_test \
219
+ "audit gate passes (plan.json valid)" \
220
+ "$SCRIPT_DIR/07-valid-medium-full" \
221
+ "audit" \
222
+ "true"
223
+
224
+ run_test \
225
+ "approve gate passes (project.json with tasks)" \
226
+ "$SCRIPT_DIR/07-valid-medium-full" \
227
+ "approve" \
228
+ "true"
229
+
230
+ run_test \
231
+ "execute gate passes (approval completed)" \
232
+ "$SCRIPT_DIR/07-valid-medium-full" \
233
+ "execute" \
234
+ "true"
235
+
236
+ # ---------------------------------------------------------------------------
237
+ # Test 08: Fail — invalid JSON in plan.json
238
+ # ---------------------------------------------------------------------------
239
+ echo ""
240
+ echo "08: Invalid JSON in artifact"
241
+
242
+ run_test \
243
+ "audit gate fails (plan.json is invalid JSON)" \
244
+ "$SCRIPT_DIR/08-fail-invalid-json" \
245
+ "audit" \
246
+ "false" \
247
+ "" \
248
+ "not valid JSON"
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # Test 09: Fail — malformed directive.json
252
+ # ---------------------------------------------------------------------------
253
+ echo ""
254
+ echo "09: Malformed directive.json"
255
+
256
+ run_test \
257
+ "plan gate fails (directive.json is not valid JSON)" \
258
+ "$SCRIPT_DIR/09-malformed-directive-json" \
259
+ "plan" \
260
+ "false" \
261
+ "" \
262
+ "not valid JSON"
263
+
264
+ # ---------------------------------------------------------------------------
265
+ # Summary
266
+ # ---------------------------------------------------------------------------
267
+ echo ""
268
+ echo "==========================="
269
+ TOTAL=$((PASS_COUNT + FAIL_COUNT))
270
+ echo -e "Results: ${GREEN}${PASS_COUNT}${NC} passed, ${RED}${FAIL_COUNT}${NC} failed, ${TOTAL} total"
271
+
272
+ if [[ $FAIL_COUNT -gt 0 ]]; then
273
+ echo ""
274
+ echo "Failures:"
275
+ for f in "${FAILURES[@]}"; do
276
+ echo -e " ${RED}-${NC} $f"
277
+ done
278
+ exit 1
279
+ fi
280
+
281
+ echo ""
282
+ echo -e "${GREEN}All tests passed.${NC}"
283
+ exit 0