A full writeup of the investigation, fix, and shipped release for ruvnet/ruflo's two-install-track divergence (npm vs. the Claude Code marketplace plugin).
- ADR:
v3/docs/adr/ADR-382-init-scaffold-content-drift-remediation.md - Tracking issue: #2971
- Shipped:
@claude-flow/cli,claude-flow,ruflo@ 3.38.0 — release notes
ruflo ships two independent install tracks:
npm install @claude-flow/cli(or theruflo/claude-flowwrappers) — pinned, versioned tarball.- The Claude Code marketplace plugin — installed via
git clone/git pullof this repo, no build step, always resolves whatever's onmain.
An investigation into where those two tracks silently diverge surfaced four independently-verified defects. All four were re-verified directly against main at HEAD (through commit 4ac1ab9ff, v3.37.0) before any fix landed — none of the numbers below were trusted from the originating report without a fresh grep/ls/diff against the live tree.
ADR-128 Phase 2 deleted 9 forked agent files from the init template — coder.md, researcher.md, reviewer.md, tester.md, memory-specialist.md, security-auditor.md, sparc-orchestrator.md, goal-planner.md, adr-architect.md — making each owning plugin (ruflo-core, ruflo-testgen, ruflo-rag-memory, ruflo-security-audit, ruflo-sparc, ruflo-goals, ruflo-adr) canonical instead.
$ ls v3/@claude-flow/cli/.claude/agents/ | grep -E 'coder|researcher|reviewer|tester'
(empty)
ADR-128's own Consequences section named the fix: "Mitigation: ruflo migrate should detect removed agents and print install suggestions." Reading migrate.ts in full (783 lines) showed five subcommands (status/run/verify/rollback/breaking) — all scoped to the unrelated v2→v3 config-format migration. None referenced agent files, AGENTS_MAP, or plugin install suggestions. The promised mitigation simply didn't exist.
$ grep -rl 'npx claude-flow' v3/@claude-flow/cli/.claude | wc -l
172
$ grep -rlE 'sparc_mode|task_orchestrate|memory_usage' v3/@claude-flow/cli/.claude | wc -l
96
172 files referenced the stale npx claude-flow invocation form (superseded by npx @claude-flow/cli@latest). 96 referenced MCP tool names (sparc_mode, task_orchestrate, memory_usage, and more) absent from the live mcp__claude-flow__* registry. A fresh ruflo init shipped hundreds of files whose worked examples silently fail if copied verbatim.
// plugins/ruflo-core/.mcp.json (before)
{
"mcpServers": {
"ruflo": {
"command": "npx",
"args": ["-y", "@claude-flow/cli@latest"]
}
}
}Every marketplace-plugin user's MCP server re-resolved to whatever was newest on the npm registry at each launch — independent of, and often diverging from, whatever version they'd separately npm install-ed. .claude/helpers/hook-handler.cjs already solved this exact problem for its own hook-refresh spawn path (resolveCliBinForHook() — a local-bin-candidate walk with a dist/src/index.js existence guard against source-only marketplace checkouts). The plugin's own MCP launch path had no equivalent.
$ ls plugins/ | wc -l # 37 (36 plugin dirs + README.md)
$ jq '.plugins | length' .claude-plugin/marketplace.json # 35
ruflo-agntcy, ruflo-bbs-federation, and ruflo-business-pods existed fully built in plugins/ but were absent from the marketplace manifest — uninstallable via ruflo plugins install <name> despite shipping in the repo.
Rather than one sequential PR, ADR-382's three parts were dispatched as independent background agents, each in its own isolated git worktree (avoiding write conflicts on shared files), each responsible for its own branch → PR → tests → CI:
| Part | Scope | PR |
|---|---|---|
| A | Local-bin-first resolver for plugins/ruflo-core/.mcp.json (mirrors resolveCliBinForHook()), + registered the 3 missing plugins in marketplace.json |
#2975 |
| B | Implemented ADR-128's named mitigation — migrate.ts now detects the 9 removed agents against a project's .claude/agents/** tree and Claude Code's real plugin-install manifest, printing the exact ruflo plugins install <plugin> command per gap |
#2973 |
| C | scripts/smoke-init-scaffold-references.mjs — a warn-only CI guard deriving live MCP-tool and CLI-subcommand sets (not hand-maintained lists) and flagging drift; a deterministic remap of all 701 npx claude-flow occurrences across 142 files; 410 dead-tool-name occurrences left deliberately FLAGged rather than guessed at |
#2974 |
Part C's guard was scoped up mid-flight to cover two more regression classes: an unpinned-@latest check across every plugins/*/.mcp.json (not just ruflo-core), and a plugins/* ↔ marketplace.json completeness check — so the exact regression class that hid 3 plugins can't recur silently.
Each PR ran the full CI matrix (Test Suite, Type Check V3, CodeQL, ~140 smoke jobs spanning ADR-102/112/125/127/128/129/130/131, Windows/macOS/Linux hook execution, plugin install-safety, witness-manifest verification) before merge. Merge order was A → B → C: Part C's own guard checks (plugin pin regression + marketplace completeness) depend on Part A's fix being present on main to report clean rather than warn-only-red, so after A and B landed, C's branch was merged with main and its guard re-run to confirm 0 violations on those two checks before merging.
FAIL: check 2 (dead MCP tool references) — 410 violation(s) ← expected, tracked follow-up
total: 410 violation(s) across 4 checks ← checks 1/3/4 clean
Backward-compatible additions, no breaking changes → minor bump, 3.37.0 → 3.38.0, all three packages in lockstep (@claude-flow/cli, claude-flow, ruflo), all three dist-tags (latest/alpha/v3alpha) verified against the live registry post-publish.
Post-publish, the actual published tarball was pulled and inspected — not just the dist-tag metadata:
$ npm pack @claude-flow/cli@3.38.0 && tar -xzf claude-flow-cli-3.38.0.tgz
$ grep -c "REMOVED_AGENTS" package/dist/src/commands/migrate.js
3
$ grep -rn 'npx claude-flow[^-@/]' package/.claude/ | grep -v '@claude-flow@\|claude-flow-codex'
(0 matches — was 172)
Part A's fix lives on the git-based marketplace-plugin track directly (already effective the moment it merged to main) — it isn't part of the npm tarball's files array, so no publish was required for that piece specifically.