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

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.10"
9
+ "version": "2.0.0-beta.11"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.0.0-beta.10",
3
+ "version": "2.0.0-beta.11",
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",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,43 @@ All notable changes to CurDX-Flow will be documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+
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.
11
+
12
+ ## [2.0.0-beta.10] - 2026-04-21
13
+
14
+ ### Fixed
15
+
16
+ - `cli/utils.js` — `listMcps` rewritten to handle the actual `claude mcp list` format captured from claude 2.1.117. The previous regex matched only a single kebab-case token before the first colon, so `plugin:curdx-flow:context7: npx ...` parsed as name=`plugin` and silently dropped the rest. New parser exports `{ name, plugin, fullName, status, command }` and is fixture-tested in `test/utils.test.js`.
17
+ - `cli/doctor.js` — MCP presence check now accepts both standalone (`context7`) and plugin-bundled (`plugin:curdx-flow:context7`) registrations, with source in the status line. Fixes the same class of false-positive that bit `chrome-devtools` in beta.7. Also gated `--verbose` raw-plugin dump behind `claude` being on PATH, so it no longer runs a meaningless empty child when the CLI is absent.
18
+ - `cli/install.js` — step counter was `1/4`..`4/4` but there are five steps; `Step 5: inject global protocols` was emitted via bare `console.log` so the progress display lied. All five steps now use `log.step(N, 5, ...)`. Also switched `⏳ Installing` and `✅ Install complete` glyphs to the `▸` / `✓` symbols used elsewhere in the project's `log.*` helpers.
19
+ - `cli/uninstall.js` — same `⏳` → `▸` glyph normalisation.
20
+ - `cli/protocols.js` + `cli/utils.js` + `cli/uninstall.js` — switched from `process.env.HOME || ""` to `os.homedir()`. `$HOME` is only guaranteed in interactive login shells; CI containers, some `docker exec` invocations, and non-login spawns can leave it empty, which resolved `GLOBAL_CLAUDE_MD` to `/.claude/CLAUDE.md` and broke `ensureRuntimeInPath` runtime candidate lookup.
21
+ - `bin/curdx-flow.js` — `main()` was called unconditionally at module scope, so `import()` from a test would spawn the CLI, parse the test runner's argv, and `exit(1)`. Now guarded by the ESM direct-invocation idiom (`import.meta.url === pathToFileURL(process.argv[1]).href`). Added one real-import test.
22
+ - `.claude-plugin/plugin.json` — `homepage` and `repository` pointed at `github.com/wdx/curdx-flow` but the authoritative remote is `github.com/curdx/curdx-flow`. Plugin-marketplace UI clicks previously 404'd. Aligned to the real owner.
23
+ - `.github/workflows/npm-publish.yml` — bumped `actions/setup-node` from Node 20 to Node 22 LTS ahead of the 2026-06-02 Node 20 removal.
24
+ - `scripts/release.sh` — lockstep bump of `plugin.json` + `marketplace.json` now parses both files in memory before writing either, with `git checkout -- package.json` auto-revert on failure. Previously the second file could fail to write while the first was already disk-committed.
25
+
26
+ ### Changed
27
+
28
+ - `commands/start.md` — replaced the brittle `xargs`/`awk`/`sed` flag-parsing block with a model-directed parse that tolerates quoted strings in `$ARGUMENTS` (e.g. `my-feature "Fix user's login bug"` no longer hits `xargs: unmatched quote`).
29
+ - `commands/init.md` — Step 5 used to say "Run `npx @curdx/flow doctor`" but the slash command runs inside Claude Code where a spawned CLI's output doesn't render cleanly. Reframed as "verify inline + suggest the user run the CLI in a separate terminal if they want the full report."
30
+ - `commands/implement.md` — replaced `❌` emoji with `✗` to match the project's own `log.err` helper.
31
+ - `docs/architecture.md` — Hook System section and CLI layout were **entirely fabricated** (listed non-existent hooks `pre-spec.sh` / `post-implement.sh` / `pre-verify.sh` / `post-review.sh` / `on-gate-fail.sh` and bogus paths `cli/bin/flow` / `cli/commands/install.js` / `cli/lib/…`). Rewritten to match the real `hooks/scripts/` layout (4 hooks: session-start / inject-karpathy / quick-mode-guard / stop-watcher) and real `bin/` + `cli/` paths including `cli/registry.js` and the `test/` directory.
32
+
33
+ ### Added
34
+
35
+ - `test/utils.test.js` grew 5 fixture tests for the new `parseMcpList` against a captured `claude mcp list` output.
36
+ - `test/cli-entrypoints.test.js` — 5 smoke tests that each dynamically-import `cli/doctor.js` / `cli/install.js` / `cli/upgrade.js` / `cli/uninstall.js` and assert the expected top-level function exports. Guards against the "deleted an import but forgot the consumer" class of regression at CI time. Plus one test that imports `bin/curdx-flow.js` now that it no longer runs `main()` at module load.
37
+
38
+ ### Removed (docs compliance)
39
+
40
+ - Chinese trigger phrases from `skills/*/SKILL.md` descriptions (5 files). Keeping bilingual triggers violated the project's own language-separation rule (`CLAUDE.md`: "Documentation layer = English"). Semantic matching on the English phrases plus the model's own bilingual understanding covers the same invocation surface.
41
+
42
+ ## [2.0.0-beta.9] - 2026-04-21
43
+
7
44
  ### Fixed (P0)
8
45
 
9
46
  - `cli/utils.js` — `findRuntime()` referenced `existsSync` without importing it; any user whose `bun`/`uv` was not on PATH hit `ReferenceError` during install or doctor.
@@ -18,6 +55,10 @@ All notable changes to CurDX-Flow will be documented here.
18
55
  - `commands/start.md` and `commands/spec.md` now produce `.state.json` files that match `schemas/spec-state.schema.json` (field names: `spec_name` / `created` / `updated` / `version`; initial phase is `research`, not the undefined `created`).
19
56
  - All python heredocs inside hook scripts use quoted delimiters (`<<'PY'`) and read `STATE_FILE` via `os.environ`, closing a shell→python code-injection surface triggered by unusual spec names.
20
57
 
58
+ ### Added
59
+
60
+ - First real test suite — `node --test`-based, 18 regression tests covering `cli/protocols.js`, `cli/registry.js`, and `cli/utils.js`. Wired into `package.json` `scripts.test` and the CI `publish` job so a failing test blocks `npm publish`.
61
+
21
62
  ### Removed
22
63
 
23
64
  - `hooks/scripts/fail-tracker.sh` and its `PostToolUseFailure` registration — the counter was written but never read by any consumer (the intended pua escalation was never implemented). Can be reintroduced when the consumer exists.
package/cli/doctor.js CHANGED
@@ -12,6 +12,8 @@ import {
12
12
  listPlugins,
13
13
  listMcps,
14
14
  ensureClaudeMemRuntimes,
15
+ readUserMcpConfig,
16
+ findDuplicateMcps,
15
17
  } from "./utils.js";
16
18
  import { RECOMMENDED_PLUGINS } from "./registry.js";
17
19
 
@@ -102,6 +104,49 @@ export async function doctor(args = []) {
102
104
  }
103
105
  }
104
106
 
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.
115
+ if (cv) {
116
+ const userCfg = readUserMcpConfig();
117
+ const duplicates = findDuplicateMcps(mcps, userCfg);
118
+ if (duplicates.length > 0) {
119
+ console.log(`\n${color.bold("Duplicate MCP servers (user-level vs plugin-bundled):")}`);
120
+ for (const d of duplicates) {
121
+ const userArgs = (d.userConfig.args || []).join(" ");
122
+ const hasApiKey = userArgs.includes("api-key") || userArgs.includes("ctx7sk");
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}`)
128
+ );
129
+ console.log(
130
+ color.dim(` plugin : ${d.pluginEntry.command || "?"}`)
131
+ );
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
+ warnings++;
146
+ }
147
+ }
148
+ }
149
+
105
150
  // ---------- Runtime PATH guards (only if claude-mem is installed) ----------
106
151
  if (claudeMemEnabled) {
107
152
  console.log(`\n${color.bold("Runtime (claude-mem dependencies):")}`);
package/cli/utils.js CHANGED
@@ -215,6 +215,52 @@ export function listPlugins() {
215
215
  return plugins;
216
216
  }
217
217
 
218
+ /**
219
+ * Read the user-level MCP registrations from ~/.claude.json. These are the
220
+ * MCPs the user added manually via `claude mcp add …` — distinct from
221
+ * plugin-bundled MCPs (which live in plugin.json).
222
+ *
223
+ * Returns a Map keyed by server name with the raw config object. Returns
224
+ * an empty Map if the file is missing / unreadable / has no mcpServers
225
+ * section — all of which are normal states and not errors.
226
+ */
227
+ export function readUserMcpConfig() {
228
+ try {
229
+ const path = join(HOME, ".claude.json");
230
+ if (!existsSync(path)) return new Map();
231
+ const cfg = JSON.parse(readFileSync(path, "utf-8"));
232
+ const servers = cfg?.mcpServers || {};
233
+ return new Map(Object.entries(servers));
234
+ } catch {
235
+ return new Map();
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Given the output of listMcps() and a user-level MCP config map, find
241
+ * MCPs that are registered BOTH as user-level AND as plugin-bundled.
242
+ * The plugin-bundled form shows up as `plugin:<plugin>:<name>` in
243
+ * listMcps output, so a user-level "context7" and a plugin-level
244
+ * "plugin:curdx-flow:context7" are a duplicate pair.
245
+ *
246
+ * Returns array of { name, userConfig, pluginEntry }.
247
+ */
248
+ export function findDuplicateMcps(mcps, userConfig) {
249
+ const duplicates = [];
250
+ for (const m of mcps) {
251
+ // Only look at plugin-prefixed entries — they're the reference for
252
+ // what's bundled. Check if user has their own non-prefixed version.
253
+ if (m.plugin && userConfig.has(m.name)) {
254
+ duplicates.push({
255
+ name: m.name,
256
+ userConfig: userConfig.get(m.name),
257
+ pluginEntry: m,
258
+ });
259
+ }
260
+ }
261
+ return duplicates;
262
+ }
263
+
218
264
  /**
219
265
  * List MCP servers registered with the `claude` CLI. Returns array of
220
266
  * { name, plugin, fullName, status, command }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.0.0-beta.10",
3
+ "version": "2.0.0-beta.11",
4
4
  "description": "CLI installer for CurDX-Flow — AI engineering workflow meta-framework for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {