@drunkcoding/agents-and-skills 0.0.24 → 0.0.26
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 +14 -33
- package/README.md +1 -0
- package/package.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-share/.claude-plugin/plugin.json +17 -0
- package/plugins/team-share/README.md +30 -0
- package/plugins/team-share/agents/team-share.md +140 -0
- package/plugins/team-share/commands/team-share.md +4 -0
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
- package/plugins/auto-power/.claude-plugin/plugin.json +0 -17
- package/plugins/auto-power/README.md +0 -80
- package/plugins/auto-power/assets/CHECKPOINT_SCHEMA.md +0 -69
- package/plugins/auto-power/assets/ESCALATION_TEMPLATE.md +0 -67
- package/plugins/auto-power/commands/auto-power-resume.md +0 -32
- package/plugins/auto-power/commands/auto-power.md +0 -46
- package/plugins/auto-power/skills/auto-power-runtime/SKILL.md +0 -220
- package/plugins/team-superpower/.claude-plugin/plugin.json +0 -21
- package/plugins/team-superpower/README.md +0 -294
- package/plugins/team-superpower/agents/backend-developer.md +0 -221
- package/plugins/team-superpower/agents/feature-planner.md +0 -66
- package/plugins/team-superpower/agents/frontend-developer.md +0 -242
- package/plugins/team-superpower/agents/orchestrator.md +0 -83
- package/plugins/team-superpower/agents/qc-engineer.md +0 -84
- package/plugins/team-superpower/agents/security-engineer.md +0 -175
- package/plugins/team-superpower/agents/solution-architect.md +0 -80
- package/plugins/team-superpower/agents/team-leader.md +0 -100
- package/plugins/team-superpower/assets/AGENTS.md.template +0 -23
- package/plugins/team-superpower/assets/CLAUDE.md.template +0 -117
- package/plugins/team-superpower/assets/ESCALATION.md +0 -142
- package/plugins/team-superpower/assets/SESSION_README.md +0 -338
- package/plugins/team-superpower/commands/team-cleanup.md +0 -70
- package/plugins/team-superpower/commands/team-feature.md +0 -317
- package/plugins/team-superpower/hooks/hooks.json +0 -25
- package/plugins/team-superpower/hooks/task-completed.sh +0 -254
- package/plugins/team-superpower/hooks/task-created.sh +0 -174
- package/plugins/team-superpower/hooks/teammate-idle.sh +0 -149
- package/plugins/team-superpower/scripts/assess-complexity.sh +0 -194
- package/plugins/team-superpower/scripts/detect-stack.sh +0 -473
- package/plugins/team-superpower/scripts/parse-claudemd.sh +0 -194
- package/plugins/team-superpower/scripts/team-state.sh +0 -313
- package/plugins/team-superpower/scripts/wave-collision-check.sh +0 -60
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# TaskCreated hook — v5 prefix + wave validation for shared task list entries.
|
|
3
|
-
#
|
|
4
|
-
# Accepts a JSON payload on stdin. Required field:
|
|
5
|
-
# - task.title: string
|
|
6
|
-
#
|
|
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).
|
|
26
|
-
|
|
27
|
-
set -euo pipefail
|
|
28
|
-
|
|
29
|
-
LOG_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/hooks"
|
|
30
|
-
LOG_FILE="$LOG_DIR/log.jsonl"
|
|
31
|
-
SESSIONS_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/docs/superpowers/sessions"
|
|
32
|
-
mkdir -p "$LOG_DIR"
|
|
33
|
-
|
|
34
|
-
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
35
|
-
|
|
36
|
-
payload="$(cat || true)"
|
|
37
|
-
|
|
38
|
-
if [ -z "$payload" ]; then
|
|
39
|
-
printf '{"ts":"%s","hook":"task-created","skipped":"empty payload"}\n' "$ts" >> "$LOG_FILE"
|
|
40
|
-
exit 0
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
44
|
-
printf '{"ts":"%s","hook":"task-created","skipped":"jq not installed"}\n' "$ts" >> "$LOG_FILE"
|
|
45
|
-
exit 0
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
title="$(printf '%s' "$payload" | jq -r '.task.title // .title // ""' 2>/dev/null || echo "")"
|
|
49
|
-
slug="$(printf '%s' "$payload" | jq -r '.task.metadata.slug // .metadata.slug // ""' 2>/dev/null || echo "")"
|
|
50
|
-
|
|
51
|
-
# Resolve shape from the slug's marker file. If slug is unknown but exactly
|
|
52
|
-
# one .shape file exists, use it (single in-flight feature is the common case).
|
|
53
|
-
shape=""
|
|
54
|
-
if [ -d "$SESSIONS_DIR" ]; then
|
|
55
|
-
shape_file=""
|
|
56
|
-
if [ -n "$slug" ] && [ -f "$SESSIONS_DIR/$slug.shape" ]; then
|
|
57
|
-
shape_file="$SESSIONS_DIR/$slug.shape"
|
|
58
|
-
else
|
|
59
|
-
count="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.shape' 2>/dev/null | wc -l | tr -d ' ')"
|
|
60
|
-
if [ "$count" = "1" ]; then
|
|
61
|
-
shape_file="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.shape' 2>/dev/null | head -n1)"
|
|
62
|
-
fi
|
|
63
|
-
fi
|
|
64
|
-
if [ -n "$shape_file" ] && [ -f "$shape_file" ]; then
|
|
65
|
-
shape="$(head -n1 "$shape_file" | tr -d '[:space:]')"
|
|
66
|
-
fi
|
|
67
|
-
fi
|
|
68
|
-
|
|
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' \
|
|
72
|
-
"$ts" \
|
|
73
|
-
"$(printf '%s' "$title" | jq -Rs .)" \
|
|
74
|
-
"$(printf '%s' "$shape" | jq -Rs .)" \
|
|
75
|
-
"$(printf '%s' "$wave" | jq -Rs .)" \
|
|
76
|
-
>> "$LOG_FILE"
|
|
77
|
-
|
|
78
|
-
# Top-level prefix check
|
|
79
|
-
case "$title" in
|
|
80
|
-
review:*|meta:*|block:*) exit 0 ;;
|
|
81
|
-
impl:*) ;;
|
|
82
|
-
"")
|
|
83
|
-
printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","reason":"title missing"}\n' "$ts" >> "$LOG_FILE"
|
|
84
|
-
exit 0
|
|
85
|
-
;;
|
|
86
|
-
*)
|
|
87
|
-
printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
|
|
88
|
-
exit 0
|
|
89
|
-
;;
|
|
90
|
-
esac
|
|
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
|
-
|
|
128
|
-
# At this point title starts with `impl:`. Strip prefix and require a known
|
|
129
|
-
# v5 sub-prefix.
|
|
130
|
-
rest="${title#impl:}"
|
|
131
|
-
case "$rest" in
|
|
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-*)
|
|
140
|
-
sub="be"
|
|
141
|
-
;;
|
|
142
|
-
fe-*)
|
|
143
|
-
sub="fe"
|
|
144
|
-
;;
|
|
145
|
-
contract-update-*)
|
|
146
|
-
sub="contract"
|
|
147
|
-
;;
|
|
148
|
-
*)
|
|
149
|
-
printf '{"ts":"%s","hook":"task-created","warn":"bad_subprefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
|
|
150
|
-
exit 0
|
|
151
|
-
;;
|
|
152
|
-
esac
|
|
153
|
-
|
|
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.
|
|
157
|
-
case "$shape" in
|
|
158
|
-
be-only)
|
|
159
|
-
if [ "$sub" = "fe" ]; then
|
|
160
|
-
printf '{"ts":"%s","hook":"task-created","warn":"shape_rejected","shape":"be-only","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
|
|
161
|
-
fi
|
|
162
|
-
;;
|
|
163
|
-
fe-only)
|
|
164
|
-
if [ "$sub" = "be" ]; then
|
|
165
|
-
printf '{"ts":"%s","hook":"task-created","warn":"shape_rejected","shape":"fe-only","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
|
|
166
|
-
fi
|
|
167
|
-
;;
|
|
168
|
-
full-stack|"")
|
|
169
|
-
# full-stack: anything goes. Empty shape: hook can't tell — accept and
|
|
170
|
-
# let the lead serialize/validate. Logged above for tuning.
|
|
171
|
-
;;
|
|
172
|
-
esac
|
|
173
|
-
|
|
174
|
-
exit 0
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# TeammateIdle hook — v5 role-aware routing.
|
|
3
|
-
#
|
|
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)
|
|
9
|
-
#
|
|
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)
|
|
35
|
-
#
|
|
36
|
-
# Logs every invocation to .claude/hooks/log.jsonl in the project root for tuning.
|
|
37
|
-
|
|
38
|
-
set -euo pipefail
|
|
39
|
-
|
|
40
|
-
LOG_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/hooks"
|
|
41
|
-
LOG_FILE="$LOG_DIR/log.jsonl"
|
|
42
|
-
mkdir -p "$LOG_DIR"
|
|
43
|
-
|
|
44
|
-
payload="$(cat || true)"
|
|
45
|
-
|
|
46
|
-
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
47
|
-
|
|
48
|
-
if [ -z "$payload" ]; then
|
|
49
|
-
printf '{"ts":"%s","hook":"teammate-idle","skipped":"empty payload"}\n' "$ts" >> "$LOG_FILE"
|
|
50
|
-
exit 0
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
if ! command -v jq >/dev/null 2>&1; then
|
|
54
|
-
printf '{"ts":"%s","hook":"teammate-idle","skipped":"jq not installed"}\n' "$ts" >> "$LOG_FILE"
|
|
55
|
-
exit 0
|
|
56
|
-
fi
|
|
57
|
-
|
|
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=""
|
|
88
|
-
|
|
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
|
|
135
|
-
|
|
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"
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
exit 0
|
|
@@ -1,194 +0,0 @@
|
|
|
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
|