@drunkcoding/agents-and-skills 0.0.8 → 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.
@@ -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 "$@"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tech-graph",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Step-by-step wizard for generating technical diagrams as SVG+PNG.",
5
5
  "author": {
6
6
  "name": "steven"