@drunkcoding/agents-and-skills 0.0.19 → 0.0.23

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 (30) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/package.json +1 -1
  3. package/plugins/auto-power/.claude-plugin/plugin.json +1 -1
  4. package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
  5. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  6. package/plugins/team-superpower/.claude-plugin/plugin.json +1 -1
  7. package/plugins/team-superpower/README.md +186 -115
  8. package/plugins/team-superpower/agents/backend-developer.md +105 -73
  9. package/plugins/team-superpower/agents/feature-planner.md +66 -0
  10. package/plugins/team-superpower/agents/frontend-developer.md +108 -70
  11. package/plugins/team-superpower/agents/orchestrator.md +83 -0
  12. package/plugins/team-superpower/agents/qc-engineer.md +84 -0
  13. package/plugins/team-superpower/agents/security-engineer.md +79 -44
  14. package/plugins/team-superpower/agents/solution-architect.md +80 -0
  15. package/plugins/team-superpower/agents/team-leader.md +100 -0
  16. package/plugins/team-superpower/assets/CLAUDE.md.template +22 -18
  17. package/plugins/team-superpower/assets/ESCALATION.md +114 -66
  18. package/plugins/team-superpower/assets/SESSION_README.md +232 -159
  19. package/plugins/team-superpower/commands/team-feature.md +191 -645
  20. package/plugins/team-superpower/hooks/task-completed.sh +100 -182
  21. package/plugins/team-superpower/hooks/task-created.sh +55 -38
  22. package/plugins/team-superpower/hooks/teammate-idle.sh +118 -13
  23. package/plugins/team-superpower/scripts/team-state.sh +106 -37
  24. package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
  25. package/plugins/team-superpower/agents/designer.md +0 -65
  26. package/plugins/team-superpower/agents/planner.md +0 -242
  27. package/plugins/team-superpower/agents/qa-engineer.md +0 -103
  28. package/plugins/team-superpower/agents/reviewer.md +0 -175
  29. package/plugins/team-superpower/agents/software-architect.md +0 -60
  30. package/plugins/team-superpower/commands/team-feature-resume.md +0 -185
@@ -1,20 +1,30 @@
1
1
  #!/usr/bin/env bash
2
- # TaskCompleted hook — gate implementation completions on plan approval and
3
- # validate any embedded escalation entries.
2
+ # TaskCompleted hook — v5 gate for implementation completions.
4
3
  #
5
4
  # Accepts a JSON payload on stdin with at minimum:
6
5
  # - task.title: string
7
- # - task.metadata.plan_approved_at: string (ISO datetime), required for impl: tasks
8
- # - task.metadata.blocked_questions: array of strings (optional)
9
- # - task.metadata.commits: array of git SHAs (optional, used by v2 checks)
6
+ # - task.metadata.wave: string (e.g. "1.1", "1.rework", "qc-rework")
7
+ # - task.metadata.iteration_count: integer (optional)
8
+ # - task.metadata.reflection: string (optional, required when iteration_count > cap)
9
+ # - task.metadata.retrieval_requests: integer (optional)
10
+ # - task.metadata.commits: array of git SHAs (optional)
10
11
  # - task.metadata.contract_files: array of paths (optional, for contract-publish)
11
12
  #
12
- # v2 rules added on top of v1:
13
- # - impl:be-migration-* completions: refuse if another in-progress migration
14
- # exists in the shared task list payload (anti-race).
15
- # - impl:be-contract-publish-* completions: at least one commit on this task
16
- # must touch a contracts file (default `contracts/` directory, or paths
17
- # listed in metadata.contract_files). Verified via `git show --name-only`.
13
+ # v5 checks (delta from v4):
14
+ # - REMOVED: per-task QA verification (qa_verified_at, QA-verified commit line, qa_rounds cap, trivial=true claims)
15
+ # - REMOVED: 6-field escalation template validation (Phase/Context/Options/Recommendation/Need from you/Peer attempts)
16
+ # - REMOVED: v4 wave format (single integer); v5 wave is dotted (e.g. "1.1") or rework label
17
+ # - ADDED: MISSING_STATIC_CHECKS every impl:* task must have a .team-superpower/static-check-<task-id>.log
18
+ # containing exit=0 for all sections (lint, format, typecheck).
19
+ # - ADDED: MISSING_REWORK_REFERENCE — every impl:rework-* task must have a `Reworks: <orig-id>` line
20
+ # in at least one commit body.
21
+ #
22
+ # v5 preserved checks:
23
+ # - migration serialization (impl:*-migration-* anti-race)
24
+ # - contract-publish must touch a contracts file
25
+ # - AGENTS.md protection (no agent commit may touch docs/superpowers/AGENTS.md)
26
+ # - retrieval budget cap + premature Flagged-assumptions guard
27
+ # - MAX_ITERATIONS guardrail
18
28
 
19
29
  set -euo pipefail
20
30
 
@@ -37,48 +47,96 @@ if ! command -v jq >/dev/null 2>&1; then
37
47
  fi
38
48
 
39
49
  title="$(printf '%s' "$payload" | jq -r '.task.title // .title // ""' 2>/dev/null || echo "")"
40
- plan_approved_at="$(printf '%s' "$payload" | jq -r '.task.metadata.plan_approved_at // .metadata.plan_approved_at // ""' 2>/dev/null || echo "")"
41
50
 
42
- printf '{"ts":"%s","hook":"task-completed","title":%s,"plan_approved_at":%s}\n' \
51
+ printf '{"ts":"%s","hook":"task-completed","title":%s}\n' \
43
52
  "$ts" \
44
53
  "$(printf '%s' "$title" | jq -Rs .)" \
45
- "$(printf '%s' "$plan_approved_at" | jq -Rs .)" \
46
54
  >> "$LOG_FILE"
47
55
 
56
+ # Resolve parse-claudemd.sh helper for limits.* lookups.
57
+ parse_helper="${CLAUDE_PLUGIN_ROOT:-}/scripts/parse-claudemd.sh"
58
+ if [ -z "${CLAUDE_PLUGIN_ROOT:-}" ]; then
59
+ hook_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
60
+ parse_helper="$hook_dir/../scripts/parse-claudemd.sh"
61
+ fi
62
+
63
+ # v5: wave metadata. impl:* tasks must carry a `wave:` string. Accepted shapes:
64
+ # - "<plan-phase>.<wave>" e.g. "1.1", "2.3"
65
+ # - "<plan-phase>.rework" e.g. "1.rework"
66
+ # - "qc-rework"
48
67
  case "$title" in
49
68
  impl:*)
50
- if [ -z "$plan_approved_at" ]; then
51
- printf '{"ts":"%s","hook":"task-completed","warn":"no_plan_approval","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
69
+ wave="$(printf '%s' "$payload" | jq -r '.task.metadata.wave // .metadata.wave // ""' 2>/dev/null || echo "")"
70
+ if [ -z "$wave" ] || ! printf '%s' "$wave" | grep -qE '^([0-9]+\.[0-9]+|[0-9]+\.rework|qc-rework)$'; then
71
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_WAVE_METADATA","title":%s,"wave":%s}\n' \
72
+ "$ts" \
73
+ "$(printf '%s' "$title" | jq -Rs .)" \
74
+ "$(printf '%s' "$wave" | jq -Rs .)" \
75
+ >> "$LOG_FILE"
52
76
  fi
53
77
  ;;
54
78
  esac
55
79
 
56
- # v3: wave metadata. impl:* tasks must carry a `wave:` integer (planner sets
57
- # it; the lead mirrors it into shared-task-list metadata at dispatch time).
80
+ # v5 check 10: MISSING_STATIC_CHECKS. Every impl:* task must have a
81
+ # .team-superpower/static-check-<task-id>.log with exit=0 across lint/format/typecheck.
58
82
  case "$title" in
59
83
  impl:*)
60
- wave="$(printf '%s' "$payload" | jq -r '.task.metadata.wave // .metadata.wave // ""' 2>/dev/null || echo "")"
61
- if [ -z "$wave" ] || ! printf '%s' "$wave" | grep -qE '^[0-9]+$'; then
62
- printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_WAVE_METADATA","title":%s}\n' \
63
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
84
+ project_dir="${CLAUDE_PROJECT_DIR:-$PWD}"
85
+ static_log="$project_dir/.team-superpower/static-check-${title}.log"
86
+ if [ ! -f "$static_log" ]; then
87
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_STATIC_CHECKS","title":%s,"reason":"log file not found","path":%s}\n' \
88
+ "$ts" \
89
+ "$(printf '%s' "$title" | jq -Rs .)" \
90
+ "$(printf '%s' "$static_log" | jq -Rs .)" \
91
+ >> "$LOG_FILE"
92
+ else
93
+ # Any exit= line with a non-zero value fails the check.
94
+ if grep -E '^exit=' "$static_log" | grep -qvE '^exit=0$'; then
95
+ bad="$(grep -E '^exit=' "$static_log" | grep -vE '^exit=0$' | head -n1 || true)"
96
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_STATIC_CHECKS","title":%s,"reason":"non-zero exit","line":%s}\n' \
97
+ "$ts" \
98
+ "$(printf '%s' "$title" | jq -Rs .)" \
99
+ "$(printf '%s' "$bad" | jq -Rs .)" \
100
+ >> "$LOG_FILE"
101
+ fi
102
+ fi
103
+ ;;
104
+ esac
105
+
106
+ # v5 check 11: MISSING_REWORK_REFERENCE. impl:rework-* tasks must carry a
107
+ # `Reworks: <orig-id-or-qc-issue-id>` line in at least one commit body.
108
+ case "$title" in
109
+ impl:rework-*)
110
+ if command -v git >/dev/null 2>&1; then
111
+ rw_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
112
+ if [ -n "$rw_commits" ]; then
113
+ rw_found=0
114
+ while IFS= read -r sha; do
115
+ [ -z "$sha" ] && continue
116
+ body="$(git show --no-color --no-patch --format=%B "$sha" 2>/dev/null || true)"
117
+ if printf '%s\n' "$body" | grep -qE '^Reworks:[[:space:]]*[^[:space:]]+'; then
118
+ rw_found=1; break
119
+ fi
120
+ done <<< "$rw_commits"
121
+ if [ "$rw_found" -ne 1 ]; then
122
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_REWORK_REFERENCE","title":%s,"reason":"no Reworks: line in commit body"}\n' \
123
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
124
+ fi
125
+ else
126
+ printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_REWORK_REFERENCE","title":%s,"reason":"no commits recorded"}\n' \
127
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
128
+ fi
64
129
  fi
65
130
  ;;
66
131
  esac
67
132
 
68
- # v3: MAX_ITERATIONS guardrail. impl:* completions whose iteration_count
69
- # exceeds the per-project cap (default 8) must carry a reflection: block.
133
+ # MAX_ITERATIONS guardrail (preserved from v3/v4).
70
134
  case "$title" in
71
135
  impl:*)
72
136
  iteration_count="$(printf '%s' "$payload" | jq -r '.task.metadata.iteration_count // .metadata.iteration_count // 0' 2>/dev/null || echo 0)"
73
137
  reflection="$(printf '%s' "$payload" | jq -r '.task.metadata.reflection // .metadata.reflection // ""' 2>/dev/null || echo "")"
74
138
 
75
139
  cap=8
76
- parse_helper="${CLAUDE_PLUGIN_ROOT:-}/scripts/parse-claudemd.sh"
77
- if [ -z "${CLAUDE_PLUGIN_ROOT:-}" ]; then
78
- # Fall back to a relative path the hook can find when CLAUDE_PLUGIN_ROOT is unset.
79
- hook_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
80
- parse_helper="$hook_dir/../scripts/parse-claudemd.sh"
81
- fi
82
140
  if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
83
141
  configured="$(bash "$parse_helper" get limits.max_iterations_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
84
142
  if [ -n "$configured" ] && printf '%s' "$configured" | grep -qE '^[0-9]+$'; then
@@ -97,111 +155,7 @@ case "$title" in
97
155
  ;;
98
156
  esac
99
157
 
100
- # v4: per-task QA verification. impl:* completions must carry
101
- # `qa_verified_at:` metadata AND a `QA-verified: round=N` line in at least one
102
- # commit message body (N ≤ max_qa_rounds_per_task, default 3). The hook also
103
- # rejects `trivial=true` claims for diffs >20 lines or new-file additions, and
104
- # rejects `qa_rounds: 3` escalations missing the cross-role fields.
105
- case "$title" in
106
- impl:*)
107
- qa_verified_at="$(printf '%s' "$payload" | jq -r '.task.metadata.qa_verified_at // .metadata.qa_verified_at // ""' 2>/dev/null || echo "")"
108
- qa_rounds="$(printf '%s' "$payload" | jq -r '.task.metadata.qa_rounds // .metadata.qa_rounds // 0' 2>/dev/null || echo 0)"
109
- trivial_claim="$(printf '%s' "$payload" | jq -r '.task.metadata.trivial // .metadata.trivial // false' 2>/dev/null || echo false)"
110
-
111
- qa_cap=3
112
- if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
113
- configured_qa="$(bash "$parse_helper" get limits.max_qa_rounds_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
114
- if [ -n "$configured_qa" ] && printf '%s' "$configured_qa" | grep -qE '^[0-9]+$'; then
115
- qa_cap="$configured_qa"
116
- fi
117
- fi
118
-
119
- # Check 7a: qa_verified_at metadata present.
120
- if [ -z "$qa_verified_at" ]; then
121
- printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_QA_VERIFICATION","title":%s,"reason":"qa_verified_at metadata missing"}\n' \
122
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
123
- fi
124
-
125
- # Check 7b: QA-verified: round=N commit body line, verified via git log if commits recorded.
126
- if command -v git >/dev/null 2>&1; then
127
- qa_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
128
- if [ -n "$qa_commits" ]; then
129
- qa_line_found=0
130
- qa_round_observed=0
131
- while IFS= read -r sha; do
132
- [ -z "$sha" ] && continue
133
- body="$(git show --no-color --no-patch --format=%B "$sha" 2>/dev/null || true)"
134
- line="$(printf '%s\n' "$body" | grep -E '^QA-verified:[[:space:]]*round=[0-9]+' | head -n1 || true)"
135
- if [ -n "$line" ]; then
136
- qa_line_found=1
137
- n="$(printf '%s' "$line" | sed -nE 's/^QA-verified:[[:space:]]*round=([0-9]+).*/\1/p')"
138
- if [ -n "$n" ] && [ "$n" -gt "$qa_round_observed" ]; then
139
- qa_round_observed="$n"
140
- fi
141
- fi
142
- done <<< "$qa_commits"
143
- if [ "$qa_line_found" -ne 1 ]; then
144
- printf '{"ts":"%s","hook":"task-completed","warn":"MISSING_QA_VERIFICATION","title":%s,"reason":"no QA-verified line in commit body"}\n' \
145
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
146
- elif [ "$qa_round_observed" -gt "$qa_cap" ]; then
147
- printf '{"ts":"%s","hook":"task-completed","warn":"QA_ROUND_OVER_CAP","title":%s,"round":%d,"cap":%d}\n' \
148
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$qa_round_observed" "$qa_cap" >> "$LOG_FILE"
149
- fi
150
- fi
151
- fi
152
-
153
- # Check 8: qa_rounds at cap requires §7 cross-role escalation.
154
- if [ "${qa_rounds:-0}" -ge "$qa_cap" ] && [ -z "$qa_verified_at" ]; then
155
- # An escalation entry tagged class:cross-role with qa_rounds, what_failed, one_change_to_fix must be present.
156
- escalation_ok=0
157
- escalation_entries="$(printf '%s' "$payload" | jq -c '(.task.metadata.blocked_questions // .metadata.blocked_questions // [])[]?' 2>/dev/null || true)"
158
- if [ -n "$escalation_entries" ]; then
159
- while IFS= read -r entry; do
160
- [ -z "$entry" ] && continue
161
- raw="$(printf '%s' "$entry" | jq -r '.' 2>/dev/null || printf '%s' "$entry")"
162
- if printf '%s' "$raw" | grep -qE 'class[[:space:]]*:[[:space:]]*cross-role' \
163
- && printf '%s' "$raw" | grep -qE 'qa_rounds[[:space:]]*:' \
164
- && printf '%s' "$raw" | grep -qE 'what_failed[[:space:]]*:' \
165
- && printf '%s' "$raw" | grep -qE 'one_change_to_fix[[:space:]]*:'; then
166
- escalation_ok=1
167
- break
168
- fi
169
- done <<< "$escalation_entries"
170
- fi
171
- if [ "$escalation_ok" -ne 1 ]; then
172
- printf '{"ts":"%s","hook":"task-completed","warn":"QA_CAP_EXCEEDED","title":%s,"qa_rounds":%d,"cap":%d}\n' \
173
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$qa_rounds" "$qa_cap" >> "$LOG_FILE"
174
- fi
175
- fi
176
-
177
- # Pre-check: INVALID_TRIVIAL_CLAIM — trivial=true requires diff ≤20 lines AND no new files.
178
- if [ "$trivial_claim" = "true" ] && command -v git >/dev/null 2>&1; then
179
- tc_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
180
- tc_added=0
181
- tc_lines=0
182
- if [ -n "$tc_commits" ]; then
183
- while IFS= read -r sha; do
184
- [ -z "$sha" ] && continue
185
- st="$(git show --no-color --name-status --pretty=format: "$sha" 2>/dev/null || true)"
186
- if printf '%s\n' "$st" | grep -qE '^A[[:space:]]'; then
187
- tc_added=1
188
- fi
189
- n="$(git show --no-color --shortstat --pretty=format: "$sha" 2>/dev/null | grep -oE '[0-9]+ insertion' | head -n1 | grep -oE '[0-9]+' || echo 0)"
190
- tc_lines=$((tc_lines + ${n:-0}))
191
- done <<< "$tc_commits"
192
- if [ "$tc_added" -eq 1 ] || [ "$tc_lines" -gt 20 ]; then
193
- printf '{"ts":"%s","hook":"task-completed","warn":"INVALID_TRIVIAL_CLAIM","title":%s,"added_files":%d,"insertions":%d}\n' \
194
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" "$tc_added" "$tc_lines" >> "$LOG_FILE"
195
- fi
196
- fi
197
- fi
198
- ;;
199
- esac
200
-
201
- # v4: retrieval budget. impl:* tasks may not exceed `retrieval_budget_per_task`
202
- # (default 2). Also: a `Flagged-assumptions:` line in a commit body is only
203
- # permitted when retrieval_requests == budget (cannot flag assumptions
204
- # prematurely without exhausting the budget).
158
+ # Retrieval budget (preserved from v4).
205
159
  case "$title" in
206
160
  impl:*)
207
161
  retrieval_requests="$(printf '%s' "$payload" | jq -r '.task.metadata.retrieval_requests // .metadata.retrieval_requests // 0' 2>/dev/null || echo 0)"
@@ -238,10 +192,7 @@ case "$title" in
238
192
  ;;
239
193
  esac
240
194
 
241
- # v4: AGENTS.md protection. Agents never write to docs/superpowers/AGENTS.md.
242
- # Reviewer writes only to docs/superpowers/AGENTS.suggestions.md. If any commit
243
- # on this task touches AGENTS.md, warn AGENT_WROTE_AGENTS_MD — the owner is the
244
- # only role that may promote suggestions to AGENTS.md.
195
+ # AGENTS.md protection (preserved from v4).
245
196
  if command -v git >/dev/null 2>&1; then
246
197
  agm_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
247
198
  if [ -n "$agm_commits" ]; then
@@ -256,27 +207,27 @@ if command -v git >/dev/null 2>&1; then
256
207
  fi
257
208
  fi
258
209
 
259
- # v2: migration serialization. If this is a migration task, verify no other
260
- # `impl:be-migration-*` task is currently in_progress in the shared task list
261
- # (payload.tasks[]). The lead also enforces this; the hook is a backstop.
210
+ # Migration serialization (preserved). v5 widens the prefix match: any task
211
+ # whose title contains the qualifier `-migration-` (e.g. impl:be-migration-*,
212
+ # impl:rework-be-migration-*) is migration-class.
262
213
  case "$title" in
263
- impl:be-migration-*)
214
+ *-migration-*)
264
215
  other_in_progress="$(printf '%s' "$payload" | jq -r --arg me "$title" '
265
216
  [ .tasks[]?
266
217
  | select((.title // "") != $me)
267
- | select(((.title // "") | startswith("impl:be-migration-")))
218
+ | select(((.title // "") | contains("-migration-")))
268
219
  | select((.status // "") == "in_progress")
269
220
  ] | length' 2>/dev/null || echo 0)"
270
221
  if [ "${other_in_progress:-0}" -gt 0 ]; then
271
- printf '{"ts":"%s","hook":"task-completed","warn":"migration_race","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
222
+ printf '{"ts":"%s","hook":"task-completed","warn":"MIGRATION_RACE","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
272
223
  fi
273
224
  ;;
274
225
  esac
275
226
 
276
- # v2: contract-publish must actually touch a contracts file in at least one
277
- # of its commits. Best-effort: skip if git unavailable or no commits recorded.
227
+ # Contract-publish must touch a contracts file (preserved). v5 widens to any
228
+ # task whose title contains the qualifier `-contract-publish-`.
278
229
  case "$title" in
279
- impl:be-contract-publish-*)
230
+ *-contract-publish-*)
280
231
  if command -v git >/dev/null 2>&1; then
281
232
  commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
282
233
  patterns="$(printf '%s' "$payload" | jq -r '(.task.metadata.contract_files // .metadata.contract_files // ["contracts/"])[]?' 2>/dev/null || echo "contracts/")"
@@ -293,44 +244,11 @@ case "$title" in
293
244
  done <<< "$patterns"
294
245
  done <<< "$commits"
295
246
  if [ "$touched" -ne 1 ]; then
296
- printf '{"ts":"%s","hook":"task-completed","warn":"empty_contract_publish","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
247
+ printf '{"ts":"%s","hook":"task-completed","warn":"EMPTY_CONTRACT_PUBLISH","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
297
248
  fi
298
249
  fi
299
250
  fi
300
251
  ;;
301
252
  esac
302
253
 
303
- # Validate escalation entries if present.
304
- required_fields=("Phase" "Context" "Options" "Recommendation" "Need from you" "Peer attempts")
305
- missing_any=""
306
- entries="$(printf '%s' "$payload" | jq -c '(.task.metadata.blocked_questions // .metadata.blocked_questions // [])[]?' 2>/dev/null || true)"
307
-
308
- if [ -n "$entries" ]; then
309
- while IFS= read -r entry; do
310
- [ -z "$entry" ] && continue
311
- raw="$(printf '%s' "$entry" | jq -r '.' 2>/dev/null || printf '%s' "$entry")"
312
- missing=""
313
- for field in "${required_fields[@]}"; do
314
- if ! printf '%s' "$raw" | grep -qE "(^|[^A-Za-z])${field}[[:space:]]*:"; then
315
- if [ -z "$missing" ]; then
316
- missing="$field"
317
- else
318
- missing="$missing, $field"
319
- fi
320
- fi
321
- done
322
- if [ -n "$missing" ]; then
323
- if [ -z "$missing_any" ]; then
324
- missing_any="$missing"
325
- else
326
- missing_any="$missing_any | $missing"
327
- fi
328
- fi
329
- done <<< "$entries"
330
- fi
331
-
332
- if [ -n "$missing_any" ]; then
333
- printf '{"ts":"%s","hook":"task-completed","warn":"bad_escalation","missing":%s}\n' "$ts" "$(printf '%s' "$missing_any" | jq -Rs .)" >> "$LOG_FILE"
334
- fi
335
-
336
254
  exit 0
@@ -1,21 +1,28 @@
1
1
  #!/usr/bin/env bash
2
- # TaskCreated hook — enforce title prefix on shared task list entries.
2
+ # TaskCreated hook — v5 prefix + wave validation for shared task list entries.
3
3
  #
4
4
  # Accepts a JSON payload on stdin. Required field:
5
5
  # - task.title: string
6
6
  #
7
- # Title MUST start with one of: impl:, review:, meta:, block:
8
- # v2 additions:
9
- # - impl: tasks MUST carry a sub-prefix (be-, fe-, qa-fix-be-, qa-fix-fe-,
10
- # review-fix-be-, review-fix-fe-, contract-update-, be-migration-,
11
- # be-contract-publish-) bare `impl:foo` is rejected.
12
- # - Shape-marker file at docs/superpowers/sessions/<slug>.shape (written by
13
- # the lead in phase 0) restricts which sub-prefixes are allowed:
14
- # be-only → only `impl:be-*`, `impl:contract-update-*` allowed
15
- # fe-only → only `impl:fe-*`, `impl:contract-update-*` allowed
16
- # full-stack → all sub-prefixes allowed
17
- # - Slug is taken from .task.metadata.slug or .metadata.slug, with fallback
18
- # to the most-recent .shape file when only one exists.
7
+ # v5 changes (delta from v4):
8
+ # - ADDED: impl:rework-* allowed top-level. Rework tasks dispatched by
9
+ # team-leader (phase-end SOLID/DRY review) or qc-engineer (end-of-plan QC)
10
+ # do not carry a be-/fe- prefix; their original-task id is in the
11
+ # `Reworks:` line of the implementer's commit body and validated by
12
+ # task-completed.sh.
13
+ # - ADDED: INVALID_WAVE_REFERENCE v5 waves are dotted strings, not single
14
+ # integers. Accepted shapes: "<plan-phase>.<wave>" (e.g. "1.1", "2.3"),
15
+ # "<plan-phase>.rework" (e.g. "1.rework"), or "qc-rework".
16
+ # - REMOVED: qa-fix-be-, qa-fix-fe-, review-fix-be-, review-fix-fe- prefixes
17
+ # (v4 QA loop is gone). Use impl:rework-* instead.
18
+ # - REMOVED: bare integer wave format.
19
+ #
20
+ # Preserved checks:
21
+ # - Top-level prefix must be impl: / review: / meta: / block:.
22
+ # - impl:* must carry a known sub-prefix per the shape file.
23
+ # - solo-mode guard: impl:* invalid when checkpoint mode is solo.
24
+ # - Shape-marker file at docs/superpowers/sessions/<slug>.shape restricts
25
+ # allowed sub-prefixes (be-only / fe-only / full-stack).
19
26
 
20
27
  set -euo pipefail
21
28
 
@@ -60,16 +67,12 @@ if [ -d "$SESSIONS_DIR" ]; then
60
67
  fi
61
68
 
62
69
  wave="$(printf '%s' "$payload" | jq -r '.task.metadata.wave // .metadata.wave // ""' 2>/dev/null || echo "")"
63
- wave_json="null"
64
- if printf '%s' "$wave" | grep -qE '^[0-9]+$'; then
65
- wave_json="$wave"
66
- fi
67
70
 
68
71
  printf '{"ts":"%s","hook":"task-created","title":%s,"shape":%s,"wave":%s}\n' \
69
72
  "$ts" \
70
73
  "$(printf '%s' "$title" | jq -Rs .)" \
71
74
  "$(printf '%s' "$shape" | jq -Rs .)" \
72
- "$wave_json" \
75
+ "$(printf '%s' "$wave" | jq -Rs .)" \
73
76
  >> "$LOG_FILE"
74
77
 
75
78
  # Top-level prefix check
@@ -78,18 +81,18 @@ case "$title" in
78
81
  impl:*) ;;
79
82
  "")
80
83
  printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","reason":"title missing"}\n' "$ts" >> "$LOG_FILE"
84
+ exit 0
81
85
  ;;
82
86
  *)
83
87
  printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
88
+ exit 0
84
89
  ;;
85
90
  esac
86
91
 
87
- # v3: solo-mode guard. If checkpoint mode is solo, impl:* tasks are invalid
88
- # (solo means the lead does the work itself; no implementer is spawned).
92
+ # v3: solo-mode guard. If checkpoint mode is solo, impl:* tasks are invalid.
89
93
  mode_meta="$(printf '%s' "$payload" | jq -r '.task.metadata.mode // .metadata.mode // ""' 2>/dev/null || echo "")"
90
94
  mode_marker=""
91
95
  if [ -z "$mode_meta" ] && [ -d "$SESSIONS_DIR" ]; then
92
- # Look for a <slug>.mode file the lead writes when it picks a mode.
93
96
  marker_file=""
94
97
  if [ -n "$slug" ] && [ -f "$SESSIONS_DIR/$slug.mode" ]; then
95
98
  marker_file="$SESSIONS_DIR/$slug.mode"
@@ -105,27 +108,38 @@ if [ -z "$mode_meta" ] && [ -d "$SESSIONS_DIR" ]; then
105
108
  fi
106
109
  effective_mode="${mode_meta:-$mode_marker}"
107
110
 
108
- case "$title" in
109
- impl:*)
110
- if [ "$effective_mode" = "solo" ]; then
111
- printf '{"ts":"%s","hook":"task-created","warn":"INVALID_FOR_SOLO_MODE","title":%s}\n' \
112
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
113
- fi
114
- if [ -z "$wave" ]; then
115
- printf '{"ts":"%s","hook":"task-created","warn":"MISSING_WAVE_METADATA","title":%s}\n' \
116
- "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
117
- fi
118
- ;;
119
- esac
111
+ if [ "$effective_mode" = "solo" ]; then
112
+ printf '{"ts":"%s","hook":"task-created","warn":"INVALID_FOR_SOLO_MODE","title":%s}\n' \
113
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
114
+ fi
115
+
116
+ # v5 INVALID_WAVE_REFERENCE: wave metadata is required and must match the v5 shape.
117
+ if [ -z "$wave" ]; then
118
+ printf '{"ts":"%s","hook":"task-created","warn":"INVALID_WAVE_REFERENCE","title":%s,"reason":"wave metadata missing"}\n' \
119
+ "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
120
+ elif ! printf '%s' "$wave" | grep -qE '^([0-9]+\.[0-9]+|[0-9]+\.rework|qc-rework)$'; then
121
+ printf '{"ts":"%s","hook":"task-created","warn":"INVALID_WAVE_REFERENCE","title":%s,"wave":%s,"reason":"unrecognised wave shape"}\n' \
122
+ "$ts" \
123
+ "$(printf '%s' "$title" | jq -Rs .)" \
124
+ "$(printf '%s' "$wave" | jq -Rs .)" \
125
+ >> "$LOG_FILE"
126
+ fi
120
127
 
121
128
  # At this point title starts with `impl:`. Strip prefix and require a known
122
- # sub-prefix.
129
+ # v5 sub-prefix.
123
130
  rest="${title#impl:}"
124
131
  case "$rest" in
125
- be-*|qa-fix-be-*|review-fix-be-*|be-migration-*|be-contract-publish-*)
132
+ rework-*)
133
+ # Rework tasks dispatched by team-leader (phase-end review) or qc-engineer
134
+ # (end-of-plan QC). The Reworks: <orig-id> line in the commit body is
135
+ # validated by task-completed.sh (MISSING_REWORK_REFERENCE). No shape check
136
+ # — rework inherits the originating task's scope.
137
+ sub="rework"
138
+ ;;
139
+ be-*|be-migration-*|be-contract-publish-*)
126
140
  sub="be"
127
141
  ;;
128
- fe-*|qa-fix-fe-*|review-fix-fe-*)
142
+ fe-*)
129
143
  sub="fe"
130
144
  ;;
131
145
  contract-update-*)
@@ -133,10 +147,13 @@ case "$rest" in
133
147
  ;;
134
148
  *)
135
149
  printf '{"ts":"%s","hook":"task-created","warn":"bad_subprefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
136
- exit 0 ;;
150
+ exit 0
151
+ ;;
137
152
  esac
138
153
 
139
- # Shape-aware enforcement
154
+ # Shape-aware enforcement. rework + contract-update are scope-neutral and
155
+ # allowed under any shape — they inherit scope from the originating task or
156
+ # from BE ownership of the contract.
140
157
  case "$shape" in
141
158
  be-only)
142
159
  if [ "$sub" = "fe" ]; then