@curdx/flow 2.3.8 → 2.3.10

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.3.8"
9
+ "version": "2.3.10"
10
10
  },
11
11
  "allowCrossMarketplaceDependenciesOn": [
12
12
  "context7-marketplace"
@@ -16,7 +16,7 @@
16
16
  "name": "curdx-flow",
17
17
  "source": "./",
18
18
  "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.",
19
- "version": "2.3.8",
19
+ "version": "2.3.10",
20
20
  "author": {
21
21
  "name": "wdx",
22
22
  "email": "bydongxin@gmail.com"
@@ -40,7 +40,6 @@
40
40
  "dependencies": [
41
41
  {
42
42
  "name": "context7-plugin",
43
- "version": "^1.0.0",
44
43
  "marketplace": "context7-marketplace"
45
44
  }
46
45
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.3.8",
3
+ "version": "2.3.10",
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",
@@ -37,7 +37,6 @@
37
37
  "./agents/flow-ux-designer.md",
38
38
  "./agents/flow-verifier.md"
39
39
  ],
40
- "hooks": "./hooks/hooks.json",
41
40
  "outputStyles": "./output-styles/",
42
41
  "monitors": "./monitors/monitors.json",
43
42
  "userConfig": {
@@ -65,7 +64,6 @@
65
64
  "dependencies": [
66
65
  {
67
66
  "name": "context7-plugin",
68
- "version": "^1.0.0",
69
67
  "marketplace": "context7-marketplace"
70
68
  }
71
69
  ]
@@ -2,7 +2,9 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
+ import { removeMcp } from "./lib/claude-ops.js";
5
6
  import { readProjectClaudeSettings } from "./lib/doctor-claude-settings.js";
7
+ import { CONFIG_FILE, readConfig, writeConfig } from "./lib/config.js";
6
8
  import { inspectRuntimeEnvironment } from "./lib/doctor-runtime-environment.js";
7
9
  import {
8
10
  claudeVersion,
@@ -310,6 +312,7 @@ export async function collectDoctorData(
310
312
  readProjectTeamConfigImpl = readProjectTeamConfig,
311
313
  readProjectClaudeSettingsImpl = readProjectClaudeSettings,
312
314
  readBundledPluginRuntimeDefaultsImpl = readBundledPluginRuntimeDefaults,
315
+ readConfigImpl = readConfig,
313
316
  } = {}
314
317
  ) {
315
318
  const claudeVersionValue = claudeVersionImpl();
@@ -338,6 +341,10 @@ export async function collectDoctorData(
338
341
  projectTeamConfig: await readProjectTeamConfigImpl(cwd),
339
342
  projectClaudeSettings: await readProjectClaudeSettingsImpl(cwd),
340
343
  bundledPluginRuntimeDefaults: await readBundledPluginRuntimeDefaultsImpl(),
344
+ legacyInstallState: {
345
+ configPath: CONFIG_FILE,
346
+ hasLegacyContext7ApiKey: Object.prototype.hasOwnProperty.call(readConfigImpl(), "context7ApiKey"),
347
+ },
341
348
  };
342
349
  }
343
350
 
@@ -345,9 +352,47 @@ export async function applyDoctorFixes(
345
352
  doctorData,
346
353
  {
347
354
  ensureClaudeMemRuntimesImpl = ensureClaudeMemRuntimes,
355
+ removeMcpImpl = removeMcp,
356
+ readConfigImpl = readConfig,
357
+ writeConfigImpl = writeConfig,
348
358
  } = {}
349
359
  ) {
350
360
  const fixes = [];
361
+ const context7PluginOwnsMcp = doctorData.mcps.some(
362
+ (entry) => entry.name === "context7" && entry.plugin === "context7-plugin"
363
+ );
364
+ const hasLegacyUserContext7Mcp =
365
+ doctorData.userMcpConfig instanceof Map && doctorData.userMcpConfig.has("context7");
366
+
367
+ if (context7PluginOwnsMcp && hasLegacyUserContext7Mcp) {
368
+ const result = await removeMcpImpl({ name: "context7" });
369
+ if (result.code === 0) {
370
+ doctorData.userMcpConfig.delete("context7");
371
+ doctorData.mcps = doctorData.mcps.filter(
372
+ (entry) => !(entry.name === "context7" && entry.plugin == null)
373
+ );
374
+ fixes.push({
375
+ kind: "legacy-context7-user-mcp-removed",
376
+ });
377
+ }
378
+ }
379
+
380
+ if (doctorData.legacyInstallState?.hasLegacyContext7ApiKey) {
381
+ const config = readConfigImpl();
382
+ if (Object.prototype.hasOwnProperty.call(config, "context7ApiKey")) {
383
+ delete config.context7ApiKey;
384
+ writeConfigImpl(config);
385
+ doctorData.legacyInstallState = {
386
+ ...doctorData.legacyInstallState,
387
+ hasLegacyContext7ApiKey: false,
388
+ };
389
+ fixes.push({
390
+ kind: "legacy-context7-api-key-removed",
391
+ configPath: CONFIG_FILE,
392
+ });
393
+ }
394
+ }
395
+
351
396
  const claudeMemEnabled = doctorData.plugins.some(
352
397
  (plugin) => plugin.name === "claude-mem" && plugin.status === "enabled"
353
398
  );
@@ -1,97 +1,78 @@
1
1
  /**
2
- * Context7 API-key configuration during install. Private to the
3
- * required-plugins concern; not re-exported from install-companions.js.
2
+ * Reconcile legacy Context7 install state after the official plugin is
3
+ * installed. Private to the required-plugins concern; not re-exported from
4
+ * install-companions.js.
4
5
  */
5
6
 
6
- import { addMcp, removeMcp } from "./lib/claude-ops.js";
7
+ import { removeMcp } from "./lib/claude-ops.js";
7
8
  import {
8
9
  color,
9
10
  log,
10
- note,
11
+ readUserMcpConfig,
11
12
  resultLastLine,
12
- text,
13
13
  writeConfig,
14
14
  } from "./utils.js";
15
15
 
16
- const CONTEXT7_COMMAND = "npx";
17
- const CONTEXT7_ARGS = ["-y", "@upstash/context7-mcp"];
18
-
19
- export async function installContext7Config(plugin, language, config) {
16
+ export async function installContext7Config(
17
+ plugin,
18
+ language,
19
+ config,
20
+ {
21
+ removeMcpImpl = removeMcp,
22
+ readUserMcpConfigImpl = readUserMcpConfig,
23
+ writeConfigImpl = writeConfig,
24
+ logImpl = log,
25
+ } = {}
26
+ ) {
20
27
  if (plugin.name !== "context7-plugin") return;
21
28
 
22
- log.blank();
23
- await note(
24
- language === "zh"
25
- ? "Context7 需要 API key 才能使用。\n获取 API key: https://console.upstash.com/context7"
26
- : "Context7 requires an API key to function.\nGet your API key at: https://console.upstash.com/context7",
27
- language === "zh" ? "配置 Context7" : "Configure Context7"
28
- );
29
-
30
- const apiKey = await text({
31
- message: language === "zh"
32
- ? "输入你的 Context7 API key(或按 Enter 跳过)"
33
- : "Enter your Context7 API key (or press Enter to skip)",
34
- placeholder: "ctx7sk-...",
35
- validate: (value) => {
36
- if (!value) return;
37
- if (!value.startsWith("ctx7sk-") && !value.startsWith("ctx7_")) {
38
- return language === "zh"
39
- ? "API key 应该以 ctx7sk- 或 ctx7_ 开头"
40
- : "API key should start with ctx7sk- or ctx7_";
41
- }
42
- },
43
- });
44
-
45
- if (apiKey) {
46
- config.context7ApiKey = apiKey;
47
- writeConfig(config);
29
+ let removedStoredApiKey = false;
30
+ if (config && Object.hasOwn(config, "context7ApiKey")) {
31
+ delete config.context7ApiKey;
32
+ writeConfigImpl(config);
33
+ removedStoredApiKey = true;
34
+ logImpl.info(
35
+ language === "zh"
36
+ ? " 已移除旧的 Context7 API key 本地配置,当前官方插件不再使用该安装方式"
37
+ : " Removed legacy local Context7 API key config; the official plugin no longer uses that install path"
38
+ );
39
+ }
48
40
 
49
- const r = await addMcp(buildContext7Mcp(apiKey));
41
+ const userMcps = readUserMcpConfigImpl();
42
+ if (!userMcps.has("context7")) {
43
+ return {
44
+ removedLegacyMcp: false,
45
+ removedStoredApiKey,
46
+ };
47
+ }
50
48
 
51
- if (r.code === 0) {
52
- log.ok(
53
- language === "zh"
54
- ? " Context7 API key 已配置"
55
- : " Context7 API key configured"
56
- );
57
- } else if (r.stderr.includes("already exists")) {
58
- await removeMcp({ name: "context7" });
59
- const r2 = await addMcp(buildContext7Mcp(apiKey));
60
- if (r2.code === 0) {
61
- log.ok(
62
- language === "zh"
63
- ? " Context7 API key 已更新"
64
- : " Context7 API key updated"
65
- );
66
- }
67
- } else {
68
- log.warn(
69
- language === "zh"
70
- ? ` Context7 MCP 配置失败: ${resultLastLine(r)}`
71
- : ` Context7 MCP configuration failed: ${resultLastLine(r)}`
72
- );
73
- log.info(
74
- color.dim(
75
- language === "zh"
76
- ? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
77
- : ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
78
- )
79
- );
80
- }
81
- } else {
82
- log.info(
49
+ const result = await removeMcpImpl({ name: "context7" });
50
+ if (result.code === 0) {
51
+ logImpl.ok(
83
52
  language === "zh"
84
- ? " 跳过 Context7 配置(稍后可运行 curdx-flow install 重新配置)"
85
- : " Skipped Context7 configuration (run curdx-flow install later to reconfigure)"
53
+ ? " 已移除遗留的用户级 context7 MCP,避免与官方 Context7 插件重复注册"
54
+ : " Removed legacy user-level context7 MCP so the official Context7 plugin is the only owner"
86
55
  );
56
+ return {
57
+ removedLegacyMcp: true,
58
+ removedStoredApiKey,
59
+ };
87
60
  }
88
- }
89
61
 
90
- function buildContext7Mcp(apiKey) {
62
+ logImpl.warn(
63
+ language === "zh"
64
+ ? ` 清理旧的 context7 MCP 失败: ${resultLastLine(result)}`
65
+ : ` Failed to remove legacy context7 MCP: ${resultLastLine(result)}`
66
+ );
67
+ logImpl.info(
68
+ color.dim(
69
+ language === "zh"
70
+ ? " 手动运行: claude mcp remove --scope user context7"
71
+ : " Run manually: claude mcp remove --scope user context7"
72
+ )
73
+ );
91
74
  return {
92
- name: "context7",
93
- env: [`CONTEXT7_API_KEY=${apiKey}`],
94
- command: CONTEXT7_COMMAND,
95
- args: CONTEXT7_ARGS,
75
+ removedLegacyMcp: false,
76
+ removedStoredApiKey,
96
77
  };
97
78
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Install required Claude Code companion plugins (today: context7-plugin).
3
- * Post-install, dispatches Context7 API-key configuration if applicable.
3
+ * Post-install, reconcile any legacy Context7 user-level MCP/config state.
4
4
  */
5
5
 
6
6
  import { addPluginMarketplace, installPlugin } from "./lib/claude-ops.js";
@@ -35,7 +35,7 @@ export async function installRequiredPlugins({
35
35
  if (ir.code === 0) {
36
36
  console.log(` ${color.green("✓")} ${plugin.name} installed`);
37
37
 
38
- if (plugin.requiresConfig && plugin.configType === "apiKey" && !yes) {
38
+ if (plugin.name === "context7-plugin") {
39
39
  await installContext7Config(plugin, language, config);
40
40
  }
41
41
  } else {
package/cli/lib/config.js CHANGED
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
5
  const CONFIG_DIR = join(homedir(), ".claude");
6
- const CONFIG_FILE = join(CONFIG_DIR, "curdx-flow-config.json");
6
+ export const CONFIG_FILE = join(CONFIG_DIR, "curdx-flow-config.json");
7
7
 
8
8
  export function readConfig() {
9
9
  if (!existsSync(CONFIG_FILE)) {
@@ -224,6 +224,7 @@ export function buildDoctorReport({
224
224
  projectTeamConfig,
225
225
  projectClaudeSettings,
226
226
  bundledPluginRuntimeDefaults,
227
+ legacyInstallState,
227
228
  }) {
228
229
  const lines = [];
229
230
  const sections = [];
@@ -402,6 +403,12 @@ export function buildDoctorReport({
402
403
  "migration: claude plugin update curdx-flow@curdx-flow-marketplace",
403
404
  "then restart Claude Code",
404
405
  ]
406
+ : duplicate.pluginEntry.plugin === "context7-plugin" && duplicate.name === "context7"
407
+ ? [
408
+ "official Context7 plugin already owns the context7 MCP server",
409
+ "run: npx @curdx/flow doctor --fix",
410
+ `or run manually: claude mcp remove --scope user ${duplicate.name}`,
411
+ ]
405
412
  : [
406
413
  `remove the duplicate user-level server if plugin:${duplicate.pluginEntry.plugin} should own it`,
407
414
  `run: claude mcp remove --scope user ${duplicate.name}`,
@@ -415,6 +422,20 @@ export function buildDoctorReport({
415
422
  }
416
423
  }
417
424
 
425
+ if (legacyInstallState?.hasLegacyContext7ApiKey) {
426
+ const legacySection = createSection("Legacy installer state:");
427
+ pushSectionLine(
428
+ legacySection,
429
+ "warn",
430
+ "legacy Context7 API key stored in curdx-flow install config",
431
+ [
432
+ `path: ${legacyInstallState.configPath}`,
433
+ "this key belonged to the retired user-level Context7 MCP install flow and is no longer used by the official context7-plugin",
434
+ "run: npx @curdx/flow doctor --fix",
435
+ ]
436
+ );
437
+ }
438
+
418
439
  if (claudeMemEnabled && runtimeStatus) {
419
440
  const runtimeSection = createSection("Runtime (claude-mem dependencies):");
420
441
  for (const [name, status] of Object.entries(runtimeStatus)) {
@@ -124,7 +124,7 @@ export async function maybeRemoveBundledMcps(
124
124
  } = {}
125
125
  ) {
126
126
  logImpl.blank();
127
- logImpl.info("Required MCP servers (context7, sequential-thinking)");
127
+ logImpl.info("Required user-level MCP servers (sequential-thinking)");
128
128
  if (shouldKeepBundledMcpsImpl({ yes, keepRecommended })) {
129
129
  logImpl.info(
130
130
  color.dim("--yes or --keep-recommended: keeping user-level MCPs (remove manually with `claude mcp remove <name>`)")
@@ -22,6 +22,7 @@ When a behavior is unclear, prefer the official docs and `claude plugin validate
22
22
 
23
23
  ## Hook Output Rules
24
24
 
25
+ - Standard plugin hooks live at `hooks/hooks.json` in the plugin root and are discovered automatically. Do not also set `plugin.json.hooks` to that same file; current Claude runtimes treat that as a duplicate load.
25
26
  - `SessionStart` context injection must use:
26
27
  - `hookSpecificOutput.hookEventName = "SessionStart"`
27
28
  - `hookSpecificOutput.additionalContext = "..."`
@@ -74,9 +75,10 @@ Guarded artifact targets:
74
75
  - Claude Code plugin-root `settings.json` currently supports only `agent` and `subagentStatusLine`.
75
76
  - CurDX-Flow ships both:
76
77
  - `agent: "flow-orchestrator"` to route the main thread through the CurDX-Flow coordinator by default.
77
- - `subagentStatusLine`, pointing at `${CLAUDE_PLUGIN_ROOT}/hooks/scripts/subagent-statusline.sh`.
78
+ - `subagentStatusLine`, pointing at `${CLAUDE_PLUGIN_ROOT}/hooks/scripts/subagent-statusline.sh`.
78
79
  - The status-line script must fail open on malformed input or missing `python3`; UI decoration must never break agent execution.
79
80
  - Plugin-root references must never traverse outside the plugin directory. Installed marketplace plugins run from Claude Code's plugin cache, so parent-directory references are invalid even if they work in a development checkout.
81
+ - CurDX-Flow should not declare `plugin.json.hooks` while using the standard `hooks/hooks.json` location; keep the file on disk, let Claude discover it implicitly, and reserve `plugin.json.hooks` for non-default or additional hook files only.
80
82
  - If adding plugin settings, update `schemas/plugin-settings.schema.json`, `test/plugin-structure-contract.test.js`, `test/pack-tarball-smoke.test.js`, and `scripts/validate-plugin-contracts.mjs` in the same change.
81
83
 
82
84
  ## Plugin Monitors and User Config
@@ -94,7 +96,7 @@ Guarded artifact targets:
94
96
  ## Plugin Dependency Constraints
95
97
 
96
98
  - Official dependency version constraints require upstream plugin release tags in the `{plugin-name}--v{version}` format.
97
- - Do not add a version constraint to the `context7-plugin` dependency unless the Upstash marketplace has matching `context7-plugin--v*` tags. A semver range without those tags can disable dependency resolution.
99
+ - Do not add a version constraint to the `context7-plugin` dependency unless the Upstash marketplace has matching `context7-plugin--v*` tags. Claude resolves plugin dependency ranges against `{plugin-name}--v*` tags; a semver range without those tags can disable dependency resolution and surface the installed plugin version as `unknown`.
98
100
  - Keep the CLI registry and `.claude-plugin/plugin.json` dependency entry aligned: Context7 remains a required companion plugin, while optional tools stay in `RECOMMENDED_PLUGINS`.
99
101
 
100
102
  ## Shared Settings Guardrails
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.3.8",
3
+ "version": "2.3.10",
4
4
  "description": "Skill-first discipline layer and CLI installer for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {