@drunkcoding/agents-and-skills 0.0.18 → 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/README.md +23 -0
- 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 +161 -39
- package/plugins/team-superpower/agents/feature-planner.md +66 -0
- package/plugins/team-superpower/agents/frontend-developer.md +163 -34
- 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 +90 -40
- 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/AGENTS.md.template +23 -0
- package/plugins/team-superpower/assets/CLAUDE.md.template +25 -4
- package/plugins/team-superpower/assets/ESCALATION.md +114 -66
- package/plugins/team-superpower/assets/SESSION_README.md +233 -113
- package/plugins/team-superpower/commands/team-feature.md +195 -443
- package/plugins/team-superpower/hooks/task-completed.sh +180 -59
- package/plugins/team-superpower/hooks/task-created.sh +77 -19
- package/plugins/team-superpower/hooks/teammate-idle.sh +118 -13
- package/plugins/team-superpower/scripts/assess-complexity.sh +194 -0
- package/plugins/team-superpower/scripts/detect-stack.sh +39 -0
- package/plugins/team-superpower/scripts/team-state.sh +106 -37
- package/plugins/team-superpower/scripts/wave-collision-check.sh +60 -0
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
- package/plugins/html-effectiveness/tests/_tmp.test.js +0 -58
- package/plugins/html-effectiveness/tests/fixtures/01-exploration-code-approaches.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/02-exploration-visual-designs.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/03-code-review-pr.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/04-code-understanding.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/05-design-system.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/06-component-variants.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/07-prototype-animation.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/08-prototype-interaction.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/09-slide-deck.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/10-svg-illustrations.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/11-status-report.data.json +0 -37
- package/plugins/html-effectiveness/tests/fixtures/12-incident-report.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/13-flowchart-diagram.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/14-research-feature-explainer.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/15-research-concept-explainer.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/16-implementation-plan.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/17-pr-writeup.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/18-editor-triage-board.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/19-editor-feature-flags.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/20-editor-prompt-tuner.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/_canned.data.json +0 -7
- package/plugins/html-effectiveness/tests/fixtures/_canned.html.tmpl +0 -7
- package/plugins/html-effectiveness/tests/fixtures/_canned.manifest.json +0 -18
- package/plugins/html-effectiveness/tests/manifest.test.js +0 -61
- package/plugins/html-effectiveness/tests/mustache.test.js +0 -47
- package/plugins/html-effectiveness/tests/render.test.js +0 -118
- package/plugins/team-superpower/agents/designer.md +0 -50
- package/plugins/team-superpower/agents/planner.md +0 -186
- package/plugins/team-superpower/agents/qa-engineer.md +0 -47
- package/plugins/team-superpower/agents/reviewer.md +0 -110
- package/plugins/team-superpower/agents/software-architect.md +0 -45
- 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,43 +47,187 @@ 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"
|
|
76
|
+
fi
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
|
|
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.
|
|
82
|
+
case "$title" in
|
|
83
|
+
impl:*)
|
|
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
|
|
52
129
|
fi
|
|
53
130
|
;;
|
|
54
131
|
esac
|
|
55
132
|
|
|
56
|
-
#
|
|
57
|
-
# `impl:be-migration-*` task is currently in_progress in the shared task list
|
|
58
|
-
# (payload.tasks[]). The lead also enforces this; the hook is a backstop.
|
|
133
|
+
# MAX_ITERATIONS guardrail (preserved from v3/v4).
|
|
59
134
|
case "$title" in
|
|
60
|
-
impl
|
|
135
|
+
impl:*)
|
|
136
|
+
iteration_count="$(printf '%s' "$payload" | jq -r '.task.metadata.iteration_count // .metadata.iteration_count // 0' 2>/dev/null || echo 0)"
|
|
137
|
+
reflection="$(printf '%s' "$payload" | jq -r '.task.metadata.reflection // .metadata.reflection // ""' 2>/dev/null || echo "")"
|
|
138
|
+
|
|
139
|
+
cap=8
|
|
140
|
+
if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
|
|
141
|
+
configured="$(bash "$parse_helper" get limits.max_iterations_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
|
|
142
|
+
if [ -n "$configured" ] && printf '%s' "$configured" | grep -qE '^[0-9]+$'; then
|
|
143
|
+
cap="$configured"
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [ "${iteration_count:-0}" -gt "$cap" ] && [ -z "$reflection" ]; then
|
|
148
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"ITERATION_CAP_EXCEEDED","title":%s,"iteration_count":%d,"cap":%d}\n' \
|
|
149
|
+
"$ts" \
|
|
150
|
+
"$(printf '%s' "$title" | jq -Rs .)" \
|
|
151
|
+
"$iteration_count" \
|
|
152
|
+
"$cap" \
|
|
153
|
+
>> "$LOG_FILE"
|
|
154
|
+
fi
|
|
155
|
+
;;
|
|
156
|
+
esac
|
|
157
|
+
|
|
158
|
+
# Retrieval budget (preserved from v4).
|
|
159
|
+
case "$title" in
|
|
160
|
+
impl:*)
|
|
161
|
+
retrieval_requests="$(printf '%s' "$payload" | jq -r '.task.metadata.retrieval_requests // .metadata.retrieval_requests // 0' 2>/dev/null || echo 0)"
|
|
162
|
+
retrieval_cap=2
|
|
163
|
+
if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
|
|
164
|
+
configured_r="$(bash "$parse_helper" get limits.retrieval_budget_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
|
|
165
|
+
if [ -n "$configured_r" ] && printf '%s' "$configured_r" | grep -qE '^[0-9]+$'; then
|
|
166
|
+
retrieval_cap="$configured_r"
|
|
167
|
+
fi
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
if [ "${retrieval_requests:-0}" -gt "$retrieval_cap" ]; then
|
|
171
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"RETRIEVAL_BUDGET_EXCEEDED","title":%s,"requests":%d,"cap":%d}\n' \
|
|
172
|
+
"$ts" "$(printf '%s' "$title" | jq -Rs .)" "$retrieval_requests" "$retrieval_cap" >> "$LOG_FILE"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if command -v git >/dev/null 2>&1; then
|
|
176
|
+
r_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
|
|
177
|
+
if [ -n "$r_commits" ]; then
|
|
178
|
+
flagged=0
|
|
179
|
+
while IFS= read -r sha; do
|
|
180
|
+
[ -z "$sha" ] && continue
|
|
181
|
+
body="$(git show --no-color --no-patch --format=%B "$sha" 2>/dev/null || true)"
|
|
182
|
+
if printf '%s\n' "$body" | grep -qE '^Flagged-assumptions:'; then
|
|
183
|
+
flagged=1; break
|
|
184
|
+
fi
|
|
185
|
+
done <<< "$r_commits"
|
|
186
|
+
if [ "$flagged" -eq 1 ] && [ "${retrieval_requests:-0}" -lt "$retrieval_cap" ]; then
|
|
187
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"PREMATURE_ASSUMPTION_FLAG","title":%s,"requests":%d,"cap":%d}\n' \
|
|
188
|
+
"$ts" "$(printf '%s' "$title" | jq -Rs .)" "$retrieval_requests" "$retrieval_cap" >> "$LOG_FILE"
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
fi
|
|
192
|
+
;;
|
|
193
|
+
esac
|
|
194
|
+
|
|
195
|
+
# AGENTS.md protection (preserved from v4).
|
|
196
|
+
if command -v git >/dev/null 2>&1; then
|
|
197
|
+
agm_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
|
|
198
|
+
if [ -n "$agm_commits" ]; then
|
|
199
|
+
while IFS= read -r sha; do
|
|
200
|
+
[ -z "$sha" ] && continue
|
|
201
|
+
files="$(git show --no-color --name-only --pretty=format: "$sha" 2>/dev/null || true)"
|
|
202
|
+
if printf '%s\n' "$files" | grep -qE '^docs/superpowers/AGENTS\.md$'; then
|
|
203
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"AGENT_WROTE_AGENTS_MD","title":%s,"sha":%s}\n' \
|
|
204
|
+
"$ts" "$(printf '%s' "$title" | jq -Rs .)" "$(printf '%s' "$sha" | jq -Rs .)" >> "$LOG_FILE"
|
|
205
|
+
fi
|
|
206
|
+
done <<< "$agm_commits"
|
|
207
|
+
fi
|
|
208
|
+
fi
|
|
209
|
+
|
|
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.
|
|
213
|
+
case "$title" in
|
|
214
|
+
*-migration-*)
|
|
61
215
|
other_in_progress="$(printf '%s' "$payload" | jq -r --arg me "$title" '
|
|
62
216
|
[ .tasks[]?
|
|
63
217
|
| select((.title // "") != $me)
|
|
64
|
-
| select(((.title // "") |
|
|
218
|
+
| select(((.title // "") | contains("-migration-")))
|
|
65
219
|
| select((.status // "") == "in_progress")
|
|
66
220
|
] | length' 2>/dev/null || echo 0)"
|
|
67
221
|
if [ "${other_in_progress:-0}" -gt 0 ]; then
|
|
68
|
-
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"
|
|
69
223
|
fi
|
|
70
224
|
;;
|
|
71
225
|
esac
|
|
72
226
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
227
|
+
# Contract-publish must touch a contracts file (preserved). v5 widens to any
|
|
228
|
+
# task whose title contains the qualifier `-contract-publish-`.
|
|
75
229
|
case "$title" in
|
|
76
|
-
|
|
230
|
+
*-contract-publish-*)
|
|
77
231
|
if command -v git >/dev/null 2>&1; then
|
|
78
232
|
commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
|
|
79
233
|
patterns="$(printf '%s' "$payload" | jq -r '(.task.metadata.contract_files // .metadata.contract_files // ["contracts/"])[]?' 2>/dev/null || echo "contracts/")"
|
|
@@ -90,44 +244,11 @@ case "$title" in
|
|
|
90
244
|
done <<< "$patterns"
|
|
91
245
|
done <<< "$commits"
|
|
92
246
|
if [ "$touched" -ne 1 ]; then
|
|
93
|
-
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"
|
|
94
248
|
fi
|
|
95
249
|
fi
|
|
96
250
|
fi
|
|
97
251
|
;;
|
|
98
252
|
esac
|
|
99
253
|
|
|
100
|
-
# Validate escalation entries if present.
|
|
101
|
-
required_fields=("Phase" "Context" "Options" "Recommendation" "Need from you" "Peer attempts")
|
|
102
|
-
missing_any=""
|
|
103
|
-
entries="$(printf '%s' "$payload" | jq -c '(.task.metadata.blocked_questions // .metadata.blocked_questions // [])[]?' 2>/dev/null || true)"
|
|
104
|
-
|
|
105
|
-
if [ -n "$entries" ]; then
|
|
106
|
-
while IFS= read -r entry; do
|
|
107
|
-
[ -z "$entry" ] && continue
|
|
108
|
-
raw="$(printf '%s' "$entry" | jq -r '.' 2>/dev/null || printf '%s' "$entry")"
|
|
109
|
-
missing=""
|
|
110
|
-
for field in "${required_fields[@]}"; do
|
|
111
|
-
if ! printf '%s' "$raw" | grep -qE "(^|[^A-Za-z])${field}[[:space:]]*:"; then
|
|
112
|
-
if [ -z "$missing" ]; then
|
|
113
|
-
missing="$field"
|
|
114
|
-
else
|
|
115
|
-
missing="$missing, $field"
|
|
116
|
-
fi
|
|
117
|
-
fi
|
|
118
|
-
done
|
|
119
|
-
if [ -n "$missing" ]; then
|
|
120
|
-
if [ -z "$missing_any" ]; then
|
|
121
|
-
missing_any="$missing"
|
|
122
|
-
else
|
|
123
|
-
missing_any="$missing_any | $missing"
|
|
124
|
-
fi
|
|
125
|
-
fi
|
|
126
|
-
done <<< "$entries"
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
if [ -n "$missing_any" ]; then
|
|
130
|
-
printf '{"ts":"%s","hook":"task-completed","warn":"bad_escalation","missing":%s}\n' "$ts" "$(printf '%s' "$missing_any" | jq -Rs .)" >> "$LOG_FILE"
|
|
131
|
-
fi
|
|
132
|
-
|
|
133
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
|
|
|
@@ -59,10 +66,13 @@ if [ -d "$SESSIONS_DIR" ]; then
|
|
|
59
66
|
fi
|
|
60
67
|
fi
|
|
61
68
|
|
|
62
|
-
printf '
|
|
69
|
+
wave="$(printf '%s' "$payload" | jq -r '.task.metadata.wave // .metadata.wave // ""' 2>/dev/null || echo "")"
|
|
70
|
+
|
|
71
|
+
printf '{"ts":"%s","hook":"task-created","title":%s,"shape":%s,"wave":%s}\n' \
|
|
63
72
|
"$ts" \
|
|
64
73
|
"$(printf '%s' "$title" | jq -Rs .)" \
|
|
65
74
|
"$(printf '%s' "$shape" | jq -Rs .)" \
|
|
75
|
+
"$(printf '%s' "$wave" | jq -Rs .)" \
|
|
66
76
|
>> "$LOG_FILE"
|
|
67
77
|
|
|
68
78
|
# Top-level prefix check
|
|
@@ -71,20 +81,65 @@ case "$title" in
|
|
|
71
81
|
impl:*) ;;
|
|
72
82
|
"")
|
|
73
83
|
printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","reason":"title missing"}\n' "$ts" >> "$LOG_FILE"
|
|
84
|
+
exit 0
|
|
74
85
|
;;
|
|
75
86
|
*)
|
|
76
87
|
printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
|
|
88
|
+
exit 0
|
|
77
89
|
;;
|
|
78
90
|
esac
|
|
79
91
|
|
|
92
|
+
# v3: solo-mode guard. If checkpoint mode is solo, impl:* tasks are invalid.
|
|
93
|
+
mode_meta="$(printf '%s' "$payload" | jq -r '.task.metadata.mode // .metadata.mode // ""' 2>/dev/null || echo "")"
|
|
94
|
+
mode_marker=""
|
|
95
|
+
if [ -z "$mode_meta" ] && [ -d "$SESSIONS_DIR" ]; then
|
|
96
|
+
marker_file=""
|
|
97
|
+
if [ -n "$slug" ] && [ -f "$SESSIONS_DIR/$slug.mode" ]; then
|
|
98
|
+
marker_file="$SESSIONS_DIR/$slug.mode"
|
|
99
|
+
else
|
|
100
|
+
count="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.mode' 2>/dev/null | wc -l | tr -d ' ')"
|
|
101
|
+
if [ "$count" = "1" ]; then
|
|
102
|
+
marker_file="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.mode' 2>/dev/null | head -n1)"
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
if [ -n "$marker_file" ] && [ -f "$marker_file" ]; then
|
|
106
|
+
mode_marker="$(head -n1 "$marker_file" | tr -d '[:space:]')"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
effective_mode="${mode_meta:-$mode_marker}"
|
|
110
|
+
|
|
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
|
|
127
|
+
|
|
80
128
|
# At this point title starts with `impl:`. Strip prefix and require a known
|
|
81
|
-
# sub-prefix.
|
|
129
|
+
# v5 sub-prefix.
|
|
82
130
|
rest="${title#impl:}"
|
|
83
131
|
case "$rest" in
|
|
84
|
-
|
|
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-*)
|
|
85
140
|
sub="be"
|
|
86
141
|
;;
|
|
87
|
-
fe
|
|
142
|
+
fe-*)
|
|
88
143
|
sub="fe"
|
|
89
144
|
;;
|
|
90
145
|
contract-update-*)
|
|
@@ -92,10 +147,13 @@ case "$rest" in
|
|
|
92
147
|
;;
|
|
93
148
|
*)
|
|
94
149
|
printf '{"ts":"%s","hook":"task-created","warn":"bad_subprefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
|
|
95
|
-
exit 0
|
|
150
|
+
exit 0
|
|
151
|
+
;;
|
|
96
152
|
esac
|
|
97
153
|
|
|
98
|
-
# 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.
|
|
99
157
|
case "$shape" in
|
|
100
158
|
be-only)
|
|
101
159
|
if [ "$sub" = "fe" ]; then
|
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# TeammateIdle hook —
|
|
2
|
+
# TeammateIdle hook — v5 role-aware routing.
|
|
3
3
|
#
|
|
4
|
-
# Reads the hook event payload from stdin. Expected JSON fields
|
|
5
|
-
#
|
|
6
|
-
# -
|
|
7
|
-
#
|
|
4
|
+
# Reads the hook event payload from stdin. Expected JSON fields:
|
|
5
|
+
# - mailbox: array of { from, kind, replied, ... } — recent SendMessage history
|
|
6
|
+
# - teammate: string (the idling teammate's role; one of
|
|
7
|
+
# orchestrator | team-leader | solution-architect | feature-planner |
|
|
8
|
+
# security-engineer | backend-developer | frontend-developer | qc-engineer)
|
|
8
9
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# -
|
|
12
|
-
#
|
|
10
|
+
# v5 behaviour: routing depends on role.
|
|
11
|
+
#
|
|
12
|
+
# backend-developer / frontend-developer:
|
|
13
|
+
# - block if any unanswered message FROM team-leader (implementer owes a reply)
|
|
14
|
+
# - block if a self-sent ESCALATE has no reply within heartbeat (advisory log)
|
|
15
|
+
# - otherwise advisory: wave dispatch may still be in flight; ok to idle
|
|
16
|
+
#
|
|
17
|
+
# team-leader:
|
|
18
|
+
# - block if SPAWN_REQUEST or RESTART_REQUEST has no SPAWN_DONE / RESTART_DONE
|
|
19
|
+
# reply from lead/orchestrator within heartbeat (must keep coordinating)
|
|
20
|
+
# - block if any unanswered ESCALATE from an implementer (must route)
|
|
21
|
+
# - otherwise advisory tick
|
|
22
|
+
#
|
|
23
|
+
# qc-engineer:
|
|
24
|
+
# - block if QC_REWORK_NEEDED has no reply (lead must acknowledge before shutdown)
|
|
25
|
+
# - otherwise advisory tick
|
|
26
|
+
#
|
|
27
|
+
# solution-architect / feature-planner / security-engineer (phase A roles):
|
|
28
|
+
# - block if owner sign-off touchpoint message has no reply yet
|
|
29
|
+
# - otherwise advisory tick (shutdown is owner-driven via lead)
|
|
30
|
+
#
|
|
31
|
+
# orchestrator (lead):
|
|
32
|
+
# - block if any unanswered SPAWN_REQUEST / RESTART_REQUEST inbound from
|
|
33
|
+
# team-leader (lead is the single spawner)
|
|
34
|
+
# - otherwise advisory tick (lead drives owner touchpoints separately)
|
|
13
35
|
#
|
|
14
36
|
# Logs every invocation to .claude/hooks/log.jsonl in the project root for tuning.
|
|
15
37
|
|
|
@@ -33,12 +55,95 @@ if ! command -v jq >/dev/null 2>&1; then
|
|
|
33
55
|
exit 0
|
|
34
56
|
fi
|
|
35
57
|
|
|
36
|
-
|
|
58
|
+
teammate="$(printf '%s' "$payload" | jq -r '.teammate // ""' 2>/dev/null || echo "")"
|
|
59
|
+
|
|
60
|
+
# Helper: count unanswered inbound messages where .from matches a pattern.
|
|
61
|
+
count_unanswered_from() {
|
|
62
|
+
local from_pattern="$1"
|
|
63
|
+
printf '%s' "$payload" \
|
|
64
|
+
| jq --arg p "$from_pattern" \
|
|
65
|
+
'[.mailbox[]?
|
|
66
|
+
| select(((.from // "") | test($p)))
|
|
67
|
+
| select((.replied // false) == false)
|
|
68
|
+
] | length' \
|
|
69
|
+
2>/dev/null \
|
|
70
|
+
|| echo 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Helper: count self-sent outbound messages of a given kind with no reply yet.
|
|
74
|
+
count_outbound_unanswered_kind() {
|
|
75
|
+
local kind_pattern="$1"
|
|
76
|
+
printf '%s' "$payload" \
|
|
77
|
+
| jq --arg k "$kind_pattern" \
|
|
78
|
+
'[.mailbox[]?
|
|
79
|
+
| select(((.direction // "in") == "out"))
|
|
80
|
+
| select(((.kind // "") | test($k)))
|
|
81
|
+
| select((.replied // false) == false)
|
|
82
|
+
] | length' \
|
|
83
|
+
2>/dev/null \
|
|
84
|
+
|| echo 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
warn=""
|
|
37
88
|
|
|
38
|
-
|
|
89
|
+
case "$teammate" in
|
|
90
|
+
backend-developer|frontend-developer)
|
|
91
|
+
unanswered_from_leader="$(count_unanswered_from '^team-leader$')"
|
|
92
|
+
if [ "${unanswered_from_leader:-0}" -gt 0 ]; then
|
|
93
|
+
warn="BLOCKED_IDLE_implementer_owes_team-leader_reply"
|
|
94
|
+
fi
|
|
95
|
+
;;
|
|
96
|
+
team-leader)
|
|
97
|
+
spawn_pending="$(count_outbound_unanswered_kind 'SPAWN_REQUEST|RESTART_REQUEST')"
|
|
98
|
+
escalate_pending="$(count_unanswered_from '^(backend-developer|frontend-developer)$')"
|
|
99
|
+
if [ "${spawn_pending:-0}" -gt 0 ]; then
|
|
100
|
+
warn="BLOCKED_IDLE_team-leader_awaiting_lead_on_spawn_or_restart"
|
|
101
|
+
elif [ "${escalate_pending:-0}" -gt 0 ]; then
|
|
102
|
+
warn="BLOCKED_IDLE_team-leader_owes_implementer_escalate_reply"
|
|
103
|
+
fi
|
|
104
|
+
;;
|
|
105
|
+
qc-engineer)
|
|
106
|
+
qc_pending="$(count_outbound_unanswered_kind 'QC_REWORK_NEEDED')"
|
|
107
|
+
if [ "${qc_pending:-0}" -gt 0 ]; then
|
|
108
|
+
warn="BLOCKED_IDLE_qc-engineer_awaiting_lead_ack"
|
|
109
|
+
fi
|
|
110
|
+
;;
|
|
111
|
+
solution-architect|feature-planner|security-engineer)
|
|
112
|
+
handover_pending="$(count_outbound_unanswered_kind 'HANDOVER_READY|SEC_PASSED|SEC_BLOCKED')"
|
|
113
|
+
if [ "${handover_pending:-0}" -gt 0 ]; then
|
|
114
|
+
warn="BLOCKED_IDLE_phaseA_awaiting_owner_signoff"
|
|
115
|
+
fi
|
|
116
|
+
;;
|
|
117
|
+
orchestrator)
|
|
118
|
+
lead_inbound_pending="$(count_unanswered_from '^team-leader$')"
|
|
119
|
+
if [ "${lead_inbound_pending:-0}" -gt 0 ]; then
|
|
120
|
+
warn="BLOCKED_IDLE_orchestrator_unhandled_team-leader_request"
|
|
121
|
+
fi
|
|
122
|
+
;;
|
|
123
|
+
"")
|
|
124
|
+
# Unknown role: fall back to legacy v4 behaviour (any unanswered non-lead inbound).
|
|
125
|
+
legacy_unanswered="$(printf '%s' "$payload" | jq '[.mailbox[]? | select((.from // "") != "lead") | select((.replied // false) == false)] | length' 2>/dev/null || echo 0)"
|
|
126
|
+
if [ "${legacy_unanswered:-0}" -gt 0 ]; then
|
|
127
|
+
warn="BLOCKED_IDLE_legacy_unanswered_inbound"
|
|
128
|
+
fi
|
|
129
|
+
;;
|
|
130
|
+
*)
|
|
131
|
+
# Unknown but non-empty role — log advisory only, do not block.
|
|
132
|
+
:
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
39
135
|
|
|
40
|
-
if [ "$
|
|
41
|
-
printf '{"ts":"%s","hook":"teammate-idle","
|
|
136
|
+
if [ -n "$warn" ]; then
|
|
137
|
+
printf '{"ts":"%s","hook":"teammate-idle","teammate":%s,"warn":%s}\n' \
|
|
138
|
+
"$ts" \
|
|
139
|
+
"$(printf '%s' "$teammate" | jq -Rs .)" \
|
|
140
|
+
"$(printf '%s' "$warn" | jq -Rs .)" \
|
|
141
|
+
>> "$LOG_FILE"
|
|
142
|
+
else
|
|
143
|
+
printf '{"ts":"%s","hook":"teammate-idle","teammate":%s,"ok":true}\n' \
|
|
144
|
+
"$ts" \
|
|
145
|
+
"$(printf '%s' "$teammate" | jq -Rs .)" \
|
|
146
|
+
>> "$LOG_FILE"
|
|
42
147
|
fi
|
|
43
148
|
|
|
44
149
|
exit 0
|