@mmerterden/multi-agent-pipeline 10.6.0 → 10.7.1
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 +36 -0
- package/README.md +10 -39
- package/install/index.mjs +9 -101
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/delete.md +4 -5
- package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +1 -11
- package/pipeline/commands/multi-agent/refs/picker-contract.md +6 -10
- package/pipeline/commands/multi-agent/setup.md +0 -1
- package/pipeline/commands/multi-agent/sync.md +4 -73
- package/pipeline/commands/multi-agent/update.md +9 -0
- package/pipeline/lib/ask-choice.sh +1 -1
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +0 -7
- package/pipeline/scripts/smoke-install-layout.sh +1 -2
- package/pipeline/skills/.skills-index.json +65 -20
- package/pipeline/skills/shared/README.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +4 -4
- package/pipeline/skills/skills-index.md +24 -19
- package/install/_adapters.mjs +0 -73
- package/pipeline/adapters/_base.mjs +0 -640
- package/pipeline/adapters/antigravity.mjs +0 -140
- package/pipeline/adapters/codex.mjs +0 -159
- package/pipeline/adapters/copilot-chat-orchestration.mjs +0 -148
- package/pipeline/adapters/copilot-chat.mjs +0 -124
- package/pipeline/adapters/cursor-orchestration.mjs +0 -152
- package/pipeline/adapters/cursor.mjs +0 -146
- package/pipeline/scripts/smoke-adapters.sh +0 -276
- package/pipeline/scripts/smoke-delete-flow.sh +0 -151
- package/pipeline/scripts/smoke-shared-runtime.sh +0 -108
- package/pipeline/scripts/smoke-sync-adapters.sh +0 -113
- package/pipeline/scripts/sync-adapters.mjs +0 -183
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-delete-flow.sh - v7.7.0 token-preserving uninstall.
|
|
3
|
-
#
|
|
4
|
-
# Round-trip integrity for `multi-agent uninstall`:
|
|
5
|
-
# 1. --dry-run produces a preview without modifying the filesystem
|
|
6
|
-
# 2. Real uninstall removes pipeline-managed files
|
|
7
|
-
# 3. User-authored content survives (CLAUDE.md, preferences, marker-wrapped blocks)
|
|
8
|
-
# 4. Selective targets (--cursor, --copilot-chat) only touch the requested tree
|
|
9
|
-
# 5. Adapter `uninstall()` is invoked and its result reflects in the output
|
|
10
|
-
# 6. (Bonus) Slash command + Copilot skill exist with byte-eq description (parity)
|
|
11
|
-
#
|
|
12
|
-
# We never invoke the uninstaller against $HOME - the test scaffolds an
|
|
13
|
-
# isolated `--target` tree and exercises the adapter portion only. Claude /
|
|
14
|
-
# Copilot uninstall paths are exercised via dry-run (which reports them).
|
|
15
|
-
|
|
16
|
-
set -uo pipefail
|
|
17
|
-
|
|
18
|
-
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
19
|
-
INSTALL="$REPO_ROOT/install.js"
|
|
20
|
-
UNINSTALL="$REPO_ROOT/pipeline/scripts/uninstall.mjs"
|
|
21
|
-
|
|
22
|
-
PASS=0
|
|
23
|
-
FAIL=0
|
|
24
|
-
pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
|
|
25
|
-
fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
|
|
26
|
-
|
|
27
|
-
cleanup() {
|
|
28
|
-
[ -n "${TMP:-}" ] && rm -rf "$TMP"
|
|
29
|
-
}
|
|
30
|
-
trap cleanup EXIT
|
|
31
|
-
|
|
32
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
33
|
-
echo "→ 1. Slash command + Copilot peer exist"
|
|
34
|
-
if [ -f "$REPO_ROOT/pipeline/commands/multi-agent/delete.md" ]; then
|
|
35
|
-
pass "pipeline/commands/multi-agent/delete.md present"
|
|
36
|
-
else
|
|
37
|
-
fail "delete.md missing"
|
|
38
|
-
fi
|
|
39
|
-
if [ -f "$REPO_ROOT/pipeline/skills/shared/core/multi-agent-delete/SKILL.md" ]; then
|
|
40
|
-
pass "pipeline/skills/shared/core/multi-agent-delete/SKILL.md present"
|
|
41
|
-
else
|
|
42
|
-
fail "multi-agent-delete SKILL.md missing"
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
46
|
-
echo "→ 2. --dry-run produces preview without side effects"
|
|
47
|
-
TMP=$(mktemp -d)
|
|
48
|
-
node "$INSTALL" --cursor --target="$TMP" >/dev/null 2>&1
|
|
49
|
-
pre_count=$(find "$TMP" -type f | wc -l | tr -d ' ')
|
|
50
|
-
|
|
51
|
-
OUTPUT=$(node "$UNINSTALL" --dry-run --yes --cursor --target="$TMP" 2>&1)
|
|
52
|
-
post_count=$(find "$TMP" -type f | wc -l | tr -d ' ')
|
|
53
|
-
|
|
54
|
-
if [ "$pre_count" = "$post_count" ] && [ "$post_count" -gt 100 ]; then
|
|
55
|
-
pass "--dry-run preserved file tree ($post_count files)"
|
|
56
|
-
else
|
|
57
|
-
fail "--dry-run changed file tree (pre=$pre_count post=$post_count)"
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
if echo "$OUTPUT" | grep -q "DRY-RUN"; then
|
|
61
|
-
pass "--dry-run output mentions DRY-RUN banner"
|
|
62
|
-
else
|
|
63
|
-
fail "--dry-run banner missing from output"
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
|
-
if echo "$OUTPUT" | grep -q "Personal access tokens were NOT touched"; then
|
|
67
|
-
pass "--dry-run output reaffirms token preservation"
|
|
68
|
-
else
|
|
69
|
-
fail "token-preservation message missing from --dry-run output"
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
73
|
-
echo "→ 3. Real uninstall removes pipeline files"
|
|
74
|
-
node "$UNINSTALL" --yes --cursor --target="$TMP" >/dev/null 2>&1
|
|
75
|
-
remaining=$(find "$TMP" -type f | wc -l | tr -d ' ')
|
|
76
|
-
if [ "$remaining" = "0" ]; then
|
|
77
|
-
pass "real uninstall: 0 pipeline files remain (was $pre_count)"
|
|
78
|
-
else
|
|
79
|
-
fail "real uninstall left $remaining file(s)"
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
83
|
-
echo "→ 4. User content outside markers survives uninstall (copilot-chat)"
|
|
84
|
-
rm -rf "$TMP" && TMP=$(mktemp -d)
|
|
85
|
-
mkdir -p "$TMP/.github"
|
|
86
|
-
USER_LINE="DO_NOT_TOUCH_$(date +%s)"
|
|
87
|
-
echo "$USER_LINE" > "$TMP/.github/copilot-instructions.md"
|
|
88
|
-
node "$INSTALL" --copilot-chat --target="$TMP" >/dev/null 2>&1
|
|
89
|
-
node "$UNINSTALL" --yes --copilot-chat --target="$TMP" >/dev/null 2>&1
|
|
90
|
-
|
|
91
|
-
if grep -q "$USER_LINE" "$TMP/.github/copilot-instructions.md" 2>/dev/null; then
|
|
92
|
-
pass "user content outside markers survived uninstall"
|
|
93
|
-
else
|
|
94
|
-
fail "user content lost during uninstall"
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
98
|
-
echo "→ 5. Selective target - --copilot-chat doesn't touch cursor tree"
|
|
99
|
-
rm -rf "$TMP" && TMP=$(mktemp -d)
|
|
100
|
-
node "$INSTALL" --cursor --target="$TMP" >/dev/null 2>&1
|
|
101
|
-
node "$INSTALL" --copilot-chat --target="$TMP" >/dev/null 2>&1
|
|
102
|
-
|
|
103
|
-
# Now uninstall ONLY copilot-chat; cursor tree should remain
|
|
104
|
-
node "$UNINSTALL" --yes --copilot-chat --target="$TMP" >/dev/null 2>&1
|
|
105
|
-
|
|
106
|
-
cursor_remaining=$(find "$TMP/.cursor" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
107
|
-
copilot_remaining=$(find "$TMP/.github/instructions" -name "multi-agent-*" 2>/dev/null | wc -l | tr -d ' ')
|
|
108
|
-
|
|
109
|
-
if [ "$cursor_remaining" -gt 0 ]; then
|
|
110
|
-
pass "selective uninstall: cursor tree untouched ($cursor_remaining files)"
|
|
111
|
-
else
|
|
112
|
-
fail "selective uninstall: cursor tree was deleted"
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
if [ "$copilot_remaining" = "0" ]; then
|
|
116
|
-
pass "selective uninstall: copilot-chat per-skill files cleaned"
|
|
117
|
-
else
|
|
118
|
-
fail "selective uninstall: $copilot_remaining copilot-chat file(s) remain"
|
|
119
|
-
fi
|
|
120
|
-
|
|
121
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
122
|
-
echo "→ 6. CLI router - 'uninstall' and 'delete' both reach the script"
|
|
123
|
-
OUT_UNINSTALL=$(node "$REPO_ROOT/index.js" uninstall --dry-run --yes --cursor --target=/tmp/fake 2>&1 || true)
|
|
124
|
-
OUT_DELETE=$(node "$REPO_ROOT/index.js" delete --dry-run --yes --cursor --target=/tmp/fake 2>&1 || true)
|
|
125
|
-
|
|
126
|
-
if echo "$OUT_UNINSTALL" | grep -q "multi-agent-pipeline uninstaller"; then
|
|
127
|
-
pass "'index.js uninstall' invokes the uninstaller"
|
|
128
|
-
else
|
|
129
|
-
fail "'index.js uninstall' did not reach uninstaller"
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
if echo "$OUT_DELETE" | grep -q "multi-agent-pipeline uninstaller"; then
|
|
133
|
-
pass "'index.js delete' alias also invokes the uninstaller"
|
|
134
|
-
else
|
|
135
|
-
fail "'index.js delete' alias did not reach uninstaller"
|
|
136
|
-
fi
|
|
137
|
-
|
|
138
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
139
|
-
echo "→ 7. Token preservation - uninstaller never invokes credential-store"
|
|
140
|
-
# Static check: uninstall.mjs must not import credential-store.sh or call
|
|
141
|
-
# `security delete-generic-password` / `cmdkey /delete` / `secret-tool clear`.
|
|
142
|
-
if grep -qE "credential-store|delete-generic-password|cmdkey /delete|secret-tool clear|secret-tool del" "$UNINSTALL"; then
|
|
143
|
-
fail "uninstall.mjs references credential-store deletion APIs (token leak risk)"
|
|
144
|
-
else
|
|
145
|
-
pass "uninstall.mjs has no credential-store deletion references"
|
|
146
|
-
fi
|
|
147
|
-
|
|
148
|
-
# ──────────────────────────────────────────────────────────────────────────
|
|
149
|
-
echo ""
|
|
150
|
-
echo "══ delete-flow smoke: $PASS passed, $FAIL failed ══"
|
|
151
|
-
[ "$FAIL" -eq 0 ]
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-shared-runtime.sh - the 3 adapter platforms (Cursor, Antigravity, VS
|
|
3
|
-
# Code Copilot Chat) must install the gate scripts to a shared ~/.multi-agent/
|
|
4
|
-
# runtime AND rewrite their emitted agent/command references to that absolute
|
|
5
|
-
# path, so the deterministic gates can actually execute (not just be referenced).
|
|
6
|
-
#
|
|
7
|
-
# Runs in a sandbox HOME so the developer's real ~/.multi-agent is untouched.
|
|
8
|
-
# Exit 0 = all pass, 1 = any failure.
|
|
9
|
-
|
|
10
|
-
set -uo pipefail
|
|
11
|
-
|
|
12
|
-
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
13
|
-
PASS=0; FAIL=0
|
|
14
|
-
pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
15
|
-
fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
|
|
16
|
-
command -v node >/dev/null 2>&1 || { echo "error: node required" >&2; exit 127; }
|
|
17
|
-
|
|
18
|
-
run_adapter() {
|
|
19
|
-
# $1 = adapter module path, $2 = sandbox HOME, $3 = target
|
|
20
|
-
HOME="$2" TARGET="$3" node -e '
|
|
21
|
-
import(process.argv[1]).then(m => {
|
|
22
|
-
m.default.install({ pipelineSrc: process.cwd()+"/pipeline", target: process.env.TARGET, platformFilter: "all" });
|
|
23
|
-
}).catch(e => { console.error(e.message); process.exit(1); });
|
|
24
|
-
' "$1" >/dev/null 2>&1
|
|
25
|
-
}
|
|
26
|
-
uninstall_adapter() {
|
|
27
|
-
HOME="$2" TARGET="$3" node -e '
|
|
28
|
-
import(process.argv[1]).then(m => { m.default.uninstall({ target: process.env.TARGET }); }).catch(()=>process.exit(1));
|
|
29
|
-
' "$1" >/dev/null 2>&1
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for adapter in cursor antigravity copilot-chat; do
|
|
33
|
-
echo "→ $adapter"
|
|
34
|
-
SBHOME=$(mktemp -d); TGT=$(mktemp -d); git init -q "$TGT" 2>/dev/null
|
|
35
|
-
run_adapter "$ROOT/pipeline/adapters/$adapter.mjs" "$SBHOME" "$TGT"
|
|
36
|
-
|
|
37
|
-
# 1. shared runtime populated with the gate scripts + lib + schemas
|
|
38
|
-
if [ -f "$SBHOME/.multi-agent/scripts/evidence-gate.mjs" ] \
|
|
39
|
-
&& [ -f "$SBHOME/.multi-agent/scripts/learnings-ledger.mjs" ] \
|
|
40
|
-
&& [ -f "$SBHOME/.multi-agent/lib/classify-intent.sh" ] \
|
|
41
|
-
&& [ -f "$SBHOME/.multi-agent/schemas/triage-output.schema.json" ]; then
|
|
42
|
-
pass "$adapter: gate scripts installed to shared runtime"
|
|
43
|
-
else
|
|
44
|
-
fail "$adapter: shared runtime missing gate scripts"
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# 2. dev-only / PII files NOT copied into the shared runtime
|
|
48
|
-
if [ -f "$SBHOME/.multi-agent/scripts/figma-placeholder-map.json" ] \
|
|
49
|
-
|| [ -f "$SBHOME/.multi-agent/scripts/smoke-personal-data.sh" ]; then
|
|
50
|
-
fail "$adapter: dev-only/PII file leaked into shared runtime"
|
|
51
|
-
else
|
|
52
|
-
pass "$adapter: dev-only/PII files excluded from shared runtime"
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# 3. The ORCHESTRATION files (agents + command/prompt/workflow - NOT the
|
|
56
|
-
# knowledge-layer rules, which legitimately mention pipeline/scripts in
|
|
57
|
-
# prose) reference the absolute runtime path and carry no bare,
|
|
58
|
-
# non-resolving pipeline/scripts invocation.
|
|
59
|
-
case "$adapter" in
|
|
60
|
-
cursor) orch=$(ls "$TGT/.cursor/agents/"*.md "$TGT/.cursor/commands/multi-agent.md" 2>/dev/null) ;;
|
|
61
|
-
antigravity) orch=$(ls "$TGT/.agent/workflows/multi-agent.md" 2>/dev/null) ;;
|
|
62
|
-
copilot-chat) orch=$(ls "$TGT/.github/agents/"*.agent.md "$TGT/.github/prompts/multi-agent.prompt.md" 2>/dev/null) ;;
|
|
63
|
-
esac
|
|
64
|
-
if [ -n "$orch" ]; then
|
|
65
|
-
if grep -qF '$HOME/.multi-agent/' $orch 2>/dev/null; then
|
|
66
|
-
pass "$adapter: orchestration files reference the shared runtime path"
|
|
67
|
-
else
|
|
68
|
-
fail "$adapter: no shared-runtime reference in orchestration files"
|
|
69
|
-
fi
|
|
70
|
-
if grep -qE '[^.a-z/]pipeline/scripts/' $orch 2>/dev/null; then
|
|
71
|
-
fail "$adapter: a bare pipeline/scripts/ invocation survived (won't resolve)"
|
|
72
|
-
else
|
|
73
|
-
pass "$adapter: no unresolved bare pipeline/scripts/ invocation"
|
|
74
|
-
fi
|
|
75
|
-
else
|
|
76
|
-
fail "$adapter: no orchestration files found"
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
# 3b. cross-vendor 2-model review: a second reviewer pinned to a different
|
|
80
|
-
# model (cursor + copilot-chat; antigravity is dropdown-selected so it
|
|
81
|
-
# documents the pair in the workflow instead).
|
|
82
|
-
case "$adapter" in
|
|
83
|
-
cursor)
|
|
84
|
-
r1=$(grep '^model:' "$TGT/.cursor/agents/ma-code-reviewer.md" 2>/dev/null)
|
|
85
|
-
r2=$(grep '^model:' "$TGT/.cursor/agents/ma-code-reviewer-x.md" 2>/dev/null)
|
|
86
|
-
if [ -n "$r2" ] && [ "$r1" != "$r2" ]; then pass "cursor: two reviewers on distinct models ($r1 vs $r2)"; else fail "cursor: missing distinct cross-model reviewer"; fi ;;
|
|
87
|
-
copilot-chat)
|
|
88
|
-
r1=$(grep '^model:' "$TGT/.github/agents/ma-code-reviewer.agent.md" 2>/dev/null)
|
|
89
|
-
r2=$(grep '^model:' "$TGT/.github/agents/ma-code-reviewer-x.agent.md" 2>/dev/null)
|
|
90
|
-
if [ -n "$r2" ] && [ "$r1" != "$r2" ]; then pass "copilot-chat: two reviewers on distinct models"; else fail "copilot-chat: missing distinct cross-model reviewer"; fi ;;
|
|
91
|
-
antigravity)
|
|
92
|
-
if grep -qi "different model" "$TGT/.agent/workflows/multi-agent.md" 2>/dev/null; then pass "antigravity: workflow documents diverse-model parallel reviewers"; else fail "antigravity: workflow missing diverse-model instruction"; fi ;;
|
|
93
|
-
esac
|
|
94
|
-
|
|
95
|
-
# 4. uninstall removes the shared runtime
|
|
96
|
-
uninstall_adapter "$ROOT/pipeline/adapters/$adapter.mjs" "$SBHOME" "$TGT"
|
|
97
|
-
if [ -d "$SBHOME/.multi-agent" ]; then
|
|
98
|
-
fail "$adapter: uninstall left the shared runtime behind"
|
|
99
|
-
else
|
|
100
|
-
pass "$adapter: uninstall removed the shared runtime"
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
rm -rf "$SBHOME" "$TGT"
|
|
104
|
-
done
|
|
105
|
-
|
|
106
|
-
echo ""
|
|
107
|
-
echo "══ shared-runtime smoke: $PASS passed, $FAIL failed ══"
|
|
108
|
-
[ "$FAIL" -eq 0 ]
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# smoke-sync-adapters.sh
|
|
3
|
-
#
|
|
4
|
-
# Verifies the per-project adapter sync runner:
|
|
5
|
-
# 1. pipeline/scripts/sync-adapters.mjs exists and is executable
|
|
6
|
-
# 2. node sync-adapters.mjs --help exits 0
|
|
7
|
-
# 3. --doctor on empty prefs reports a clean no-op
|
|
8
|
-
# 4. --target=<empty-dir> reports [skip] (no adapter markers)
|
|
9
|
-
# 5. --target=. on the pipeline repo itself triggers cursor adapter
|
|
10
|
-
# (pipeline + .git makes cwd the canonical cursor consumer)
|
|
11
|
-
# 6. After running on pipeline repo, .cursor/rules/ exists and has
|
|
12
|
-
# >100 .mdc files
|
|
13
|
-
# 7. .cursorrules legacy digest exists
|
|
14
|
-
# 8. Unknown arg exits non-zero
|
|
15
|
-
# 9. sync.md references sync-adapters.mjs (so the docs and the runtime agree)
|
|
16
|
-
#
|
|
17
|
-
# Exit 0 = all pass, 1 = any failure.
|
|
18
|
-
|
|
19
|
-
set -uo pipefail
|
|
20
|
-
|
|
21
|
-
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
22
|
-
RUNNER="$ROOT/pipeline/scripts/sync-adapters.mjs"
|
|
23
|
-
SYNC_MD="$ROOT/pipeline/commands/multi-agent/sync.md"
|
|
24
|
-
|
|
25
|
-
pass=0
|
|
26
|
-
fail=0
|
|
27
|
-
failures=()
|
|
28
|
-
record_pass() { pass=$((pass + 1)); printf ' \033[0;32mPASS\033[0m %s\n' "$1"; }
|
|
29
|
-
record_fail() { fail=$((fail + 1)); failures+=("$1"); printf ' \033[0;31mFAIL\033[0m %s\n' "$1"; }
|
|
30
|
-
|
|
31
|
-
printf '→ smoke-sync-adapters: per-project adapter sync runner contract\n'
|
|
32
|
-
|
|
33
|
-
# 1. Runner exists + executable
|
|
34
|
-
if [ ! -f "$RUNNER" ]; then
|
|
35
|
-
record_fail "pipeline/scripts/sync-adapters.mjs missing"
|
|
36
|
-
elif [ ! -x "$RUNNER" ]; then
|
|
37
|
-
record_fail "sync-adapters.mjs not executable"
|
|
38
|
-
else
|
|
39
|
-
record_pass "sync-adapters.mjs exists and is executable"
|
|
40
|
-
fi
|
|
41
|
-
|
|
42
|
-
# 2. --help exits 0
|
|
43
|
-
if node "$RUNNER" --help >/dev/null 2>&1; then
|
|
44
|
-
record_pass "--help exits 0"
|
|
45
|
-
else
|
|
46
|
-
record_fail "--help should exit 0"
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# 3. --doctor on empty env runs without error
|
|
50
|
-
if node "$RUNNER" --doctor 2>&1 | grep -qE "projectsTouched is empty|sync-adapters: 0 ok"; then
|
|
51
|
-
record_pass "--doctor reports clean state when projectsTouched is empty"
|
|
52
|
-
else
|
|
53
|
-
record_pass "--doctor exits cleanly" # doctor on a populated env is also fine
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# 4. --target=<empty-dir>
|
|
57
|
-
EMPTY=$(mktemp -d)
|
|
58
|
-
out=$(node "$RUNNER" --target="$EMPTY" 2>&1)
|
|
59
|
-
if echo "$out" | grep -q "\[skip\]"; then
|
|
60
|
-
record_pass "empty target reports [skip]"
|
|
61
|
-
else
|
|
62
|
-
record_fail "empty target should report [skip] (got: $out)"
|
|
63
|
-
fi
|
|
64
|
-
rm -rf "$EMPTY"
|
|
65
|
-
|
|
66
|
-
# 5. Pipeline repo cursor sync - must produce output mentioning cursor
|
|
67
|
-
out=$(node "$RUNNER" --target="$ROOT" 2>&1)
|
|
68
|
-
if echo "$out" | grep -qE "\[cursor\]"; then
|
|
69
|
-
record_pass "pipeline repo target triggers cursor adapter"
|
|
70
|
-
else
|
|
71
|
-
record_fail "pipeline repo target did NOT trigger cursor adapter (got: $out)"
|
|
72
|
-
fi
|
|
73
|
-
|
|
74
|
-
# 6. .cursor/rules/ exists with >100 mdc
|
|
75
|
-
if [ -d "$ROOT/.cursor/rules" ]; then
|
|
76
|
-
count=$(ls "$ROOT/.cursor/rules"/*.mdc 2>/dev/null | wc -l | tr -d ' ')
|
|
77
|
-
if [ "$count" -gt 100 ]; then
|
|
78
|
-
record_pass ".cursor/rules contains $count .mdc files (>100 expected)"
|
|
79
|
-
else
|
|
80
|
-
record_fail ".cursor/rules has only $count files (expected >100)"
|
|
81
|
-
fi
|
|
82
|
-
else
|
|
83
|
-
record_fail ".cursor/rules dir missing"
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
# 7. Legacy .cursorrules
|
|
87
|
-
if [ -f "$ROOT/.cursorrules" ]; then
|
|
88
|
-
record_pass ".cursorrules legacy digest exists"
|
|
89
|
-
else
|
|
90
|
-
record_fail ".cursorrules legacy digest missing"
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
# 8. Unknown arg
|
|
94
|
-
if node "$RUNNER" --garbage >/dev/null 2>&1; then
|
|
95
|
-
record_fail "unknown arg should exit non-zero"
|
|
96
|
-
else
|
|
97
|
-
record_pass "unknown arg rejected"
|
|
98
|
-
fi
|
|
99
|
-
|
|
100
|
-
# 9. sync.md references the runner
|
|
101
|
-
if grep -qF "sync-adapters.mjs" "$SYNC_MD"; then
|
|
102
|
-
record_pass "sync.md references sync-adapters.mjs"
|
|
103
|
-
else
|
|
104
|
-
record_fail "sync.md missing sync-adapters.mjs reference"
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
printf '\n══ sync-adapters smoke: %d passed, %d failed ══\n' "$pass" "$fail"
|
|
108
|
-
if [ "$fail" -gt 0 ]; then
|
|
109
|
-
printf '\nFailures:\n'
|
|
110
|
-
for msg in "${failures[@]}"; do printf ' - %s\n' "$msg"; done
|
|
111
|
-
exit 1
|
|
112
|
-
fi
|
|
113
|
-
exit 0
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* sync-adapters.mjs - wire the orphaned per-project adapter installers
|
|
4
|
-
* (cursor / copilot-chat) into the sync flow.
|
|
5
|
-
*
|
|
6
|
-
* Three call shapes:
|
|
7
|
-
*
|
|
8
|
-
* 1. Single target (this repo, or any project root):
|
|
9
|
-
* node sync-adapters.mjs --target=. [--platform=ios|android|all]
|
|
10
|
-
*
|
|
11
|
-
* 2. All projects registered in prefs.global.projectsTouched[]:
|
|
12
|
-
* node sync-adapters.mjs --all
|
|
13
|
-
*
|
|
14
|
-
* 3. Doctor - show what would happen, no writes:
|
|
15
|
-
* node sync-adapters.mjs --doctor
|
|
16
|
-
*
|
|
17
|
-
* Discovery contract:
|
|
18
|
-
* - --target=. is the cwd; --target=<path> overrides.
|
|
19
|
-
* - --all reads prefs.global.projectsTouched[] (LRU). Skips entries
|
|
20
|
-
* whose path no longer exists.
|
|
21
|
-
* - For each target, the script inspects the per-project marker files
|
|
22
|
-
* (.cursor/, .cursorrules, .copilot/, .github/copilot-instructions.md)
|
|
23
|
-
* and only runs the matching adapter if the marker is present -
|
|
24
|
-
* a project that never set up Cursor won't get .cursor/rules/ filled
|
|
25
|
-
* in by sync. This mirrors Step 2b's "setup-then-sync" contract.
|
|
26
|
-
*
|
|
27
|
-
* Source layout: this script is the bridge between sync.md Step 2b
|
|
28
|
-
* (which used to reference a non-existent template path) and the
|
|
29
|
-
* adapters under pipeline/adapters/{cursor,copilot-chat}.mjs.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
33
|
-
import { dirname, join, resolve } from "node:path";
|
|
34
|
-
import { fileURLToPath } from "node:url";
|
|
35
|
-
|
|
36
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
|
-
const PIPELINE_ROOT = resolve(__dirname, "..");
|
|
38
|
-
|
|
39
|
-
// --- arg parser ------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
const argv = process.argv.slice(2);
|
|
42
|
-
const args = { target: null, all: false, doctor: false, platform: "all" };
|
|
43
|
-
for (const a of argv) {
|
|
44
|
-
if (a === "--all") args.all = true;
|
|
45
|
-
else if (a === "--doctor") args.doctor = true;
|
|
46
|
-
else if (a.startsWith("--target=")) args.target = a.slice("--target=".length);
|
|
47
|
-
else if (a.startsWith("--platform=")) args.platform = a.slice("--platform=".length);
|
|
48
|
-
else if (a === "--help" || a === "-h") {
|
|
49
|
-
console.log(
|
|
50
|
-
"usage: sync-adapters.mjs --target=<path> single project\n" +
|
|
51
|
-
" sync-adapters.mjs --all every project in projectsTouched\n" +
|
|
52
|
-
" sync-adapters.mjs --doctor dry-run summary",
|
|
53
|
-
);
|
|
54
|
-
process.exit(0);
|
|
55
|
-
} else {
|
|
56
|
-
console.error(`sync-adapters: unknown arg ${a}`);
|
|
57
|
-
process.exit(2);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (!args.target && !args.all && !args.doctor) {
|
|
62
|
-
// Default to current dir - most common case (sync running from the
|
|
63
|
-
// pipeline repo itself).
|
|
64
|
-
args.target = ".";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// --- helpers ---------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
function loadPrefs() {
|
|
70
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
71
|
-
if (!home) return { global: { projectsTouched: [] } };
|
|
72
|
-
const p = join(home, ".claude", "multi-agent-preferences.json");
|
|
73
|
-
if (!existsSync(p)) return { global: { projectsTouched: [] } };
|
|
74
|
-
try {
|
|
75
|
-
return JSON.parse(readFileSync(p, "utf-8"));
|
|
76
|
-
} catch {
|
|
77
|
-
return { global: { projectsTouched: [] } };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function detectAdapters(target) {
|
|
82
|
-
const out = [];
|
|
83
|
-
if (existsSync(join(target, ".cursor")) || existsSync(join(target, ".cursorrules"))) out.push("cursor");
|
|
84
|
-
if (existsSync(join(target, ".copilot")) || existsSync(join(target, ".github/copilot-instructions.md"))) out.push("copilot-chat");
|
|
85
|
-
if (existsSync(join(target, ".agent")) || existsSync(join(target, "AGENTS.md"))) out.push("antigravity");
|
|
86
|
-
return out;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function runAdapter(name, target) {
|
|
90
|
-
const adapterPath = join(PIPELINE_ROOT, "adapters", `${name}.mjs`);
|
|
91
|
-
if (!existsSync(adapterPath)) {
|
|
92
|
-
console.error(`sync-adapters: adapter not found at ${adapterPath}`);
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
const mod = await import(adapterPath);
|
|
96
|
-
if (typeof mod.install !== "function") {
|
|
97
|
-
console.error(`sync-adapters: adapter ${name} has no install() export`);
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
mod.install({ pipelineSrc: PIPELINE_ROOT, target, platformFilter: args.platform });
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function syncTarget(target) {
|
|
105
|
-
const absTarget = resolve(target);
|
|
106
|
-
if (!existsSync(absTarget)) {
|
|
107
|
-
console.error(`sync-adapters: target ${absTarget} does not exist`);
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
// Always-on first-time install signal: if the target is the pipeline
|
|
111
|
-
// repo itself (has pipeline/ + .git/), we ALWAYS install Cursor since
|
|
112
|
-
// the maintainer is the canonical cursor consumer.
|
|
113
|
-
const isPipelineRepo =
|
|
114
|
-
existsSync(join(absTarget, "pipeline")) && existsSync(join(absTarget, ".git"));
|
|
115
|
-
const adapters = detectAdapters(absTarget);
|
|
116
|
-
if (isPipelineRepo && !adapters.includes("cursor")) adapters.push("cursor");
|
|
117
|
-
if (adapters.length === 0) {
|
|
118
|
-
console.log(` [skip] ${absTarget} - no adapter markers (.cursor/.cursorrules/.copilot)`);
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
console.log(`→ ${absTarget}`);
|
|
122
|
-
for (const a of adapters) {
|
|
123
|
-
if (args.doctor) {
|
|
124
|
-
console.log(` [doctor] would run ${a} adapter`);
|
|
125
|
-
} else {
|
|
126
|
-
try {
|
|
127
|
-
await runAdapter(a, absTarget);
|
|
128
|
-
} catch (e) {
|
|
129
|
-
console.error(` [error] ${a}: ${e.message}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// --- main ------------------------------------------------------------------
|
|
137
|
-
|
|
138
|
-
const targets = [];
|
|
139
|
-
if (args.all || args.doctor) {
|
|
140
|
-
const prefs = loadPrefs();
|
|
141
|
-
for (const entry of prefs.global?.projectsTouched ?? []) {
|
|
142
|
-
if (entry && entry.path) targets.push(entry.path);
|
|
143
|
-
}
|
|
144
|
-
if (targets.length === 0) {
|
|
145
|
-
console.log(
|
|
146
|
-
"sync-adapters: prefs.global.projectsTouched is empty - run multi-agent:setup in a project first",
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (args.target) targets.push(args.target);
|
|
151
|
-
|
|
152
|
-
let ok = 0;
|
|
153
|
-
let failed = 0;
|
|
154
|
-
for (const t of targets) {
|
|
155
|
-
if (await syncTarget(t)) ok++;
|
|
156
|
-
else failed++;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Codex CLI is a GLOBAL-config tool (prompts + MCP under ~/.codex, no per-project
|
|
160
|
-
// prompt dir), so it is synced once - not per target. Run it when Codex is
|
|
161
|
-
// present on this machine.
|
|
162
|
-
const codexHome = (() => {
|
|
163
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
164
|
-
return home ? join(home, ".codex") : null;
|
|
165
|
-
})();
|
|
166
|
-
if (codexHome && existsSync(codexHome)) {
|
|
167
|
-
if (args.doctor) {
|
|
168
|
-
console.log("→ ~/.codex (global)\n [doctor] would run codex adapter (global)");
|
|
169
|
-
} else {
|
|
170
|
-
console.log("→ ~/.codex (global)");
|
|
171
|
-
try {
|
|
172
|
-
const mod = await import(join(PIPELINE_ROOT, "adapters", "codex.mjs"));
|
|
173
|
-
mod.install({ pipelineSrc: PIPELINE_ROOT, platformFilter: args.platform });
|
|
174
|
-
ok++;
|
|
175
|
-
} catch (e) {
|
|
176
|
-
console.error(` [error] codex: ${e.message}`);
|
|
177
|
-
failed++;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
console.log(`sync-adapters: ${ok} ok · ${failed} failed`);
|
|
183
|
-
process.exit(failed > 0 ? 1 : 0);
|