@mmerterden/multi-agent-pipeline 11.4.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.
- package/CHANGELOG.md +172 -0
- package/README.md +1 -0
- package/docs/adr/0007-multi-tool-adapter-framework.md +2 -2
- package/index.js +9 -2
- package/install/_common.mjs +107 -5
- package/install/_telemetry.mjs +5 -23
- package/install/claude.mjs +95 -11
- package/install/copilot.mjs +75 -6
- package/install/index.mjs +34 -1
- package/package.json +4 -10
- package/pipeline/commands/multi-agent/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/analysis-resolve/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/build-optimize/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/channels/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/create-jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/dev-local/SKILL.md +2 -1
- package/pipeline/commands/multi-agent/dev-local-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/diff-explain/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/finish/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +29 -3
- package/pipeline/commands/multi-agent/help/SKILL.md +3 -2
- package/pipeline/commands/multi-agent/issue/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/kill/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/language/SKILL.md +20 -1
- package/pipeline/commands/multi-agent/local/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/local-autopilot/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/log/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/purge/SKILL.md +27 -24
- package/pipeline/commands/multi-agent/refactor/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review-issue/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/review-jira/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/scan/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/search/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/stack/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/sync/SKILL.md +20 -2
- package/pipeline/commands/multi-agent/test/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/uninstall/SKILL.md +87 -0
- package/pipeline/commands/multi-agent/update/SKILL.md +1 -0
- package/pipeline/lib/account-resolver.sh +61 -23
- package/pipeline/lib/channels-multi-repo.sh +7 -2
- package/pipeline/lib/count-lib.sh +27 -0
- package/pipeline/lib/credential-store.sh +23 -6
- package/pipeline/lib/extract-conventions.sh +27 -21
- package/pipeline/lib/fetch-confluence.sh +25 -13
- package/pipeline/lib/fetch-crashlytics.sh +30 -8
- package/pipeline/lib/fetch-fortify.sh +11 -2
- package/pipeline/lib/fetch-swagger.sh +18 -7
- package/pipeline/lib/figma-mcp-refresh.sh +26 -11
- package/pipeline/lib/figma-screenshot.sh +11 -2
- package/pipeline/lib/issue-fetcher.sh +31 -6
- package/pipeline/lib/multi-repo-pipeline.sh +84 -20
- package/pipeline/lib/plan-todos.sh +12 -3
- package/pipeline/lib/post-pr-review.sh +5 -3
- package/pipeline/lib/repo-cache.sh +53 -9
- package/pipeline/lib/review-watch.sh +26 -6
- package/pipeline/lib/shadow-git.sh +45 -4
- package/pipeline/lib/vercel-deploy.sh +10 -1
- package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -5
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +12 -1
- package/pipeline/scripts/audit-log-rotate.sh +38 -10
- package/pipeline/scripts/check-md-links.mjs +34 -9
- package/pipeline/scripts/diff-risk-score.mjs +4 -1
- package/pipeline/scripts/evidence-gate.mjs +10 -1
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -4
- package/pipeline/scripts/fixtures/pack-expected-count.txt +1 -0
- package/pipeline/scripts/gc-tmp.sh +67 -8
- package/pipeline/scripts/gc-worktrees.sh +140 -0
- package/pipeline/scripts/keychain-save.sh +13 -9
- package/pipeline/scripts/lint-skills.mjs +40 -2
- package/pipeline/scripts/localize-commands.mjs +130 -0
- package/pipeline/scripts/migrate-prefs.mjs +26 -7
- package/pipeline/scripts/phase-tracker.sh +71 -0
- package/pipeline/scripts/pre-commit-check.sh +39 -0
- package/pipeline/scripts/prune-logs.sh +100 -15
- package/pipeline/scripts/purge.sh +235 -0
- package/pipeline/scripts/scan-skills.sh +217 -127
- package/pipeline/scripts/smoke-bitbucket-contract.sh +21 -5
- package/pipeline/scripts/smoke-description-tr.sh +82 -0
- package/pipeline/scripts/smoke-fetchers-offline.sh +382 -0
- package/pipeline/scripts/smoke-gc-tmp.sh +50 -4
- package/pipeline/scripts/smoke-gc-worktrees.sh +125 -0
- package/pipeline/scripts/smoke-install-layout.sh +11 -3
- package/pipeline/scripts/smoke-lib-scripts.sh +54 -1
- package/pipeline/scripts/smoke-pack-contents.sh +140 -0
- package/pipeline/scripts/smoke-plugin-validate.sh +64 -0
- package/pipeline/scripts/smoke-prune-logs.sh +57 -7
- package/pipeline/scripts/smoke-purge.sh +138 -0
- package/pipeline/scripts/smoke-shadow-git.sh +48 -1
- package/pipeline/scripts/smoke-skill-authoring.sh +1 -1
- package/pipeline/scripts/smoke-update-check.sh +13 -0
- package/pipeline/scripts/smoke-workflow-audit.sh +69 -0
- package/pipeline/scripts/test-gap-scan.mjs +12 -1
- package/pipeline/scripts/triage-memory.mjs +10 -1
- package/pipeline/scripts/uninstall.mjs +146 -42
- package/pipeline/scripts/update-check.sh +9 -0
- package/pipeline/scripts/update-issue-progress.sh +60 -41
- package/pipeline/scripts/write-state.mjs +14 -4
- package/pipeline/skills/.skill-manifest.json +7 -7
- package/pipeline/skills/.skills-index.json +9 -9
- package/pipeline/skills/shared/README.md +19 -10
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-uninstall/SKILL.md +74 -0
- package/pipeline/skills/skills-index.md +1 -1
- package/pipeline/commands/multi-agent/delete/SKILL.md +0 -67
- package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +0 -66
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# smoke-workflow-audit.sh - security + correctness audit of
|
|
4
|
+
# `.github/workflows/` with the two dedicated external linters:
|
|
5
|
+
#
|
|
6
|
+
# - zizmor GitHub Actions security auditor (template injection,
|
|
7
|
+
# credential persistence, unpinned actions, ...). Runs in
|
|
8
|
+
# offline mode so the smoke never touches the network.
|
|
9
|
+
# - actionlint workflow correctness linter (expression syntax, matrix
|
|
10
|
+
# typos, shellcheck of run: blocks, ...).
|
|
11
|
+
#
|
|
12
|
+
# Both tools are optional: each is skipped with a SKIP line when its binary
|
|
13
|
+
# is not on PATH (billing-friendly CI runners install neither; run locally
|
|
14
|
+
# via `brew install zizmor actionlint` for full coverage). Structural
|
|
15
|
+
# baseline checks that need no external binary live in
|
|
16
|
+
# smoke-ci-workflows.sh - this smoke is the deeper, tool-backed layer.
|
|
17
|
+
#
|
|
18
|
+
# Any finding from either tool fails the smoke.
|
|
19
|
+
#
|
|
20
|
+
# Exit codes: 0=pass or all tools skipped, 1=findings, 2=workflows missing
|
|
21
|
+
|
|
22
|
+
set -uo pipefail
|
|
23
|
+
|
|
24
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
25
|
+
WF_DIR="$REPO_ROOT/.github/workflows"
|
|
26
|
+
|
|
27
|
+
[ -d "$WF_DIR" ] || { echo "FAIL: .github/workflows/ missing"; exit 2; }
|
|
28
|
+
|
|
29
|
+
PASS=0
|
|
30
|
+
FAIL=0
|
|
31
|
+
SKIPPED=0
|
|
32
|
+
pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
|
|
33
|
+
fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
|
|
34
|
+
skip() { SKIPPED=$((SKIPPED + 1)); echo " ↷ SKIP: $1"; }
|
|
35
|
+
|
|
36
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
37
|
+
echo "→ 1. zizmor (security audit, offline)"
|
|
38
|
+
if command -v zizmor >/dev/null 2>&1; then
|
|
39
|
+
# Flag names shifted across zizmor releases (--offline vs --no-online-audits);
|
|
40
|
+
# probe --help so the smoke works with whichever is installed.
|
|
41
|
+
HELP=$(zizmor --help 2>&1 || true)
|
|
42
|
+
OFFLINE_FLAG="--offline"
|
|
43
|
+
echo "$HELP" | grep -q -- '--offline' || OFFLINE_FLAG="--no-online-audits"
|
|
44
|
+
if OUT=$(zizmor "$WF_DIR" --min-severity medium "$OFFLINE_FLAG" 2>&1); then
|
|
45
|
+
pass "zizmor: no findings at medium+ severity"
|
|
46
|
+
else
|
|
47
|
+
fail "zizmor reported findings (medium+ severity)"
|
|
48
|
+
echo "$OUT" | sed 's/^/ /'
|
|
49
|
+
fi
|
|
50
|
+
else
|
|
51
|
+
skip "zizmor not on PATH (brew install zizmor)"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
55
|
+
echo "→ 2. actionlint (workflow correctness)"
|
|
56
|
+
if command -v actionlint >/dev/null 2>&1; then
|
|
57
|
+
if OUT=$(cd "$REPO_ROOT" && actionlint 2>&1); then
|
|
58
|
+
pass "actionlint: no findings"
|
|
59
|
+
else
|
|
60
|
+
fail "actionlint reported findings"
|
|
61
|
+
echo "$OUT" | sed 's/^/ /'
|
|
62
|
+
fi
|
|
63
|
+
else
|
|
64
|
+
skip "actionlint not on PATH (brew install actionlint)"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo ""
|
|
68
|
+
echo "══ smoke-workflow-audit: $PASS passed, $FAIL failed, $SKIPPED skipped ══"
|
|
69
|
+
[ "$FAIL" -gt 0 ] && exit 1 || exit 0
|
|
@@ -114,7 +114,18 @@ function isExcluded(path) {
|
|
|
114
114
|
if (pat.endsWith("/")) {
|
|
115
115
|
if (path.includes(pat) || path.startsWith(pat)) return true;
|
|
116
116
|
} else if (pat.includes("**")) {
|
|
117
|
-
|
|
117
|
+
// Tokenize ** before the single-* pass so the * rewrite cannot mangle
|
|
118
|
+
// the already-translated .* (which broke cross-directory matches).
|
|
119
|
+
const DOUBLE_STAR = "\u0000";
|
|
120
|
+
const re = new RegExp(
|
|
121
|
+
"^" +
|
|
122
|
+
pat
|
|
123
|
+
.replace(/\./g, "\\.")
|
|
124
|
+
.replace(/\*\*/g, DOUBLE_STAR)
|
|
125
|
+
.replace(/\*/g, "[^/]*")
|
|
126
|
+
.replace(new RegExp(DOUBLE_STAR, "g"), ".*") +
|
|
127
|
+
"$",
|
|
128
|
+
);
|
|
118
129
|
if (re.test(path)) return true;
|
|
119
130
|
} else if (path.endsWith(pat)) return true;
|
|
120
131
|
}
|
|
@@ -110,7 +110,16 @@ function score(queryTokens, row) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
function globToRegExp(glob) {
|
|
113
|
-
|
|
113
|
+
// Tokenize ** before the single-* pass: a sequential replace would turn
|
|
114
|
+
// ** into .* and then the global * pass would rewrite it to .[^/]*,
|
|
115
|
+
// breaking cross-directory matches (src/**/x vs src/a/b/x).
|
|
116
|
+
const DOUBLE_STAR = "\u0000";
|
|
117
|
+
const escaped = glob
|
|
118
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
119
|
+
.replace(/\*\*/g, DOUBLE_STAR)
|
|
120
|
+
.replace(/\*/g, "[^/]*")
|
|
121
|
+
.replace(/\?/g, ".")
|
|
122
|
+
.replace(new RegExp(DOUBLE_STAR, "g"), ".*");
|
|
114
123
|
return new RegExp("^" + escaped + "$", "i");
|
|
115
124
|
}
|
|
116
125
|
|
|
@@ -12,30 +12,63 @@
|
|
|
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:
|
|
20
|
-
* - `multi-agent-
|
|
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
|
|
|
33
38
|
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
34
39
|
import { join } from "path";
|
|
40
|
+
import { pathToFileURL } from "url";
|
|
35
41
|
import { createInterface } from "readline";
|
|
36
42
|
|
|
37
43
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
38
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Agent definition files the pipeline ships (pipeline/agents/). This runs
|
|
47
|
+
* standalone from ~/.claude/scripts, so the list is static here; a unit test
|
|
48
|
+
* (test/uninstall.test.mjs) asserts it matches the source tree so it cannot
|
|
49
|
+
* go stale. Only these file names are removed from the installed agents
|
|
50
|
+
* dirs - user-authored agent files are never touched.
|
|
51
|
+
*/
|
|
52
|
+
export const PIPELINE_AGENT_FILES = [
|
|
53
|
+
"android-architect.md",
|
|
54
|
+
"backend-architect.md",
|
|
55
|
+
"code-reviewer.md",
|
|
56
|
+
"dev-critic.md",
|
|
57
|
+
"explorer.md",
|
|
58
|
+
"ios-architect.md",
|
|
59
|
+
"security-auditor.md",
|
|
60
|
+
"task-clarifier.md",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Non-multi-agent core skills the installer ships from
|
|
65
|
+
* pipeline/skills/shared/core/ (the multi-agent* dirs are matched by prefix).
|
|
66
|
+
*/
|
|
67
|
+
export const PIPELINE_CORE_SKILL_DIRS = [
|
|
68
|
+
"apple-archive-compliance",
|
|
69
|
+
"google-play-compliance",
|
|
70
|
+
];
|
|
71
|
+
|
|
39
72
|
const flags = process.argv.slice(2).filter((a) => a !== "uninstall");
|
|
40
73
|
|
|
41
74
|
const TOOL_FLAGS = [
|
|
@@ -58,6 +91,9 @@ const forCodex = flags.includes("--codex") || flags.includes("--all") || !explic
|
|
|
58
91
|
|
|
59
92
|
const dryRun = flags.includes("--dry-run");
|
|
60
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");
|
|
61
97
|
|
|
62
98
|
const adapterTarget = (() => {
|
|
63
99
|
const raw = flags.find((f) => f.startsWith("--target="));
|
|
@@ -122,6 +158,33 @@ function rmMatchingFiles(parent, predicate) {
|
|
|
122
158
|
return count;
|
|
123
159
|
}
|
|
124
160
|
|
|
161
|
+
// Explicit terminator install/copilot.mjs writes after the pipeline section
|
|
162
|
+
// (v11.4.1+). Must stay in sync with INSTRUCTIONS_END_MARKER there.
|
|
163
|
+
const COPILOT_END_MARKER = "<!-- multi-agent-pipeline:copilot-instructions:end -->";
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Legacy copilot-instructions files (written before the end marker existed)
|
|
167
|
+
* have no explicit terminator. Bound the pipeline span at the next top-level
|
|
168
|
+
* "# " heading after the start marker when one exists OUTSIDE fenced code
|
|
169
|
+
* blocks (the pipeline body carries bash comments like "# Bootstrap once ..."
|
|
170
|
+
* inside fences); otherwise the span runs to EOF (pre-marker behavior).
|
|
171
|
+
* Mirrors install/copilot.mjs legacyTrailingContent.
|
|
172
|
+
* @param {string} section - content from the start marker onward
|
|
173
|
+
* @returns {string} user content trailing the pipeline span ("" if none)
|
|
174
|
+
*/
|
|
175
|
+
function legacyTrailingContent(section) {
|
|
176
|
+
const lines = section.split("\n");
|
|
177
|
+
let inFence = false;
|
|
178
|
+
for (let i = 1; i < lines.length; i++) {
|
|
179
|
+
if (/^\s*(```|~~~)/.test(lines[i])) {
|
|
180
|
+
inFence = !inFence;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!inFence && /^# /.test(lines[i])) return lines.slice(i).join("\n");
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
|
|
125
188
|
/**
|
|
126
189
|
* Strip a marker-wrapped `<!-- multi-agent-pipeline:begin/end -->` block from
|
|
127
190
|
* a user-owned file. Preserves everything outside the markers. Deletes the
|
|
@@ -135,25 +198,33 @@ function stripManagedBlock(filePath) {
|
|
|
135
198
|
/\s*<!-- multi-agent-pipeline:begin -->[\s\S]*?<!-- multi-agent-pipeline:end -->\s*/m;
|
|
136
199
|
if (!re.test(content)) {
|
|
137
200
|
// Copilot's install (install/copilot.mjs) does not wrap the block in
|
|
138
|
-
// begin/end markers
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// used to orphan the block in files >50 KB).
|
|
201
|
+
// begin/end markers. v11.4.1+ writes an explicit end marker after the
|
|
202
|
+
// pipeline section; strip only the start..end span so user content BELOW
|
|
203
|
+
// the section survives. Legacy files without the end marker are bounded
|
|
204
|
+
// at the next top-level heading outside code fences, else EOF.
|
|
143
205
|
if (content.includes("# Multi-Agent Development Pipeline")) {
|
|
144
|
-
// Heuristic: Copilot's generateCopilotInstructions output. Only strip the
|
|
145
|
-
// pipeline section, not the whole file.
|
|
146
206
|
const marker = "# Multi-Agent Development Pipeline";
|
|
147
207
|
const idx = content.indexOf(marker);
|
|
148
208
|
if (idx >= 0) {
|
|
149
209
|
const before = content.slice(0, idx).trimEnd();
|
|
210
|
+
const fromStart = content.slice(idx);
|
|
211
|
+
const endIdx = fromStart.indexOf(COPILOT_END_MARKER);
|
|
212
|
+
const rawTrailing =
|
|
213
|
+
endIdx >= 0
|
|
214
|
+
? fromStart.slice(endIdx + COPILOT_END_MARKER.length)
|
|
215
|
+
: legacyTrailingContent(fromStart);
|
|
216
|
+
const trailing = rawTrailing.replace(/^[\r\n]+/, "").trimEnd();
|
|
217
|
+
let remaining = before;
|
|
218
|
+
if (trailing.length > 0) {
|
|
219
|
+
remaining = before.length > 0 ? before + "\n\n" + trailing : trailing;
|
|
220
|
+
}
|
|
150
221
|
if (dryRun) {
|
|
151
222
|
report("would update", filePath);
|
|
152
|
-
} else if (
|
|
223
|
+
} else if (remaining.length === 0) {
|
|
153
224
|
rmSync(filePath, { force: true });
|
|
154
225
|
console.log(` removed (was pipeline-only): ${filePath}`);
|
|
155
226
|
} else {
|
|
156
|
-
writeFileSync(filePath,
|
|
227
|
+
writeFileSync(filePath, remaining + "\n");
|
|
157
228
|
console.log(` cleaned pipeline section: ${filePath}`);
|
|
158
229
|
}
|
|
159
230
|
return true;
|
|
@@ -200,9 +271,12 @@ function cleanClaudeSettings(settingsPath) {
|
|
|
200
271
|
|
|
201
272
|
if (settings.hooks?.PreToolUse?.length) {
|
|
202
273
|
const before = settings.hooks.PreToolUse.length;
|
|
274
|
+
// Match both the current matcher ("Bash") and the dead pre-v11.4.1 one
|
|
275
|
+
// ("Bash(git commit:*)"); the pre-commit-check.sh command identifies the
|
|
276
|
+
// entry as ours either way.
|
|
203
277
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
204
278
|
(h) =>
|
|
205
|
-
!(h.matcher === "Bash(git commit:*)" &&
|
|
279
|
+
!((h.matcher === "Bash" || h.matcher === "Bash(git commit:*)") &&
|
|
206
280
|
h.hooks?.some((sub) => sub.command?.includes("pre-commit-check.sh"))),
|
|
207
281
|
);
|
|
208
282
|
if (settings.hooks.PreToolUse.length < before) touched = true;
|
|
@@ -239,18 +313,24 @@ async function main() {
|
|
|
239
313
|
if (dryRun) console.log(" [DRY-RUN] Nothing will be modified.");
|
|
240
314
|
console.log("");
|
|
241
315
|
console.log(" Targets:");
|
|
242
|
-
if (forClaude) console.log(" - Claude Code (~/.claude/{commands,
|
|
243
|
-
if (forCopilot) console.log(" - Copilot CLI (~/.copilot/{
|
|
316
|
+
if (forClaude) console.log(" - Claude Code (~/.claude/{commands/multi-agent, scripts, multi-agent-refs, schemas, lib} + pipeline skills + pipeline agent files)");
|
|
317
|
+
if (forCopilot) console.log(" - Copilot CLI (~/.copilot/{scripts, schemas, lib} + pipeline skills + pipeline agent files + copilot-instructions.md section)");
|
|
244
318
|
if (forCursor) console.log(` - Cursor (${adapterTarget}/.cursor/rules/multi-agent-* + .cursorrules)`);
|
|
245
319
|
if (forCopilotChat) console.log(` - GitHub Copilot Chat (${adapterTarget}/.github/copilot-instructions.md block + .github/instructions/multi-agent-*)`);
|
|
246
320
|
if (forAntigravity) console.log(` - Antigravity (${adapterTarget}/.agent/rules + .agent/workflows + AGENTS.md block + .agent/mcp_config.json)`);
|
|
247
321
|
if (forCodex) console.log(" - OpenAI Codex CLI (~/.codex/prompts/multi-agent.md + AGENTS.md block + config.toml mcp block)");
|
|
248
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
|
+
}
|
|
249
329
|
console.log(" PRESERVED (never touched):");
|
|
250
330
|
console.log(" - Personal access tokens in OS credential store (Keychain / Credential Manager / libsecret)");
|
|
251
331
|
console.log(" - ~/.claude/CLAUDE.md (your customizations)");
|
|
252
332
|
console.log(" - ~/.claude/rules/ (user-owned; installed write-if-missing, never overwritten)");
|
|
253
|
-
console.log(" - ~/.claude/multi-agent-preferences.json (your settings)");
|
|
333
|
+
if (!allData) console.log(" - ~/.claude/multi-agent-preferences.json (your settings)");
|
|
254
334
|
console.log(" - Your own content in copilot-instructions.md above the pipeline section");
|
|
255
335
|
console.log("");
|
|
256
336
|
|
|
@@ -265,6 +345,11 @@ async function main() {
|
|
|
265
345
|
const CLAUDE = join(HOME, ".claude");
|
|
266
346
|
rmIfExists(join(CLAUDE, "commands", "multi-agent"));
|
|
267
347
|
rmIfExists(join(CLAUDE, "scripts"));
|
|
348
|
+
// Pipeline-managed trees the installer lays down alongside scripts/.
|
|
349
|
+
rmIfExists(join(CLAUDE, "multi-agent-refs"));
|
|
350
|
+
rmIfExists(join(CLAUDE, "schemas"));
|
|
351
|
+
rmIfExists(join(CLAUDE, "lib"));
|
|
352
|
+
rmIfExists(join(CLAUDE, ".pipeline-version"));
|
|
268
353
|
// NOTE: ~/.claude/rules/ is USER-OWNED. install lays baseline rules down
|
|
269
354
|
// write-if-missing and NEVER overwrites them (install/claude.mjs
|
|
270
355
|
// installRules, skipExisting) so users can evolve their global rules
|
|
@@ -274,20 +359,14 @@ async function main() {
|
|
|
274
359
|
const skills = join(CLAUDE, "skills");
|
|
275
360
|
let n = rmMatchingDirs(skills, (name) => name === "multi-agent" || name.startsWith("multi-agent-"));
|
|
276
361
|
n += rmMatchingDirs(skills, (name) => name === "figma-ios" || name === "figma-android" || name === "figma-common" || name === "figma-to-component");
|
|
362
|
+
n += rmMatchingDirs(skills, (name) => PIPELINE_CORE_SKILL_DIRS.includes(name));
|
|
277
363
|
if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
|
|
278
364
|
rmIfExists(join(skills, ".skills-index.json"));
|
|
279
365
|
rmIfExists(join(skills, "skills-index.md"));
|
|
280
366
|
rmIfExists(join(skills, "README.md"));
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
"explorer.md",
|
|
285
|
-
"ios-architect.md",
|
|
286
|
-
"android-architect.md",
|
|
287
|
-
"backend-architect.md",
|
|
288
|
-
"security-auditor.md",
|
|
289
|
-
].includes(name),
|
|
290
|
-
);
|
|
367
|
+
// Agent definitions are FILES; remove only the pipeline-shipped names so
|
|
368
|
+
// user-authored agents survive.
|
|
369
|
+
rmMatchingFiles(join(CLAUDE, "agents"), (name) => PIPELINE_AGENT_FILES.includes(name));
|
|
291
370
|
cleanClaudeSettings(join(CLAUDE, "settings.json"));
|
|
292
371
|
}
|
|
293
372
|
|
|
@@ -296,24 +375,34 @@ async function main() {
|
|
|
296
375
|
console.log(" [Copilot CLI] Removing...");
|
|
297
376
|
const COP = join(HOME, ".copilot");
|
|
298
377
|
rmIfExists(join(COP, "scripts"));
|
|
378
|
+
// Pipeline-managed trees the installer lays down alongside scripts/.
|
|
379
|
+
rmIfExists(join(COP, "schemas"));
|
|
380
|
+
rmIfExists(join(COP, "lib"));
|
|
381
|
+
rmIfExists(join(COP, ".pipeline-version"));
|
|
299
382
|
const skills = join(COP, "skills");
|
|
300
383
|
let n = rmMatchingDirs(skills, (name) => name === "multi-agent" || name.startsWith("multi-agent-"));
|
|
301
384
|
n += rmMatchingDirs(skills, (name) => name === "figma-ios" || name === "figma-android" || name === "figma-common" || name === "figma-to-component");
|
|
385
|
+
n += rmMatchingDirs(skills, (name) => PIPELINE_CORE_SKILL_DIRS.includes(name));
|
|
302
386
|
if (n > 0) console.log(` removed ${n} skill dir(s) under ${skills}`);
|
|
387
|
+
rmIfExists(join(skills, ".skills-index.json"));
|
|
388
|
+
rmIfExists(join(skills, "skills-index.md"));
|
|
303
389
|
rmIfExists(join(skills, "README.md"));
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
"explorer.md",
|
|
308
|
-
"ios-architect.md",
|
|
309
|
-
"android-architect.md",
|
|
310
|
-
"backend-architect.md",
|
|
311
|
-
"security-auditor.md",
|
|
312
|
-
].includes(name),
|
|
313
|
-
);
|
|
390
|
+
// Agent definitions are FILES; remove only the pipeline-shipped names so
|
|
391
|
+
// user-authored agents survive.
|
|
392
|
+
rmMatchingFiles(join(COP, "agents"), (name) => PIPELINE_AGENT_FILES.includes(name));
|
|
314
393
|
stripManagedBlock(join(COP, "copilot-instructions.md"));
|
|
315
394
|
}
|
|
316
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
|
+
|
|
317
406
|
// Legacy adapter-file cleanup (adapters removed in v10.7.0). These blocks
|
|
318
407
|
// delete files a pre-v10.7.0 install generated; they never touch user files
|
|
319
408
|
// outside the multi-agent-* namespace / managed markers.
|
|
@@ -360,6 +449,15 @@ async function main() {
|
|
|
360
449
|
|
|
361
450
|
console.log("");
|
|
362
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
|
+
}
|
|
363
461
|
console.log("");
|
|
364
462
|
console.log(" Personal access tokens were NOT touched. Inspect with:");
|
|
365
463
|
console.log(" macOS: security find-generic-password -s \"<service>\" -w");
|
|
@@ -370,7 +468,13 @@ async function main() {
|
|
|
370
468
|
console.log("");
|
|
371
469
|
}
|
|
372
470
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
471
|
+
// Run only when executed directly (node uninstall.mjs). Guard lets tests
|
|
472
|
+
// import the exported constants without triggering the uninstall.
|
|
473
|
+
const isMainModule =
|
|
474
|
+
process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
475
|
+
if (isMainModule) {
|
|
476
|
+
main().catch((e) => {
|
|
477
|
+
console.error("uninstall failed:", e.message);
|
|
478
|
+
process.exit(1);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
@@ -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)
|
|
@@ -29,12 +29,63 @@
|
|
|
29
29
|
|
|
30
30
|
set -euo pipefail
|
|
31
31
|
|
|
32
|
+
# Rewrite the `### Progress` block of the body file ($2) with $1 and print the
|
|
33
|
+
# result to stdout. The replaced region is bounded at the FIRST of:
|
|
34
|
+
# - a line starting with `<!--` (the legend comment, printed and kept), or
|
|
35
|
+
# - the next heading line (^#, printed and kept), or
|
|
36
|
+
# - EOF.
|
|
37
|
+
# Bounding at the next heading matters: bodies without the legend comment used
|
|
38
|
+
# to have everything from the table to EOF silently dropped and persisted.
|
|
39
|
+
rewrite_progress_body() {
|
|
40
|
+
# Block passed via environment: BSD awk rejects literal newlines in -v
|
|
41
|
+
# assignments ("newline in string"), so -v cannot carry the table.
|
|
42
|
+
NEW_BLOCK_ENV="$1" awk '
|
|
43
|
+
BEGIN { new_block = ENVIRON["NEW_BLOCK_ENV"] }
|
|
44
|
+
BEGIN { in_progress = 0 }
|
|
45
|
+
/^### Progress/ {
|
|
46
|
+
print
|
|
47
|
+
print ""
|
|
48
|
+
print new_block
|
|
49
|
+
print ""
|
|
50
|
+
in_progress = 1
|
|
51
|
+
next
|
|
52
|
+
}
|
|
53
|
+
in_progress && /^<!--/ { in_progress = 0; print; next }
|
|
54
|
+
in_progress && /^#/ { in_progress = 0; print; next }
|
|
55
|
+
in_progress { next }
|
|
56
|
+
{ print }
|
|
57
|
+
' "$2"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
build_progress_block() {
|
|
61
|
+
cat <<EOF
|
|
62
|
+
| Flag | State |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Implementation | ${1} |
|
|
65
|
+
| Testing | ${2} |
|
|
66
|
+
| Code Connect | ${3} |
|
|
67
|
+
| Wiki | ${4} |
|
|
68
|
+
EOF
|
|
69
|
+
}
|
|
70
|
+
|
|
32
71
|
TASK_ID="${1:-}"
|
|
33
72
|
if [ -z "$TASK_ID" ]; then
|
|
34
73
|
echo "usage: update-issue-progress.sh <task-id>" >&2
|
|
35
74
|
exit 64
|
|
36
75
|
fi
|
|
37
76
|
|
|
77
|
+
# Internal test seam: `--rewrite <body-file>` runs only the Progress-block
|
|
78
|
+
# rewrite (placeholder glyphs) and prints the result. No state, no gh.
|
|
79
|
+
if [ "$TASK_ID" = "--rewrite" ]; then
|
|
80
|
+
BODY_IN="${2:-}"
|
|
81
|
+
if [ -z "$BODY_IN" ] || [ ! -f "$BODY_IN" ]; then
|
|
82
|
+
echo "usage: update-issue-progress.sh --rewrite <body-file>" >&2
|
|
83
|
+
exit 64
|
|
84
|
+
fi
|
|
85
|
+
rewrite_progress_body "$(build_progress_block "⚪" "⚪" "⚪" "⚪")" "$BODY_IN"
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
38
89
|
# Resolve the project state directory (matches phase-tracker convention).
|
|
39
90
|
STATE_GLOB="$HOME/.claude/projects/*"
|
|
40
91
|
AGENT_STATE=""
|
|
@@ -75,9 +126,9 @@ F_WIKI=$(jq -r '.flags.wiki // false' "$AGENT_STATE")
|
|
|
75
126
|
# Each flag may be: true | false | "yellow" (validated against older impl).
|
|
76
127
|
glyph_for() {
|
|
77
128
|
case "$1" in
|
|
78
|
-
true
|
|
79
|
-
yellow
|
|
80
|
-
*)
|
|
129
|
+
true) echo "🟢" ;;
|
|
130
|
+
yellow) echo "🟡" ;;
|
|
131
|
+
*) echo "⚪" ;;
|
|
81
132
|
esac
|
|
82
133
|
}
|
|
83
134
|
|
|
@@ -87,49 +138,17 @@ G_CC=$(glyph_for "$F_CC")
|
|
|
87
138
|
G_WIKI=$(glyph_for "$F_WIKI")
|
|
88
139
|
|
|
89
140
|
# Fetch current body, replace the Progress table, write back.
|
|
90
|
-
TMP_BODY=$(mktemp /tmp/issue-progress
|
|
141
|
+
TMP_BODY=$(mktemp /tmp/issue-progress-"${TASK_ID}"-XXXXXX.md)
|
|
91
142
|
trap 'rm -f "$TMP_BODY"' EXIT
|
|
92
143
|
|
|
93
144
|
gh issue view "$ISSUE_NUM" --repo "$ORG_REPO" --json body --jq .body > "$TMP_BODY"
|
|
94
145
|
|
|
95
146
|
# Build the new Progress block.
|
|
96
|
-
NEW_BLOCK=$(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
| Code Connect | ${G_CC} |
|
|
102
|
-
| Wiki | ${G_WIKI} |
|
|
103
|
-
EOF
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
# Replace the table between `### Progress` and the next `<!--` HTML comment.
|
|
107
|
-
# Use awk to keep the legend comment in place.
|
|
108
|
-
awk -v new_block="$NEW_BLOCK" '
|
|
109
|
-
BEGIN { in_progress = 0; replaced = 0 }
|
|
110
|
-
/^### Progress/ {
|
|
111
|
-
print
|
|
112
|
-
print ""
|
|
113
|
-
print new_block
|
|
114
|
-
print ""
|
|
115
|
-
in_progress = 1
|
|
116
|
-
replaced = 1
|
|
117
|
-
next
|
|
118
|
-
}
|
|
119
|
-
in_progress && /^<!--/ {
|
|
120
|
-
in_progress = 0
|
|
121
|
-
print
|
|
122
|
-
next
|
|
123
|
-
}
|
|
124
|
-
in_progress { next }
|
|
125
|
-
{ print }
|
|
126
|
-
END {
|
|
127
|
-
if (replaced == 0) {
|
|
128
|
-
# Body had no Progress section; nothing to replace.
|
|
129
|
-
exit 0
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
' "$TMP_BODY" > "${TMP_BODY}.new"
|
|
147
|
+
NEW_BLOCK=$(build_progress_block "$G_IMPL" "$G_TEST" "$G_CC" "$G_WIKI")
|
|
148
|
+
|
|
149
|
+
# Replace the table between `### Progress` and the first terminator (legend
|
|
150
|
+
# comment, next heading, or EOF) - see rewrite_progress_body above.
|
|
151
|
+
rewrite_progress_body "$NEW_BLOCK" "$TMP_BODY" > "${TMP_BODY}.new"
|
|
133
152
|
|
|
134
153
|
# Check if body actually changed (idempotent no-op).
|
|
135
154
|
if cmp -s "$TMP_BODY" "${TMP_BODY}.new"; then
|
|
@@ -130,6 +130,10 @@ function isLockStale(lockPath, staleMs) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Path whose lock this process currently holds (null when none). Lets the
|
|
134
|
+
// top-level error handler release our lock on unexpected failures.
|
|
135
|
+
let heldLockFor = null;
|
|
136
|
+
|
|
133
137
|
/** @param {string} path */
|
|
134
138
|
function releaseLock(path) {
|
|
135
139
|
const lockPath = `${path}.lock`;
|
|
@@ -138,6 +142,7 @@ function releaseLock(path) {
|
|
|
138
142
|
} catch {
|
|
139
143
|
/* already gone - fine */
|
|
140
144
|
}
|
|
145
|
+
if (heldLockFor === path) heldLockFor = null;
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
/**
|
|
@@ -178,6 +183,7 @@ async function main() {
|
|
|
178
183
|
|
|
179
184
|
try {
|
|
180
185
|
await acquireLock(path);
|
|
186
|
+
heldLockFor = path;
|
|
181
187
|
} catch (err) {
|
|
182
188
|
if (err.code === "LOCK_TIMEOUT") {
|
|
183
189
|
console.error(err.message);
|
|
@@ -187,6 +193,10 @@ async function main() {
|
|
|
187
193
|
process.exit(3);
|
|
188
194
|
}
|
|
189
195
|
|
|
196
|
+
// process.exit() inside a try block skips `finally`, so every exit path in
|
|
197
|
+
// here must release OUR lock explicitly before exiting - otherwise a corrupt
|
|
198
|
+
// state file (or any I/O error) leaks agent-state.json.lock and blocks the
|
|
199
|
+
// next writer until the stale-lock window elapses.
|
|
190
200
|
try {
|
|
191
201
|
let next;
|
|
192
202
|
if (replace) {
|
|
@@ -198,8 +208,7 @@ async function main() {
|
|
|
198
208
|
try {
|
|
199
209
|
current = JSON.parse(readFileSync(path, "utf-8"));
|
|
200
210
|
} catch (err) {
|
|
201
|
-
|
|
202
|
-
process.exit(3);
|
|
211
|
+
throw new Error(`existing state unreadable - ${err.message}`, { cause: err });
|
|
203
212
|
}
|
|
204
213
|
}
|
|
205
214
|
next = deepMerge(current, payload);
|
|
@@ -210,15 +219,16 @@ async function main() {
|
|
|
210
219
|
renameSync(tmp, path); // atomic on POSIX
|
|
211
220
|
} catch (err) {
|
|
212
221
|
console.error(`write-state: ${err.message}`);
|
|
213
|
-
process.exit(3);
|
|
214
|
-
} finally {
|
|
215
222
|
releaseLock(path);
|
|
223
|
+
process.exit(3);
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
releaseLock(path);
|
|
218
227
|
process.exit(0);
|
|
219
228
|
}
|
|
220
229
|
|
|
221
230
|
main().catch((err) => {
|
|
222
231
|
console.error(`write-state: unhandled - ${err.message}`);
|
|
232
|
+
if (heldLockFor) releaseLock(heldLockFor);
|
|
223
233
|
process.exit(3);
|
|
224
234
|
});
|