@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.
- package/.claude-plugin/marketplace.json +5 -5
- package/package.json +1 -1
- package/plugins/auto-power/.claude-plugin/plugin.json +1 -1
- package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
- package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
- package/plugins/team-superpower/.claude-plugin/plugin.json +1 -1
- package/plugins/team-superpower/README.md +186 -115
- package/plugins/team-superpower/agents/backend-developer.md +105 -73
- package/plugins/team-superpower/agents/feature-planner.md +66 -0
- package/plugins/team-superpower/agents/frontend-developer.md +108 -70
- package/plugins/team-superpower/agents/orchestrator.md +83 -0
- package/plugins/team-superpower/agents/qc-engineer.md +84 -0
- package/plugins/team-superpower/agents/security-engineer.md +79 -44
- package/plugins/team-superpower/agents/solution-architect.md +80 -0
- package/plugins/team-superpower/agents/team-leader.md +100 -0
- package/plugins/team-superpower/assets/CLAUDE.md.template +22 -18
- package/plugins/team-superpower/assets/ESCALATION.md +114 -66
- package/plugins/team-superpower/assets/SESSION_README.md +232 -159
- package/plugins/team-superpower/commands/team-feature.md +191 -645
- package/plugins/team-superpower/hooks/task-completed.sh +100 -182
- package/plugins/team-superpower/hooks/task-created.sh +55 -38
- package/plugins/team-superpower/hooks/teammate-idle.sh +118 -13
- package/plugins/team-superpower/scripts/team-state.sh +106 -37
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
- package/plugins/team-superpower/agents/designer.md +0 -65
- package/plugins/team-superpower/agents/planner.md +0 -242
- package/plugins/team-superpower/agents/qa-engineer.md +0 -103
- package/plugins/team-superpower/agents/reviewer.md +0 -175
- package/plugins/team-superpower/agents/software-architect.md +0 -60
- 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
|
|
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.
|
|
8
|
-
# - task.metadata.
|
|
9
|
-
# - task.metadata.
|
|
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
|
-
#
|
|
13
|
-
# -
|
|
14
|
-
#
|
|
15
|
-
# -
|
|
16
|
-
#
|
|
17
|
-
#
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
#
|
|
57
|
-
#
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
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
|
-
|
|
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 // "") |
|
|
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":"
|
|
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
|
-
#
|
|
277
|
-
#
|
|
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
|
-
|
|
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":"
|
|
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 —
|
|
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
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
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
|
-
"$
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|