@mmerterden/multi-agent-pipeline 11.5.0 → 12.0.0

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 (69) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/docs/adr/0007-multi-tool-adapter-framework.md +2 -2
  3. package/install/claude.mjs +25 -1
  4. package/install/index.mjs +34 -1
  5. package/package.json +1 -1
  6. package/pipeline/commands/multi-agent/SKILL.md +1 -0
  7. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -0
  8. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -0
  9. package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -0
  10. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -0
  11. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -0
  12. package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -0
  13. package/pipeline/commands/multi-agent/dev/SKILL.md +1 -0
  14. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -0
  15. package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -0
  16. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -0
  17. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -0
  18. package/pipeline/commands/multi-agent/finish/SKILL.md +1 -0
  19. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +29 -3
  20. package/pipeline/commands/multi-agent/help/SKILL.md +3 -2
  21. package/pipeline/commands/multi-agent/issue/SKILL.md +1 -0
  22. package/pipeline/commands/multi-agent/jira/SKILL.md +1 -0
  23. package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
  24. package/pipeline/commands/multi-agent/language/SKILL.md +20 -1
  25. package/pipeline/commands/multi-agent/local/SKILL.md +1 -0
  26. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -0
  27. package/pipeline/commands/multi-agent/log/SKILL.md +1 -0
  28. package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -0
  29. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
  30. package/pipeline/commands/multi-agent/purge/SKILL.md +27 -24
  31. package/pipeline/commands/multi-agent/refactor/SKILL.md +1 -0
  32. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
  33. package/pipeline/commands/multi-agent/review/SKILL.md +1 -0
  34. package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -0
  35. package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -0
  36. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -0
  37. package/pipeline/commands/multi-agent/search/SKILL.md +1 -0
  38. package/pipeline/commands/multi-agent/setup/SKILL.md +1 -0
  39. package/pipeline/commands/multi-agent/stack/SKILL.md +1 -0
  40. package/pipeline/commands/multi-agent/status/SKILL.md +1 -0
  41. package/pipeline/commands/multi-agent/sync/SKILL.md +20 -2
  42. package/pipeline/commands/multi-agent/test/SKILL.md +1 -0
  43. package/pipeline/commands/multi-agent/uninstall/SKILL.md +87 -0
  44. package/pipeline/commands/multi-agent/update/SKILL.md +1 -0
  45. package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -5
  46. package/pipeline/multi-agent-refs/phases/phase-0-init.md +12 -1
  47. package/pipeline/scripts/fixtures/install-layout.tsv +4 -2
  48. package/pipeline/scripts/gc-tmp.sh +67 -8
  49. package/pipeline/scripts/gc-worktrees.sh +140 -0
  50. package/pipeline/scripts/localize-commands.mjs +130 -0
  51. package/pipeline/scripts/prune-logs.sh +100 -15
  52. package/pipeline/scripts/purge.sh +235 -0
  53. package/pipeline/scripts/smoke-description-tr.sh +82 -0
  54. package/pipeline/scripts/smoke-gc-tmp.sh +50 -4
  55. package/pipeline/scripts/smoke-gc-worktrees.sh +125 -0
  56. package/pipeline/scripts/smoke-prune-logs.sh +57 -7
  57. package/pipeline/scripts/smoke-purge.sh +138 -0
  58. package/pipeline/scripts/smoke-update-check.sh +13 -0
  59. package/pipeline/scripts/uninstall.mjs +41 -6
  60. package/pipeline/scripts/update-check.sh +9 -0
  61. package/pipeline/skills/.skill-manifest.json +7 -7
  62. package/pipeline/skills/.skills-index.json +9 -9
  63. package/pipeline/skills/shared/README.md +19 -10
  64. package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -0
  65. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  66. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +74 -0
  67. package/pipeline/skills/skills-index.md +1 -1
  68. package/pipeline/commands/multi-agent/delete/SKILL.md +0 -67
  69. package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +0 -66
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-gc-worktrees.sh - contract of the worktree residue sweeper.
3
+ #
4
+ # Verifies gc-worktrees.sh: dry-run-by-default, orphan-dir removal only with
5
+ # --yes, registered worktrees untouched, gitlink residue unstaged, exclude
6
+ # guard added idempotently, --older-than sparing, non-repo no-op, and flag
7
+ # validation.
8
+
9
+ set -uo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ SCRIPT="$SCRIPT_DIR/gc-worktrees.sh"
13
+
14
+ PASS=0
15
+ FAIL=0
16
+ record_pass() { PASS=$((PASS + 1)); printf ' \342\234\223 %s\n' "$1"; }
17
+ record_fail() { FAIL=$((FAIL + 1)); printf ' \342\234\227 %s\n' "$1"; }
18
+
19
+ printf '\342\206\222 smoke-gc-worktrees: worktree residue sweep contract\n'
20
+
21
+ tmpdir=$(mktemp -d)
22
+ trap 'rm -rf "$tmpdir"' EXIT
23
+
24
+ # Fixture repo: one registered worktree, one orphan dir, one gitlink residue.
25
+ REPO="$tmpdir/repo"
26
+ mkdir -p "$REPO"
27
+ git -C "$REPO" init -q -b main
28
+ git -C "$REPO" config user.email smoke@test
29
+ git -C "$REPO" config user.name smoke
30
+ echo hi > "$REPO/f.txt"
31
+ git -C "$REPO" add f.txt
32
+ git -C "$REPO" commit -qm init
33
+ git -C "$REPO" worktree add -q "$REPO/.worktrees/REG" -b reg-branch
34
+ mkdir -p "$REPO/.worktrees/ORPHAN"
35
+ echo x > "$REPO/.worktrees/ORPHAN/file"
36
+ git -C "$REPO" update-index --add --cacheinfo "160000,$(git -C "$REPO" rev-parse HEAD),.worktrees/GONE"
37
+
38
+ # 1. Script parses
39
+ if bash -n "$SCRIPT" 2>/dev/null; then
40
+ record_pass "gc-worktrees.sh parses (bash -n)"
41
+ else
42
+ record_fail "gc-worktrees.sh has syntax errors"
43
+ fi
44
+
45
+ # 2. Dry-run lists all three findings and deletes nothing
46
+ out=$(bash "$SCRIPT" --repo "$REPO" 2>&1); rc=$?
47
+ if [ "$rc" -eq 0 ] \
48
+ && printf '%s' "$out" | grep -q "would remove orphan worktree dir: .worktrees/ORPHAN" \
49
+ && printf '%s' "$out" | grep -q "would unstage gitlink residue from the index: .worktrees/GONE" \
50
+ && printf '%s' "$out" | grep -q "would add .worktrees/ to .git/info/exclude"; then
51
+ record_pass "dry-run lists orphan dir + gitlink + missing guard"
52
+ else
53
+ record_fail "dry-run should list all three findings (rc=$rc): $out"
54
+ fi
55
+ if [ -d "$REPO/.worktrees/ORPHAN" ] && git -C "$REPO" ls-files -s -- .worktrees | grep -q 160000; then
56
+ record_pass "dry-run changed nothing"
57
+ else
58
+ record_fail "dry-run must not delete or unstage anything"
59
+ fi
60
+
61
+ # 3. --older-than spares the freshly-touched orphan
62
+ out=$(bash "$SCRIPT" --repo "$REPO" --older-than=60 2>&1)
63
+ if printf '%s' "$out" | grep -q "would remove orphan"; then
64
+ record_fail "--older-than=60 should spare the fresh orphan dir"
65
+ else
66
+ record_pass "--older-than spares recently-touched orphan dirs"
67
+ fi
68
+
69
+ # 4. --yes applies all three
70
+ out=$(bash "$SCRIPT" --repo "$REPO" --yes 2>&1); rc=$?
71
+ if [ "$rc" -eq 0 ] && [ ! -d "$REPO/.worktrees/ORPHAN" ]; then
72
+ record_pass "--yes removes the orphan dir"
73
+ else
74
+ record_fail "--yes should remove the orphan dir (rc=$rc): $out"
75
+ fi
76
+ if git -C "$REPO" ls-files -s -- .worktrees | grep -q 160000; then
77
+ record_fail "--yes should unstage the gitlink residue"
78
+ else
79
+ record_pass "--yes unstages the gitlink residue"
80
+ fi
81
+ if [ "$(grep -cxF '.worktrees/' "$REPO/.git/info/exclude" 2>/dev/null)" = "1" ]; then
82
+ record_pass "--yes adds the exclude guard exactly once"
83
+ else
84
+ record_fail "exclude guard missing or duplicated"
85
+ fi
86
+
87
+ # 5. Registered worktree untouched
88
+ if [ -d "$REPO/.worktrees/REG" ] && git -C "$REPO" worktree list | grep -q "REG"; then
89
+ record_pass "registered worktree left untouched"
90
+ else
91
+ record_fail "registered worktree must never be touched"
92
+ fi
93
+
94
+ # 6. Re-run -> nothing to do, guard not duplicated
95
+ out=$(bash "$SCRIPT" --repo "$REPO" 2>&1); rc=$?
96
+ if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "nothing to do"; then
97
+ record_pass "clean repo -> nothing to do (exit 0)"
98
+ else
99
+ record_fail "clean repo should be a no-op (rc=$rc): $out"
100
+ fi
101
+ bash "$SCRIPT" --repo "$REPO" --yes >/dev/null 2>&1
102
+ if [ "$(grep -cxF '.worktrees/' "$REPO/.git/info/exclude")" = "1" ]; then
103
+ record_pass "exclude guard is idempotent across --yes reruns"
104
+ else
105
+ record_fail "exclude guard duplicated on rerun"
106
+ fi
107
+
108
+ # 7. Non-repo -> silent no-op exit 0
109
+ out=$(bash "$SCRIPT" --repo "$tmpdir" 2>&1); rc=$?
110
+ if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q "not a git work tree"; then
111
+ record_pass "non-repo -> exit 0 no-op"
112
+ else
113
+ record_fail "non-repo should exit 0 with a no-op message (rc=$rc)"
114
+ fi
115
+
116
+ # 8. Unknown flag -> usage error exit 2
117
+ bash "$SCRIPT" --definitely-not-a-flag >/dev/null 2>&1; rc=$?
118
+ if [ "$rc" -eq 2 ]; then
119
+ record_pass "unknown flag -> exit 2"
120
+ else
121
+ record_fail "unknown flag should exit 2 (got $rc)"
122
+ fi
123
+
124
+ printf '\n\342\225\220\342\225\220 smoke-gc-worktrees: %d passed, %d failed \342\225\220\342\225\220\n' "$PASS" "$FAIL"
125
+ [ "$FAIL" -eq 0 ]
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env bash
2
2
  # smoke-prune-logs.sh - prune-logs.sh removes per-task log dirs, honors the
3
- # project/task/age filters, is dry-run by default, and NEVER deletes the audit
4
- # trail or metrics corpus.
3
+ # project/task/age filters, is dry-run by default, NEVER deletes the audit
4
+ # trail or metrics corpus (even under adversarial dir names), refuses unsafe
5
+ # log roots, and spares active task dirs.
5
6
  #
6
7
  # Exit 0 = all pass, 1 = any failure.
7
8
 
@@ -40,7 +41,7 @@ seed() {
40
41
 
41
42
  echo "→ 1. dry-run lists task dirs and deletes nothing"
42
43
  seed
43
- out=$(PRUNE_LOG_ROOT="$LR" bash "$PL" 2>&1)
44
+ out=$(PRUNE_LOG_ROOT="$LR" PRUNE_GRACE_MIN=0 bash "$PL" 2>&1)
44
45
  echo "$out" | grep -q "DRY-RUN" && pass "dry-run banner shown" || fail "dry-run banner missing"
45
46
  [ -f "$LR/my-ios-app/PROJ-1/agent-state.json" ] && pass "dry-run kept task dirs" || fail "dry-run deleted a task dir"
46
47
  echo "$out" | grep -qi "preserved" && pass "dry-run states audit/metrics are preserved" || fail "no preservation note"
@@ -48,7 +49,7 @@ echo "$out" | grep -qi "preserved" && pass "dry-run states audit/metrics are pre
48
49
  echo ""
49
50
  echo "→ 2. --yes deletes task dirs but preserves audit + metrics + .counter"
50
51
  seed
51
- PRUNE_LOG_ROOT="$LR" bash "$PL" --yes >/dev/null 2>&1
52
+ PRUNE_LOG_ROOT="$LR" PRUNE_GRACE_MIN=0 bash "$PL" --yes >/dev/null 2>&1
52
53
  [ -f "$LR/audit.jsonl" ] && pass "audit.jsonl preserved" || fail "audit.jsonl was deleted"
53
54
  [ -f "$LR/metrics.jsonl" ] && pass "metrics.jsonl preserved" || fail "metrics.jsonl was deleted"
54
55
  [ -f "$LR/my-ios-app/.counter" ] && pass ".counter preserved" || fail ".counter was deleted"
@@ -58,21 +59,21 @@ PRUNE_LOG_ROOT="$LR" bash "$PL" --yes >/dev/null 2>&1
58
59
  echo ""
59
60
  echo "→ 3. --project scopes deletion to one project"
60
61
  seed
61
- PRUNE_LOG_ROOT="$LR" bash "$PL" --project=my-ios-app --yes >/dev/null 2>&1
62
+ PRUNE_LOG_ROOT="$LR" PRUNE_GRACE_MIN=0 bash "$PL" --project=my-ios-app --yes >/dev/null 2>&1
62
63
  [ ! -e "$LR/my-ios-app/PROJ-1" ] && pass "my-ios-app task removed" || fail "my-ios-app task survived"
63
64
  [ -f "$LR/other/PROJ-9/agent-log.md" ] && pass "other project untouched" || fail "other project was deleted"
64
65
 
65
66
  echo ""
66
67
  echo "→ 4. --task scopes deletion to one task dir"
67
68
  seed
68
- PRUNE_LOG_ROOT="$LR" bash "$PL" --task=PROJ-1 --yes >/dev/null 2>&1
69
+ PRUNE_LOG_ROOT="$LR" PRUNE_GRACE_MIN=0 bash "$PL" --task=PROJ-1 --yes >/dev/null 2>&1
69
70
  [ ! -e "$LR/my-ios-app/PROJ-1" ] && pass "PROJ-1 removed" || fail "PROJ-1 survived"
70
71
  [ -f "$LR/my-ios-app/PROJ-2/tracker-state.json" ] && pass "PROJ-2 untouched" || fail "PROJ-2 was deleted"
71
72
 
72
73
  echo ""
73
74
  echo "→ 5. --older-than excludes fresh task dirs"
74
75
  seed
75
- out=$(PRUNE_LOG_ROOT="$LR" bash "$PL" --older-than=1 2>&1)
76
+ out=$(PRUNE_LOG_ROOT="$LR" PRUNE_GRACE_MIN=0 bash "$PL" --older-than=1 2>&1)
76
77
  echo "$out" | grep -q "nothing" && pass "freshly-created dirs excluded by --older-than=1" || fail "--older-than did not exclude fresh dirs"
77
78
 
78
79
  echo ""
@@ -82,6 +83,55 @@ PRUNE_LOG_ROOT="$LR" bash "$PL" --bogus >/dev/null 2>&1
82
83
  PRUNE_LOG_ROOT="/nonexistent-$$-dir" bash "$PL" >/dev/null 2>&1
83
84
  [ "$?" = "0" ] && pass "missing log root exits 0 (no-op)" || fail "missing log root did not exit 0"
84
85
 
86
+ echo ""
87
+ echo "→ 7. adversarial dirs named like preserved files survive --yes"
88
+ rm -rf "${LR:?}"/*
89
+ mkdir -p "$LR/audit.jsonl" "$LR/proj/metrics.jsonl" "$LR/proj/metrics-summary.md" "$LR/proj/.counter" "$LR/proj/PROJ-5"
90
+ echo '{}' >"$LR/audit.jsonl/agent-state.json"
91
+ echo '{}' >"$LR/proj/metrics.jsonl/agent-log.md"
92
+ echo '{}' >"$LR/proj/metrics-summary.md/tracker-state.json"
93
+ echo '{}' >"$LR/proj/.counter/agent-state.json"
94
+ echo '{}' >"$LR/proj/PROJ-5/agent-state.json"
95
+ PRUNE_LOG_ROOT="$LR" PRUNE_GRACE_MIN=0 bash "$PL" --yes >/dev/null 2>&1
96
+ [ -d "$LR/audit.jsonl" ] && pass "dir named audit.jsonl spared" || fail "dir named audit.jsonl deleted"
97
+ [ -d "$LR/proj/metrics.jsonl" ] && pass "dir named metrics.jsonl spared" || fail "dir named metrics.jsonl deleted"
98
+ [ -d "$LR/proj/metrics-summary.md" ] && pass "dir named metrics-summary.md spared" || fail "dir named metrics-summary.md deleted"
99
+ [ -d "$LR/proj/.counter" ] && pass "dir named .counter spared" || fail "dir named .counter deleted"
100
+ [ ! -e "$LR/proj/PROJ-5" ] && pass "normal task dir beside them still removed" || fail "normal task dir survived"
101
+
102
+ echo ""
103
+ echo "→ 8. unsafe log roots are refused (exit 2, dry-run only)"
104
+ PRUNE_LOG_ROOT="$HOME" bash "$PL" >/dev/null 2>&1
105
+ [ "$?" = "2" ] && pass "\$HOME as log root refused" || fail "\$HOME as log root not refused"
106
+ GITROOT="$(mktemp -d)"
107
+ mkdir -p "$GITROOT/.git"
108
+ PRUNE_LOG_ROOT="$GITROOT" bash "$PL" --yes >/dev/null 2>&1
109
+ rc=$?
110
+ rm -rf "$GITROOT"
111
+ [ "$rc" = "2" ] && pass "git work tree as log root refused" || fail "git work tree as log root not refused"
112
+
113
+ echo ""
114
+ echo "→ 9. default grace spares freshly-touched (active) task dirs"
115
+ seed
116
+ out=$(PRUNE_LOG_ROOT="$LR" bash "$PL" --yes 2>&1)
117
+ rc=$?
118
+ [ "$rc" = "0" ] && pass "grace run exits 0" || fail "grace run exited $rc"
119
+ [ -f "$LR/my-ios-app/PROJ-1/agent-state.json" ] && pass "active task dir spared by default grace" || fail "default grace deleted an active task dir"
120
+ echo "$out" | grep -qi "spared" && pass "output mentions spared active dirs" || fail "no spared-active note in output"
121
+ PRUNE_LOG_ROOT="$LR" bash "$PL" --task=PROJ-1 --yes >/dev/null 2>&1
122
+ [ ! -e "$LR/my-ios-app/PROJ-1" ] && pass "explicit --task bypasses the grace" || fail "--task did not bypass the grace"
123
+
124
+ echo ""
125
+ echo "→ 10. traversal-shaped --task/--project values are rejected"
126
+ PRUNE_LOG_ROOT="$LR" bash "$PL" "--task=../evil" >/dev/null 2>&1
127
+ [ "$?" = "2" ] && pass "--task=../evil exits 2" || fail "--task=../evil accepted"
128
+ PRUNE_LOG_ROOT="$LR" bash "$PL" "--task=.counter" >/dev/null 2>&1
129
+ [ "$?" = "2" ] && pass "--task=.counter (leading dot) exits 2" || fail "--task=.counter accepted"
130
+ PRUNE_LOG_ROOT="$LR" bash "$PL" "--project=a/b" >/dev/null 2>&1
131
+ [ "$?" = "2" ] && pass "--project=a/b exits 2" || fail "--project=a/b accepted"
132
+ PRUNE_LOG_ROOT="$LR" bash "$PL" "--task=" >/dev/null 2>&1
133
+ [ "$?" = "2" ] && pass "empty --task exits 2" || fail "empty --task accepted"
134
+
85
135
  echo ""
86
136
  echo "══ prune-logs smoke: $PASS passed, $FAIL failed ══"
87
137
  [ "$FAIL" -eq 0 ]
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-purge.sh - purge.sh wipes .worktrees/ worktrees (registered and
3
+ # orphaned), their local branches, and the project task counter; is dry-run
4
+ # by default; preserves the main worktree, protected branches, and the
5
+ # audit/metrics corpus; and never follows symlinks out of .worktrees/.
6
+ #
7
+ # Exit 0 = all pass, 1 = any failure.
8
+
9
+ set -uo pipefail
10
+
11
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
12
+ PG="$ROOT/pipeline/scripts/purge.sh"
13
+ PASS=0
14
+ FAIL=0
15
+ pass() {
16
+ PASS=$((PASS + 1))
17
+ echo " ✓ $1"
18
+ }
19
+ fail() {
20
+ FAIL=$((FAIL + 1))
21
+ echo " ✗ $1"
22
+ }
23
+ [ -f "$PG" ] || {
24
+ echo "FAIL: purge.sh missing" >&2
25
+ exit 1
26
+ }
27
+
28
+ SB="$(cd "$(mktemp -d)" && pwd -P)"
29
+ trap 'rm -rf "$SB"' EXIT
30
+
31
+ REPO="$SB/repo"
32
+ LR="$SB/logs"
33
+ VICTIM="$SB/victim"
34
+
35
+ seed() {
36
+ rm -rf "$REPO" "$LR" "$VICTIM"
37
+ mkdir -p "$REPO" "$LR/repo" "$VICTIM"
38
+ echo keep >"$VICTIM/keep.txt"
39
+ git -C "$REPO" init -q -b main
40
+ git -C "$REPO" -c user.email=t@t.t -c user.name=t commit -q --allow-empty -m init
41
+ git -C "$REPO" worktree add -q -b feature/PROJ-1-x "$REPO/.worktrees/PROJ-1" >/dev/null 2>&1
42
+ git -C "$REPO" worktree add -q -b bugfix/PROJ-2-y "$REPO/.worktrees/PROJ-2" >/dev/null 2>&1
43
+ mkdir -p "$REPO/.worktrees/PROJ-9" # orphan: dir git no longer registers
44
+ echo '{}' >"$REPO/.worktrees/PROJ-9/agent-state.json"
45
+ ln -s "$VICTIM" "$REPO/.worktrees/evil-link" # symlink escape attempt
46
+ echo '7' >"$LR/repo/.counter"
47
+ echo audit >"$LR/audit.jsonl"
48
+ echo metric >"$LR/metrics.jsonl"
49
+ }
50
+
51
+ has_branch() {
52
+ git -C "$REPO" show-ref --verify -q "refs/heads/$1"
53
+ }
54
+
55
+ echo "→ 1. dry-run lists everything and deletes nothing"
56
+ seed
57
+ out=$(PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$REPO" 2>&1)
58
+ rc=$?
59
+ [ "$rc" = "0" ] && pass "dry-run exits 0" || fail "dry-run exited $rc"
60
+ echo "$out" | grep -q "DRY-RUN" && pass "dry-run banner shown" || fail "dry-run banner missing"
61
+ echo "$out" | grep -q "PROJ-1" && pass "worktree PROJ-1 listed" || fail "worktree PROJ-1 not listed"
62
+ echo "$out" | grep -q "feature/PROJ-1-x" && pass "branch listed" || fail "branch not listed"
63
+ echo "$out" | grep -q ".counter" && pass "counter listed" || fail "counter not listed"
64
+ [ -d "$REPO/.worktrees/PROJ-1" ] && pass "dry-run kept worktrees" || fail "dry-run deleted a worktree"
65
+ [ -f "$LR/repo/.counter" ] && pass "dry-run kept the counter" || fail "dry-run deleted the counter"
66
+ has_branch feature/PROJ-1-x && pass "dry-run kept branches" || fail "dry-run deleted a branch"
67
+
68
+ echo ""
69
+ echo "→ 2. --yes wipes worktrees + branches + counter, preserves the rest"
70
+ seed
71
+ PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$REPO" --yes >/dev/null 2>&1
72
+ rc=$?
73
+ [ "$rc" = "0" ] && pass "--yes exits 0" || fail "--yes exited $rc"
74
+ [ ! -e "$REPO/.worktrees/PROJ-1" ] && pass "registered worktree PROJ-1 removed" || fail "PROJ-1 survived"
75
+ [ ! -e "$REPO/.worktrees/PROJ-2" ] && pass "registered worktree PROJ-2 removed" || fail "PROJ-2 survived"
76
+ [ ! -e "$REPO/.worktrees/PROJ-9" ] && pass "orphan worktree dir removed" || fail "orphan PROJ-9 survived"
77
+ ! has_branch feature/PROJ-1-x && pass "branch feature/PROJ-1-x deleted" || fail "feature/PROJ-1-x survived"
78
+ ! has_branch bugfix/PROJ-2-y && pass "branch bugfix/PROJ-2-y deleted" || fail "bugfix/PROJ-2-y survived"
79
+ has_branch main && pass "main branch preserved" || fail "main branch was deleted"
80
+ git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 && pass "main worktree still a git repo" || fail "main worktree destroyed"
81
+ wt_count=$(git -C "$REPO" worktree list 2>/dev/null | wc -l | tr -d ' ')
82
+ [ "$wt_count" = "1" ] && pass "only the main worktree remains registered" || fail "expected 1 registered worktree, got $wt_count"
83
+ [ ! -e "$LR/repo/.counter" ] && pass "task counter removed" || fail "task counter survived"
84
+ [ -f "$LR/audit.jsonl" ] && pass "audit.jsonl preserved" || fail "audit.jsonl was deleted"
85
+ [ -f "$LR/metrics.jsonl" ] && pass "metrics.jsonl preserved" || fail "metrics.jsonl was deleted"
86
+ [ -f "$VICTIM/keep.txt" ] && pass "symlink target outside repo untouched" || fail "symlink target was deleted"
87
+
88
+ echo ""
89
+ echo "→ 3. protected branch carried by a worktree survives, worktree still removed"
90
+ seed
91
+ git -C "$REPO" worktree add -q -b develop "$REPO/.worktrees/DEV-1" >/dev/null 2>&1
92
+ PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$REPO" --yes >/dev/null 2>&1
93
+ [ ! -e "$REPO/.worktrees/DEV-1" ] && pass "develop worktree removed" || fail "develop worktree survived"
94
+ has_branch develop && pass "develop branch preserved" || fail "develop branch was deleted"
95
+
96
+ echo ""
97
+ echo "→ 4. clean repo and non-repo are no-ops (exit 0)"
98
+ CLEAN="$SB/clean"
99
+ mkdir -p "$CLEAN"
100
+ git -C "$CLEAN" init -q -b main
101
+ out=$(PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$CLEAN" 2>&1)
102
+ rc=$?
103
+ { [ "$rc" = "0" ] && echo "$out" | grep -q "nothing to do"; } && pass "clean repo is a no-op" || fail "clean repo not a clean no-op (rc=$rc)"
104
+ PLAIN="$SB/plain"
105
+ mkdir -p "$PLAIN"
106
+ out=$(bash "$PG" --repo "$PLAIN" 2>&1)
107
+ rc=$?
108
+ { [ "$rc" = "0" ] && echo "$out" | grep -q "not a git work tree"; } && pass "non-repo dir is a no-op" || fail "non-repo dir not a clean no-op (rc=$rc)"
109
+
110
+ echo ""
111
+ echo "→ 5. usage errors exit 2"
112
+ bash "$PG" --bogus >/dev/null 2>&1
113
+ [ "$?" = "2" ] && pass "unknown argument exits 2" || fail "unknown argument did not exit 2"
114
+ bash "$PG" --repo >/dev/null 2>&1
115
+ [ "$?" = "2" ] && pass "--repo without a path exits 2" || fail "--repo without a path did not exit 2"
116
+
117
+ echo ""
118
+ echo "→ 6. --delete-remote without a remote warns but does not fail"
119
+ seed
120
+ out=$(PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$REPO" --delete-remote 2>&1)
121
+ echo "$out" | grep -q "remote branch origin/feature/PROJ-1-x" && pass "dry-run lists remote branch deletion" || fail "dry-run missing remote branch line"
122
+ out=$(PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$REPO" --delete-remote --yes 2>&1)
123
+ rc=$?
124
+ [ "$rc" = "0" ] && pass "--delete-remote --yes exits 0 without a remote" || fail "--delete-remote --yes exited $rc"
125
+ echo "$out" | grep -qi "warning" && pass "missing remote surfaces a warning" || fail "no warning for missing remote"
126
+
127
+ echo ""
128
+ echo "→ 7. purge from inside a linked worktree still anchors on the main repo"
129
+ seed
130
+ out=$(PURGE_LOG_ROOT="$LR" bash "$PG" --repo "$REPO/.worktrees/PROJ-1" --yes 2>&1)
131
+ rc=$?
132
+ [ "$rc" = "0" ] && pass "run from linked worktree exits 0" || fail "run from linked worktree exited $rc"
133
+ [ ! -e "$REPO/.worktrees/PROJ-1" ] && pass "worktrees removed when invoked from inside one" || fail "worktrees survived"
134
+ git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 && pass "main repo intact" || fail "main repo destroyed"
135
+
136
+ echo ""
137
+ echo "══ purge smoke: $PASS passed, $FAIL failed ══"
138
+ [ "$FAIL" -eq 0 ]
@@ -67,6 +67,19 @@ else
67
67
  record_fail "local-ahead should be silent (got '$out', rc=$rc)"
68
68
  fi
69
69
 
70
+ # 3b2. No --local, no repo clone -> falls back to the install-time version
71
+ # marker (npx-only installs have no clone to read a version from)
72
+ marker_home="$tmpdir/marker-home"
73
+ mkdir -p "$marker_home/.claude"
74
+ printf '10.0.0\n' > "$marker_home/.claude/.pipeline-version"
75
+ printf '%s|99.0.0\n' "$now" > "$CACHE"
76
+ out=$(HOME="$marker_home" UPDATE_CHECK_CACHE="$CACHE" bash "$SCRIPT" 2>/dev/null); rc=$?
77
+ if [ "$rc" -eq 0 ] && [ "$out" = "10.0.0|99.0.0" ]; then
78
+ record_pass "no clone -> .pipeline-version marker fallback works"
79
+ else
80
+ record_fail "marker fallback should print '10.0.0|99.0.0' (got '$out', rc=$rc)"
81
+ fi
82
+
70
83
  # 3c. Offline + stale cache -> silent exit 0 (advisory: never blocks)
71
84
  printf '0|10.0.0\n' > "$CACHE"
72
85
  out=$(UPDATE_CHECK_CACHE="$CACHE" http_proxy="http://127.0.0.1:1" https_proxy="http://127.0.0.1:1" \
@@ -12,21 +12,26 @@
12
12
  * node uninstall.mjs # interactive, removes from all installed targets
13
13
  * node uninstall.mjs --yes # skip prompts, remove from all
14
14
  * node uninstall.mjs --claude # only Claude Code
15
+ * node uninstall.mjs --all-data # ALSO remove pipeline settings + logs/state/metrics
15
16
  * node uninstall.mjs --cursor --target=/path/to/repo # legacy adapter-file cleanup
16
17
  * node uninstall.mjs --dry-run # report what would be removed, change nothing
17
18
  *
18
19
  * Targeted by:
19
- * - `/multi-agent:delete` slash command (Claude Code)
20
- * - `multi-agent-delete` skill (Copilot CLI)
20
+ * - `/multi-agent:uninstall` slash command (Claude Code)
21
+ * - `multi-agent-uninstall` skill (Copilot CLI)
21
22
  * - `npx @mmerterden/multi-agent-pipeline uninstall` (direct CLI)
22
23
  *
23
- * Always preserved:
24
- * - $HOME/.claude/CLAUDE.md (user customizations)
25
- * - $HOME/.claude/multi-agent-preferences.json
24
+ * Always preserved (both modes):
26
25
  * - Personal access tokens in the OS credential store
26
+ * - $HOME/.claude/CLAUDE.md (user customizations)
27
+ * - $HOME/.claude/rules/ (user-owned; installed write-if-missing)
27
28
  * - User content outside the `<!-- multi-agent-pipeline:begin/end -->` markers
28
29
  * in shared rules files (copilot-instructions.md)
29
30
  *
31
+ * Preserved by default, removed with --all-data:
32
+ * - $HOME/.claude/multi-agent-preferences.json (pipeline settings)
33
+ * - $HOME/.claude/logs/multi-agent/ (task logs, state, metrics, audit trail)
34
+ *
30
35
  * @module pipeline/scripts/runtime/uninstall
31
36
  */
32
37
 
@@ -86,6 +91,9 @@ const forCodex = flags.includes("--codex") || flags.includes("--all") || !explic
86
91
 
87
92
  const dryRun = flags.includes("--dry-run");
88
93
  const skipPrompt = flags.includes("--yes") || flags.includes("-y");
94
+ // Full cleanup: also remove pipeline settings + the multi-agent log root
95
+ // (task logs, state, metrics, audit). Tokens, CLAUDE.md and rules/ stay.
96
+ const allData = flags.includes("--all-data");
89
97
 
90
98
  const adapterTarget = (() => {
91
99
  const raw = flags.find((f) => f.startsWith("--target="));
@@ -312,11 +320,17 @@ async function main() {
312
320
  if (forAntigravity) console.log(` - Antigravity (${adapterTarget}/.agent/rules + .agent/workflows + AGENTS.md block + .agent/mcp_config.json)`);
313
321
  if (forCodex) console.log(" - OpenAI Codex CLI (~/.codex/prompts/multi-agent.md + AGENTS.md block + config.toml mcp block)");
314
322
  console.log("");
323
+ if (allData) {
324
+ console.log(" FULL CLEANUP (--all-data) also removes:");
325
+ console.log(" - ~/.claude/multi-agent-preferences.json (pipeline settings)");
326
+ console.log(" - ~/.claude/logs/multi-agent/ (task logs, state, metrics, audit trail)");
327
+ console.log("");
328
+ }
315
329
  console.log(" PRESERVED (never touched):");
316
330
  console.log(" - Personal access tokens in OS credential store (Keychain / Credential Manager / libsecret)");
317
331
  console.log(" - ~/.claude/CLAUDE.md (your customizations)");
318
332
  console.log(" - ~/.claude/rules/ (user-owned; installed write-if-missing, never overwritten)");
319
- console.log(" - ~/.claude/multi-agent-preferences.json (your settings)");
333
+ if (!allData) console.log(" - ~/.claude/multi-agent-preferences.json (your settings)");
320
334
  console.log(" - Your own content in copilot-instructions.md above the pipeline section");
321
335
  console.log("");
322
336
 
@@ -335,6 +349,7 @@ async function main() {
335
349
  rmIfExists(join(CLAUDE, "multi-agent-refs"));
336
350
  rmIfExists(join(CLAUDE, "schemas"));
337
351
  rmIfExists(join(CLAUDE, "lib"));
352
+ rmIfExists(join(CLAUDE, ".pipeline-version"));
338
353
  // NOTE: ~/.claude/rules/ is USER-OWNED. install lays baseline rules down
339
354
  // write-if-missing and NEVER overwrites them (install/claude.mjs
340
355
  // installRules, skipExisting) so users can evolve their global rules
@@ -363,6 +378,7 @@ async function main() {
363
378
  // Pipeline-managed trees the installer lays down alongside scripts/.
364
379
  rmIfExists(join(COP, "schemas"));
365
380
  rmIfExists(join(COP, "lib"));
381
+ rmIfExists(join(COP, ".pipeline-version"));
366
382
  const skills = join(COP, "skills");
367
383
  let n = rmMatchingDirs(skills, (name) => name === "multi-agent" || name.startsWith("multi-agent-"));
368
384
  n += rmMatchingDirs(skills, (name) => name === "figma-ios" || name === "figma-android" || name === "figma-common" || name === "figma-to-component");
@@ -377,6 +393,16 @@ async function main() {
377
393
  stripManagedBlock(join(COP, "copilot-instructions.md"));
378
394
  }
379
395
 
396
+ // Full cleanup: pipeline settings + the multi-agent log root. Both are
397
+ // pipeline-generated data (not user-authored like CLAUDE.md / rules), so
398
+ // --all-data removes them; the default run leaves them in place.
399
+ if (allData && HOME) {
400
+ console.log("");
401
+ console.log(" [Full cleanup] Removing pipeline settings + logs...");
402
+ rmIfExists(join(HOME, ".claude", "multi-agent-preferences.json"));
403
+ rmIfExists(join(HOME, ".claude", "logs", "multi-agent"));
404
+ }
405
+
380
406
  // Legacy adapter-file cleanup (adapters removed in v10.7.0). These blocks
381
407
  // delete files a pre-v10.7.0 install generated; they never touch user files
382
408
  // outside the multi-agent-* namespace / managed markers.
@@ -423,6 +449,15 @@ async function main() {
423
449
 
424
450
  console.log("");
425
451
  console.log(" ✓ Pipeline removed.");
452
+ if (!allData) {
453
+ console.log("");
454
+ console.log(" Kept your pipeline settings + logs. To remove those too, re-run with --all-data.");
455
+ } else {
456
+ console.log("");
457
+ console.log(" Full cleanup done. Still on disk (user-owned, remove by hand if you want a bare system):");
458
+ console.log(" - ~/.claude/CLAUDE.md");
459
+ console.log(" - ~/.claude/rules/");
460
+ }
426
461
  console.log("");
427
462
  console.log(" Personal access tokens were NOT touched. Inspect with:");
428
463
  console.log(" macOS: security find-generic-password -s \"<service>\" -w");
@@ -46,6 +46,15 @@ if [ -z "$LOCAL_VERSION" ]; then
46
46
  fi
47
47
  done
48
48
  fi
49
+ # npx-only installs have no repo clone; the installer stamps the version here.
50
+ if [ -z "$LOCAL_VERSION" ]; then
51
+ for marker in "$HOME/.claude/.pipeline-version" "$HOME/.copilot/.pipeline-version"; do
52
+ if [ -f "$marker" ]; then
53
+ LOCAL_VERSION=$(head -1 "$marker" 2>/dev/null | tr -d '[:space:]')
54
+ [ -n "$LOCAL_VERSION" ] && break
55
+ fi
56
+ done
57
+ fi
49
58
  [ -z "$LOCAL_VERSION" ] && exit 0 # cannot determine local version -> silent no-op
50
59
 
51
60
  now=$(date +%s)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-07-10T13:33:19Z",
3
+ "generatedAt": "2026-07-20T07:34:26Z",
4
4
  "skillCount": 189,
5
5
  "entries": [
6
6
  {
@@ -35,10 +35,6 @@
35
35
  "path": "shared/core/multi-agent-create-jira/SKILL.md",
36
36
  "sha256": "40caf0489ad8322cd00d792a244e772d4ddb6ccfd7c3807729d6b17590e7f81c"
37
37
  },
38
- {
39
- "path": "shared/core/multi-agent-delete/SKILL.md",
40
- "sha256": "aa48d1e0fe111b9dc1b681025f16880410d47266f12f522df1835857e6cb9551"
41
- },
42
38
  {
43
39
  "path": "shared/core/multi-agent-dev-autopilot/SKILL.md",
44
40
  "sha256": "82b1cf4a4bbd84fcfef54fcdbcdefa27f2747df6d4fac5c86b42e861013c6d58"
@@ -153,19 +149,23 @@
153
149
  },
154
150
  {
155
151
  "path": "shared/core/multi-agent-sync/SKILL.md",
156
- "sha256": "28a057b30ec642f5cc1f22c9c50834aae280e6abaf58194b36d85bde717cfb14"
152
+ "sha256": "de38486056c99a599d93ae5166a2071875331a7d77f1e34f66d8b72657a73c2f"
157
153
  },
158
154
  {
159
155
  "path": "shared/core/multi-agent-test/SKILL.md",
160
156
  "sha256": "f90c34f71ea6cd5cccbfa128597eaaa221a0ca5f62924eb9cfb6b1ed2d07de04"
161
157
  },
158
+ {
159
+ "path": "shared/core/multi-agent-uninstall/SKILL.md",
160
+ "sha256": "775bcaf9248659986c3c5ef1c4f07d3d85784bd10c73cb33c47648bdfc8d834c"
161
+ },
162
162
  {
163
163
  "path": "shared/core/multi-agent-update/SKILL.md",
164
164
  "sha256": "4f31addb21ab5ec31a1288dc4ee56f102d0936473a964f8a6f4075a9c44889cd"
165
165
  },
166
166
  {
167
167
  "path": "shared/core/multi-agent/SKILL.md",
168
- "sha256": "758646b660047bb4595528a57153afda0e1b331c70ad8c04bbd18066fc4bb336"
168
+ "sha256": "fe03eb27cae0489178cc327b3ca370e998638b6de08d4caa8c755dd53f45a30e"
169
169
  },
170
170
  {
171
171
  "path": "shared/external/accessibility-compliance-accessibility-audit/SKILL.md",
@@ -794,15 +794,6 @@
794
794
  "triggerPaths": [],
795
795
  "relativePath": "shared/core/multi-agent-create-jira/SKILL.md"
796
796
  },
797
- {
798
- "name": "multi-agent-delete",
799
- "description": "Uninstall the pipeline from Claude Code + Copilot CLI. Keychain access tokens are left untouched. Asks for double confirmation.",
800
- "platform": null,
801
- "group": "core",
802
- "triggerKeywords": [],
803
- "triggerPaths": [],
804
- "relativePath": "shared/core/multi-agent-delete/SKILL.md"
805
- },
806
797
  {
807
798
  "name": "multi-agent-dev",
808
799
  "description": "Fast development mode: Init → Dev (Opus) → Test → Commit → Report. Analysis, planning, and review phases are skipped.",
@@ -1073,6 +1064,15 @@
1073
1064
  "triggerPaths": [],
1074
1065
  "relativePath": "shared/core/multi-agent-test/SKILL.md"
1075
1066
  },
1067
+ {
1068
+ "name": "multi-agent-uninstall",
1069
+ "description": "Uninstall the pipeline from Claude Code + Copilot CLI. Keychain access tokens are always left untouched; --all-data also clears pipeline settings and logs. Asks for double confirmation.",
1070
+ "platform": null,
1071
+ "group": "core",
1072
+ "triggerKeywords": [],
1073
+ "triggerPaths": [],
1074
+ "relativePath": "shared/core/multi-agent-uninstall/SKILL.md"
1075
+ },
1076
1076
  {
1077
1077
  "name": "multi-agent-update",
1078
1078
  "description": "Update the pipeline to the latest version: git pull, install, migrate.",