@drunkcoding/agents-and-skills 0.0.25 → 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.
- package/.claude-plugin/marketplace.json +14 -33
- package/README.md +1 -0
- package/package.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-share/.claude-plugin/plugin.json +17 -0
- package/plugins/team-share/README.md +30 -0
- package/plugins/team-share/agents/team-share.md +140 -0
- package/plugins/team-share/commands/team-share.md +4 -0
- package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
- package/plugins/auto-power/.claude-plugin/plugin.json +0 -17
- package/plugins/auto-power/README.md +0 -80
- package/plugins/auto-power/assets/CHECKPOINT_SCHEMA.md +0 -69
- package/plugins/auto-power/assets/ESCALATION_TEMPLATE.md +0 -67
- package/plugins/auto-power/commands/auto-power-resume.md +0 -32
- package/plugins/auto-power/commands/auto-power.md +0 -46
- package/plugins/auto-power/skills/auto-power-runtime/SKILL.md +0 -220
- package/plugins/team-superpower/.claude-plugin/plugin.json +0 -21
- package/plugins/team-superpower/README.md +0 -294
- package/plugins/team-superpower/agents/backend-developer.md +0 -221
- package/plugins/team-superpower/agents/feature-planner.md +0 -66
- package/plugins/team-superpower/agents/frontend-developer.md +0 -242
- package/plugins/team-superpower/agents/qc-engineer.md +0 -84
- package/plugins/team-superpower/agents/security-engineer.md +0 -175
- package/plugins/team-superpower/agents/solution-architect.md +0 -80
- package/plugins/team-superpower/agents/team-leader.md +0 -100
- package/plugins/team-superpower/assets/AGENTS.md.template +0 -23
- package/plugins/team-superpower/assets/CLAUDE.md.template +0 -117
- package/plugins/team-superpower/assets/ESCALATION.md +0 -142
- package/plugins/team-superpower/assets/SESSION_README.md +0 -337
- package/plugins/team-superpower/commands/team-cleanup.md +0 -70
- package/plugins/team-superpower/commands/team-feature.md +0 -319
- package/plugins/team-superpower/hooks/hooks.json +0 -25
- package/plugins/team-superpower/hooks/task-completed.sh +0 -254
- package/plugins/team-superpower/hooks/task-created.sh +0 -174
- package/plugins/team-superpower/hooks/teammate-idle.sh +0 -142
- package/plugins/team-superpower/scripts/assess-complexity.sh +0 -194
- package/plugins/team-superpower/scripts/detect-stack.sh +0 -473
- package/plugins/team-superpower/scripts/parse-claudemd.sh +0 -194
- package/plugins/team-superpower/scripts/team-state.sh +0 -313
- package/plugins/team-superpower/scripts/wave-collision-check.sh +0 -60
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# team-state.sh — inspect and clean up team-superpower v5 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
|
-
# members <slug> Read team config.json and list current members + roles.
|
|
8
|
-
# cleanup <slug> Dry-run cleanup; print what would be removed (exit 1).
|
|
9
|
-
# cleanup <slug> --force Remove team config, task list, .team-superpower/ artefacts.
|
|
10
|
-
#
|
|
11
|
-
# Flags (cleanup only):
|
|
12
|
-
# --force Apply the cleanup (otherwise dry-run).
|
|
13
|
-
# --ignore-heartbeat Skip the "lead may still be alive" refusal.
|
|
14
|
-
# Required when the heartbeat file is < 10 min old.
|
|
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
|
-
#
|
|
25
|
-
# What this script preserves (always):
|
|
26
|
-
# - docs/superpowers/{specs,plans,reviews,handovers} durable artefacts
|
|
27
|
-
# - the project-side checkpoint markdown kept; appended with a Cleanup record
|
|
28
|
-
#
|
|
29
|
-
# What it removes (with --force):
|
|
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
|
|
34
|
-
#
|
|
35
|
-
# Idempotent: re-running is safe.
|
|
36
|
-
#
|
|
37
|
-
# Exit codes:
|
|
38
|
-
# 0 success
|
|
39
|
-
# 1 dry-run completed with items to remove (caller should re-run with --force)
|
|
40
|
-
# 2 bad arguments
|
|
41
|
-
# 3 heartbeat indicates a live lead; refuse without --ignore-heartbeat
|
|
42
|
-
# 4 nothing to clean up (cleanup only; scan is read-only and always exits 0)
|
|
43
|
-
|
|
44
|
-
set -euo pipefail
|
|
45
|
-
|
|
46
|
-
CLAUDE_HOME="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
47
|
-
TEAMS_DIR="$CLAUDE_HOME/teams"
|
|
48
|
-
TASKS_DIR="$CLAUDE_HOME/tasks"
|
|
49
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
50
|
-
SESSIONS_DIR="$PROJECT_DIR/docs/superpowers/sessions"
|
|
51
|
-
ARTEFACTS_DIR="$PROJECT_DIR/.team-superpower"
|
|
52
|
-
HEARTBEAT_TTL_SECONDS=600 # 10 min
|
|
53
|
-
|
|
54
|
-
print_usage() {
|
|
55
|
-
sed -n '1,/^set -euo pipefail/{/^set -euo pipefail/d;s/^# \{0,1\}//;p;}' "$0"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
team_name_for_slug() { printf 'superpower-%s' "$1"; }
|
|
59
|
-
|
|
60
|
-
heartbeat_file_for_slug() {
|
|
61
|
-
local slug="$1"
|
|
62
|
-
printf '%s/%s.heartbeat' "$SESSIONS_DIR" "$slug"
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
cycle_file_for_slug() {
|
|
66
|
-
local slug="$1"
|
|
67
|
-
printf '%s/%s.cycle' "$SESSIONS_DIR" "$slug"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
checkpoint_for_slug() {
|
|
71
|
-
local slug="$1"
|
|
72
|
-
if [ ! -d "$SESSIONS_DIR" ]; then return; fi
|
|
73
|
-
local match
|
|
74
|
-
match="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name "*-$slug.md" 2>/dev/null | sort | tail -n1)"
|
|
75
|
-
printf '%s' "$match"
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
heartbeat_age_seconds() {
|
|
79
|
-
local hb="$1"
|
|
80
|
-
if [ ! -f "$hb" ]; then echo "-1"; return; fi
|
|
81
|
-
local mtime now
|
|
82
|
-
mtime="$(stat -c %Y "$hb" 2>/dev/null || stat -f %m "$hb" 2>/dev/null || echo 0)"
|
|
83
|
-
now="$(date +%s)"
|
|
84
|
-
echo $(( now - mtime ))
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
list_all_team_slugs() {
|
|
88
|
-
[ -d "$TEAMS_DIR" ] || return 0
|
|
89
|
-
find "$TEAMS_DIR" -maxdepth 1 -mindepth 1 -type d -name 'superpower-*' 2>/dev/null \
|
|
90
|
-
| sed 's|.*/superpower-||' | sort
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
cmd_scan() {
|
|
94
|
-
local slug="${1:-}"
|
|
95
|
-
|
|
96
|
-
if [ -z "$slug" ]; then
|
|
97
|
-
local slugs
|
|
98
|
-
slugs="$(list_all_team_slugs)"
|
|
99
|
-
if [ -z "$slugs" ]; then
|
|
100
|
-
echo "No team-superpower teams found under $TEAMS_DIR"
|
|
101
|
-
return 0
|
|
102
|
-
fi
|
|
103
|
-
echo "team-superpower teams on this machine:"
|
|
104
|
-
while IFS= read -r s; do
|
|
105
|
-
[ -z "$s" ] && continue
|
|
106
|
-
local hb age
|
|
107
|
-
hb="$(heartbeat_file_for_slug "$s")"
|
|
108
|
-
age="$(heartbeat_age_seconds "$hb")"
|
|
109
|
-
if [ "$age" -lt 0 ]; then
|
|
110
|
-
printf ' %s\t(no heartbeat)\n' "$s"
|
|
111
|
-
else
|
|
112
|
-
printf ' %s\theartbeat: %ds ago\n' "$s" "$age"
|
|
113
|
-
fi
|
|
114
|
-
done <<< "$slugs"
|
|
115
|
-
return 0
|
|
116
|
-
fi
|
|
117
|
-
|
|
118
|
-
local team team_dir task_dir hb age ckpt cycle_file restart_count
|
|
119
|
-
team="$(team_name_for_slug "$slug")"
|
|
120
|
-
team_dir="$TEAMS_DIR/$team"
|
|
121
|
-
task_dir="$TASKS_DIR/$team"
|
|
122
|
-
hb="$(heartbeat_file_for_slug "$slug")"
|
|
123
|
-
age="$(heartbeat_age_seconds "$hb")"
|
|
124
|
-
ckpt="$(checkpoint_for_slug "$slug")"
|
|
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}"
|
|
139
|
-
if [ "$age" -lt 0 ]; then
|
|
140
|
-
printf 'heartbeat: <none>\n'
|
|
141
|
-
printf 'liveness: unknown (no heartbeat — treat as stale)\n'
|
|
142
|
-
else
|
|
143
|
-
printf 'heartbeat: %s (%ds ago)\n' "$hb" "$age"
|
|
144
|
-
if [ "$age" -lt "$HEARTBEAT_TTL_SECONDS" ]; then
|
|
145
|
-
printf 'liveness: LIKELY ALIVE (heartbeat < %ds)\n' "$HEARTBEAT_TTL_SECONDS"
|
|
146
|
-
else
|
|
147
|
-
printf 'liveness: stale\n'
|
|
148
|
-
fi
|
|
149
|
-
fi
|
|
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}"
|
|
156
|
-
else
|
|
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
|
|
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"
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
cmd_cleanup() {
|
|
188
|
-
local slug="${1:-}"
|
|
189
|
-
shift || true
|
|
190
|
-
local force=0
|
|
191
|
-
local ignore_hb=0
|
|
192
|
-
for arg in "$@"; do
|
|
193
|
-
case "$arg" in
|
|
194
|
-
--force) force=1 ;;
|
|
195
|
-
--ignore-heartbeat) ignore_hb=1 ;;
|
|
196
|
-
*) echo "unknown flag: $arg" >&2; return 2 ;;
|
|
197
|
-
esac
|
|
198
|
-
done
|
|
199
|
-
if [ -z "$slug" ]; then
|
|
200
|
-
echo "usage: team-state.sh cleanup <slug> [--force] [--ignore-heartbeat]" >&2
|
|
201
|
-
return 2
|
|
202
|
-
fi
|
|
203
|
-
|
|
204
|
-
local team team_dir task_dir hb age ckpt
|
|
205
|
-
team="$(team_name_for_slug "$slug")"
|
|
206
|
-
team_dir="$TEAMS_DIR/$team"
|
|
207
|
-
task_dir="$TASKS_DIR/$team"
|
|
208
|
-
hb="$(heartbeat_file_for_slug "$slug")"
|
|
209
|
-
age="$(heartbeat_age_seconds "$hb")"
|
|
210
|
-
ckpt="$(checkpoint_for_slug "$slug")"
|
|
211
|
-
|
|
212
|
-
# Build the work list.
|
|
213
|
-
local items=()
|
|
214
|
-
[ -d "$team_dir" ] && items+=("team_config:$team_dir")
|
|
215
|
-
[ -d "$task_dir" ] && items+=("task_list:$task_dir")
|
|
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)")
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
if [ ${#items[@]} -eq 0 ]; then
|
|
228
|
-
echo "Nothing to clean up for slug '$slug'."
|
|
229
|
-
[ -n "$ckpt" ] && echo "Checkpoint untouched: $ckpt"
|
|
230
|
-
return 4
|
|
231
|
-
fi
|
|
232
|
-
|
|
233
|
-
# Heartbeat refusal (only when --force, since dry-run is harmless).
|
|
234
|
-
if [ "$force" -eq 1 ] && [ "$age" -ge 0 ] && [ "$age" -lt "$HEARTBEAT_TTL_SECONDS" ] && [ "$ignore_hb" -ne 1 ]; then
|
|
235
|
-
echo "REFUSED: heartbeat at $hb is ${age}s old (< ${HEARTBEAT_TTL_SECONDS}s)." >&2
|
|
236
|
-
echo " A lead may still be alive. Re-run with --ignore-heartbeat to override." >&2
|
|
237
|
-
return 3
|
|
238
|
-
fi
|
|
239
|
-
|
|
240
|
-
if [ "$force" -ne 1 ]; then
|
|
241
|
-
echo "Dry-run for slug '$slug' (pass --force to apply):"
|
|
242
|
-
for it in "${items[@]}"; do printf ' would remove: %s\n' "$it"; done
|
|
243
|
-
[ -n "$ckpt" ] && echo " would append cleanup record to: $ckpt"
|
|
244
|
-
return 1
|
|
245
|
-
fi
|
|
246
|
-
|
|
247
|
-
# Apply.
|
|
248
|
-
local removed=0
|
|
249
|
-
if [ -d "$team_dir" ]; then
|
|
250
|
-
rm -rf -- "$team_dir"
|
|
251
|
-
echo "removed: $team_dir"
|
|
252
|
-
removed=$((removed+1))
|
|
253
|
-
fi
|
|
254
|
-
if [ -d "$task_dir" ]; then
|
|
255
|
-
rm -rf -- "$task_dir"
|
|
256
|
-
echo "removed: $task_dir"
|
|
257
|
-
removed=$((removed+1))
|
|
258
|
-
fi
|
|
259
|
-
if [ -d "$ARTEFACTS_DIR/spawn-briefs" ]; then
|
|
260
|
-
rm -rf -- "$ARTEFACTS_DIR/spawn-briefs"
|
|
261
|
-
echo "removed: $ARTEFACTS_DIR/spawn-briefs"
|
|
262
|
-
removed=$((removed+1))
|
|
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
|
|
277
|
-
# Heartbeat is informational; remove it so future scans don't see a stale "alive" signal.
|
|
278
|
-
if [ -f "$hb" ]; then
|
|
279
|
-
rm -f -- "$hb"
|
|
280
|
-
echo "removed heartbeat: $hb"
|
|
281
|
-
fi
|
|
282
|
-
|
|
283
|
-
if [ -n "$ckpt" ] && [ -f "$ckpt" ]; then
|
|
284
|
-
local ts
|
|
285
|
-
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
286
|
-
if ! grep -q '^## Cleanup' "$ckpt"; then
|
|
287
|
-
{
|
|
288
|
-
printf '\n## Cleanup\n'
|
|
289
|
-
printf -- '- cleaned at: %s\n' "$ts"
|
|
290
|
-
printf -- '- removed: %d resource(s) (team_config, task_list, spawn_briefs, static_check_logs as applicable)\n' "$removed"
|
|
291
|
-
printf -- '- status: cleaned\n'
|
|
292
|
-
} >> "$ckpt"
|
|
293
|
-
echo "appended cleanup record to: $ckpt"
|
|
294
|
-
fi
|
|
295
|
-
fi
|
|
296
|
-
|
|
297
|
-
echo "Cleanup complete for $slug."
|
|
298
|
-
return 0
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
main() {
|
|
302
|
-
local sub="${1:-}"
|
|
303
|
-
shift || true
|
|
304
|
-
case "$sub" in
|
|
305
|
-
scan) cmd_scan "$@" ;;
|
|
306
|
-
members) cmd_members "$@" ;;
|
|
307
|
-
cleanup) cmd_cleanup "$@" ;;
|
|
308
|
-
-h|--help|help|"") print_usage; [ -z "$sub" ] && return 2 || return 0 ;;
|
|
309
|
-
*) echo "unknown subcommand: $sub" >&2; print_usage; return 2 ;;
|
|
310
|
-
esac
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
main "$@"
|
|
@@ -1,60 +0,0 @@
|
|
|
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
|