@curdx/flow 2.0.0-beta.11 → 2.0.0-beta.13

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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
9
- "version": "2.0.0-beta.11"
9
+ "version": "2.0.0-beta.13"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.0.0-beta.11",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
5
5
  "author": {
6
6
  "name": "wdx",
@@ -16,21 +16,5 @@
16
16
  "orchestration",
17
17
  "karpathy",
18
18
  "claude-code"
19
- ],
20
- "mcpServers": {
21
- "context7": {
22
- "command": "npx",
23
- "args": [
24
- "-y",
25
- "@upstash/context7-mcp@latest"
26
- ]
27
- },
28
- "sequential-thinking": {
29
- "command": "npx",
30
- "args": [
31
- "-y",
32
- "@modelcontextprotocol/server-sequential-thinking"
33
- ]
34
- }
35
- }
19
+ ]
36
20
  }
package/CHANGELOG.md CHANGED
@@ -4,10 +4,46 @@ All notable changes to CurDX-Flow will be documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### BREAKING
8
+
9
+ - **`context7` and `sequential-thinking` moved from plugin-bundled MCPs to user-level MCPs.** Previously `.claude-plugin/plugin.json` declared both in `mcpServers`, so Claude Code auto-registered them as `plugin:curdx-flow:context7` and `plugin:curdx-flow:sequential-thinking` when the plugin installed. This is no longer the case — the `mcpServers` block is gone. Instead, `curdx-flow install` now runs `claude mcp add context7 …` + `claude mcp add sequential-thinking …` against the user's `~/.claude.json`, which keeps them as standard user-level registrations named `context7` and `sequential-thinking`.
10
+
11
+ **Why**: every early adopter who ran `claude mcp add context7 -- npx -y @upstash/context7-mcp --api-key ctx7sk-…` before installing curdx-flow ended up with *both* entries side-by-side — doubling MCP processes, making API-key routing unpredictable (random between free-tier plugin copy and paid user copy), and forcing an awkward env-var migration path. User-level registration matches the way every other MCP in the ecosystem is installed, keeps tool names standard (`mcp__context7__*` instead of `mcp__plugin_curdx-flow_context7__*`), and lets users put an `--api-key` directly in their own entry without any env-var indirection. Every agent / knowledge doc / command in the repo already calls the standard tool-name form, so this is a **zero-change-for-callers** migration.
12
+
13
+ **Migration for existing users**:
14
+
15
+ ```bash
16
+ # Upgrade the plugin — this removes the plugin-bundled MCPs and
17
+ # registers them at user-level (preserving any existing user-level entry
18
+ # with a custom --api-key).
19
+ npx @curdx/flow@latest upgrade
20
+
21
+ # Restart Claude Code so the plugin manifest reloads.
22
+ ```
23
+
24
+ After upgrade, `claude mcp list` will show `context7` and
25
+ `sequential-thinking` (user-level) but NOT `plugin:curdx-flow:context7` etc.
26
+ `npx @curdx/flow doctor` confirms with "user-level (standard)" in the
27
+ "MCP Servers" section.
28
+
29
+ ### Added
30
+
31
+ - `cli/registry.js` exports a new `BUNDLED_MCPS` constant — the source-of-truth list of MCPs that `install` registers at user-level and `uninstall` optionally removes.
32
+ - `cli/install.js` Step 3.5 — registers the required MCPs via `claude mcp add`. Detects pre-existing user-level entries (e.g. your hand-configured context7 with an API key) and preserves them instead of overwriting.
33
+ - `cli/uninstall.js` Step 4.5 — asks whether to `claude mcp remove` the registered MCPs (default: no, because other tools may depend on them).
34
+ - `cli/doctor.js` — now reports "user-level (standard)" for each required MCP when it's registered the right way, and "(legacy)" with a migration hint when the old plugin-bundled form is still active.
35
+
36
+ ### Changed
37
+
38
+ - The "Duplicate MCP" warning in `doctor` (added in beta.11) is now framed as "Legacy plugin-bundled MCPs still present" with a concrete migration command, since the new architecture reserves duplication strictly for the upgrade-transition window.
39
+ - `docs/getting-started.md` "Optional: use your paid context7 API key" section rewritten around the user-level path (add `--api-key` to your `claude mcp add` command) since env-var indirection is no longer the only way to route a key to the plugin copy.
40
+
41
+ ## [2.0.0-beta.11] - 2026-04-22
42
+
7
43
  ### Added
8
44
 
9
- - `cli/doctor.js` now detects when a user-level MCP in `~/.claude.json` (typically `context7` added via `claude mcp add …`) duplicates a plugin-bundled MCP from `plugin.json` (`plugin:curdx-flow:context7`). The two run as independent processes and Claude routes tool calls unpredictably between them. Doctor surfaces the overlap, shows the user-level command (with any `--api-key` flag) alongside the plugin command, and prints the exact remediation: `export CONTEXT7_API_KEY=…` + `claude mcp remove context7`. Backed by `cli/utils.js:readUserMcpConfig()` + `findDuplicateMcps()` and 3 new tests in `test/utils.test.js`.
10
- - `docs/getting-started.md` gained an "Optional: use your paid context7 API key" section documenting the env-var path (upstream `@upstash/context7-mcp` natively reads `process.env.CONTEXT7_API_KEY`) and the cleanup command for users with a pre-existing user-level registration.
45
+ - `cli/doctor.js` detects when a user-level MCP in `~/.claude.json` (typically `context7` added via `claude mcp add …`) duplicates a plugin-bundled MCP from `plugin.json` (`plugin:curdx-flow:context7`). Backed by `cli/utils.js:readUserMcpConfig()` + `findDuplicateMcps()` and 3 new tests in `test/utils.test.js`.
46
+ - `docs/getting-started.md` "Optional: use your paid context7 API key" section documenting the env-var path.
11
47
 
12
48
  ## [2.0.0-beta.10] - 2026-04-21
13
49
 
package/bin/curdx-flow.js CHANGED
@@ -20,7 +20,8 @@
20
20
  * for the full command/workflow reference)
21
21
  */
22
22
 
23
- import { pathToFileURL } from "node:url";
23
+ import { fileURLToPath } from "node:url";
24
+ import { realpathSync } from "node:fs";
24
25
 
25
26
  import { install } from "../cli/install.js";
26
27
  import { doctor } from "../cli/doctor.js";
@@ -131,13 +132,29 @@ async function main() {
131
132
  }
132
133
 
133
134
  // Only execute main() when invoked directly (`node bin/curdx-flow.js ...`
134
- // or via the npm bin shim). When the file is imported by tests or tooling,
135
- // we want the module graph to load without side-effects. This idiom is
136
- // the ESM equivalent of Python's `if __name__ == "__main__"`.
137
- const invokedDirectly =
138
- process.argv[1] &&
139
- import.meta.url === pathToFileURL(process.argv[1]).href;
140
-
141
- if (invokedDirectly) {
135
+ // or via the npm bin shim at node_modules/.bin/<name>). When the file is
136
+ // imported by tests or tooling, we want the module graph to load without
137
+ // side-effects.
138
+ //
139
+ // CRITICAL: compare RESOLVED real paths. npm installs the bin as a symlink
140
+ // (node_modules/.bin/curdx-flow → ../@curdx/flow/bin/curdx-flow.js), so
141
+ // process.argv[1] is the symlink path while import.meta.url resolves to
142
+ // the real file. Comparing them directly (the pre-beta.13 behavior)
143
+ // silently skipped main() for every single npx / global-install user,
144
+ // producing a completely broken CLI that exited with no output. Regression
145
+ // caught by user report + reproduced in CI via test/cli-entrypoints.test.js.
146
+ function isInvokedDirectly() {
147
+ if (!process.argv[1]) return false;
148
+ try {
149
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
150
+ } catch {
151
+ // argv[1] is not a real filesystem path (e.g. `node -e` eval forms or
152
+ // a worker pipe). Treat as "not invoked directly" — the caller is
153
+ // doing something non-standard.
154
+ return false;
155
+ }
156
+ }
157
+
158
+ if (isInvokedDirectly()) {
142
159
  main();
143
160
  }
package/cli/doctor.js CHANGED
@@ -57,27 +57,31 @@ export async function doctor(args = []) {
57
57
  // chrome-devtools is NOT here anymore — it was extracted into its own
58
58
  // recommended plugin (see below) to align with the "each MCP owned by one
59
59
  // plugin" model and avoid double-spawning the chrome-devtools-mcp process.
60
- console.log(`\n${color.bold("MCP Servers:")}`);
60
+ console.log(`\n${color.bold("MCP Servers (required by L2 mandatory tools):")}`);
61
61
  const mcps = cv ? listMcps() : [];
62
62
  const expectedMcps = ["context7", "sequential-thinking"];
63
63
  for (const m of expectedMcps) {
64
- // `claude mcp list` reports plugin-bundled MCPs as
65
- // "plugin:curdx-flow:<name>" and standalone MCPs as "<name>". Accept
66
- // either form previously this was a bare .name === m check, so a
67
- // plugin MCP named "plugin:curdx-flow:context7" would silently report
68
- // as not-installed even though it was running (the same class of bug
69
- // that bit chrome-devtools in beta.7).
70
- const found = mcps.find(
71
- (x) =>
72
- x.name === m &&
73
- (x.plugin === null || x.plugin === "curdx-flow")
74
- );
75
- if (found) {
76
- const via = found.plugin ? `via plugin:${found.plugin}` : "standalone";
77
- log.ok(`${m.padEnd(22)} ${color.dim(`auto-loaded (${via})`)}`);
64
+ // Beta.12 onward: the required MCPs are registered at user-level
65
+ // (via `claude mcp add`), NOT plugin-bundled. Both still show up in
66
+ // `claude mcp list`. Accept a standalone user-level registration
67
+ // as the primary form, and warn if only a plugin-bundled copy exists
68
+ // (because that's the legacy layout that beta.11 had).
69
+ const userLevel = mcps.find((x) => x.name === m && x.plugin === null);
70
+ const pluginLevel = mcps.find((x) => x.name === m && x.plugin !== null);
71
+
72
+ if (userLevel) {
73
+ log.ok(`${m.padEnd(22)} ${color.dim("user-level (standard)")}`);
74
+ } else if (pluginLevel) {
75
+ log.warn(
76
+ `${m.padEnd(22)} registered via plugin:${pluginLevel.plugin} (legacy). ` +
77
+ `Run ${color.cyan("npx @curdx/flow install --all")} to migrate to user-level.`
78
+ );
79
+ warnings++;
78
80
  } else {
79
81
  if (curdx) {
80
- log.warn(`${m.padEnd(22)} not shown in claude mcp list (restart Claude Code may fix)`);
82
+ log.warn(
83
+ `${m.padEnd(22)} missing. Run: ${color.cyan(`claude mcp add ${m} -- npx -y @upstash/context7-mcp@latest`).replace("context7-mcp", m === "context7" ? "context7-mcp" : "server-sequential-thinking")}`
84
+ );
81
85
  warnings++;
82
86
  } else {
83
87
  log.info(`${m.padEnd(22)} waiting for curdx-flow install`);
@@ -104,44 +108,28 @@ export async function doctor(args = []) {
104
108
  }
105
109
  }
106
110
 
107
- // ---------- Duplicate user-level / plugin-level MCPs ----------
108
- // Context: the bundled context7 + sequential-thinking MCPs (registered
109
- // via plugin.json) show up as `plugin:curdx-flow:<name>`. If the user
110
- // also added `<name>` manually via `claude mcp add …` at some earlier
111
- // point (common with context7 + a personal API key), both instances
112
- // run side-by-side. That's not a correctness bug tool namespaces
113
- // differ — but it doubles MCP processes and makes API-key routing
114
- // unpredictable. Surface the duplicates + concrete remediation.
111
+ // ---------- Legacy plugin-bundled MCP residue (beta.11 and earlier) ----------
112
+ // Beta.12 moved context7 + sequential-thinking to user-level registration.
113
+ // If both a user-level and a plugin-bundled copy are visible, the user
114
+ // was likely installed from an older beta.7-beta.11 that still had
115
+ // mcpServers in plugin.json and a `claude mcp update` hasn't refreshed
116
+ // the plugin cache yet. Point them at the migration command.
115
117
  if (cv) {
116
118
  const userCfg = readUserMcpConfig();
117
119
  const duplicates = findDuplicateMcps(mcps, userCfg);
118
120
  if (duplicates.length > 0) {
119
- console.log(`\n${color.bold("Duplicate MCP servers (user-level vs plugin-bundled):")}`);
121
+ console.log(`\n${color.bold("Legacy plugin-bundled MCPs still present:")}`);
120
122
  for (const d of duplicates) {
121
- const userArgs = (d.userConfig.args || []).join(" ");
122
- const hasApiKey = userArgs.includes("api-key") || userArgs.includes("ctx7sk");
123
123
  log.warn(
124
- `${d.name.padEnd(22)} registered BOTH in ~/.claude.json AND via plugin:${d.pluginEntry.plugin}`
125
- );
126
- console.log(
127
- color.dim(` user-level : ${d.userConfig.command || "?"} ${userArgs}`)
124
+ `${d.name.padEnd(22)} both user-level AND plugin:${d.pluginEntry.plugin} active`
128
125
  );
129
126
  console.log(
130
- color.dim(` plugin : ${d.pluginEntry.command || "?"}`)
127
+ color.dim(
128
+ ` → Migration: ${color.cyan(`claude plugin update curdx-flow@curdx-flow-marketplace`)}\n` +
129
+ ` then restart Claude Code. Beta.12+ removes plugin-bundled\n` +
130
+ ` versions in favor of the user-level entry you already have.`
131
+ )
131
132
  );
132
- if (hasApiKey && d.name === "context7") {
133
- console.log(
134
- color.dim(
135
- ` → Fix: export CONTEXT7_API_KEY=<your-key> in your shell rc,\n` +
136
- ` then: claude mcp remove ${d.name}\n` +
137
- ` (plugin MCP will inherit the env var — no key is lost)`
138
- )
139
- );
140
- } else {
141
- console.log(
142
- color.dim(` → Fix: claude mcp remove ${d.name}`)
143
- );
144
- }
145
133
  warnings++;
146
134
  }
147
135
  }
package/cli/install.js CHANGED
@@ -17,7 +17,8 @@ import {
17
17
  ensureClaudeMemRuntimes,
18
18
  } from "./utils.js";
19
19
  import { injectGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
20
- import { RECOMMENDED_PLUGINS } from "./registry.js";
20
+ import { RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
21
+ import { readUserMcpConfig } from "./utils.js";
21
22
 
22
23
  // When installed via npm, this CLI file lives at <pkg-root>/cli/install.js.
23
24
  // The npm package bundles the full plugin body (.claude-plugin/, agents/,
@@ -90,7 +91,7 @@ export async function install(args = []) {
90
91
 
91
92
  // ---------- Step 3: Install curdx-flow plugin ----------
92
93
  log.blank();
93
- log.step(3, 5, "Installing curdx-flow plugin (2 MCPs will auto-start)...");
94
+ log.step(3, 5, "Installing curdx-flow plugin...");
94
95
  // Read the version the marketplace is shipping so we can decide whether an
95
96
  // already-installed plugin needs an update (same name but stale version
96
97
  // previously silently skipped the upgrade — caused the beta.1 → beta.7 drift).
@@ -136,6 +137,40 @@ export async function install(args = []) {
136
137
  log.ok("curdx-flow installed");
137
138
  }
138
139
 
140
+ // ---------- Step 3.5: Register user-level MCPs (context7, sequential-thinking) ----------
141
+ // Beta.12: migrated from plugin.json bundling. See cli/registry.js for
142
+ // the rationale (avoids plugin:curdx-flow:context7 vs context7 duplicate
143
+ // registration + keeps tool names standard for third-party agent reuse).
144
+ log.blank();
145
+ log.info("Registering required MCP servers (user-level)...");
146
+ const existingUserMcps = readUserMcpConfig();
147
+ for (const mcp of BUNDLED_MCPS) {
148
+ if (mcp.preserveExisting && existingUserMcps.has(mcp.name)) {
149
+ const existing = existingUserMcps.get(mcp.name);
150
+ log.info(
151
+ ` ${mcp.name.padEnd(22)} ${color.dim(`already registered (${(existing.args || []).join(" ")}) — preserving`)}`
152
+ );
153
+ continue;
154
+ }
155
+ const r = await run(
156
+ "claude",
157
+ ["mcp", "add", mcp.name, "--", mcp.command, ...mcp.args],
158
+ { silent: true }
159
+ );
160
+ if (r.code === 0) {
161
+ log.ok(` ${mcp.name.padEnd(22)} ${color.dim("registered")}`);
162
+ } else if (r.stderr.includes("already exists")) {
163
+ log.info(` ${mcp.name.padEnd(22)} ${color.dim("already exists — skipped")}`);
164
+ } else {
165
+ log.warn(
166
+ ` ${mcp.name.padEnd(22)} registration failed: ${r.stderr.trim().split("\n").pop()}`
167
+ );
168
+ log.info(
169
+ ` Run manually: claude mcp add ${mcp.name} -- ${mcp.command} ${mcp.args.join(" ")}`
170
+ );
171
+ }
172
+ }
173
+
139
174
  // ---------- Step 4: Recommended plugins ----------
140
175
  log.blank();
141
176
  log.step(4, 5, "Recommended plugins");
package/cli/registry.js CHANGED
@@ -52,6 +52,40 @@ export const RECOMMENDED_PLUGINS = [
52
52
  },
53
53
  ];
54
54
 
55
+ /**
56
+ * Bundled MCP servers that curdx-flow depends on for its core discipline
57
+ * rules (context7 for library docs in L2, sequential-thinking for
58
+ * structured reasoning in L2). Starting beta.12 these are registered at
59
+ * USER-LEVEL via `claude mcp add` instead of plugin.json bundling, so:
60
+ *
61
+ * - Tool names stay standard (mcp__context7__*, mcp__sequential-thinking__*)
62
+ * — matching every agent's and knowledge doc's hardcoded references.
63
+ * - Users with a paid context7 API key can set it with --api-key in their
64
+ * own user-level entry; the install command will detect and respect the
65
+ * existing entry instead of overwriting it.
66
+ * - No more `plugin:curdx-flow:context7` + `context7` duplicate registration
67
+ * — the DX pitfall that bit every early adopter with a pre-existing
68
+ * `claude mcp add context7 …` history.
69
+ */
70
+ export const BUNDLED_MCPS = [
71
+ {
72
+ name: "context7",
73
+ command: "npx",
74
+ args: ["-y", "@upstash/context7-mcp@latest"],
75
+ purpose: "library / framework docs lookup (L2 Mandatory Tool)",
76
+ // Respect any user-level entry with a custom `--api-key` or differing
77
+ // package spec — don't overwrite it on subsequent install runs.
78
+ preserveExisting: true,
79
+ },
80
+ {
81
+ name: "sequential-thinking",
82
+ command: "npx",
83
+ args: ["-y", "@modelcontextprotocol/server-sequential-thinking"],
84
+ purpose: "structured reasoning for design / review (L2 Mandatory Tool)",
85
+ preserveExisting: true,
86
+ },
87
+ ];
88
+
55
89
  /**
56
90
  * Marketplaces to refresh during `upgrade`. Derived from RECOMMENDED_PLUGINS
57
91
  * plus the curdx-flow marketplace itself.
package/cli/uninstall.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  listPlugins,
17
17
  } from "./utils.js";
18
18
  import { removeGlobalProtocols, GLOBAL_CLAUDE_MD } from "./protocols.js";
19
- import { RECOMMENDED_PLUGINS } from "./registry.js";
19
+ import { RECOMMENDED_PLUGINS, BUNDLED_MCPS } from "./registry.js";
20
20
 
21
21
  const HOME = homedir();
22
22
 
@@ -131,6 +131,38 @@ export async function uninstall(args = []) {
131
131
  }
132
132
  }
133
133
 
134
+ // ---------- Step 4.5: optionally remove user-level MCPs ----------
135
+ // Starting beta.12, the install command registers context7 +
136
+ // sequential-thinking at user-level (not plugin-bundled). Ask before
137
+ // removing because the user may have customised args (e.g. --api-key)
138
+ // or still be using these MCPs outside curdx-flow.
139
+ log.blank();
140
+ log.info("Required MCP servers (context7, sequential-thinking)");
141
+ if (keepRecommended || yes) {
142
+ log.info(
143
+ color.dim("--yes or --keep-recommended: keeping user-level MCPs (remove manually with `claude mcp remove <name>`)")
144
+ );
145
+ } else {
146
+ const removeMcps = await confirm(
147
+ `Remove user-level MCPs registered by install (${BUNDLED_MCPS.map((m) => m.name).join(", ")})? ${color.dim("(keeps them if other tools depend on them)")}`,
148
+ false
149
+ );
150
+ if (removeMcps) {
151
+ for (const mcp of BUNDLED_MCPS) {
152
+ const r = await run("claude", ["mcp", "remove", mcp.name], {
153
+ silent: true,
154
+ });
155
+ if (r.code === 0) {
156
+ log.ok(` ${mcp.name.padEnd(22)} removed`);
157
+ } else {
158
+ log.info(` ${mcp.name.padEnd(22)} ${color.dim("not present or already removed")}`);
159
+ }
160
+ }
161
+ } else {
162
+ log.info("Keeping user-level MCPs");
163
+ }
164
+ }
165
+
134
166
  // ---------- Step 5: cleanup symlinks (only with --purge) ----------
135
167
  log.blank();
136
168
  log.step(3, 4, "Runtime symlinks");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.0.0-beta.11",
3
+ "version": "2.0.0-beta.13",
4
4
  "description": "CLI installer for CurDX-Flow — AI engineering workflow meta-framework for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {