@drunkcoding/agents-and-skills 0.0.7 → 0.0.9
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 +18 -2
- package/README.md +1 -0
- package/package.json +1 -1
- package/plugins/html-effectiveness/.claude-plugin/plugin.json +4 -1
- package/plugins/html-effectiveness/commands/html-report.md +13 -0
- package/plugins/team-superpower/.claude-plugin/plugin.json +30 -0
- package/plugins/team-superpower/README.md +150 -0
- package/plugins/team-superpower/agents/designer.md +31 -0
- package/plugins/team-superpower/agents/implementer.md +46 -0
- package/plugins/team-superpower/agents/planner.md +42 -0
- package/plugins/team-superpower/agents/reviewer.md +47 -0
- package/plugins/team-superpower/assets/ESCALATION.md +47 -0
- package/plugins/team-superpower/assets/SESSION_README.md +98 -0
- package/plugins/team-superpower/commands/team-cleanup.md +70 -0
- package/plugins/team-superpower/commands/team-feature-resume.md +111 -0
- package/plugins/team-superpower/commands/team-feature.md +190 -0
- package/plugins/team-superpower/hooks/hooks.json +28 -0
- package/plugins/team-superpower/hooks/task-completed.sh +88 -0
- package/plugins/team-superpower/hooks/task-created.sh +43 -0
- package/plugins/team-superpower/hooks/teammate-idle.sh +45 -0
- package/plugins/team-superpower/scripts/team-state.sh +244 -0
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# team-state.sh — inspect and clean up team-superpower team state.
|
|
3
|
+
#
|
|
4
|
+
# Subcommands:
|
|
5
|
+
# scan List every known superpower-* team on this machine.
|
|
6
|
+
# scan <slug> Inspect a single slug; print state for the lead/owner.
|
|
7
|
+
# cleanup <slug> Dry-run cleanup; print what would be removed (exit 1).
|
|
8
|
+
# cleanup <slug> --force Remove team config, task list, tmux session.
|
|
9
|
+
#
|
|
10
|
+
# Flags (cleanup only):
|
|
11
|
+
# --force Apply the cleanup (otherwise dry-run).
|
|
12
|
+
# --ignore-heartbeat Skip the "lead may still be alive" refusal.
|
|
13
|
+
# Required when the heartbeat file is < 10 min old.
|
|
14
|
+
#
|
|
15
|
+
# 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
|
|
18
|
+
#
|
|
19
|
+
# 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
|
|
23
|
+
#
|
|
24
|
+
# Idempotent: re-running is safe.
|
|
25
|
+
#
|
|
26
|
+
# Exit codes:
|
|
27
|
+
# 0 success
|
|
28
|
+
# 1 dry-run completed with items to remove (caller should re-run with --force)
|
|
29
|
+
# 2 bad arguments
|
|
30
|
+
# 3 heartbeat indicates a live lead; refuse without --ignore-heartbeat
|
|
31
|
+
# 4 nothing to clean up (cleanup) / no teams found (scan)
|
|
32
|
+
|
|
33
|
+
set -euo pipefail
|
|
34
|
+
|
|
35
|
+
CLAUDE_HOME="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
36
|
+
TEAMS_DIR="$CLAUDE_HOME/teams"
|
|
37
|
+
TASKS_DIR="$CLAUDE_HOME/tasks"
|
|
38
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
39
|
+
SESSIONS_DIR="$PROJECT_DIR/docs/superpowers/sessions"
|
|
40
|
+
HEARTBEAT_TTL_SECONDS=600 # 10 min
|
|
41
|
+
|
|
42
|
+
print_usage() {
|
|
43
|
+
sed -n '1,/^set -euo pipefail/{/^set -euo pipefail/d;s/^# \{0,1\}//;p;}' "$0"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
team_name_for_slug() { printf 'superpower-%s' "$1"; }
|
|
47
|
+
tmux_name_for_slug() { printf 'claude-superpower-%s' "$1"; }
|
|
48
|
+
|
|
49
|
+
heartbeat_file_for_slug() {
|
|
50
|
+
local slug="$1"
|
|
51
|
+
printf '%s/%s.heartbeat' "$SESSIONS_DIR" "$slug"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
checkpoint_for_slug() {
|
|
55
|
+
local slug="$1"
|
|
56
|
+
if [ ! -d "$SESSIONS_DIR" ]; then return; fi
|
|
57
|
+
local match
|
|
58
|
+
match="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name "*-$slug.md" 2>/dev/null | sort | tail -n1)"
|
|
59
|
+
printf '%s' "$match"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
heartbeat_age_seconds() {
|
|
63
|
+
local hb="$1"
|
|
64
|
+
if [ ! -f "$hb" ]; then echo "-1"; return; fi
|
|
65
|
+
local mtime now
|
|
66
|
+
mtime="$(stat -c %Y "$hb" 2>/dev/null || stat -f %m "$hb" 2>/dev/null || echo 0)"
|
|
67
|
+
now="$(date +%s)"
|
|
68
|
+
echo $(( now - mtime ))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
list_all_team_slugs() {
|
|
72
|
+
[ -d "$TEAMS_DIR" ] || return 0
|
|
73
|
+
find "$TEAMS_DIR" -maxdepth 1 -mindepth 1 -type d -name 'superpower-*' 2>/dev/null \
|
|
74
|
+
| sed 's|.*/superpower-||' | sort
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
cmd_scan() {
|
|
78
|
+
local slug="${1:-}"
|
|
79
|
+
|
|
80
|
+
if [ -z "$slug" ]; then
|
|
81
|
+
local slugs
|
|
82
|
+
slugs="$(list_all_team_slugs)"
|
|
83
|
+
if [ -z "$slugs" ]; then
|
|
84
|
+
echo "No team-superpower teams found under $TEAMS_DIR"
|
|
85
|
+
return 4
|
|
86
|
+
fi
|
|
87
|
+
echo "team-superpower teams on this machine:"
|
|
88
|
+
while IFS= read -r s; do
|
|
89
|
+
[ -z "$s" ] && continue
|
|
90
|
+
local team team_dir hb age
|
|
91
|
+
team="$(team_name_for_slug "$s")"
|
|
92
|
+
team_dir="$TEAMS_DIR/$team"
|
|
93
|
+
hb="$(heartbeat_file_for_slug "$s")"
|
|
94
|
+
age="$(heartbeat_age_seconds "$hb")"
|
|
95
|
+
if [ "$age" -lt 0 ]; then
|
|
96
|
+
printf ' %s\t(no heartbeat)\n' "$s"
|
|
97
|
+
else
|
|
98
|
+
printf ' %s\theartbeat: %ds ago\n' "$s" "$age"
|
|
99
|
+
fi
|
|
100
|
+
done <<< "$slugs"
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
local team team_dir task_dir hb age ckpt tmux_name
|
|
105
|
+
team="$(team_name_for_slug "$slug")"
|
|
106
|
+
team_dir="$TEAMS_DIR/$team"
|
|
107
|
+
task_dir="$TASKS_DIR/$team"
|
|
108
|
+
hb="$(heartbeat_file_for_slug "$slug")"
|
|
109
|
+
age="$(heartbeat_age_seconds "$hb")"
|
|
110
|
+
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>}"
|
|
120
|
+
if [ "$age" -lt 0 ]; then
|
|
121
|
+
printf 'heartbeat: <none>\n'
|
|
122
|
+
printf 'liveness: unknown (no heartbeat — treat as stale)\n'
|
|
123
|
+
else
|
|
124
|
+
printf 'heartbeat: %s (%ds ago)\n' "$hb" "$age"
|
|
125
|
+
if [ "$age" -lt "$HEARTBEAT_TTL_SECONDS" ]; then
|
|
126
|
+
printf 'liveness: LIKELY ALIVE (heartbeat < %ds)\n' "$HEARTBEAT_TTL_SECONDS"
|
|
127
|
+
else
|
|
128
|
+
printf 'liveness: stale\n'
|
|
129
|
+
fi
|
|
130
|
+
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'
|
|
134
|
+
else
|
|
135
|
+
printf 'tmux_state: absent\n'
|
|
136
|
+
fi
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
cmd_cleanup() {
|
|
140
|
+
local slug="${1:-}"
|
|
141
|
+
shift || true
|
|
142
|
+
local force=0
|
|
143
|
+
local ignore_hb=0
|
|
144
|
+
for arg in "$@"; do
|
|
145
|
+
case "$arg" in
|
|
146
|
+
--force) force=1 ;;
|
|
147
|
+
--ignore-heartbeat) ignore_hb=1 ;;
|
|
148
|
+
*) echo "unknown flag: $arg" >&2; return 2 ;;
|
|
149
|
+
esac
|
|
150
|
+
done
|
|
151
|
+
if [ -z "$slug" ]; then
|
|
152
|
+
echo "usage: team-state.sh cleanup <slug> [--force] [--ignore-heartbeat]" >&2
|
|
153
|
+
return 2
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
local team team_dir task_dir hb age ckpt tmux_name
|
|
157
|
+
team="$(team_name_for_slug "$slug")"
|
|
158
|
+
team_dir="$TEAMS_DIR/$team"
|
|
159
|
+
task_dir="$TASKS_DIR/$team"
|
|
160
|
+
hb="$(heartbeat_file_for_slug "$slug")"
|
|
161
|
+
age="$(heartbeat_age_seconds "$hb")"
|
|
162
|
+
ckpt="$(checkpoint_for_slug "$slug")"
|
|
163
|
+
tmux_name="$(tmux_name_for_slug "$slug")"
|
|
164
|
+
|
|
165
|
+
# Build the work list.
|
|
166
|
+
local items=()
|
|
167
|
+
[ -d "$team_dir" ] && items+=("team_config:$team_dir")
|
|
168
|
+
[ -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")
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
if [ ${#items[@]} -eq 0 ]; then
|
|
174
|
+
echo "Nothing to clean up for slug '$slug'."
|
|
175
|
+
[ -n "$ckpt" ] && echo "Checkpoint untouched: $ckpt"
|
|
176
|
+
return 4
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# Heartbeat refusal (only when --force, since dry-run is harmless).
|
|
180
|
+
if [ "$force" -eq 1 ] && [ "$age" -ge 0 ] && [ "$age" -lt "$HEARTBEAT_TTL_SECONDS" ] && [ "$ignore_hb" -ne 1 ]; then
|
|
181
|
+
echo "REFUSED: heartbeat at $hb is ${age}s old (< ${HEARTBEAT_TTL_SECONDS}s)." >&2
|
|
182
|
+
echo " A lead may still be alive. Re-run with --ignore-heartbeat to override." >&2
|
|
183
|
+
return 3
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if [ "$force" -ne 1 ]; then
|
|
187
|
+
echo "Dry-run for slug '$slug' (pass --force to apply):"
|
|
188
|
+
for it in "${items[@]}"; do printf ' would remove: %s\n' "$it"; done
|
|
189
|
+
[ -n "$ckpt" ] && echo " would append cleanup record to: $ckpt"
|
|
190
|
+
return 1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Apply.
|
|
194
|
+
local removed=0
|
|
195
|
+
if [ -d "$team_dir" ]; then
|
|
196
|
+
rm -rf -- "$team_dir"
|
|
197
|
+
echo "removed: $team_dir"
|
|
198
|
+
removed=$((removed+1))
|
|
199
|
+
fi
|
|
200
|
+
if [ -d "$task_dir" ]; then
|
|
201
|
+
rm -rf -- "$task_dir"
|
|
202
|
+
echo "removed: $task_dir"
|
|
203
|
+
removed=$((removed+1))
|
|
204
|
+
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"
|
|
207
|
+
removed=$((removed+1))
|
|
208
|
+
fi
|
|
209
|
+
# Heartbeat is informational; remove it so future scans don't see a stale "alive" signal.
|
|
210
|
+
if [ -f "$hb" ]; then
|
|
211
|
+
rm -f -- "$hb"
|
|
212
|
+
echo "removed heartbeat: $hb"
|
|
213
|
+
fi
|
|
214
|
+
|
|
215
|
+
if [ -n "$ckpt" ] && [ -f "$ckpt" ]; then
|
|
216
|
+
local ts
|
|
217
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
218
|
+
if ! grep -q '^## Cleanup' "$ckpt"; then
|
|
219
|
+
{
|
|
220
|
+
printf '\n## Cleanup\n'
|
|
221
|
+
printf -- '- cleaned at: %s\n' "$ts"
|
|
222
|
+
printf -- '- removed: %d resource(s) (team_config, task_list, tmux_session as applicable)\n' "$removed"
|
|
223
|
+
printf -- '- status: cleaned\n'
|
|
224
|
+
} >> "$ckpt"
|
|
225
|
+
echo "appended cleanup record to: $ckpt"
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
echo "Cleanup complete for $slug."
|
|
230
|
+
return 0
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
main() {
|
|
234
|
+
local sub="${1:-}"
|
|
235
|
+
shift || true
|
|
236
|
+
case "$sub" in
|
|
237
|
+
scan) cmd_scan "$@" ;;
|
|
238
|
+
cleanup) cmd_cleanup "$@" ;;
|
|
239
|
+
-h|--help|help|"") print_usage; [ -z "$sub" ] && return 2 || return 0 ;;
|
|
240
|
+
*) echo "unknown subcommand: $sub" >&2; print_usage; return 2 ;;
|
|
241
|
+
esac
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
main "$@"
|