@drunkcoding/agents-and-skills 0.0.17 → 0.0.19
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 +19 -4
- package/README.md +24 -0
- package/package.json +1 -1
- package/plugins/auto-power/.claude-plugin/plugin.json +17 -0
- package/plugins/auto-power/README.md +80 -0
- package/plugins/auto-power/assets/CHECKPOINT_SCHEMA.md +69 -0
- package/plugins/auto-power/assets/ESCALATION_TEMPLATE.md +67 -0
- package/plugins/auto-power/commands/auto-power-resume.md +32 -0
- package/plugins/auto-power/commands/auto-power.md +46 -0
- package/plugins/auto-power/skills/auto-power-runtime/SKILL.md +220 -0
- 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/agents/backend-developer.md +93 -3
- package/plugins/team-superpower/agents/designer.md +17 -2
- package/plugins/team-superpower/agents/frontend-developer.md +94 -3
- package/plugins/team-superpower/agents/planner.md +62 -6
- package/plugins/team-superpower/agents/qa-engineer.md +73 -17
- package/plugins/team-superpower/agents/reviewer.md +71 -6
- package/plugins/team-superpower/agents/security-engineer.md +16 -1
- package/plugins/team-superpower/agents/software-architect.md +16 -1
- package/plugins/team-superpower/assets/AGENTS.md.template +23 -0
- package/plugins/team-superpower/assets/CLAUDE.md.template +17 -0
- package/plugins/team-superpower/assets/SESSION_README.md +47 -0
- package/plugins/team-superpower/commands/team-feature.md +217 -11
- package/plugins/team-superpower/hooks/task-completed.sh +203 -0
- package/plugins/team-superpower/hooks/task-created.sh +42 -1
- 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/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
|
@@ -53,6 +53,209 @@ case "$title" in
|
|
|
53
53
|
;;
|
|
54
54
|
esac
|
|
55
55
|
|
|
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).
|
|
58
|
+
case "$title" in
|
|
59
|
+
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"
|
|
64
|
+
fi
|
|
65
|
+
;;
|
|
66
|
+
esac
|
|
67
|
+
|
|
68
|
+
# v3: MAX_ITERATIONS guardrail. impl:* completions whose iteration_count
|
|
69
|
+
# exceeds the per-project cap (default 8) must carry a reflection: block.
|
|
70
|
+
case "$title" in
|
|
71
|
+
impl:*)
|
|
72
|
+
iteration_count="$(printf '%s' "$payload" | jq -r '.task.metadata.iteration_count // .metadata.iteration_count // 0' 2>/dev/null || echo 0)"
|
|
73
|
+
reflection="$(printf '%s' "$payload" | jq -r '.task.metadata.reflection // .metadata.reflection // ""' 2>/dev/null || echo "")"
|
|
74
|
+
|
|
75
|
+
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
|
+
if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
|
|
83
|
+
configured="$(bash "$parse_helper" get limits.max_iterations_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
|
|
84
|
+
if [ -n "$configured" ] && printf '%s' "$configured" | grep -qE '^[0-9]+$'; then
|
|
85
|
+
cap="$configured"
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [ "${iteration_count:-0}" -gt "$cap" ] && [ -z "$reflection" ]; then
|
|
90
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"ITERATION_CAP_EXCEEDED","title":%s,"iteration_count":%d,"cap":%d}\n' \
|
|
91
|
+
"$ts" \
|
|
92
|
+
"$(printf '%s' "$title" | jq -Rs .)" \
|
|
93
|
+
"$iteration_count" \
|
|
94
|
+
"$cap" \
|
|
95
|
+
>> "$LOG_FILE"
|
|
96
|
+
fi
|
|
97
|
+
;;
|
|
98
|
+
esac
|
|
99
|
+
|
|
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).
|
|
205
|
+
case "$title" in
|
|
206
|
+
impl:*)
|
|
207
|
+
retrieval_requests="$(printf '%s' "$payload" | jq -r '.task.metadata.retrieval_requests // .metadata.retrieval_requests // 0' 2>/dev/null || echo 0)"
|
|
208
|
+
retrieval_cap=2
|
|
209
|
+
if [ -f "$parse_helper" ] && [ -f "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" ]; then
|
|
210
|
+
configured_r="$(bash "$parse_helper" get limits.retrieval_budget_per_task "${CLAUDE_PROJECT_DIR:-$PWD}/CLAUDE.md" 2>/dev/null || true)"
|
|
211
|
+
if [ -n "$configured_r" ] && printf '%s' "$configured_r" | grep -qE '^[0-9]+$'; then
|
|
212
|
+
retrieval_cap="$configured_r"
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
if [ "${retrieval_requests:-0}" -gt "$retrieval_cap" ]; then
|
|
217
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"RETRIEVAL_BUDGET_EXCEEDED","title":%s,"requests":%d,"cap":%d}\n' \
|
|
218
|
+
"$ts" "$(printf '%s' "$title" | jq -Rs .)" "$retrieval_requests" "$retrieval_cap" >> "$LOG_FILE"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
if command -v git >/dev/null 2>&1; then
|
|
222
|
+
r_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
|
|
223
|
+
if [ -n "$r_commits" ]; then
|
|
224
|
+
flagged=0
|
|
225
|
+
while IFS= read -r sha; do
|
|
226
|
+
[ -z "$sha" ] && continue
|
|
227
|
+
body="$(git show --no-color --no-patch --format=%B "$sha" 2>/dev/null || true)"
|
|
228
|
+
if printf '%s\n' "$body" | grep -qE '^Flagged-assumptions:'; then
|
|
229
|
+
flagged=1; break
|
|
230
|
+
fi
|
|
231
|
+
done <<< "$r_commits"
|
|
232
|
+
if [ "$flagged" -eq 1 ] && [ "${retrieval_requests:-0}" -lt "$retrieval_cap" ]; then
|
|
233
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"PREMATURE_ASSUMPTION_FLAG","title":%s,"requests":%d,"cap":%d}\n' \
|
|
234
|
+
"$ts" "$(printf '%s' "$title" | jq -Rs .)" "$retrieval_requests" "$retrieval_cap" >> "$LOG_FILE"
|
|
235
|
+
fi
|
|
236
|
+
fi
|
|
237
|
+
fi
|
|
238
|
+
;;
|
|
239
|
+
esac
|
|
240
|
+
|
|
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.
|
|
245
|
+
if command -v git >/dev/null 2>&1; then
|
|
246
|
+
agm_commits="$(printf '%s' "$payload" | jq -r '(.task.metadata.commits // .metadata.commits // [])[]?' 2>/dev/null || true)"
|
|
247
|
+
if [ -n "$agm_commits" ]; then
|
|
248
|
+
while IFS= read -r sha; do
|
|
249
|
+
[ -z "$sha" ] && continue
|
|
250
|
+
files="$(git show --no-color --name-only --pretty=format: "$sha" 2>/dev/null || true)"
|
|
251
|
+
if printf '%s\n' "$files" | grep -qE '^docs/superpowers/AGENTS\.md$'; then
|
|
252
|
+
printf '{"ts":"%s","hook":"task-completed","warn":"AGENT_WROTE_AGENTS_MD","title":%s,"sha":%s}\n' \
|
|
253
|
+
"$ts" "$(printf '%s' "$title" | jq -Rs .)" "$(printf '%s' "$sha" | jq -Rs .)" >> "$LOG_FILE"
|
|
254
|
+
fi
|
|
255
|
+
done <<< "$agm_commits"
|
|
256
|
+
fi
|
|
257
|
+
fi
|
|
258
|
+
|
|
56
259
|
# v2: migration serialization. If this is a migration task, verify no other
|
|
57
260
|
# `impl:be-migration-*` task is currently in_progress in the shared task list
|
|
58
261
|
# (payload.tasks[]). The lead also enforces this; the hook is a backstop.
|
|
@@ -59,10 +59,17 @@ if [ -d "$SESSIONS_DIR" ]; then
|
|
|
59
59
|
fi
|
|
60
60
|
fi
|
|
61
61
|
|
|
62
|
-
printf '
|
|
62
|
+
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
|
+
|
|
68
|
+
printf '{"ts":"%s","hook":"task-created","title":%s,"shape":%s,"wave":%s}\n' \
|
|
63
69
|
"$ts" \
|
|
64
70
|
"$(printf '%s' "$title" | jq -Rs .)" \
|
|
65
71
|
"$(printf '%s' "$shape" | jq -Rs .)" \
|
|
72
|
+
"$wave_json" \
|
|
66
73
|
>> "$LOG_FILE"
|
|
67
74
|
|
|
68
75
|
# Top-level prefix check
|
|
@@ -77,6 +84,40 @@ case "$title" in
|
|
|
77
84
|
;;
|
|
78
85
|
esac
|
|
79
86
|
|
|
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).
|
|
89
|
+
mode_meta="$(printf '%s' "$payload" | jq -r '.task.metadata.mode // .metadata.mode // ""' 2>/dev/null || echo "")"
|
|
90
|
+
mode_marker=""
|
|
91
|
+
if [ -z "$mode_meta" ] && [ -d "$SESSIONS_DIR" ]; then
|
|
92
|
+
# Look for a <slug>.mode file the lead writes when it picks a mode.
|
|
93
|
+
marker_file=""
|
|
94
|
+
if [ -n "$slug" ] && [ -f "$SESSIONS_DIR/$slug.mode" ]; then
|
|
95
|
+
marker_file="$SESSIONS_DIR/$slug.mode"
|
|
96
|
+
else
|
|
97
|
+
count="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.mode' 2>/dev/null | wc -l | tr -d ' ')"
|
|
98
|
+
if [ "$count" = "1" ]; then
|
|
99
|
+
marker_file="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.mode' 2>/dev/null | head -n1)"
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
if [ -n "$marker_file" ] && [ -f "$marker_file" ]; then
|
|
103
|
+
mode_marker="$(head -n1 "$marker_file" | tr -d '[:space:]')"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
effective_mode="${mode_meta:-$mode_marker}"
|
|
107
|
+
|
|
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
|
|
120
|
+
|
|
80
121
|
# At this point title starts with `impl:`. Strip prefix and require a known
|
|
81
122
|
# sub-prefix.
|
|
82
123
|
rest="${title#impl:}"
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# assess-complexity.sh — encode the team-superpower v3 heuristic ladder.
|
|
3
|
+
#
|
|
4
|
+
# Inputs:
|
|
5
|
+
# $1 — owner's launch message (required)
|
|
6
|
+
# $2 — repo root (optional, default $PWD); reads CLAUDE.md's
|
|
7
|
+
# security.domain to bias size and stack to seed `shape:`.
|
|
8
|
+
#
|
|
9
|
+
# Output: YAML on stdout. Always emits `mode:`, `shape:`, `mode_reasoning:`.
|
|
10
|
+
# Emits `size:` only when mode == team.
|
|
11
|
+
#
|
|
12
|
+
# Exit codes:
|
|
13
|
+
# 0 — confident decision
|
|
14
|
+
# 1 — ambiguous (mode could plausibly be single-agent OR team; defaults to
|
|
15
|
+
# team and notes the ambiguity in mode_reasoning).
|
|
16
|
+
#
|
|
17
|
+
# This script never spawns or writes outside stdout. The lead handles writes.
|
|
18
|
+
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
if [ "${1:-}" = "" ]; then
|
|
22
|
+
echo "usage: assess-complexity.sh <launch-message> [repo-root]" >&2
|
|
23
|
+
exit 2
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
MSG="$1"
|
|
27
|
+
ROOT="${2:-$PWD}"
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
DETECT="$SCRIPT_DIR/detect-stack.sh"
|
|
30
|
+
PARSE="$SCRIPT_DIR/parse-claudemd.sh"
|
|
31
|
+
|
|
32
|
+
lc="$(printf '%s' "$MSG" | tr '[:upper:]' '[:lower:]')"
|
|
33
|
+
length="${#MSG}"
|
|
34
|
+
|
|
35
|
+
# --- Discovery / size keyword tables --------------------------------------
|
|
36
|
+
|
|
37
|
+
discovery_keywords=(
|
|
38
|
+
"design" "architecture" "system" "flow" "feature" "epic" "refactor"
|
|
39
|
+
"migrate" "migrate to" "replace with" "replace" "rewrite" "redesign"
|
|
40
|
+
"overhaul"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
trivial_keywords=(
|
|
44
|
+
"fix typo" "typo in" "rename" "rename variable" "update copy"
|
|
45
|
+
"update text" "change wording" "bump version" "update readme"
|
|
46
|
+
"comment out" "add comment" "remove unused" "format" "prettify"
|
|
47
|
+
"lint fix"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
tiny_scope_phrases=(
|
|
51
|
+
"one line" "single line" "one file" "just change" "quick fix"
|
|
52
|
+
"tiny" "trivial"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
small_scope_verbs=(
|
|
56
|
+
"add" "create" "new " "introduce" "fix"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
small_scope_nouns=(
|
|
60
|
+
"endpoint" "/" "component" "field" "column" "validation"
|
|
61
|
+
"bug" "error" "test" "migration" "button" "page" "route"
|
|
62
|
+
"controller" "service"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
regulated_keywords=(
|
|
66
|
+
"compliance" "audit" "regulatory" "pii" "pci" "gdpr" "hipaa"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
low_stakes_keywords=(
|
|
70
|
+
"prototype" "spike" "internal-only" "experiment" "poc"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
team_request_keywords=(
|
|
74
|
+
"team" "agents" "full feature"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
contains_any() {
|
|
78
|
+
# contains_any <text-lc> <array-name>
|
|
79
|
+
# Compatible with Bash 3.2 (no namerefs).
|
|
80
|
+
local txt="$1"
|
|
81
|
+
local arr_name="$2"
|
|
82
|
+
eval 'local kw; for kw in "${'"$arr_name"'[@]}"; do
|
|
83
|
+
case "$txt" in *"$kw"*) return 0 ;; esac
|
|
84
|
+
done'
|
|
85
|
+
return 1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Rung 1: trivial / tiny-scope / single named file
|
|
89
|
+
single_file_match=0
|
|
90
|
+
# Detect a single path literal in the message (very rough: looks like x/y or x.y).
|
|
91
|
+
if printf '%s' "$MSG" | grep -qE '(^|[[:space:]])[A-Za-z0-9_./-]+\.[A-Za-z0-9]+([[:space:]]|$)'; then
|
|
92
|
+
# Count distinct file-ish tokens; if exactly 1, treat as named file.
|
|
93
|
+
count="$(printf '%s\n' "$MSG" | grep -oE '(^|[[:space:]])[A-Za-z0-9_./-]+\.[A-Za-z0-9]+([[:space:]]|$)' | tr -d '[:space:]' | sort -u | wc -l | tr -d ' ')"
|
|
94
|
+
if [ "$count" = "1" ]; then single_file_match=1; fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
solo=0
|
|
98
|
+
solo_why=""
|
|
99
|
+
if contains_any "$lc" trivial_keywords; then solo=1; solo_why="trivial keyword match";
|
|
100
|
+
elif contains_any "$lc" tiny_scope_phrases; then solo=1; solo_why="tiny-scope phrase match";
|
|
101
|
+
elif [ "$single_file_match" = "1" ]; then solo=1; solo_why="single named file in message";
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Rung 2: small-scope + single-side + no discovery language
|
|
105
|
+
side="none"
|
|
106
|
+
if [ -x "$DETECT" ] || [ -f "$DETECT" ]; then
|
|
107
|
+
side="$(bash "$DETECT" detect-side "$MSG" 2>/dev/null || echo none)"
|
|
108
|
+
fi
|
|
109
|
+
single_agent=0
|
|
110
|
+
single_agent_why=""
|
|
111
|
+
if [ "$solo" = "0" ]; then
|
|
112
|
+
if contains_any "$lc" small_scope_verbs \
|
|
113
|
+
&& contains_any "$lc" small_scope_nouns \
|
|
114
|
+
&& { [ "$side" = "be-only" ] || [ "$side" = "fe-only" ]; } \
|
|
115
|
+
&& ! contains_any "$lc" discovery_keywords; then
|
|
116
|
+
single_agent=1
|
|
117
|
+
single_agent_why="small-scope verb+noun + single-side ($side) + no discovery language"
|
|
118
|
+
fi
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# Rung 3: team (default)
|
|
122
|
+
verb_count=0
|
|
123
|
+
# Cheap multi-verb heuristic: count occurrences of " and " between action words.
|
|
124
|
+
verb_count="$(printf '%s' "$lc" | { grep -oE '[[:space:]]and[[:space:]]' || true; } | wc -l | tr -d ' ')"
|
|
125
|
+
long_message=0
|
|
126
|
+
if [ "$length" -gt 200 ]; then long_message=1; fi
|
|
127
|
+
|
|
128
|
+
team=0
|
|
129
|
+
team_why=""
|
|
130
|
+
if [ "$solo" = "0" ] && [ "$single_agent" = "0" ]; then
|
|
131
|
+
team=1
|
|
132
|
+
if [ "$side" = "mixed" ]; then team_why="multi-side signal (BE+FE)";
|
|
133
|
+
elif contains_any "$lc" discovery_keywords; then team_why="discovery language match";
|
|
134
|
+
elif [ "$verb_count" -ge 1 ]; then team_why="multi-verb message ($verb_count 'and' joiners)";
|
|
135
|
+
elif [ "$long_message" = "1" ]; then team_why="long message (>200 chars)";
|
|
136
|
+
elif contains_any "$lc" team_request_keywords; then team_why="explicit team request";
|
|
137
|
+
else team_why="rung-1 and rung-2 did not match; defaulting to team";
|
|
138
|
+
fi
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
# Resolve mode
|
|
142
|
+
if [ "$solo" = "1" ]; then mode="solo"; reason="Rung 1 matched: $solo_why."
|
|
143
|
+
elif [ "$single_agent" = "1" ]; then mode="single-agent"; reason="Rung 2 matched: $single_agent_why."
|
|
144
|
+
else mode="team"; reason="Rung 3 matched: $team_why."
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Size (team only)
|
|
148
|
+
size=""
|
|
149
|
+
size_why=""
|
|
150
|
+
if [ "$mode" = "team" ]; then
|
|
151
|
+
domain=""
|
|
152
|
+
if [ -f "$ROOT/CLAUDE.md" ] && [ -f "$PARSE" ]; then
|
|
153
|
+
domain="$(bash "$PARSE" get security.domain "$ROOT/CLAUDE.md" 2>/dev/null || true)"
|
|
154
|
+
fi
|
|
155
|
+
if [ "$domain" = "payments" ] || [ "$domain" = "healthcare" ]; then
|
|
156
|
+
size="full"; size_why="security.domain=$domain"
|
|
157
|
+
elif contains_any "$lc" regulated_keywords; then
|
|
158
|
+
size="full"; size_why="regulated keyword match"
|
|
159
|
+
elif contains_any "$lc" low_stakes_keywords; then
|
|
160
|
+
size="minimal"; size_why="low-stakes keyword match"
|
|
161
|
+
else
|
|
162
|
+
size="standard"; size_why="default"
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Shape: prefer CLAUDE.md, fall back to mixed.
|
|
167
|
+
shape=""
|
|
168
|
+
if [ -f "$ROOT/CLAUDE.md" ] && [ -f "$PARSE" ]; then
|
|
169
|
+
shape="$(bash "$PARSE" shape "$ROOT/CLAUDE.md" 2>/dev/null || true)"
|
|
170
|
+
fi
|
|
171
|
+
if [ -z "$shape" ] || [ "$shape" = "none" ]; then
|
|
172
|
+
# Fall back to side detection: mixed → full-stack; be-only / fe-only as-is.
|
|
173
|
+
case "$side" in
|
|
174
|
+
mixed) shape="full-stack" ;;
|
|
175
|
+
be-only) shape="be-only" ;;
|
|
176
|
+
fe-only) shape="fe-only" ;;
|
|
177
|
+
*) shape="full-stack" ;;
|
|
178
|
+
esac
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# Emit
|
|
182
|
+
printf 'mode: %s\n' "$mode"
|
|
183
|
+
if [ "$mode" = "team" ]; then
|
|
184
|
+
printf 'size: %s\n' "$size"
|
|
185
|
+
fi
|
|
186
|
+
printf 'shape: %s\n' "$shape"
|
|
187
|
+
printf 'mode_reasoning: |\n'
|
|
188
|
+
printf ' %s\n' "$reason"
|
|
189
|
+
if [ "$mode" = "team" ]; then
|
|
190
|
+
printf ' Size: %s — %s.\n' "$size" "$size_why"
|
|
191
|
+
fi
|
|
192
|
+
printf ' side_signal: %s; verb_joiners: %d; length: %d chars.\n' "$side" "$verb_count" "$length"
|
|
193
|
+
|
|
194
|
+
exit 0
|
|
@@ -22,6 +22,45 @@
|
|
|
22
22
|
|
|
23
23
|
set -euo pipefail
|
|
24
24
|
|
|
25
|
+
# ---- subcommand: detect-side ---------------------------------------------
|
|
26
|
+
# Usage:
|
|
27
|
+
# bash detect-stack.sh detect-side "<launch message>"
|
|
28
|
+
# Outputs one of: be-only | fe-only | mixed | none
|
|
29
|
+
# Used by scripts/assess-complexity.sh for the rung-2 single-side signal.
|
|
30
|
+
if [ "${1:-}" = "detect-side" ]; then
|
|
31
|
+
shift
|
|
32
|
+
text="${1:-}"
|
|
33
|
+
# Normalize to lowercase for case-insensitive matching.
|
|
34
|
+
lc="$(printf '%s' "$text" | tr '[:upper:]' '[:lower:]')"
|
|
35
|
+
|
|
36
|
+
be_keywords=(
|
|
37
|
+
"endpoint" "api" "/api" "/auth" "/health" "route" "controller" "service"
|
|
38
|
+
"repository" "schema" "migration" "database" "column" "table"
|
|
39
|
+
"queue" "worker" "cron" "webhook" "grpc" "rpc"
|
|
40
|
+
)
|
|
41
|
+
fe_keywords=(
|
|
42
|
+
"component" "page" "form" "button" "modal" "input" "field"
|
|
43
|
+
"ui" "layout" "css" "tailwind" "tsx" "jsx" "react" "vue"
|
|
44
|
+
"svelte" "stylesheet" "wireframe" "design system" "screen"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
has_be=0
|
|
48
|
+
has_fe=0
|
|
49
|
+
for kw in "${be_keywords[@]}"; do
|
|
50
|
+
case "$lc" in *"$kw"*) has_be=1; break ;; esac
|
|
51
|
+
done
|
|
52
|
+
for kw in "${fe_keywords[@]}"; do
|
|
53
|
+
case "$lc" in *"$kw"*) has_fe=1; break ;; esac
|
|
54
|
+
done
|
|
55
|
+
|
|
56
|
+
if [ "$has_be" -eq 1 ] && [ "$has_fe" -eq 1 ]; then echo "mixed"
|
|
57
|
+
elif [ "$has_be" -eq 1 ]; then echo "be-only"
|
|
58
|
+
elif [ "$has_fe" -eq 1 ]; then echo "fe-only"
|
|
59
|
+
else echo "none"
|
|
60
|
+
fi
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
25
64
|
ROOT="${1:-$PWD}"
|
|
26
65
|
cd "$ROOT"
|
|
27
66
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# wave-collision-check.sh — detect file-scope collisions inside a wave.
|
|
3
|
+
#
|
|
4
|
+
# Input (stdin): one task per line, space-separated:
|
|
5
|
+
# <task-id> <file1> [<file2> ...]
|
|
6
|
+
# Files are normalized (lowercase, leading ./ stripped, slashes preserved).
|
|
7
|
+
#
|
|
8
|
+
# Exit codes:
|
|
9
|
+
# 0 — no collision
|
|
10
|
+
# 1 — at least one pair of tasks share a normalized file
|
|
11
|
+
#
|
|
12
|
+
# Stdout on collision: one line per colliding pair:
|
|
13
|
+
# COLLISION <task-i> <task-j> <file>
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
normalize() {
|
|
18
|
+
# lowercase + strip leading ./
|
|
19
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's|^\./||'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
tasks=()
|
|
23
|
+
files_list=()
|
|
24
|
+
|
|
25
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
26
|
+
[ -z "$line" ] && continue
|
|
27
|
+
# shellcheck disable=SC2206
|
|
28
|
+
parts=($line)
|
|
29
|
+
task="${parts[0]}"
|
|
30
|
+
files=()
|
|
31
|
+
for ((i=1; i<${#parts[@]}; i++)); do
|
|
32
|
+
files+=("$(normalize "${parts[$i]}")")
|
|
33
|
+
done
|
|
34
|
+
tasks+=("$task")
|
|
35
|
+
files_list+=("${files[*]}")
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
collisions=0
|
|
39
|
+
n=${#tasks[@]}
|
|
40
|
+
for ((i=0; i<n; i++)); do
|
|
41
|
+
for ((j=i+1; j<n; j++)); do
|
|
42
|
+
# shellcheck disable=SC2206
|
|
43
|
+
fi=(${files_list[$i]})
|
|
44
|
+
# shellcheck disable=SC2206
|
|
45
|
+
fj=(${files_list[$j]})
|
|
46
|
+
for f1 in "${fi[@]}"; do
|
|
47
|
+
[ -z "$f1" ] && continue
|
|
48
|
+
for f2 in "${fj[@]}"; do
|
|
49
|
+
[ -z "$f2" ] && continue
|
|
50
|
+
if [ "$f1" = "$f2" ]; then
|
|
51
|
+
printf 'COLLISION %s %s %s\n' "${tasks[$i]}" "${tasks[$j]}" "$f1"
|
|
52
|
+
collisions=$((collisions+1))
|
|
53
|
+
fi
|
|
54
|
+
done
|
|
55
|
+
done
|
|
56
|
+
done
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
if [ "$collisions" -gt 0 ]; then exit 1; fi
|
|
60
|
+
exit 0
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { existsSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { getTmpDir, cleanup, withTmp, ROOT } from '../scripts/_tmp.js';
|
|
6
|
-
|
|
7
|
-
test('getTmpDir creates a fresh dir under .tmp/html-effectiveness/', () => {
|
|
8
|
-
const dir = getTmpDir('unit-a');
|
|
9
|
-
assert.ok(dir.endsWith('/.tmp/html-effectiveness/unit-a'));
|
|
10
|
-
assert.ok(existsSync(dir));
|
|
11
|
-
cleanup('unit-a');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test('getTmpDir wipes existing dir contents', () => {
|
|
15
|
-
const dir = getTmpDir('unit-b');
|
|
16
|
-
writeFileSync(join(dir, 'old.txt'), 'stale');
|
|
17
|
-
const dir2 = getTmpDir('unit-b');
|
|
18
|
-
assert.equal(dir, dir2);
|
|
19
|
-
assert.equal(existsSync(join(dir2, 'old.txt')), false);
|
|
20
|
-
cleanup('unit-b');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test('cleanup removes dir', () => {
|
|
24
|
-
const dir = getTmpDir('unit-c');
|
|
25
|
-
cleanup('unit-c');
|
|
26
|
-
assert.equal(existsSync(dir), false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test('cleanup is idempotent on missing dir', () => {
|
|
30
|
-
cleanup('unit-never-created');
|
|
31
|
-
assert.ok(true);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('withTmp runs callback and cleans up on success', () => {
|
|
35
|
-
let observed;
|
|
36
|
-
const result = withTmp('unit-d', (dir) => {
|
|
37
|
-
observed = dir;
|
|
38
|
-
writeFileSync(join(dir, 'x'), 'y');
|
|
39
|
-
return 'done';
|
|
40
|
-
});
|
|
41
|
-
assert.equal(result, 'done');
|
|
42
|
-
assert.equal(existsSync(observed), false);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('withTmp cleans up even when callback throws', () => {
|
|
46
|
-
let observed;
|
|
47
|
-
assert.throws(() => {
|
|
48
|
-
withTmp('unit-e', (dir) => {
|
|
49
|
-
observed = dir;
|
|
50
|
-
throw new Error('boom');
|
|
51
|
-
});
|
|
52
|
-
}, /boom/);
|
|
53
|
-
assert.equal(existsSync(observed), false);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('ROOT is anchored at repo .tmp/html-effectiveness', () => {
|
|
57
|
-
assert.ok(ROOT.endsWith('/.tmp/html-effectiveness'));
|
|
58
|
-
});
|