@mmerterden/multi-agent-pipeline 10.5.0 → 10.7.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 +55 -0
- package/README.md +11 -42
- package/install/index.mjs +9 -101
- package/package.json +1 -1
- package/pipeline/agents/android-architect.md +3 -3
- package/pipeline/agents/backend-architect.md +3 -3
- package/pipeline/agents/code-reviewer.md +3 -3
- package/pipeline/agents/ios-architect.md +3 -3
- package/pipeline/commands/multi-agent/finish.md +4 -4
- package/pipeline/commands/multi-agent/refs/features/model-fallback.md +18 -10
- package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +1 -11
- package/pipeline/commands/multi-agent/setup.md +18 -1
- package/pipeline/commands/multi-agent/stack.md +1 -1
- package/pipeline/commands/multi-agent/sync.md +5 -74
- package/pipeline/commands/multi-agent/update.md +9 -0
- package/pipeline/scripts/README.md +0 -1
- package/pipeline/scripts/build-stack-plugins.mjs +2 -2
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +0 -7
- package/pipeline/scripts/smoke-install-layout.sh +1 -2
- package/pipeline/scripts/smoke-model-fallback.sh +11 -10
- package/pipeline/skills/shared/core/multi-agent-finish/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +1 -1
- 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-shared-runtime.sh +0 -108
- package/pipeline/scripts/smoke-stack-swap.sh +0 -132
- package/pipeline/scripts/smoke-sync-adapters.sh +0 -113
- package/pipeline/scripts/stack-swap.sh +0 -182
- 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 ]
|