@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.
Files changed (30) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +10 -39
  3. package/install/index.mjs +9 -101
  4. package/package.json +1 -1
  5. package/pipeline/commands/multi-agent/delete.md +4 -5
  6. package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +1 -11
  7. package/pipeline/commands/multi-agent/refs/picker-contract.md +6 -10
  8. package/pipeline/commands/multi-agent/setup.md +0 -1
  9. package/pipeline/commands/multi-agent/sync.md +4 -73
  10. package/pipeline/commands/multi-agent/update.md +9 -0
  11. package/pipeline/lib/ask-choice.sh +1 -1
  12. package/pipeline/scripts/smoke-cross-cli-behavior.sh +0 -7
  13. package/pipeline/scripts/smoke-install-layout.sh +1 -2
  14. package/pipeline/skills/.skills-index.json +65 -20
  15. package/pipeline/skills/shared/README.md +1 -1
  16. package/pipeline/skills/shared/core/multi-agent-delete/SKILL.md +4 -4
  17. package/pipeline/skills/skills-index.md +24 -19
  18. package/install/_adapters.mjs +0 -73
  19. package/pipeline/adapters/_base.mjs +0 -640
  20. package/pipeline/adapters/antigravity.mjs +0 -140
  21. package/pipeline/adapters/codex.mjs +0 -159
  22. package/pipeline/adapters/copilot-chat-orchestration.mjs +0 -148
  23. package/pipeline/adapters/copilot-chat.mjs +0 -124
  24. package/pipeline/adapters/cursor-orchestration.mjs +0 -152
  25. package/pipeline/adapters/cursor.mjs +0 -146
  26. package/pipeline/scripts/smoke-adapters.sh +0 -276
  27. package/pipeline/scripts/smoke-delete-flow.sh +0 -151
  28. package/pipeline/scripts/smoke-shared-runtime.sh +0 -108
  29. package/pipeline/scripts/smoke-sync-adapters.sh +0 -113
  30. package/pipeline/scripts/sync-adapters.mjs +0 -183
@@ -1,152 +0,0 @@
1
- /**
2
- * @file Cursor orchestration layer - emits the FULL-pipeline artifacts (not
3
- * just the knowledge layer) that let Cursor run the multi-agent pipeline:
4
- *
5
- * .cursor/agents/ma-*.md one Cursor subagent per pipeline persona
6
- * (explorer, code-reviewer, the architects,
7
- * security-auditor, dev-critic, task-clarifier),
8
- * transformed from pipeline/agents/*.md.
9
- * .cursor/commands/multi-agent.md the orchestration driver: the 8-phase
10
- * flow adapted to Cursor's runtime (parallel
11
- * subagent dispatch for review, ask-choice picker
12
- * fallback for the approval gate).
13
- * .cursor/mcp.json registers the dev-toolkit MCP server (merged,
14
- * never clobbering the user's other servers).
15
- *
16
- * Cursor 2.4+ supports `.cursor/agents/*.md` (MD + YAML frontmatter, parallel
17
- * dispatch up to 8) and `.cursor/commands/*.md`, so the orchestration is
18
- * portable - the gaps vs Claude Code are the picker (degraded to ask-choice /
19
- * MCP) and hard blocking gates (advisory rules + CI). See refs/picker-contract.md.
20
- *
21
- * @module pipeline/adapters/cursor-orchestration
22
- */
23
-
24
- import { writeFileSync } from "fs";
25
- import { join } from "path";
26
- import {
27
- ensureDir,
28
- installPersonaAgents,
29
- installSharedRuntime,
30
- mergeMcpJson,
31
- removeFileAndPrune,
32
- removePrefixedFiles,
33
- REVIEWER_MODELS,
34
- rewriteScriptRefs,
35
- uninstallSharedRuntime,
36
- unmergeMcpJson,
37
- } from "./_base.mjs";
38
-
39
- const AGENT_PREFIX = "ma-";
40
-
41
- /**
42
- * @param {{ pipelineSrc: string, target: string }} opts
43
- * @returns {{ agents: number, command: boolean, mcp: "created"|"merged"|"skipped" }}
44
- */
45
- export function installOrchestration({ pipelineSrc, target }) {
46
- // 1. Transform each pipeline persona into a Cursor subagent. The cross-vendor
47
- // second reviewer is a code-reviewer pinned to a different model so Phase 4
48
- // is a 2-model review (the primary inherits the user's model, usually
49
- // Claude; the -x variant pins gpt-5.5) instead of single-model.
50
- const cm = REVIEWER_MODELS.cursor.crossModel;
51
- const { agents } = installPersonaAgents({
52
- pipelineSrc,
53
- outDir: join(target, ".cursor", "agents"),
54
- prefix: AGENT_PREFIX,
55
- fileFor: (name) => `${AGENT_PREFIX}${name}.md`,
56
- render: renderCursorAgent,
57
- crossReviewer: {
58
- name: `${AGENT_PREFIX}code-reviewer-x`,
59
- model: cm,
60
- note: `Cross-vendor reviewer pinned to \`${cm}\` so Phase 4 runs two different models (vs the primary \`ma-code-reviewer\`, which inherits your selected model). Verify the model id matches your Cursor build.`,
61
- },
62
- });
63
-
64
- // 2. Orchestration command (script refs rewritten to the shared runtime).
65
- const cmdOut = join(target, ".cursor", "commands");
66
- ensureDir(cmdOut);
67
- writeFileSync(join(cmdOut, "multi-agent.md"), rewriteScriptRefs(ORCHESTRATION_COMMAND));
68
-
69
- // 3. MCP server registration (merge into existing .cursor/mcp.json).
70
- const mcp = mergeMcpJson(join(target, ".cursor", "mcp.json"));
71
-
72
- // 4. Shared runtime: install the gate scripts/lib/schemas so the agents'
73
- // `$HOME/.multi-agent/scripts/...` references resolve and the gates run.
74
- const shared = installSharedRuntime(pipelineSrc);
75
-
76
- return { agents, command: true, mcp, sharedRuntime: shared.trees.length > 0 };
77
- }
78
-
79
- /**
80
- * @param {{ target: string }} opts
81
- */
82
- export function uninstallOrchestration({ target }) {
83
- let removed = removePrefixedFiles(join(target, ".cursor", "agents"), AGENT_PREFIX, ".md");
84
- const cmdDir = join(target, ".cursor", "commands");
85
- removed += removeFileAndPrune(join(cmdDir, "multi-agent.md"), cmdDir);
86
- // Remove only OUR MCP server entry; leave the user's other servers + file.
87
- unmergeMcpJson(join(target, ".cursor", "mcp.json"));
88
- // Remove the shared runtime (100% pipeline-owned).
89
- if (uninstallSharedRuntime()) removed++;
90
- return { removed };
91
- }
92
-
93
- /**
94
- * Map a pipeline persona's Opus/Sonnet/Haiku tier to a Cursor `model` value.
95
- * Cursor runs the user's selected model, so we default to `inherit` and record
96
- * the intended tier in the body - the orchestrator picks per-phase if the user
97
- * has multiple models configured.
98
- */
99
- function renderCursorAgent(persona, frontmatter, body, opts = {}) {
100
- const name = opts.name || `${AGENT_PREFIX}${persona}`;
101
- const model = opts.model || "inherit";
102
- const description = (frontmatter.description || `multi-agent pipeline ${persona}`).replace(/\n/g, " ").trim();
103
- const tier = frontmatter.model || "inherit";
104
- const fm = [
105
- "---",
106
- `name: ${name}`,
107
- `description: ${JSON.stringify(description)}`,
108
- `model: ${model}`,
109
- "readonly: true",
110
- "---",
111
- "",
112
- `> Pipeline persona \`${persona}\` (intended tier: ${tier}). Transformed for Cursor by`,
113
- "> @mmerterden/multi-agent-pipeline. Read-only analysis/review role; the main",
114
- "> agent performs edits. Picker/approval steps degrade per refs/picker-contract.md.",
115
- ...(opts.note ? ["> ", `> ${opts.note}`] : []),
116
- "",
117
- ].join("\n");
118
- // Rewrite logical pipeline/scripts refs in the persona body to the absolute
119
- // shared-runtime path so the gate scripts resolve from any cwd.
120
- return `${fm}${rewriteScriptRefs(body.trim())}\n`;
121
- }
122
-
123
- const ORCHESTRATION_COMMAND = `# /multi-agent - run the pipeline in Cursor
124
-
125
- > Installed by @mmerterden/multi-agent-pipeline. Drives the 8-phase pipeline
126
- > using Cursor subagents (\`.cursor/agents/ma-*.md\`) and the dev-toolkit MCP
127
- > server. Picker/approval steps follow refs/picker-contract.md (Cursor Plan-mode
128
- > question UI where available, else a numbered choice the agent presents inline
129
- > numbered prompt). Gate scripts are installed at \`$HOME/.multi-agent/scripts/\`
130
- > (evidence-gate.mjs, classify-intent.sh, learnings-ledger.mjs, validate-triage.mjs,
131
- > pre-commit-check.sh) and the subagents invoke them there. Unlike Claude Code
132
- > there is no PreToolUse hook forcing them, so they are workflow-enforced (run as
133
- > steps) rather than OS-blocked.
134
-
135
- Input: \`$ARGUMENTS\` (a Jira id, GitHub issue, repo#N, or a free-text task).
136
-
137
- ## Phases
138
-
139
- 0. **Init** - detect stack, resolve branch (\`bugfix/…\` or \`feature/…\`), confirm scope with the user.
140
- 1. **Analysis** - dispatch the \`ma-explorer\` subagent (read-only) to map touched areas, risks, and the stack; produce the analysis object (see analysis-output schema in the rules).
141
- 2. **Planning** - draft a task breakdown. Present it and ask the user to **Approve / Cancel / edit** via the picker contract (Plan-mode UI or a numbered prompt). Edits loop until approved.
142
- 3. **Dev** - the MAIN agent implements TDD-first (test, then code, then build), following \`.cursor/rules/*\`. Optionally run \`ma-dev-critic\` before review.
143
- 4. **Review** - dispatch the reviewer subagents IN PARALLEL for cross-model coverage: \`ma-code-reviewer\` (your selected model) AND \`ma-code-reviewer-x\` (pinned to a different vendor) so the code review runs on two models, plus the stack architect (\`ma-ios-architect\` / \`ma-android-architect\` / \`ma-backend-architect\`) and \`ma-security-auditor\` when the diff touches auth/secrets. Merge their findings, then triage (accept / defer / reject); record the \`consensus\` block (treat same-model agreement on judgment calls as \`unverified\`). Fix accepted blocking/important findings and re-review.
144
- 5. **Test** - offer an interactive test pass via the picker. Use the dev-toolkit MCP server (simulator / emulator / web) for UI checks.
145
- 6. **Commit** - \`{type}(scope): description\`; never auto-close issues; PR uses \`Ref: #N\`.
146
- 7. **Report** - summarize what changed, the review consensus (per reviewer), and cost.
147
-
148
- ## Rules
149
- Follow every \`.cursor/rules/multi-agent-*\` file (code style, TDD, security, review priorities). Attribute each finding to the subagent that produced it. Do not invent design values; cite the analysis. No "AI/generated by" in code or commits.
150
- `;
151
-
152
- export default { installOrchestration, uninstallOrchestration };
@@ -1,146 +0,0 @@
1
- /**
2
- * @file Cursor adapter - emits the knowledge layer (`.cursor/rules/*.mdc`,
3
- * modern 2025+, plus a legacy `.cursorrules` fallback) AND delegates to
4
- * cursor-orchestration.mjs for the orchestration layer: Cursor subagents
5
- * (`.cursor/agents/ma-*.md`), a `/multi-agent` command, and the dev-toolkit MCP
6
- * server. The deterministic-gate SCRIPTS are installed to a shared
7
- * `~/.multi-agent/` runtime (v9.4.0+) and the subagents invoke them by absolute
8
- * path, so the gates DO run on Cursor - the only gap vs Claude Code is that
9
- * there is no PreToolUse hook, so they are workflow-enforced not OS-blocked.
10
- * What ports: rules tree + external skills catalog + subagents + command + MCP.
11
- *
12
- * Target is the project root by default (process.cwd()) since Cursor rules
13
- * are repo-scoped, not user-scoped. Use --target=<path> to override.
14
- *
15
- * @module pipeline/adapters/cursor
16
- */
17
-
18
- import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
19
- import { join } from "path";
20
- import {
21
- collectSkills,
22
- concatSkills,
23
- inferGlobs,
24
- installAdapterFiles,
25
- jsonString,
26
- rel,
27
- removeDirIfEmpty,
28
- removePrefixedFiles,
29
- walkRules,
30
- withoutOrchestrationSkills,
31
- } from "./_base.mjs";
32
- import { installOrchestration, uninstallOrchestration } from "./cursor-orchestration.mjs";
33
-
34
- const NAME = "cursor";
35
- const RULES_DIR = ".cursor/rules";
36
- const LEGACY_FILE = ".cursorrules";
37
- const FILE_PREFIX = "multi-agent-";
38
-
39
- /**
40
- * @param {{ pipelineSrc: string, target: string, platformFilter?: "all"|"ios"|"android" }} opts
41
- */
42
- export function install({ pipelineSrc, target, platformFilter = "all" }) {
43
- const rulesOut = join(target, RULES_DIR);
44
- const skills = collectSkills(pipelineSrc, platformFilter);
45
-
46
- let written = installAdapterFiles({
47
- outDir: rulesOut,
48
- items: withoutOrchestrationSkills(skills),
49
- fileFor: (skill) => `${FILE_PREFIX}${skill.name}.mdc`,
50
- render: renderMdc,
51
- });
52
-
53
- // Pipeline rules tree (TDD, security, code-review, ...) - these stay
54
- // alwaysApply because they're stack-agnostic conventions.
55
- written += installAdapterFiles({
56
- outDir: rulesOut,
57
- items: walkRules(pipelineSrc),
58
- fileFor: (rule) => `${FILE_PREFIX}rule-${rule.name}.mdc`,
59
- render: (rule) => renderRuleMdc({ name: rule.name, body: rule.body, alwaysApply: true }),
60
- });
61
-
62
- // Legacy .cursorrules - single concatenated digest of orchestration overview
63
- // + non-orchestration skills, for users on Cursor versions that don't read
64
- // `.cursor/rules/*.mdc` yet. Marker-wrapped so uninstall can target it.
65
- const legacyPath = join(target, LEGACY_FILE);
66
- writeFileSync(legacyPath, renderLegacyDigest(skills));
67
-
68
- console.log(
69
- ` -> [cursor] ${written} rule files in ${rel(target, rulesOut)} + legacy ${LEGACY_FILE}`,
70
- );
71
-
72
- // Orchestration layer - Cursor subagents + /multi-agent command + MCP, so
73
- // the FULL pipeline (not just the knowledge layer) runs in Cursor.
74
- const orch = installOrchestration({ pipelineSrc, target });
75
- console.log(
76
- ` -> [cursor] orchestration: ${orch.agents} subagents (.cursor/agents/ma-*) + /multi-agent command + mcp.json (${orch.mcp})`,
77
- );
78
- }
79
-
80
- /**
81
- * @param {{ target: string }} opts
82
- */
83
- export function uninstall({ target }) {
84
- let removed = removePrefixedFiles(join(target, RULES_DIR), FILE_PREFIX, ".mdc");
85
-
86
- const legacyPath = join(target, LEGACY_FILE);
87
- if (existsSync(legacyPath)) {
88
- const content = readFileSync(legacyPath, "utf-8");
89
- if (content.includes("multi-agent-pipeline")) {
90
- rmSync(legacyPath, { force: true });
91
- removed++;
92
- }
93
- }
94
-
95
- const orch = uninstallOrchestration({ target });
96
- removed += orch.removed;
97
-
98
- // Drop the .cursor parent if it's empty AFTER orchestration removed its
99
- // agents/ + commands/ dirs (we never remove a user's other Cursor configs).
100
- removeDirIfEmpty(join(target, ".cursor"));
101
-
102
- return { removed };
103
- }
104
-
105
- /**
106
- * @param {{ name: string, frontmatter: Record<string,string>, body: string }} skill
107
- */
108
- function renderMdc(skill) {
109
- const description = (skill.frontmatter.description || "").replace(/\n/g, " ").trim();
110
- const globs = inferGlobs(skill.name);
111
- const fmLines = ["---", `description: ${jsonString(description)}`];
112
- if (globs) fmLines.push(`globs: ${globs}`);
113
- fmLines.push(`alwaysApply: ${globs ? "false" : "false"}`);
114
- fmLines.push("---", "");
115
- return `${fmLines.join("\n")}\n${skill.body.trim()}\n`;
116
- }
117
-
118
- /**
119
- * @param {{ name: string, body: string, alwaysApply: boolean }} rule
120
- */
121
- function renderRuleMdc({ name, body, alwaysApply }) {
122
- const fmLines = [
123
- "---",
124
- `description: ${jsonString(`multi-agent-pipeline rule: ${name}`)}`,
125
- `alwaysApply: ${alwaysApply}`,
126
- "---",
127
- "",
128
- ];
129
- return `${fmLines.join("\n")}\n${body.trim()}\n`;
130
- }
131
-
132
- function renderLegacyDigest(skills) {
133
- const knowledge = withoutOrchestrationSkills(skills);
134
- const header = `# multi-agent-pipeline (Cursor legacy .cursorrules)
135
-
136
- > Installed by @mmerterden/multi-agent-pipeline.
137
- > The 8-phase pipeline orchestration (parallel review, autopilot, subagent
138
- > dispatch) is not available in Cursor - only the knowledge layer is exported
139
- > here. For full pipeline, use Claude Code or Copilot CLI.
140
- > Run \`npx @mmerterden/multi-agent-pipeline uninstall --cursor\` to remove.
141
-
142
- `;
143
- return header + concatSkills(knowledge.slice(0, 30)); // cap legacy file at top 30
144
- }
145
-
146
- export default { name: NAME, install, uninstall };
@@ -1,276 +0,0 @@
1
- #!/usr/bin/env bash
2
- # smoke-adapters.sh - third-party AI tool adapters (cursor + copilot-chat).
3
- #
4
- # As of v8.5.4 the supported set narrowed to the two adapters the pipeline
5
- # owner actually uses: Cursor (knowledge layer) and GitHub Copilot Chat
6
- # (knowledge layer). Windsurf / Cline / Zed / Continue adapters were removed.
7
- #
8
- # Round-trip integrity:
9
- # 1. Install writes the expected layout (file counts, dir structure)
10
- # 2. Orchestration skills (multi-agent-*) are NOT exported - knowledge layer only
11
- # 3. Uninstall removes ONLY pipeline-managed artifacts
12
- # 4. Marker-wrapped block (copilot-chat) preserves user-authored content
13
- # 5. Idempotent install - running twice produces identical tree
14
- # 6. Platform filter (--platform=ios|android) narrows the skill set
15
- # 7. Default behavior - no flags installs Claude only (backward compat)
16
- #
17
- # All work happens in mktemp dirs; the user's actual project root is never
18
- # touched.
19
-
20
- set -uo pipefail
21
-
22
- REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
23
- INSTALL="$REPO_ROOT/install.js"
24
-
25
- PASS=0
26
- FAIL=0
27
- pass() { PASS=$((PASS + 1)); echo " ✓ $1"; }
28
- fail() { FAIL=$((FAIL + 1)); echo " ✗ $1"; }
29
-
30
- cleanup() {
31
- [ -n "${TMP_CURSOR:-}" ] && rm -rf "$TMP_CURSOR"
32
- [ -n "${TMP_COPILOTCHAT:-}" ] && rm -rf "$TMP_COPILOTCHAT"
33
- [ -n "${TMP_ANTIGRAVITY:-}" ] && rm -rf "$TMP_ANTIGRAVITY"
34
- [ -n "${TMP_IDEMP:-}" ] && rm -rf "$TMP_IDEMP"
35
- [ -n "${TMP_FILTER:-}" ] && rm -rf "$TMP_FILTER"
36
- }
37
- trap cleanup EXIT
38
-
39
- # ──────────────────────────────────────────────────────────────────────────
40
- echo "→ 1. Cursor adapter - install + uninstall round-trip"
41
- TMP_CURSOR=$(mktemp -d)
42
- node "$INSTALL" --cursor --target="$TMP_CURSOR" >/dev/null 2>&1 || fail "cursor install exited non-zero"
43
-
44
- cursor_count=$(find "$TMP_CURSOR" -type f | wc -l | tr -d ' ')
45
- if [ "$cursor_count" -gt 100 ]; then
46
- pass "cursor install wrote $cursor_count files"
47
- else
48
- fail "cursor install produced too few files ($cursor_count, expected > 100)"
49
- fi
50
-
51
- if [ -f "$TMP_CURSOR/.cursorrules" ]; then
52
- pass "cursor wrote legacy .cursorrules"
53
- else
54
- fail "cursor missing legacy .cursorrules"
55
- fi
56
-
57
- if [ -d "$TMP_CURSOR/.cursor/rules" ]; then
58
- pass "cursor wrote .cursor/rules/ directory"
59
- else
60
- fail "cursor missing .cursor/rules/ directory"
61
- fi
62
-
63
- # Orchestration filter - multi-agent-{dev,review,...} should NOT be exported
64
- orch_leaked=$(find "$TMP_CURSOR/.cursor/rules" -name "multi-agent-multi-agent*" -o -name "multi-agent-dev.mdc" -o -name "multi-agent-review.mdc" 2>/dev/null | wc -l | tr -d ' ')
65
- if [ "$orch_leaked" -eq 0 ]; then
66
- pass "cursor: orchestration commands correctly filtered out"
67
- else
68
- fail "cursor: $orch_leaked orchestration command(s) leaked into knowledge layer"
69
- fi
70
-
71
- # Orchestration layer - Cursor subagents + /multi-agent command + MCP.
72
- ma_agents=$(find "$TMP_CURSOR/.cursor/agents" -name "ma-*.md" 2>/dev/null | wc -l | tr -d ' ')
73
- if [ "$ma_agents" -ge 8 ]; then
74
- pass "cursor orchestration: $ma_agents ma-* subagents emitted"
75
- else
76
- fail "cursor orchestration: too few ma-* subagents ($ma_agents, expected >= 8)"
77
- fi
78
- if [ -f "$TMP_CURSOR/.cursor/commands/multi-agent.md" ]; then
79
- pass "cursor orchestration: /multi-agent command present"
80
- else
81
- fail "cursor orchestration: .cursor/commands/multi-agent.md missing"
82
- fi
83
- if grep -q "dev-toolkit" "$TMP_CURSOR/.cursor/mcp.json" 2>/dev/null; then
84
- pass "cursor orchestration: mcp.json registers dev-toolkit"
85
- else
86
- fail "cursor orchestration: mcp.json missing dev-toolkit server"
87
- fi
88
-
89
- # Round-trip via direct adapter call (CLI uninstall not yet wired into smoke).
90
- node --input-type=module -e "
91
- import('$REPO_ROOT/pipeline/adapters/cursor.mjs').then(m => {
92
- m.default.uninstall({ target: '$TMP_CURSOR' });
93
- });
94
- " >/dev/null 2>&1
95
-
96
- ma_after=$(find "$TMP_CURSOR/.cursor/agents" -name "ma-*.md" 2>/dev/null | wc -l | tr -d ' ')
97
- if [ "$ma_after" -eq 0 ] && [ ! -f "$TMP_CURSOR/.cursor/commands/multi-agent.md" ]; then
98
- pass "cursor orchestration uninstall: agents + command removed"
99
- else
100
- fail "cursor orchestration uninstall: left $ma_after agent(s) / command behind"
101
- fi
102
-
103
- remaining=$(find "$TMP_CURSOR/.cursor/rules" -name "multi-agent-*" 2>/dev/null | wc -l | tr -d ' ')
104
- if [ "$remaining" -eq 0 ]; then
105
- pass "cursor uninstall: 0 files remain"
106
- else
107
- fail "cursor uninstall left $remaining file(s) behind"
108
- fi
109
-
110
- # ──────────────────────────────────────────────────────────────────────────
111
- echo "→ 2. Copilot Chat adapter - marker-wrapped block preservation"
112
- TMP_COPILOTCHAT=$(mktemp -d)
113
- mkdir -p "$TMP_COPILOTCHAT/.github"
114
- USER_CONTENT="# My personal Copilot Chat instructions"
115
- USER_MARKER="DO_NOT_DELETE_THIS_LINE"
116
- {
117
- echo "$USER_CONTENT"
118
- echo ""
119
- echo "$USER_MARKER"
120
- } > "$TMP_COPILOTCHAT/.github/copilot-instructions.md"
121
-
122
- node "$INSTALL" --copilot-chat --target="$TMP_COPILOTCHAT" >/dev/null 2>&1
123
-
124
- if grep -q "$USER_MARKER" "$TMP_COPILOTCHAT/.github/copilot-instructions.md"; then
125
- pass "copilot-chat: user content preserved through install"
126
- else
127
- fail "copilot-chat: user content lost during install"
128
- fi
129
-
130
- if grep -q "multi-agent-pipeline:begin" "$TMP_COPILOTCHAT/.github/copilot-instructions.md"; then
131
- pass "copilot-chat: BEGIN marker present"
132
- else
133
- fail "copilot-chat: BEGIN marker missing"
134
- fi
135
-
136
- if grep -q "multi-agent-pipeline:end" "$TMP_COPILOTCHAT/.github/copilot-instructions.md"; then
137
- pass "copilot-chat: END marker present"
138
- else
139
- fail "copilot-chat: END marker missing"
140
- fi
141
-
142
- # Per-skill instruction files
143
- perskill_count=$(find "$TMP_COPILOTCHAT/.github/instructions" -name "multi-agent-*.instructions.md" 2>/dev/null | wc -l | tr -d ' ')
144
- if [ "$perskill_count" -gt 50 ]; then
145
- pass "copilot-chat: $perskill_count per-skill instruction files written"
146
- else
147
- fail "copilot-chat: per-skill instruction count too low ($perskill_count)"
148
- fi
149
-
150
- # Orchestration layer - custom agents + /multi-agent prompt + VS Code MCP.
151
- cc_agents=$(find "$TMP_COPILOTCHAT/.github/agents" -name "ma-*.agent.md" 2>/dev/null | wc -l | tr -d ' ')
152
- [ "$cc_agents" -ge 8 ] && pass "copilot-chat orchestration: $cc_agents custom agents emitted" || fail "copilot-chat orchestration: too few agents ($cc_agents)"
153
- [ -f "$TMP_COPILOTCHAT/.github/prompts/multi-agent.prompt.md" ] && pass "copilot-chat orchestration: /multi-agent prompt present" || fail "copilot-chat orchestration: prompt missing"
154
- grep -q "dev-toolkit" "$TMP_COPILOTCHAT/.vscode/mcp.json" 2>/dev/null && pass "copilot-chat orchestration: .vscode/mcp.json registers dev-toolkit" || fail "copilot-chat orchestration: .vscode/mcp.json missing dev-toolkit"
155
-
156
- # Round-trip
157
- node --input-type=module -e "
158
- import('$REPO_ROOT/pipeline/adapters/copilot-chat.mjs').then(m => {
159
- m.default.uninstall({ target: '$TMP_COPILOTCHAT' });
160
- });
161
- " >/dev/null 2>&1
162
-
163
- if grep -q "$USER_MARKER" "$TMP_COPILOTCHAT/.github/copilot-instructions.md" 2>/dev/null; then
164
- pass "copilot-chat uninstall: user content survived"
165
- else
166
- fail "copilot-chat uninstall: user content lost"
167
- fi
168
-
169
- if ! grep -q "multi-agent-pipeline" "$TMP_COPILOTCHAT/.github/copilot-instructions.md" 2>/dev/null; then
170
- pass "copilot-chat uninstall: no pipeline content remains"
171
- else
172
- fail "copilot-chat uninstall: pipeline content leaked"
173
- fi
174
-
175
- # ──────────────────────────────────────────────────────────────────────────
176
- echo "→ 2c. Antigravity adapter - orchestration round-trip + AGENTS.md preservation"
177
- TMP_ANTIGRAVITY=$(mktemp -d)
178
- printf '# user agents\nKEEP_ANTI_LINE\n' > "$TMP_ANTIGRAVITY/AGENTS.md"
179
- node "$INSTALL" --antigravity --target="$TMP_ANTIGRAVITY" >/dev/null 2>&1 || fail "antigravity install exited non-zero"
180
- anti_rules=$(find "$TMP_ANTIGRAVITY/.agent/rules" -name "multi-agent-*.md" 2>/dev/null | wc -l | tr -d ' ')
181
- [ "$anti_rules" -ge 8 ] && pass "antigravity: $anti_rules rules in .agent/rules" || fail "antigravity: too few rules ($anti_rules)"
182
- [ -f "$TMP_ANTIGRAVITY/.agent/workflows/multi-agent.md" ] && pass "antigravity: /multi-agent workflow present" || fail "antigravity: workflow missing"
183
- grep -q "KEEP_ANTI_LINE" "$TMP_ANTIGRAVITY/AGENTS.md" && pass "antigravity: user AGENTS.md content preserved" || fail "antigravity: user content lost"
184
- grep -q "dev-toolkit" "$TMP_ANTIGRAVITY/.agent/mcp_config.json" 2>/dev/null && pass "antigravity: mcp_config registers dev-toolkit" || fail "antigravity: mcp_config missing dev-toolkit"
185
- node --input-type=module -e "import('$REPO_ROOT/pipeline/adapters/antigravity.mjs').then(m => m.default.uninstall({ target: '$TMP_ANTIGRAVITY' }));" >/dev/null 2>&1
186
- anti_after=$(find "$TMP_ANTIGRAVITY/.agent/rules" -name "multi-agent-*.md" 2>/dev/null | wc -l | tr -d ' ')
187
- if [ "$anti_after" -eq 0 ] && grep -q "KEEP_ANTI_LINE" "$TMP_ANTIGRAVITY/AGENTS.md" 2>/dev/null && ! grep -q "multi-agent-pipeline:begin" "$TMP_ANTIGRAVITY/AGENTS.md" 2>/dev/null; then
188
- pass "antigravity uninstall: artifacts removed, user content survived"
189
- else
190
- fail "antigravity uninstall: incomplete (rules=$anti_after)"
191
- fi
192
-
193
- # ──────────────────────────────────────────────────────────────────────────
194
- echo "→ 2d. Codex CLI adapter - global install round-trip (~/.codex)"
195
- TMP_CODEX_HOME=$(mktemp -d)
196
- printf '# user codex memory\nKEEP_CODEX_LINE\n' > "$TMP_CODEX_HOME/.codex_seed" # marker file, not AGENTS.md
197
- mkdir -p "$TMP_CODEX_HOME/.codex"
198
- printf 'model = "gpt-5.5-codex"\n' > "$TMP_CODEX_HOME/.codex/config.toml" # pre-existing user TOML
199
- HOME="$TMP_CODEX_HOME" node "$INSTALL" --codex >/dev/null 2>&1 || fail "codex install exited non-zero"
200
- [ -f "$TMP_CODEX_HOME/.codex/prompts/multi-agent.md" ] && pass "codex: /multi-agent prompt present" || fail "codex: prompt missing"
201
- [ -f "$TMP_CODEX_HOME/.codex/AGENTS.md" ] && pass "codex: AGENTS.md skill index present" || fail "codex: AGENTS.md missing"
202
- grep -q "dev-toolkit" "$TMP_CODEX_HOME/.codex/config.toml" 2>/dev/null && pass "codex: config.toml registers dev-toolkit mcp" || fail "codex: config.toml missing mcp block"
203
- grep -q 'model = "gpt-5.5-codex"' "$TMP_CODEX_HOME/.codex/config.toml" 2>/dev/null && pass "codex: pre-existing user TOML preserved" || fail "codex: user TOML clobbered"
204
- HOME="$TMP_CODEX_HOME" node --input-type=module -e "import('$REPO_ROOT/pipeline/adapters/codex.mjs').then(m => m.default.uninstall());" >/dev/null 2>&1
205
- if [ ! -f "$TMP_CODEX_HOME/.codex/prompts/multi-agent.md" ] && grep -q 'model = "gpt-5.5-codex"' "$TMP_CODEX_HOME/.codex/config.toml" 2>/dev/null && ! grep -q "dev-toolkit" "$TMP_CODEX_HOME/.codex/config.toml" 2>/dev/null; then
206
- pass "codex uninstall: artifacts removed, user TOML survived"
207
- else
208
- fail "codex uninstall: incomplete"
209
- fi
210
- rm -rf "$TMP_CODEX_HOME"
211
-
212
- # ──────────────────────────────────────────────────────────────────────────
213
- echo "→ 3. Idempotency - install twice produces identical tree"
214
- TMP_IDEMP=$(mktemp -d)
215
- node "$INSTALL" --cursor --target="$TMP_IDEMP" >/dev/null 2>&1
216
- HASH_ONE=$(find "$TMP_IDEMP" -type f -exec md5 -q {} \; 2>/dev/null | sort | md5 -q 2>/dev/null || \
217
- find "$TMP_IDEMP" -type f -exec md5sum {} \; 2>/dev/null | sort | md5sum 2>/dev/null | awk '{print $1}')
218
- node "$INSTALL" --cursor --target="$TMP_IDEMP" >/dev/null 2>&1
219
- HASH_TWO=$(find "$TMP_IDEMP" -type f -exec md5 -q {} \; 2>/dev/null | sort | md5 -q 2>/dev/null || \
220
- find "$TMP_IDEMP" -type f -exec md5sum {} \; 2>/dev/null | sort | md5sum 2>/dev/null | awk '{print $1}')
221
- if [ -n "$HASH_ONE" ] && [ "$HASH_ONE" = "$HASH_TWO" ]; then
222
- pass "cursor install idempotent (same tree hash on second run)"
223
- else
224
- fail "cursor install NOT idempotent (hash drift: $HASH_ONE vs $HASH_TWO)"
225
- fi
226
-
227
- # ──────────────────────────────────────────────────────────────────────────
228
- echo "→ 4. Platform filter - ios vs android narrow the skill set"
229
- TMP_FILTER=$(mktemp -d)
230
- node "$INSTALL" --cursor --platform=ios --target="$TMP_FILTER" >/dev/null 2>&1
231
- ios_count=$(find "$TMP_FILTER/.cursor/rules" -type f 2>/dev/null | wc -l | tr -d ' ')
232
- rm -rf "$TMP_FILTER" && TMP_FILTER=$(mktemp -d)
233
- node "$INSTALL" --cursor --platform=android --target="$TMP_FILTER" >/dev/null 2>&1
234
- android_count=$(find "$TMP_FILTER/.cursor/rules" -type f 2>/dev/null | wc -l | tr -d ' ')
235
- rm -rf "$TMP_FILTER" && TMP_FILTER=$(mktemp -d)
236
- node "$INSTALL" --cursor --platform=all --target="$TMP_FILTER" >/dev/null 2>&1
237
- all_count=$(find "$TMP_FILTER/.cursor/rules" -type f 2>/dev/null | wc -l | tr -d ' ')
238
-
239
- if [ "$ios_count" -gt 0 ] && [ "$ios_count" -lt "$all_count" ]; then
240
- pass "platform=ios filter narrowed $all_count → $ios_count files"
241
- else
242
- fail "platform=ios did not narrow (ios=$ios_count, all=$all_count)"
243
- fi
244
-
245
- if [ "$android_count" -gt 0 ] && [ "$android_count" -lt "$all_count" ]; then
246
- pass "platform=android filter narrowed $all_count → $android_count files"
247
- else
248
- fail "platform=android did not narrow (android=$android_count, all=$all_count)"
249
- fi
250
-
251
- # ──────────────────────────────────────────────────────────────────────────
252
- echo "→ 5. Default behavior - no flags installs Claude (backward compat)"
253
- # We don't actually install to $HOME here; just verify the flag math.
254
- RESULT=$(node -e "
255
- const flags = [];
256
- const TOOL_FLAGS = ['--claude', '--copilot', '--cursor', '--copilot-chat'];
257
- const isExplicitlyTargeted = flags.some(f => TOOL_FLAGS.includes(f)) || flags.includes('--all') || flags.includes('--all-tools');
258
- const forClaude = flags.includes('--claude') || flags.includes('--all') || flags.includes('--all-tools') || !isExplicitlyTargeted;
259
- const forCursor = flags.includes('--cursor') || flags.includes('--all-tools');
260
- console.log(JSON.stringify({ forClaude, forCursor }));
261
- ")
262
- if echo "$RESULT" | grep -q '"forClaude":true'; then
263
- pass "no-flag default still installs Claude (backward compat)"
264
- else
265
- fail "no-flag default broken: $RESULT"
266
- fi
267
- if echo "$RESULT" | grep -q '"forCursor":false'; then
268
- pass "no-flag default does NOT install adapter tools"
269
- else
270
- fail "no-flag default leaked into adapter install: $RESULT"
271
- fi
272
-
273
- # ──────────────────────────────────────────────────────────────────────────
274
- echo ""
275
- echo "══ adapters smoke: $PASS passed, $FAIL failed ══"
276
- [ "$FAIL" -eq 0 ]