@drunkcoding/agents-and-skills 0.0.18 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +5 -5
- package/README.md +23 -0
- package/package.json +1 -1
- package/plugins/auto-power/.claude-plugin/plugin.json +1 -1
- package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
- package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
- package/plugins/team-superpower/.claude-plugin/plugin.json +1 -1
- package/plugins/team-superpower/README.md +186 -115
- package/plugins/team-superpower/agents/backend-developer.md +161 -39
- package/plugins/team-superpower/agents/feature-planner.md +66 -0
- package/plugins/team-superpower/agents/frontend-developer.md +163 -34
- package/plugins/team-superpower/agents/orchestrator.md +83 -0
- package/plugins/team-superpower/agents/qc-engineer.md +84 -0
- package/plugins/team-superpower/agents/security-engineer.md +90 -40
- package/plugins/team-superpower/agents/solution-architect.md +80 -0
- package/plugins/team-superpower/agents/team-leader.md +100 -0
- package/plugins/team-superpower/assets/AGENTS.md.template +23 -0
- package/plugins/team-superpower/assets/CLAUDE.md.template +25 -4
- package/plugins/team-superpower/assets/ESCALATION.md +114 -66
- package/plugins/team-superpower/assets/SESSION_README.md +233 -113
- package/plugins/team-superpower/commands/team-feature.md +195 -443
- package/plugins/team-superpower/hooks/task-completed.sh +180 -59
- package/plugins/team-superpower/hooks/task-created.sh +77 -19
- package/plugins/team-superpower/hooks/teammate-idle.sh +118 -13
- package/plugins/team-superpower/scripts/assess-complexity.sh +194 -0
- package/plugins/team-superpower/scripts/detect-stack.sh +39 -0
- package/plugins/team-superpower/scripts/team-state.sh +106 -37
- package/plugins/team-superpower/scripts/wave-collision-check.sh +60 -0
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
- package/plugins/html-effectiveness/tests/_tmp.test.js +0 -58
- package/plugins/html-effectiveness/tests/fixtures/01-exploration-code-approaches.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/02-exploration-visual-designs.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/03-code-review-pr.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/04-code-understanding.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/05-design-system.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/06-component-variants.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/07-prototype-animation.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/08-prototype-interaction.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/09-slide-deck.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/10-svg-illustrations.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/11-status-report.data.json +0 -37
- package/plugins/html-effectiveness/tests/fixtures/12-incident-report.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/13-flowchart-diagram.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/14-research-feature-explainer.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/15-research-concept-explainer.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/16-implementation-plan.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/17-pr-writeup.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/18-editor-triage-board.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/19-editor-feature-flags.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/20-editor-prompt-tuner.data.json +0 -3
- package/plugins/html-effectiveness/tests/fixtures/_canned.data.json +0 -7
- package/plugins/html-effectiveness/tests/fixtures/_canned.html.tmpl +0 -7
- package/plugins/html-effectiveness/tests/fixtures/_canned.manifest.json +0 -18
- package/plugins/html-effectiveness/tests/manifest.test.js +0 -61
- package/plugins/html-effectiveness/tests/mustache.test.js +0 -47
- package/plugins/html-effectiveness/tests/render.test.js +0 -118
- package/plugins/team-superpower/agents/designer.md +0 -50
- package/plugins/team-superpower/agents/planner.md +0 -186
- package/plugins/team-superpower/agents/qa-engineer.md +0 -47
- package/plugins/team-superpower/agents/reviewer.md +0 -110
- package/plugins/team-superpower/agents/software-architect.md +0 -45
- package/plugins/team-superpower/commands/team-feature-resume.md +0 -185
|
@@ -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,
|
|
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}
|
|
17
|
-
# - the project-side checkpoint markdown
|
|
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>/
|
|
21
|
-
# - ~/.claude/tasks/superpower-<slug>/
|
|
22
|
-
# -
|
|
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
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
printf '
|
|
118
|
-
|
|
119
|
-
printf '
|
|
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:
|
|
122
|
-
printf 'liveness:
|
|
140
|
+
printf 'heartbeat: <none>\n'
|
|
141
|
+
printf 'liveness: unknown (no heartbeat — treat as stale)\n'
|
|
123
142
|
else
|
|
124
|
-
printf 'heartbeat:
|
|
143
|
+
printf 'heartbeat: %s (%ds ago)\n' "$hb" "$age"
|
|
125
144
|
if [ "$age" -lt "$HEARTBEAT_TTL_SECONDS" ]; then
|
|
126
|
-
printf 'liveness:
|
|
145
|
+
printf 'liveness: LIKELY ALIVE (heartbeat < %ds)\n' "$HEARTBEAT_TTL_SECONDS"
|
|
127
146
|
else
|
|
128
|
-
printf 'liveness:
|
|
147
|
+
printf 'liveness: stale\n'
|
|
129
148
|
fi
|
|
130
149
|
fi
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 '
|
|
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
|
|
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
|
|
170
|
-
|
|
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
|
|
206
|
-
|
|
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,
|
|
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,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
|
-
});
|