@mmerterden/multi-agent-pipeline 11.5.0 → 12.1.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 (82) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/docs/adr/0007-multi-tool-adapter-framework.md +2 -2
  3. package/docs/adr/0008-installer-modularization-and-secret-leak-defense.md +2 -0
  4. package/docs/adr/README.md +1 -0
  5. package/docs/features.md +2 -2
  6. package/install/claude.mjs +51 -1
  7. package/install/index.mjs +34 -1
  8. package/package.json +2 -2
  9. package/pipeline/commands/multi-agent/SKILL.md +1 -0
  10. package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -0
  11. package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -0
  12. package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -0
  13. package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -0
  14. package/pipeline/commands/multi-agent/channels/SKILL.md +1 -0
  15. package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -0
  16. package/pipeline/commands/multi-agent/dev/SKILL.md +1 -0
  17. package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -0
  18. package/pipeline/commands/multi-agent/dev-local/SKILL.md +1 -0
  19. package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -0
  20. package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -0
  21. package/pipeline/commands/multi-agent/finish/SKILL.md +1 -0
  22. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +29 -3
  23. package/pipeline/commands/multi-agent/help/SKILL.md +3 -2
  24. package/pipeline/commands/multi-agent/issue/SKILL.md +1 -0
  25. package/pipeline/commands/multi-agent/jira/SKILL.md +1 -0
  26. package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
  27. package/pipeline/commands/multi-agent/language/SKILL.md +20 -1
  28. package/pipeline/commands/multi-agent/local/SKILL.md +1 -0
  29. package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -0
  30. package/pipeline/commands/multi-agent/log/SKILL.md +1 -0
  31. package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -0
  32. package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
  33. package/pipeline/commands/multi-agent/purge/SKILL.md +27 -24
  34. package/pipeline/commands/multi-agent/refactor/SKILL.md +1 -0
  35. package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
  36. package/pipeline/commands/multi-agent/review/SKILL.md +1 -0
  37. package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -0
  38. package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -0
  39. package/pipeline/commands/multi-agent/scan/SKILL.md +1 -0
  40. package/pipeline/commands/multi-agent/search/SKILL.md +1 -0
  41. package/pipeline/commands/multi-agent/setup/SKILL.md +1 -0
  42. package/pipeline/commands/multi-agent/stack/SKILL.md +1 -0
  43. package/pipeline/commands/multi-agent/status/SKILL.md +1 -0
  44. package/pipeline/commands/multi-agent/sync/SKILL.md +20 -2
  45. package/pipeline/commands/multi-agent/test/SKILL.md +1 -0
  46. package/pipeline/commands/multi-agent/uninstall/SKILL.md +87 -0
  47. package/pipeline/commands/multi-agent/update/SKILL.md +1 -0
  48. package/pipeline/lib/multi-repo-pipeline.sh +6 -1
  49. package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -5
  50. package/pipeline/multi-agent-refs/phases/phase-0-init.md +12 -1
  51. package/pipeline/scripts/_cost.mjs +35 -0
  52. package/pipeline/scripts/agent-guard.py +8 -3
  53. package/pipeline/scripts/cost-budget-check.mjs +2 -1
  54. package/pipeline/scripts/cost-lib.sh +43 -0
  55. package/pipeline/scripts/fixtures/install-layout.tsv +4 -2
  56. package/pipeline/scripts/gc-tmp.sh +67 -8
  57. package/pipeline/scripts/gc-worktrees.sh +140 -0
  58. package/pipeline/scripts/localize-commands.mjs +130 -0
  59. package/pipeline/scripts/phase-tracker.sh +10 -19
  60. package/pipeline/scripts/prune-logs.sh +100 -15
  61. package/pipeline/scripts/purge.sh +235 -0
  62. package/pipeline/scripts/render-agent-log-cost.sh +7 -13
  63. package/pipeline/scripts/render-cost-summary.sh +4 -7
  64. package/pipeline/scripts/run-aggregator.mjs +2 -3
  65. package/pipeline/scripts/smoke-description-tr.sh +82 -0
  66. package/pipeline/scripts/smoke-gc-tmp.sh +50 -4
  67. package/pipeline/scripts/smoke-gc-worktrees.sh +125 -0
  68. package/pipeline/scripts/smoke-prune-logs.sh +57 -7
  69. package/pipeline/scripts/smoke-purge.sh +138 -0
  70. package/pipeline/scripts/smoke-source-parity.sh +85 -0
  71. package/pipeline/scripts/smoke-update-check.sh +13 -0
  72. package/pipeline/scripts/uninstall.mjs +41 -6
  73. package/pipeline/scripts/update-check.sh +9 -0
  74. package/pipeline/skills/.skill-manifest.json +7 -7
  75. package/pipeline/skills/.skills-index.json +9 -9
  76. package/pipeline/skills/shared/README.md +19 -10
  77. package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -0
  78. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
  79. package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +74 -0
  80. package/pipeline/skills/skills-index.md +1 -1
  81. package/pipeline/commands/multi-agent/delete/SKILL.md +0 -67
  82. package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +0 -66
@@ -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 ]
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-source-parity.sh - guard the two divergent SOURCE trees of the same
3
+ # feature set from drifting out of alignment.
4
+ #
5
+ # The pipeline ships each sub-command twice, in parallel trees:
6
+ # 1. pipeline/commands/multi-agent/<name>/ (Claude slash commands)
7
+ # 2. pipeline/skills/shared/core/multi-agent-<name>/ (Copilot skills)
8
+ # Both are meant to cover the SAME feature SET. A feature present in one tree
9
+ # but absent from the other is the single biggest maintainability liability
10
+ # here: users on one CLI silently lose a capability the other CLI advertises.
11
+ #
12
+ # This is a STRUCTURAL set-parity check only. It auto-discovers feature names
13
+ # from the directory listings (nothing is hardcoded), strips the
14
+ # 'multi-agent-' prefix from the skills tree, and FAILS if the symmetric
15
+ # difference of the two name sets is non-empty. It deliberately does NOT diff
16
+ # prose / behavior content between a matched command and its skill - content
17
+ # drift is a separate concern (see smoke-sync-parity.sh / sync-parity-check).
18
+
19
+ set -uo pipefail
20
+
21
+ REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
22
+ CMDS_DIR="$REPO_ROOT/pipeline/commands/multi-agent"
23
+ SKILLS_DIR="$REPO_ROOT/pipeline/skills/shared/core"
24
+
25
+ PASS=0
26
+ FAIL=0
27
+ pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
28
+ fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
29
+
30
+ if [ ! -d "$CMDS_DIR" ]; then
31
+ echo "FAIL: commands tree missing at $CMDS_DIR" >&2
32
+ exit 1
33
+ fi
34
+ if [ ! -d "$SKILLS_DIR" ]; then
35
+ echo "FAIL: skills tree missing at $SKILLS_DIR" >&2
36
+ exit 1
37
+ fi
38
+
39
+ echo "→ 1. discover feature names in each source tree"
40
+
41
+ # Claude slash commands: every immediate sub-directory is a feature.
42
+ CMD_FEATURES=$(find "$CMDS_DIR" -mindepth 1 -maxdepth 1 -type d \
43
+ -exec basename {} \; | sort -u)
44
+
45
+ # Copilot skills: multi-agent-<name> sub-directories; strip the prefix.
46
+ # Non-prefixed skill dirs (e.g. apple-archive-compliance) are standalone
47
+ # skills without a slash-command twin, so they are intentionally excluded.
48
+ SKILL_FEATURES=$(find "$SKILLS_DIR" -mindepth 1 -maxdepth 1 -type d \
49
+ -name 'multi-agent-*' -exec basename {} \; \
50
+ | sed 's/^multi-agent-//' | sort -u)
51
+
52
+ CMD_COUNT=$(printf '%s\n' "$CMD_FEATURES" | grep -c . || true)
53
+ SKILL_COUNT=$(printf '%s\n' "$SKILL_FEATURES" | grep -c . || true)
54
+ pass "commands tree: $CMD_COUNT features"
55
+ pass "skills tree: $SKILL_COUNT features"
56
+
57
+ echo ""
58
+ echo "→ 2. set symmetric difference must be empty"
59
+
60
+ # In commands tree but missing from skills tree.
61
+ ONLY_CMD=$(comm -23 \
62
+ <(printf '%s\n' "$CMD_FEATURES") \
63
+ <(printf '%s\n' "$SKILL_FEATURES"))
64
+ # In skills tree but missing from commands tree.
65
+ ONLY_SKILL=$(comm -13 \
66
+ <(printf '%s\n' "$CMD_FEATURES") \
67
+ <(printf '%s\n' "$SKILL_FEATURES"))
68
+
69
+ if [ -z "$ONLY_CMD" ]; then
70
+ pass "no command-only features (all present as skills)"
71
+ else
72
+ fail "features in commands/ but NOT in skills tree:"
73
+ printf ' - %s\n' $ONLY_CMD
74
+ fi
75
+
76
+ if [ -z "$ONLY_SKILL" ]; then
77
+ pass "no skill-only features (all present as commands)"
78
+ else
79
+ fail "features in skills tree but NOT in commands/:"
80
+ printf ' - %s\n' $ONLY_SKILL
81
+ fi
82
+
83
+ echo ""
84
+ echo "══ source-parity smoke: $PASS passed, $FAIL failed ══"
85
+ [ "$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.",