@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.
Files changed (42) hide show
  1. package/.claude-plugin/marketplace.json +14 -33
  2. package/README.md +1 -0
  3. package/package.json +1 -1
  4. package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
  5. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  6. package/plugins/team-share/.claude-plugin/plugin.json +17 -0
  7. package/plugins/team-share/README.md +30 -0
  8. package/plugins/team-share/agents/team-share.md +140 -0
  9. package/plugins/team-share/commands/team-share.md +4 -0
  10. package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
  11. package/plugins/auto-power/.claude-plugin/plugin.json +0 -17
  12. package/plugins/auto-power/README.md +0 -80
  13. package/plugins/auto-power/assets/CHECKPOINT_SCHEMA.md +0 -69
  14. package/plugins/auto-power/assets/ESCALATION_TEMPLATE.md +0 -67
  15. package/plugins/auto-power/commands/auto-power-resume.md +0 -32
  16. package/plugins/auto-power/commands/auto-power.md +0 -46
  17. package/plugins/auto-power/skills/auto-power-runtime/SKILL.md +0 -220
  18. package/plugins/team-superpower/.claude-plugin/plugin.json +0 -21
  19. package/plugins/team-superpower/README.md +0 -294
  20. package/plugins/team-superpower/agents/backend-developer.md +0 -221
  21. package/plugins/team-superpower/agents/feature-planner.md +0 -66
  22. package/plugins/team-superpower/agents/frontend-developer.md +0 -242
  23. package/plugins/team-superpower/agents/orchestrator.md +0 -83
  24. package/plugins/team-superpower/agents/qc-engineer.md +0 -84
  25. package/plugins/team-superpower/agents/security-engineer.md +0 -175
  26. package/plugins/team-superpower/agents/solution-architect.md +0 -80
  27. package/plugins/team-superpower/agents/team-leader.md +0 -100
  28. package/plugins/team-superpower/assets/AGENTS.md.template +0 -23
  29. package/plugins/team-superpower/assets/CLAUDE.md.template +0 -117
  30. package/plugins/team-superpower/assets/ESCALATION.md +0 -142
  31. package/plugins/team-superpower/assets/SESSION_README.md +0 -338
  32. package/plugins/team-superpower/commands/team-cleanup.md +0 -70
  33. package/plugins/team-superpower/commands/team-feature.md +0 -317
  34. package/plugins/team-superpower/hooks/hooks.json +0 -25
  35. package/plugins/team-superpower/hooks/task-completed.sh +0 -254
  36. package/plugins/team-superpower/hooks/task-created.sh +0 -174
  37. package/plugins/team-superpower/hooks/teammate-idle.sh +0 -149
  38. package/plugins/team-superpower/scripts/assess-complexity.sh +0 -194
  39. package/plugins/team-superpower/scripts/detect-stack.sh +0 -473
  40. package/plugins/team-superpower/scripts/parse-claudemd.sh +0 -194
  41. package/plugins/team-superpower/scripts/team-state.sh +0 -313
  42. 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