@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.
Files changed (62) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/README.md +23 -0
  3. package/package.json +1 -1
  4. package/plugins/auto-power/.claude-plugin/plugin.json +1 -1
  5. package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
  6. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  7. package/plugins/team-superpower/.claude-plugin/plugin.json +1 -1
  8. package/plugins/team-superpower/README.md +186 -115
  9. package/plugins/team-superpower/agents/backend-developer.md +161 -39
  10. package/plugins/team-superpower/agents/feature-planner.md +66 -0
  11. package/plugins/team-superpower/agents/frontend-developer.md +163 -34
  12. package/plugins/team-superpower/agents/orchestrator.md +83 -0
  13. package/plugins/team-superpower/agents/qc-engineer.md +84 -0
  14. package/plugins/team-superpower/agents/security-engineer.md +90 -40
  15. package/plugins/team-superpower/agents/solution-architect.md +80 -0
  16. package/plugins/team-superpower/agents/team-leader.md +100 -0
  17. package/plugins/team-superpower/assets/AGENTS.md.template +23 -0
  18. package/plugins/team-superpower/assets/CLAUDE.md.template +25 -4
  19. package/plugins/team-superpower/assets/ESCALATION.md +114 -66
  20. package/plugins/team-superpower/assets/SESSION_README.md +233 -113
  21. package/plugins/team-superpower/commands/team-feature.md +195 -443
  22. package/plugins/team-superpower/hooks/task-completed.sh +180 -59
  23. package/plugins/team-superpower/hooks/task-created.sh +77 -19
  24. package/plugins/team-superpower/hooks/teammate-idle.sh +118 -13
  25. package/plugins/team-superpower/scripts/assess-complexity.sh +194 -0
  26. package/plugins/team-superpower/scripts/detect-stack.sh +39 -0
  27. package/plugins/team-superpower/scripts/team-state.sh +106 -37
  28. package/plugins/team-superpower/scripts/wave-collision-check.sh +60 -0
  29. package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
  30. package/plugins/html-effectiveness/tests/_tmp.test.js +0 -58
  31. package/plugins/html-effectiveness/tests/fixtures/01-exploration-code-approaches.data.json +0 -3
  32. package/plugins/html-effectiveness/tests/fixtures/02-exploration-visual-designs.data.json +0 -3
  33. package/plugins/html-effectiveness/tests/fixtures/03-code-review-pr.data.json +0 -3
  34. package/plugins/html-effectiveness/tests/fixtures/04-code-understanding.data.json +0 -3
  35. package/plugins/html-effectiveness/tests/fixtures/05-design-system.data.json +0 -3
  36. package/plugins/html-effectiveness/tests/fixtures/06-component-variants.data.json +0 -3
  37. package/plugins/html-effectiveness/tests/fixtures/07-prototype-animation.data.json +0 -3
  38. package/plugins/html-effectiveness/tests/fixtures/08-prototype-interaction.data.json +0 -3
  39. package/plugins/html-effectiveness/tests/fixtures/09-slide-deck.data.json +0 -3
  40. package/plugins/html-effectiveness/tests/fixtures/10-svg-illustrations.data.json +0 -3
  41. package/plugins/html-effectiveness/tests/fixtures/11-status-report.data.json +0 -37
  42. package/plugins/html-effectiveness/tests/fixtures/12-incident-report.data.json +0 -3
  43. package/plugins/html-effectiveness/tests/fixtures/13-flowchart-diagram.data.json +0 -3
  44. package/plugins/html-effectiveness/tests/fixtures/14-research-feature-explainer.data.json +0 -3
  45. package/plugins/html-effectiveness/tests/fixtures/15-research-concept-explainer.data.json +0 -3
  46. package/plugins/html-effectiveness/tests/fixtures/16-implementation-plan.data.json +0 -3
  47. package/plugins/html-effectiveness/tests/fixtures/17-pr-writeup.data.json +0 -3
  48. package/plugins/html-effectiveness/tests/fixtures/18-editor-triage-board.data.json +0 -3
  49. package/plugins/html-effectiveness/tests/fixtures/19-editor-feature-flags.data.json +0 -3
  50. package/plugins/html-effectiveness/tests/fixtures/20-editor-prompt-tuner.data.json +0 -3
  51. package/plugins/html-effectiveness/tests/fixtures/_canned.data.json +0 -7
  52. package/plugins/html-effectiveness/tests/fixtures/_canned.html.tmpl +0 -7
  53. package/plugins/html-effectiveness/tests/fixtures/_canned.manifest.json +0 -18
  54. package/plugins/html-effectiveness/tests/manifest.test.js +0 -61
  55. package/plugins/html-effectiveness/tests/mustache.test.js +0 -47
  56. package/plugins/html-effectiveness/tests/render.test.js +0 -118
  57. package/plugins/team-superpower/agents/designer.md +0 -50
  58. package/plugins/team-superpower/agents/planner.md +0 -186
  59. package/plugins/team-superpower/agents/qa-engineer.md +0 -47
  60. package/plugins/team-superpower/agents/reviewer.md +0 -110
  61. package/plugins/team-superpower/agents/software-architect.md +0 -45
  62. package/plugins/team-superpower/commands/team-feature-resume.md +0 -185
@@ -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
 
@@ -1,25 +1,36 @@
1
1
  #!/usr/bin/env bash
2
- # team-state.sh — inspect and clean up team-superpower team state.
2
+ # team-state.sh — inspect and clean up team-superpower v5 team state.
3
3
  #
4
4
  # Subcommands:
5
5
  # scan List every known superpower-* team on this machine.
6
6
  # scan <slug> Inspect a single slug; print state for the lead/owner.
7
+ # members <slug> Read team config.json and list current members + roles.
7
8
  # cleanup <slug> Dry-run cleanup; print what would be removed (exit 1).
8
- # cleanup <slug> --force Remove team config, task list, tmux session.
9
+ # cleanup <slug> --force Remove team config, task list, .team-superpower/ artefacts.
9
10
  #
10
11
  # Flags (cleanup only):
11
12
  # --force Apply the cleanup (otherwise dry-run).
12
13
  # --ignore-heartbeat Skip the "lead may still be alive" refusal.
13
14
  # Required when the heartbeat file is < 10 min old.
14
15
  #
16
+ # v5 changes (delta from v4):
17
+ # - Removed: tmux session handling (Agent Teams runs in-process, not in tmux).
18
+ # - Added: `members` subcommand reads ~/.claude/teams/superpower-<slug>/config.json
19
+ # and lists current teammates + roles (single-team lifecycle visibility).
20
+ # - Added: cleanup now removes .team-superpower/ work artefacts (spawn-briefs,
21
+ # static-check logs, handover artefacts the lead created).
22
+ # - Added: scan shows cycle_restart_count from docs/superpowers/sessions/<slug>.cycle
23
+ # if present (for v5's restart-on-stuck telemetry).
24
+ #
15
25
  # What this script preserves (always):
16
- # - docs/superpowers/{specs,plans,reviews} durable artefacts
17
- # - the project-side checkpoint markdown kept; appended with a Cleanup record
26
+ # - docs/superpowers/{specs,plans,reviews,handovers} durable artefacts
27
+ # - the project-side checkpoint markdown kept; appended with a Cleanup record
18
28
  #
19
29
  # What it removes (with --force):
20
- # - ~/.claude/teams/superpower-<slug>/ team config
21
- # - ~/.claude/tasks/superpower-<slug>/ shared task list
22
- # - tmux session "claude-superpower-<slug>" best-effort
30
+ # - ~/.claude/teams/superpower-<slug>/ team config
31
+ # - ~/.claude/tasks/superpower-<slug>/ shared task list
32
+ # - .team-superpower/spawn-briefs/ wave brief files
33
+ # - .team-superpower/static-check-*.log per-task static-check logs
23
34
  #
24
35
  # Idempotent: re-running is safe.
25
36
  #
@@ -37,6 +48,7 @@ TEAMS_DIR="$CLAUDE_HOME/teams"
37
48
  TASKS_DIR="$CLAUDE_HOME/tasks"
38
49
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
39
50
  SESSIONS_DIR="$PROJECT_DIR/docs/superpowers/sessions"
51
+ ARTEFACTS_DIR="$PROJECT_DIR/.team-superpower"
40
52
  HEARTBEAT_TTL_SECONDS=600 # 10 min
41
53
 
42
54
  print_usage() {
@@ -44,13 +56,17 @@ print_usage() {
44
56
  }
45
57
 
46
58
  team_name_for_slug() { printf 'superpower-%s' "$1"; }
47
- tmux_name_for_slug() { printf 'claude-superpower-%s' "$1"; }
48
59
 
49
60
  heartbeat_file_for_slug() {
50
61
  local slug="$1"
51
62
  printf '%s/%s.heartbeat' "$SESSIONS_DIR" "$slug"
52
63
  }
53
64
 
65
+ cycle_file_for_slug() {
66
+ local slug="$1"
67
+ printf '%s/%s.cycle' "$SESSIONS_DIR" "$slug"
68
+ }
69
+
54
70
  checkpoint_for_slug() {
55
71
  local slug="$1"
56
72
  if [ ! -d "$SESSIONS_DIR" ]; then return; fi
@@ -87,9 +103,7 @@ cmd_scan() {
87
103
  echo "team-superpower teams on this machine:"
88
104
  while IFS= read -r s; do
89
105
  [ -z "$s" ] && continue
90
- local team team_dir hb age
91
- team="$(team_name_for_slug "$s")"
92
- team_dir="$TEAMS_DIR/$team"
106
+ local hb age
93
107
  hb="$(heartbeat_file_for_slug "$s")"
94
108
  age="$(heartbeat_age_seconds "$hb")"
95
109
  if [ "$age" -lt 0 ]; then
@@ -101,39 +115,73 @@ cmd_scan() {
101
115
  return 0
102
116
  fi
103
117
 
104
- local team team_dir task_dir hb age ckpt tmux_name
118
+ local team team_dir task_dir hb age ckpt cycle_file restart_count
105
119
  team="$(team_name_for_slug "$slug")"
106
120
  team_dir="$TEAMS_DIR/$team"
107
121
  task_dir="$TASKS_DIR/$team"
108
122
  hb="$(heartbeat_file_for_slug "$slug")"
109
123
  age="$(heartbeat_age_seconds "$hb")"
110
124
  ckpt="$(checkpoint_for_slug "$slug")"
111
- tmux_name="$(tmux_name_for_slug "$slug")"
112
-
113
- printf 'slug: %s\n' "$slug"
114
- printf 'team_name: %s\n' "$team"
115
- printf 'team_config: %s\n' "$team_dir"
116
- if [ -d "$team_dir" ]; then printf 'team_config_state: present\n'; else printf 'team_config_state: absent\n'; fi
117
- printf 'task_list: %s\n' "$task_dir"
118
- if [ -d "$task_dir" ]; then printf 'task_list_state: present\n'; else printf 'task_list_state: absent\n'; fi
119
- printf 'checkpoint: %s\n' "${ckpt:-<none>}"
125
+ cycle_file="$(cycle_file_for_slug "$slug")"
126
+ restart_count=0
127
+ if [ -f "$cycle_file" ]; then
128
+ restart_count="$(head -n1 "$cycle_file" | tr -d '[:space:]')"
129
+ fi
130
+
131
+ printf 'slug: %s\n' "$slug"
132
+ printf 'team_name: %s\n' "$team"
133
+ printf 'team_config: %s\n' "$team_dir"
134
+ if [ -d "$team_dir" ]; then printf 'team_config_state: present\n'; else printf 'team_config_state: absent\n'; fi
135
+ printf 'task_list: %s\n' "$task_dir"
136
+ if [ -d "$task_dir" ]; then printf 'task_list_state: present\n'; else printf 'task_list_state: absent\n'; fi
137
+ printf 'checkpoint: %s\n' "${ckpt:-<none>}"
138
+ printf 'cycle_restart_count: %s\n' "${restart_count:-0}"
120
139
  if [ "$age" -lt 0 ]; then
121
- printf 'heartbeat: <none>\n'
122
- printf 'liveness: unknown (no heartbeat — treat as stale)\n'
140
+ printf 'heartbeat: <none>\n'
141
+ printf 'liveness: unknown (no heartbeat — treat as stale)\n'
123
142
  else
124
- printf 'heartbeat: %s (%ds ago)\n' "$hb" "$age"
143
+ printf 'heartbeat: %s (%ds ago)\n' "$hb" "$age"
125
144
  if [ "$age" -lt "$HEARTBEAT_TTL_SECONDS" ]; then
126
- printf 'liveness: LIKELY ALIVE (heartbeat < %ds)\n' "$HEARTBEAT_TTL_SECONDS"
145
+ printf 'liveness: LIKELY ALIVE (heartbeat < %ds)\n' "$HEARTBEAT_TTL_SECONDS"
127
146
  else
128
- printf 'liveness: stale\n'
147
+ printf 'liveness: stale\n'
129
148
  fi
130
149
  fi
131
- printf 'tmux_session: %s\n' "$tmux_name"
132
- if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$tmux_name" 2>/dev/null; then
133
- printf 'tmux_state: present\n'
150
+ if [ -d "$ARTEFACTS_DIR" ]; then
151
+ local brief_count log_count
152
+ brief_count="$(find "$ARTEFACTS_DIR/spawn-briefs" -maxdepth 1 -type f -name '*.md' 2>/dev/null | wc -l | tr -d ' ')"
153
+ log_count="$(find "$ARTEFACTS_DIR" -maxdepth 1 -type f -name 'static-check-*.log' 2>/dev/null | wc -l | tr -d ' ')"
154
+ printf 'spawn_briefs: %s file(s)\n' "${brief_count:-0}"
155
+ printf 'static_check_logs: %s file(s)\n' "${log_count:-0}"
134
156
  else
135
- printf 'tmux_state: absent\n'
157
+ printf 'artefacts_dir: absent\n'
158
+ fi
159
+ }
160
+
161
+ cmd_members() {
162
+ local slug="${1:-}"
163
+ if [ -z "$slug" ]; then
164
+ echo "usage: team-state.sh members <slug>" >&2
165
+ return 2
166
+ fi
167
+ local team team_dir config
168
+ team="$(team_name_for_slug "$slug")"
169
+ team_dir="$TEAMS_DIR/$team"
170
+ config="$team_dir/config.json"
171
+ if [ ! -f "$config" ]; then
172
+ echo "team config not found: $config" >&2
173
+ return 4
136
174
  fi
175
+ if ! command -v jq >/dev/null 2>&1; then
176
+ echo "jq not installed; raw config follows:" >&2
177
+ cat "$config"
178
+ return 0
179
+ fi
180
+ printf 'team: %s\n' "$team"
181
+ printf 'config: %s\n' "$config"
182
+ printf 'members:\n'
183
+ jq -r '.members[]? | " - " + (.role // "<no-role>") + " (id: " + (.id // "<no-id>") + ", status: " + (.status // "unknown") + ")"' "$config" 2>/dev/null \
184
+ || jq -r '. | tostring' "$config"
137
185
  }
138
186
 
139
187
  cmd_cleanup() {
@@ -153,21 +201,27 @@ cmd_cleanup() {
153
201
  return 2
154
202
  fi
155
203
 
156
- local team team_dir task_dir hb age ckpt tmux_name
204
+ local team team_dir task_dir hb age ckpt
157
205
  team="$(team_name_for_slug "$slug")"
158
206
  team_dir="$TEAMS_DIR/$team"
159
207
  task_dir="$TASKS_DIR/$team"
160
208
  hb="$(heartbeat_file_for_slug "$slug")"
161
209
  age="$(heartbeat_age_seconds "$hb")"
162
210
  ckpt="$(checkpoint_for_slug "$slug")"
163
- tmux_name="$(tmux_name_for_slug "$slug")"
164
211
 
165
212
  # Build the work list.
166
213
  local items=()
167
214
  [ -d "$team_dir" ] && items+=("team_config:$team_dir")
168
215
  [ -d "$task_dir" ] && items+=("task_list:$task_dir")
169
- if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$tmux_name" 2>/dev/null; then
170
- items+=("tmux_session:$tmux_name")
216
+ if [ -d "$ARTEFACTS_DIR/spawn-briefs" ]; then
217
+ local n
218
+ n="$(find "$ARTEFACTS_DIR/spawn-briefs" -maxdepth 1 -type f -name '*.md' 2>/dev/null | wc -l | tr -d ' ')"
219
+ [ "${n:-0}" -gt 0 ] && items+=("spawn_briefs:$ARTEFACTS_DIR/spawn-briefs (${n} files)")
220
+ fi
221
+ if [ -d "$ARTEFACTS_DIR" ]; then
222
+ local n
223
+ n="$(find "$ARTEFACTS_DIR" -maxdepth 1 -type f -name 'static-check-*.log' 2>/dev/null | wc -l | tr -d ' ')"
224
+ [ "${n:-0}" -gt 0 ] && items+=("static_check_logs:$ARTEFACTS_DIR/static-check-*.log (${n} files)")
171
225
  fi
172
226
 
173
227
  if [ ${#items[@]} -eq 0 ]; then
@@ -202,10 +256,24 @@ cmd_cleanup() {
202
256
  echo "removed: $task_dir"
203
257
  removed=$((removed+1))
204
258
  fi
205
- if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$tmux_name" 2>/dev/null; then
206
- tmux kill-session -t "$tmux_name" && echo "killed tmux session: $tmux_name"
259
+ if [ -d "$ARTEFACTS_DIR/spawn-briefs" ]; then
260
+ rm -rf -- "$ARTEFACTS_DIR/spawn-briefs"
261
+ echo "removed: $ARTEFACTS_DIR/spawn-briefs"
207
262
  removed=$((removed+1))
208
263
  fi
264
+ if [ -d "$ARTEFACTS_DIR" ]; then
265
+ local cleared
266
+ cleared="$(find "$ARTEFACTS_DIR" -maxdepth 1 -type f -name 'static-check-*.log' 2>/dev/null | wc -l | tr -d ' ')"
267
+ if [ "${cleared:-0}" -gt 0 ]; then
268
+ find "$ARTEFACTS_DIR" -maxdepth 1 -type f -name 'static-check-*.log' -delete 2>/dev/null || true
269
+ echo "removed: ${cleared} static-check log(s) under $ARTEFACTS_DIR"
270
+ removed=$((removed+1))
271
+ fi
272
+ # If .team-superpower/ is now empty, drop it too.
273
+ if [ -z "$(ls -A "$ARTEFACTS_DIR" 2>/dev/null)" ]; then
274
+ rmdir "$ARTEFACTS_DIR" 2>/dev/null || true
275
+ fi
276
+ fi
209
277
  # Heartbeat is informational; remove it so future scans don't see a stale "alive" signal.
210
278
  if [ -f "$hb" ]; then
211
279
  rm -f -- "$hb"
@@ -219,7 +287,7 @@ cmd_cleanup() {
219
287
  {
220
288
  printf '\n## Cleanup\n'
221
289
  printf -- '- cleaned at: %s\n' "$ts"
222
- printf -- '- removed: %d resource(s) (team_config, task_list, tmux_session as applicable)\n' "$removed"
290
+ printf -- '- removed: %d resource(s) (team_config, task_list, spawn_briefs, static_check_logs as applicable)\n' "$removed"
223
291
  printf -- '- status: cleaned\n'
224
292
  } >> "$ckpt"
225
293
  echo "appended cleanup record to: $ckpt"
@@ -235,6 +303,7 @@ main() {
235
303
  shift || true
236
304
  case "$sub" in
237
305
  scan) cmd_scan "$@" ;;
306
+ members) cmd_members "$@" ;;
238
307
  cleanup) cmd_cleanup "$@" ;;
239
308
  -h|--help|help|"") print_usage; [ -z "$sub" ] && return 2 || return 0 ;;
240
309
  *) echo "unknown subcommand: $sub" >&2; print_usage; return 2 ;;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "tech-graph",
3
- "version": "0.0.18",
3
+ "version": "0.0.23",
4
4
  "description": "Step-by-step wizard for generating technical diagrams as SVG+PNG.",
5
5
  "author": {
6
6
  "name": "steven"
@@ -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
- });
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Code-Approaches Exploration"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Visual Design Exploration"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "PR Code Review Board"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Code Walkthrough"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Design System Sheet"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Component Variant Matrix"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Animation Prototype"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Interaction Prototype"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "Slide Deck"
3
- }
@@ -1,3 +0,0 @@
1
- {
2
- "title": "SVG Illustration Gallery"
3
- }