@curdx/flow 3.0.0 → 3.2.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.
Files changed (219) hide show
  1. package/CHANGELOG.md +33 -82
  2. package/LICENSE +1 -1
  3. package/README.md +28 -129
  4. package/dist/index.mjs +1165 -0
  5. package/package.json +33 -44
  6. package/.claude-plugin/marketplace.json +0 -48
  7. package/.claude-plugin/plugin.json +0 -52
  8. package/agent-preamble/preamble.md +0 -314
  9. package/agents/flow-adversary.md +0 -203
  10. package/agents/flow-architect.md +0 -198
  11. package/agents/flow-brownfield-analyst.md +0 -143
  12. package/agents/flow-debugger.md +0 -321
  13. package/agents/flow-edge-hunter.md +0 -289
  14. package/agents/flow-executor.md +0 -269
  15. package/agents/flow-orchestrator.md +0 -145
  16. package/agents/flow-planner.md +0 -247
  17. package/agents/flow-product-designer.md +0 -159
  18. package/agents/flow-qa-engineer.md +0 -282
  19. package/agents/flow-researcher.md +0 -166
  20. package/agents/flow-reviewer.md +0 -304
  21. package/agents/flow-security-auditor.md +0 -401
  22. package/agents/flow-triage-analyst.md +0 -272
  23. package/agents/flow-ui-researcher.md +0 -230
  24. package/agents/flow-ux-designer.md +0 -221
  25. package/agents/flow-verifier.md +0 -350
  26. package/bin/curdx-flow +0 -5
  27. package/bin/curdx-flow-state +0 -104
  28. package/bin/curdx-flow.js +0 -54
  29. package/cli/README.md +0 -104
  30. package/cli/doctor-workflow.js +0 -483
  31. package/cli/doctor.js +0 -73
  32. package/cli/help.js +0 -59
  33. package/cli/install-bundled-mcps.js +0 -37
  34. package/cli/install-companions.js +0 -19
  35. package/cli/install-context7-config.js +0 -80
  36. package/cli/install-curdx-plugin.js +0 -96
  37. package/cli/install-language.js +0 -35
  38. package/cli/install-next-steps.js +0 -29
  39. package/cli/install-options.js +0 -9
  40. package/cli/install-paths.js +0 -52
  41. package/cli/install-recommended-plugins.js +0 -104
  42. package/cli/install-required-plugins.js +0 -57
  43. package/cli/install-self-update.js +0 -62
  44. package/cli/install-workflow.js +0 -209
  45. package/cli/install.js +0 -101
  46. package/cli/lib/claude-commands.js +0 -41
  47. package/cli/lib/claude-ops.js +0 -47
  48. package/cli/lib/claude.js +0 -183
  49. package/cli/lib/config.js +0 -24
  50. package/cli/lib/doctor-claude-settings.js +0 -1186
  51. package/cli/lib/doctor-report.js +0 -978
  52. package/cli/lib/doctor-runtime-environment.js +0 -196
  53. package/cli/lib/frontmatter.js +0 -44
  54. package/cli/lib/json-schema.js +0 -57
  55. package/cli/lib/logging.js +0 -25
  56. package/cli/lib/process.js +0 -60
  57. package/cli/lib/prompts.js +0 -135
  58. package/cli/lib/runtime.js +0 -107
  59. package/cli/lib/semver.js +0 -109
  60. package/cli/lib/version.js +0 -12
  61. package/cli/protocols-body.md +0 -22
  62. package/cli/protocols.js +0 -162
  63. package/cli/registry.js +0 -123
  64. package/cli/router.js +0 -49
  65. package/cli/uninstall-actions.js +0 -360
  66. package/cli/uninstall-workflow.js +0 -146
  67. package/cli/uninstall.js +0 -42
  68. package/cli/upgrade-workflow.js +0 -80
  69. package/cli/upgrade.js +0 -91
  70. package/cli/utils.js +0 -40
  71. package/gates/adversarial-review-gate.md +0 -219
  72. package/gates/coverage-audit-gate.md +0 -182
  73. package/gates/devex-gate.md +0 -254
  74. package/gates/edge-case-gate.md +0 -194
  75. package/gates/karpathy-gate.md +0 -130
  76. package/gates/security-gate.md +0 -218
  77. package/gates/tdd-gate.md +0 -182
  78. package/gates/test-quality-gate.md +0 -59
  79. package/gates/verification-gate.md +0 -179
  80. package/hooks/hooks.json +0 -130
  81. package/hooks/scripts/common.sh +0 -237
  82. package/hooks/scripts/config-change-guard.sh +0 -94
  83. package/hooks/scripts/flow-context-watch.sh +0 -94
  84. package/hooks/scripts/inject-karpathy.sh +0 -53
  85. package/hooks/scripts/quick-mode-guard.sh +0 -69
  86. package/hooks/scripts/session-start.sh +0 -94
  87. package/hooks/scripts/session-title.sh +0 -87
  88. package/hooks/scripts/stop-watcher.sh +0 -231
  89. package/hooks/scripts/subagent-artifact-guard.sh +0 -92
  90. package/hooks/scripts/subagent-statusline.sh +0 -111
  91. package/hooks/scripts/task-lifecycle-guard.sh +0 -106
  92. package/hooks/scripts/teammate-idle-guard.sh +0 -83
  93. package/knowledge/artifact-output-discipline.md +0 -24
  94. package/knowledge/artifact-summary-contracts.md +0 -50
  95. package/knowledge/atomic-commits.md +0 -262
  96. package/knowledge/claude-code-runtime-contracts.md +0 -240
  97. package/knowledge/epic-decomposition.md +0 -307
  98. package/knowledge/execution-strategies.md +0 -303
  99. package/knowledge/karpathy-guidelines.md +0 -219
  100. package/knowledge/planning-reviews.md +0 -211
  101. package/knowledge/poc-first-workflow.md +0 -223
  102. package/knowledge/review-feedback-intake.md +0 -57
  103. package/knowledge/spec-driven-development.md +0 -180
  104. package/knowledge/systematic-debugging.md +0 -378
  105. package/knowledge/two-stage-review.md +0 -249
  106. package/knowledge/wave-execution.md +0 -403
  107. package/monitors/monitors.json +0 -8
  108. package/monitors/scripts/flow-state-monitor.sh +0 -102
  109. package/output-styles/curdx-evidence-first.md +0 -34
  110. package/output-styles/curdx-fast-mode.md +0 -42
  111. package/output-styles/curdx-spec-mode.md +0 -46
  112. package/schemas/agent-frontmatter.schema.json +0 -66
  113. package/schemas/config.schema.json +0 -134
  114. package/schemas/gate-frontmatter.schema.json +0 -30
  115. package/schemas/hooks.schema.json +0 -115
  116. package/schemas/output-style-frontmatter.schema.json +0 -22
  117. package/schemas/plugin-manifest.schema.json +0 -436
  118. package/schemas/plugin-settings.schema.json +0 -29
  119. package/schemas/skill-frontmatter.schema.json +0 -177
  120. package/schemas/spec-frontmatter.schema.json +0 -42
  121. package/schemas/spec-state.schema.json +0 -165
  122. package/settings.json +0 -8
  123. package/skills/brownfield-index/SKILL.md +0 -53
  124. package/skills/brownfield-index/references/applicability.md +0 -12
  125. package/skills/brownfield-index/references/handoff.md +0 -8
  126. package/skills/brownfield-index/references/index-contract.md +0 -10
  127. package/skills/browser-qa/SKILL.md +0 -39
  128. package/skills/browser-qa/references/handoff.md +0 -6
  129. package/skills/browser-qa/references/prerequisites.md +0 -10
  130. package/skills/browser-qa/references/qa-contract.md +0 -20
  131. package/skills/cancel/SKILL.md +0 -41
  132. package/skills/cancel/references/destructive-mode.md +0 -17
  133. package/skills/cancel/references/reporting.md +0 -18
  134. package/skills/cancel/references/state-recovery.md +0 -30
  135. package/skills/cancel/references/target-resolution.md +0 -7
  136. package/skills/debug/SKILL.md +0 -45
  137. package/skills/debug/references/context-gathering.md +0 -11
  138. package/skills/debug/references/failure-guard.md +0 -25
  139. package/skills/debug/references/intake.md +0 -12
  140. package/skills/debug/references/phase-workflow.md +0 -34
  141. package/skills/debug/references/reporting.md +0 -20
  142. package/skills/epic/SKILL.md +0 -39
  143. package/skills/epic/references/epic-artifacts.md +0 -20
  144. package/skills/epic/references/epic-intake.md +0 -9
  145. package/skills/epic/references/slice-handoff.md +0 -16
  146. package/skills/fast/SKILL.md +0 -62
  147. package/skills/fast/references/applicability.md +0 -25
  148. package/skills/fast/references/clarification.md +0 -20
  149. package/skills/fast/references/execution-contract.md +0 -56
  150. package/skills/help/SKILL.md +0 -55
  151. package/skills/help/references/dispatch.md +0 -20
  152. package/skills/help/references/overview.md +0 -39
  153. package/skills/help/references/troubleshoot.md +0 -47
  154. package/skills/help/references/workflow.md +0 -37
  155. package/skills/implement/SKILL.md +0 -104
  156. package/skills/implement/references/error-recovery.md +0 -36
  157. package/skills/implement/references/linear-execution.md +0 -43
  158. package/skills/implement/references/native-task-sync.md +0 -107
  159. package/skills/implement/references/preflight.md +0 -43
  160. package/skills/implement/references/progress-contract.md +0 -36
  161. package/skills/implement/references/state-init.md +0 -36
  162. package/skills/implement/references/stop-hook-execution.md +0 -50
  163. package/skills/implement/references/strategy-router.md +0 -38
  164. package/skills/implement/references/subagent-execution.md +0 -57
  165. package/skills/implement/references/wave-execution.md +0 -180
  166. package/skills/init/SKILL.md +0 -49
  167. package/skills/init/references/gitignore-and-health.md +0 -26
  168. package/skills/init/references/next-steps.md +0 -22
  169. package/skills/init/references/preflight.md +0 -15
  170. package/skills/init/references/scaffold-contract.md +0 -27
  171. package/skills/review/SKILL.md +0 -82
  172. package/skills/review/references/optional-passes.md +0 -48
  173. package/skills/review/references/preflight.md +0 -38
  174. package/skills/review/references/report-contract.md +0 -49
  175. package/skills/review/references/reporting.md +0 -20
  176. package/skills/review/references/stage-execution.md +0 -32
  177. package/skills/security-audit/SKILL.md +0 -47
  178. package/skills/security-audit/references/audit-contract.md +0 -21
  179. package/skills/security-audit/references/gate-handoff.md +0 -8
  180. package/skills/security-audit/references/scope-and-depth.md +0 -9
  181. package/skills/spec/SKILL.md +0 -100
  182. package/skills/spec/references/artifact-landing.md +0 -31
  183. package/skills/spec/references/phase-execution.md +0 -50
  184. package/skills/spec/references/planning-review.md +0 -31
  185. package/skills/spec/references/preflight-and-routing.md +0 -46
  186. package/skills/spec/references/reporting.md +0 -21
  187. package/skills/start/SKILL.md +0 -84
  188. package/skills/start/references/branch-routing.md +0 -51
  189. package/skills/start/references/mode-semantics.md +0 -12
  190. package/skills/start/references/preflight.md +0 -13
  191. package/skills/start/references/reporting.md +0 -20
  192. package/skills/start/references/state-seeding.md +0 -44
  193. package/skills/start/references/workflow-handoff.md +0 -26
  194. package/skills/status/SKILL.md +0 -41
  195. package/skills/status/references/gather-contract.md +0 -30
  196. package/skills/status/references/health-rules.md +0 -27
  197. package/skills/status/references/output-contract.md +0 -25
  198. package/skills/status/references/preflight.md +0 -10
  199. package/skills/status/references/recovery-hints.md +0 -18
  200. package/skills/ui-sketch/SKILL.md +0 -39
  201. package/skills/ui-sketch/references/brief-intake.md +0 -10
  202. package/skills/ui-sketch/references/iteration-handoff.md +0 -5
  203. package/skills/ui-sketch/references/variant-contract.md +0 -15
  204. package/skills/verify/SKILL.md +0 -56
  205. package/skills/verify/references/evidence-workflow.md +0 -39
  206. package/skills/verify/references/output-contract.md +0 -23
  207. package/skills/verify/references/preflight.md +0 -11
  208. package/skills/verify/references/report-handoff.md +0 -35
  209. package/skills/verify/references/strict-mode.md +0 -12
  210. package/templates/CONTEXT.md.tmpl +0 -53
  211. package/templates/PROJECT.md.tmpl +0 -59
  212. package/templates/ROADMAP.md.tmpl +0 -50
  213. package/templates/STATE.md.tmpl +0 -49
  214. package/templates/config.json.tmpl +0 -51
  215. package/templates/design.md.tmpl +0 -83
  216. package/templates/progress.md.tmpl +0 -77
  217. package/templates/requirements.md.tmpl +0 -76
  218. package/templates/research.md.tmpl +0 -83
  219. package/templates/tasks.md.tmpl +0 -107
package/bin/curdx-flow.js DELETED
@@ -1,54 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * curdx-flow CLI entry
4
- *
5
- * Usage:
6
- * npx @curdx/flow <command> [args]
7
- *
8
- * The CLI only handles install-time and lifecycle operations that must
9
- * happen outside Claude Code. Anything a user does while Claude Code is
10
- * running (init a project, start a spec, run the workflow, etc.) is a
11
- * slash command: /curdx-flow:init, /curdx-flow:start, and so on.
12
- *
13
- * Commands:
14
- * install Install curdx-flow plugin + optional recommended plugins
15
- * doctor Check health (claude CLI, plugin, MCPs, recommended plugins)
16
- * upgrade Update curdx-flow + recommended plugins to latest
17
- * uninstall Remove curdx-flow plugin (and optionally recommended / artifacts)
18
- * --version / -v
19
- * --help / -h (CLI usage summary — use /curdx-flow:help in Claude Code
20
- * for the full command/workflow reference)
21
- */
22
-
23
- import { fileURLToPath } from "node:url";
24
- import { realpathSync } from "node:fs";
25
-
26
- import { runCli } from "../cli/router.js";
27
-
28
- // Only execute main() when invoked directly (`node bin/curdx-flow.js ...`
29
- // or via the npm bin shim at node_modules/.bin/<name>). When the file is
30
- // imported by tests or tooling, we want the module graph to load without
31
- // side-effects.
32
- //
33
- // CRITICAL: compare RESOLVED real paths. npm installs the bin as a symlink
34
- // (node_modules/.bin/curdx-flow → ../@curdx/flow/bin/curdx-flow.js), so
35
- // process.argv[1] is the symlink path while import.meta.url resolves to
36
- // the real file. Comparing them directly (the pre-beta.13 behavior)
37
- // silently skipped main() for every single npx / global-install user,
38
- // producing a completely broken CLI that exited with no output. Regression
39
- // caught by user report + reproduced in CI via test/cli-entrypoints.test.js.
40
- function isInvokedDirectly() {
41
- if (!process.argv[1]) return false;
42
- try {
43
- return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
44
- } catch {
45
- // argv[1] is not a real filesystem path (e.g. `node -e` eval forms or
46
- // a worker pipe). Treat as "not invoked directly" — the caller is
47
- // doing something non-standard.
48
- return false;
49
- }
50
- }
51
-
52
- if (isInvokedDirectly()) {
53
- runCli();
54
- }
package/cli/README.md DELETED
@@ -1,104 +0,0 @@
1
- # curdx-flow CLI
2
-
3
- One-shot installer + external diagnostics + external init. Requires Node.js 18+ and Claude Code v2.1.110+.
4
-
5
- ## Quick Start
6
-
7
- ```bash
8
- # Interactive install (pick recommended plugins)
9
- npx github:curdx/curdx-flow install
10
-
11
- # Or install everything automatically
12
- npx github:curdx/curdx-flow install --all
13
-
14
- # Health check
15
- npx @curdx/flow doctor
16
-
17
- # Update all plugins
18
- npx @curdx/flow upgrade
19
- ```
20
-
21
- ## Commands
22
-
23
- ### `install [--all] [--no-deps]`
24
-
25
- Steps:
26
- 1. Verify the `claude` CLI is installed
27
- 2. `claude plugin marketplace add --scope user curdx/curdx-flow`
28
- 3. Pre-register required companion marketplaces so plugin dependencies can resolve
29
- 4. `claude plugin install --scope user curdx-flow@curdx-flow-marketplace`
30
- 5. Install required companion plugin: **context7-plugin@context7-marketplace**
31
- 6. Register required user-level MCP: **sequential-thinking**
32
- 7. Interactively (or automatically) install recommended plugins: **pua**, **claude-mem**, **frontend-design**, **chrome-devtools-mcp**
33
-
34
- | Flag | Purpose |
35
- |------|---------|
36
- | `--all` | Install all recommended plugins, no prompt |
37
- | `--no-deps` | Install only curdx-flow itself |
38
-
39
- ### `doctor [--verbose] [--fix] [--json]`
40
-
41
- External diagnostics: claude CLI / curdx-flow / required MCPs / recommended plugins / current directory `.flow/` state.
42
-
43
- `--fix` applies the safe automatic repairs the CLI can perform without guessing — currently the `bun` / `uv` PATH symlinks used by `claude-mem`. Everything else remains diagnostic-only.
44
-
45
- `--json` emits the full health result as machine-readable JSON for CI, wrappers, or external diagnostics. It includes `contractVersion`, `metadata.appliedFixes`, settings inspection scope metadata, the rendered report structure, and raw `doctorData`, including CurDX-Flow plugin option precedence, file-based managed settings, and runtime env projection.
46
-
47
- `doctor` also compares the plugin body shipped with the current CLI/package against the `curdx-flow` version Claude actually has installed, so local development and release validation do not silently run against stale plugin cache state.
48
-
49
- When the CLI is running from a git checkout, `doctor` also reports whether that source repo is dirty. This catches the common plugin-development trap where Claude is still running the previously installed cache while your local checkout has uninstalled edits.
50
-
51
- ### Project initialization (not a CLI command)
52
-
53
- Project initialization is a Claude Code slash command, not a CLI one. After `install`, open your project in Claude Code and run:
54
-
55
- ```
56
- claude
57
- /curdx-flow:init
58
- ```
59
-
60
- This keeps the CLI scoped to install-time and lifecycle operations only — anything the user does while Claude Code is running stays inside Claude Code.
61
-
62
- ### `upgrade`
63
-
64
- `claude plugin marketplace update` + `claude plugin update --scope user` for every installed curdx-flow-related plugin.
65
-
66
- After updating, `upgrade` also reconciles retired Context7 install artifacts:
67
-
68
- - removes the old user-level `context7` MCP if the official `context7-plugin` already owns it
69
- - removes any stale `context7ApiKey` entry from `~/.claude/curdx-flow-config.json`
70
-
71
- ### `uninstall [-y] [--keep-recommended] [--purge]`
72
-
73
- Inverse of `install`. By default removes only the curdx-flow plugin. Recommended plugins are kept unless selected interactively. With `--purge`, also removes third-party marketplaces, the `~/.local/bin/bun` / `~/.local/bin/uv` symlinks created by install, and any retired Context7 user-level MCP / stored API key left behind by older CurDX-Flow releases.
74
-
75
- ## Why a CLI?
76
-
77
- The **core workflow** still lives inside Claude Code (`/curdx-flow:*` commands). The CLI provides:
78
-
79
- - **One-line install**: Docker/CI friendly (`RUN npx github:curdx/curdx-flow install --all`)
80
- - **External diagnostics**: check health without entering Claude Code
81
- - **Batch deployment**: write a script for your team — `for host in ...; do ssh $host npx github:curdx/curdx-flow install; done`
82
-
83
- ## Small-dependency design
84
-
85
- - Pure ES Modules (Node 18+)
86
- - Tiny runtime dependency surface (`@clack/prompts`, `picocolors`)
87
- - No `commander` / `inquirer` / `execa`
88
- - Fast `npx` and offline-capable plugin install
89
-
90
- ## Local development
91
-
92
- ```bash
93
- # clone
94
- git clone https://github.com/curdx/curdx-flow
95
- cd curdx-flow
96
-
97
- # run directly
98
- node bin/curdx-flow.js --help
99
- node bin/curdx-flow.js doctor
100
- ```
101
-
102
- ## License
103
-
104
- MIT
@@ -1,483 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- import { removeMcp } from "./lib/claude-ops.js";
6
- import { readProjectClaudeSettings } from "./lib/doctor-claude-settings.js";
7
- import { CONFIG_FILE, readConfig, writeConfig } from "./lib/config.js";
8
- import { inspectRuntimeEnvironment } from "./lib/doctor-runtime-environment.js";
9
- import {
10
- claudeVersion,
11
- color,
12
- ensureClaudeMemRuntimes,
13
- inspectClaudeMemRuntimes,
14
- listMcps,
15
- listPluginMarketplaces,
16
- listPlugins,
17
- log,
18
- readUserMcpConfig,
19
- runSync,
20
- } from "./utils.js";
21
-
22
- export { readProjectClaudeSettings };
23
- export { inspectRuntimeEnvironment };
24
-
25
- const PACKAGE_ROOT = fileURLToPath(new URL("../", import.meta.url));
26
- export const DOCTOR_JSON_CONTRACT_VERSION = 2;
27
- export const DOCTOR_SETTINGS_INSPECTION = Object.freeze({
28
- pluginOptionScopesInspected: ["managed-file", "user", "project", "local"],
29
- pluginOptionScopesNotInspected: ["managed-server", "managed-mdm", "cli"],
30
- });
31
-
32
- export function createDoctorContext(args = []) {
33
- return {
34
- fix: args.includes("--fix"),
35
- json: args.includes("--json"),
36
- verbose: args.includes("--verbose") || args.includes("-v"),
37
- };
38
- }
39
-
40
- function isUrlLike(value) {
41
- return /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
42
- }
43
-
44
- function isWindowsAbsolutePath(value) {
45
- return /^[A-Za-z]:[\\/]/.test(value);
46
- }
47
-
48
- function looksLikeRelativePathToken(value) {
49
- if (typeof value !== "string") return false;
50
- const token = value.trim();
51
- if (!token) return false;
52
-
53
- if (token.startsWith("./") || token.startsWith("../")) return true;
54
- if (!/[\\/]/.test(token)) return false;
55
-
56
- if (
57
- token.startsWith("/") ||
58
- token.startsWith("~/") ||
59
- token.startsWith("${") ||
60
- token.startsWith("@") ||
61
- token.startsWith("--") ||
62
- isUrlLike(token) ||
63
- isWindowsAbsolutePath(token)
64
- ) {
65
- return false;
66
- }
67
-
68
- return (
69
- /\.(?:[cm]?js|mjs|ts|tsx|jsx|py|rb|sh|bash|zsh|fish|ps1|cmd|bat|exe)$/i.test(token) ||
70
- /(^|[\\/])(?:scripts?|bin|dist|src|tools|vendor|node_modules|venv|\.venv)([\\/]|$)/i.test(token)
71
- );
72
- }
73
- export async function readProjectMcpConfig(cwd = process.cwd()) {
74
- const rootPath = path.join(cwd, ".mcp.json");
75
- const misplacedPath = path.join(cwd, ".claude", ".mcp.json");
76
-
77
- const state = {
78
- exists: false,
79
- misplacedExists: false,
80
- invalid: false,
81
- parseError: null,
82
- shapeError: null,
83
- serverCount: 0,
84
- relativePathWarnings: [],
85
- };
86
-
87
- try {
88
- const misplacedStat = await fs.stat(misplacedPath);
89
- state.misplacedExists = misplacedStat.isFile();
90
- } catch {
91
- state.misplacedExists = false;
92
- }
93
-
94
- try {
95
- const stat = await fs.stat(rootPath);
96
- if (!stat.isFile()) return state;
97
- state.exists = true;
98
- } catch {
99
- return state;
100
- }
101
-
102
- let parsed;
103
- try {
104
- parsed = JSON.parse(await fs.readFile(rootPath, "utf-8"));
105
- } catch (error) {
106
- state.invalid = true;
107
- state.parseError = error.message;
108
- return state;
109
- }
110
-
111
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
112
- state.shapeError = "root value must be a JSON object";
113
- return state;
114
- }
115
-
116
- if (!parsed.mcpServers || typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
117
- state.shapeError = 'missing top-level "mcpServers" object';
118
- return state;
119
- }
120
-
121
- state.serverCount = Object.keys(parsed.mcpServers).length;
122
-
123
- for (const [serverName, config] of Object.entries(parsed.mcpServers)) {
124
- if (!config || typeof config !== "object" || Array.isArray(config)) continue;
125
-
126
- if (looksLikeRelativePathToken(config.command)) {
127
- state.relativePathWarnings.push({
128
- serverName,
129
- field: "command",
130
- value: config.command,
131
- });
132
- }
133
-
134
- const args = Array.isArray(config.args) ? config.args : [];
135
- args.forEach((arg, index) => {
136
- if (!looksLikeRelativePathToken(arg)) return;
137
- state.relativePathWarnings.push({
138
- serverName,
139
- field: `args[${index}]`,
140
- value: arg,
141
- });
142
- });
143
- }
144
-
145
- return state;
146
- }
147
-
148
- export async function readProjectTeamConfig(cwd = process.cwd()) {
149
- const teamConfigPath = path.join(cwd, ".claude", "teams", "teams.json");
150
- const state = {
151
- exists: false,
152
- };
153
-
154
- try {
155
- const stat = await fs.stat(teamConfigPath);
156
- state.exists = stat.isFile();
157
- } catch {
158
- state.exists = false;
159
- }
160
-
161
- return state;
162
- }
163
-
164
- export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROOT) {
165
- const pluginSettingsPath = path.join(packageRoot, "settings.json");
166
- const pluginManifestPath = path.join(packageRoot, ".claude-plugin", "plugin.json");
167
- const monitorsPath = path.join(packageRoot, "monitors", "monitors.json");
168
- const packageJsonPath = path.join(packageRoot, "package.json");
169
-
170
- const state = {
171
- exists: false,
172
- packageVersion: null,
173
- pluginVersion: null,
174
- sourceRepo: {
175
- isGitRepo: false,
176
- branch: null,
177
- shortSha: null,
178
- exactTag: null,
179
- dirty: false,
180
- },
181
- defaultAgent: null,
182
- subagentStatusLineCommand: null,
183
- monitorCount: 0,
184
- monitors: [],
185
- userConfig: [],
186
- };
187
-
188
- let pluginSettings = null;
189
- let pluginManifest = null;
190
- let monitors = null;
191
-
192
- try {
193
- pluginSettings = JSON.parse(await fs.readFile(pluginSettingsPath, "utf-8"));
194
- } catch {
195
- pluginSettings = null;
196
- }
197
-
198
- try {
199
- pluginManifest = JSON.parse(await fs.readFile(pluginManifestPath, "utf-8"));
200
- } catch {
201
- pluginManifest = null;
202
- }
203
-
204
- try {
205
- const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
206
- state.packageVersion = typeof pkg?.version === "string" ? pkg.version : null;
207
- } catch {
208
- state.packageVersion = null;
209
- }
210
-
211
- const gitRepoCheck = runSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: packageRoot });
212
- if (gitRepoCheck.code === 0 && gitRepoCheck.stdout.trim() === "true") {
213
- state.sourceRepo.isGitRepo = true;
214
-
215
- const branch = runSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: packageRoot });
216
- if (branch.code === 0) {
217
- state.sourceRepo.branch = branch.stdout.trim() || null;
218
- }
219
-
220
- const shortSha = runSync("git", ["rev-parse", "--short", "HEAD"], { cwd: packageRoot });
221
- if (shortSha.code === 0) {
222
- state.sourceRepo.shortSha = shortSha.stdout.trim() || null;
223
- }
224
-
225
- const exactTag = runSync("git", ["describe", "--tags", "--exact-match", "HEAD"], { cwd: packageRoot });
226
- if (exactTag.code === 0) {
227
- state.sourceRepo.exactTag = exactTag.stdout.trim() || null;
228
- }
229
-
230
- const status = runSync("git", ["status", "--short"], { cwd: packageRoot });
231
- if (status.code === 0) {
232
- state.sourceRepo.dirty = status.stdout.trim().length > 0;
233
- }
234
- }
235
-
236
- try {
237
- monitors = JSON.parse(await fs.readFile(monitorsPath, "utf-8"));
238
- } catch {
239
- monitors = null;
240
- }
241
-
242
- if (!pluginSettings && !pluginManifest && !monitors) {
243
- return state;
244
- }
245
-
246
- state.exists = true;
247
- state.pluginVersion = typeof pluginManifest?.version === "string" ? pluginManifest.version : null;
248
- state.defaultAgent = typeof pluginSettings?.agent === "string" ? pluginSettings.agent : null;
249
- state.subagentStatusLineCommand =
250
- typeof pluginSettings?.subagentStatusLine?.command === "string"
251
- ? pluginSettings.subagentStatusLine.command
252
- : null;
253
-
254
- if (Array.isArray(monitors)) {
255
- state.monitorCount = monitors.length;
256
- state.monitors = monitors
257
- .filter((entry) => entry && typeof entry === "object")
258
- .map((entry) => ({
259
- name: typeof entry.name === "string" ? entry.name : null,
260
- when: typeof entry.when === "string" ? entry.when : "always",
261
- }))
262
- .filter((entry) => entry.name);
263
- }
264
-
265
- if (pluginManifest?.userConfig && typeof pluginManifest.userConfig === "object") {
266
- state.userConfig = Object.entries(pluginManifest.userConfig)
267
- .filter(([, value]) => value && typeof value === "object" && !Array.isArray(value))
268
- .map(([key, value]) => ({
269
- key,
270
- type: value.type ?? null,
271
- default: Object.prototype.hasOwnProperty.call(value, "default") ? value.default : undefined,
272
- sensitive: value.sensitive === true,
273
- }));
274
- }
275
-
276
- return state;
277
- }
278
-
279
- export async function readProjectState(cwd = process.cwd()) {
280
- const flowDir = path.join(cwd, ".flow");
281
- try {
282
- const stat = await fs.stat(flowDir);
283
- if (!stat.isDirectory()) {
284
- return { exists: false, activeSpec: null };
285
- }
286
-
287
- try {
288
- const activeSpec = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
289
- return { exists: true, activeSpec: activeSpec.trim() };
290
- } catch {
291
- return { exists: true, activeSpec: null };
292
- }
293
- } catch {
294
- return { exists: false, activeSpec: null };
295
- }
296
- }
297
-
298
- export async function collectDoctorData(
299
- {
300
- cwd = process.cwd(),
301
- } = {},
302
- {
303
- claudeVersionImpl = claudeVersion,
304
- listPluginsImpl = listPlugins,
305
- listPluginMarketplacesImpl = listPluginMarketplaces,
306
- listMcpsImpl = listMcps,
307
- readUserMcpConfigImpl = readUserMcpConfig,
308
- inspectClaudeMemRuntimesImpl = inspectClaudeMemRuntimes,
309
- inspectRuntimeEnvironmentImpl = inspectRuntimeEnvironment,
310
- readProjectStateImpl = readProjectState,
311
- readProjectMcpConfigImpl = readProjectMcpConfig,
312
- readProjectTeamConfigImpl = readProjectTeamConfig,
313
- readProjectClaudeSettingsImpl = readProjectClaudeSettings,
314
- readBundledPluginRuntimeDefaultsImpl = readBundledPluginRuntimeDefaults,
315
- readConfigImpl = readConfig,
316
- } = {}
317
- ) {
318
- const claudeVersionValue = claudeVersionImpl();
319
- const plugins = claudeVersionValue ? listPluginsImpl() : [];
320
- const marketplaces = claudeVersionValue ? listPluginMarketplacesImpl() : [];
321
- const mcps = claudeVersionValue ? listMcpsImpl() : [];
322
- const userMcpConfig = claudeVersionValue ? readUserMcpConfigImpl() : new Map();
323
- const runtimeStatus = plugins.some(
324
- (plugin) => plugin.name === "claude-mem" && plugin.status === "enabled"
325
- )
326
- ? inspectClaudeMemRuntimesImpl()
327
- : null;
328
-
329
- return {
330
- claudeVersionValue,
331
- nodeVersion: process.version,
332
- plugins,
333
- marketplaces,
334
- mcps,
335
- userMcpConfig,
336
- runtimeStatus,
337
- runtimeEnvironment: inspectRuntimeEnvironmentImpl(),
338
- cwd,
339
- projectState: await readProjectStateImpl(cwd),
340
- projectMcpConfig: await readProjectMcpConfigImpl(cwd),
341
- projectTeamConfig: await readProjectTeamConfigImpl(cwd),
342
- projectClaudeSettings: await readProjectClaudeSettingsImpl(cwd),
343
- bundledPluginRuntimeDefaults: await readBundledPluginRuntimeDefaultsImpl(),
344
- legacyInstallState: {
345
- configPath: CONFIG_FILE,
346
- hasLegacyContext7ApiKey: Object.prototype.hasOwnProperty.call(readConfigImpl(), "context7ApiKey"),
347
- },
348
- };
349
- }
350
-
351
- export async function applyDoctorFixes(
352
- doctorData,
353
- {
354
- ensureClaudeMemRuntimesImpl = ensureClaudeMemRuntimes,
355
- removeMcpImpl = removeMcp,
356
- readConfigImpl = readConfig,
357
- writeConfigImpl = writeConfig,
358
- } = {}
359
- ) {
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
-
396
- const claudeMemEnabled = doctorData.plugins.some(
397
- (plugin) => plugin.name === "claude-mem" && plugin.status === "enabled"
398
- );
399
-
400
- if (!claudeMemEnabled) {
401
- return fixes;
402
- }
403
-
404
- const runtimeStatus = ensureClaudeMemRuntimesImpl();
405
- doctorData.runtimeStatus = runtimeStatus;
406
- fixes.push({
407
- kind: "claude-mem-runtimes",
408
- runtimeStatus,
409
- });
410
- return fixes;
411
- }
412
-
413
- export function renderReportLines(lines, { logImpl = log } = {}) {
414
- for (const line of lines) {
415
- if (line.level === "ok") {
416
- logImpl.ok(line.text);
417
- } else if (line.level === "warn") {
418
- logImpl.warn(line.text);
419
- } else if (line.level === "err") {
420
- logImpl.err(line.text);
421
- } else {
422
- logImpl.info(line.text);
423
- }
424
-
425
- for (const detail of line.details || []) {
426
- console.log(color.dim(` → ${detail}`));
427
- }
428
- }
429
- }
430
-
431
- export function buildDoctorJsonPayload({
432
- context = {},
433
- doctorData,
434
- fixes = [],
435
- report,
436
- } = {}) {
437
- return {
438
- contractVersion: DOCTOR_JSON_CONTRACT_VERSION,
439
- generatedAt: new Date().toISOString(),
440
- context: {
441
- fix: context.fix === true,
442
- json: context.json === true,
443
- verbose: context.verbose === true,
444
- },
445
- summary: {
446
- errors: report?.errors || 0,
447
- warnings: report?.warnings || 0,
448
- healthy: (report?.errors || 0) === 0,
449
- },
450
- metadata: {
451
- appliedFixes: fixes,
452
- settingsInspection: DOCTOR_SETTINGS_INSPECTION,
453
- },
454
- doctorData,
455
- report,
456
- };
457
- }
458
-
459
- export function printDoctorSummary(
460
- report,
461
- { logImpl = log, exitImpl = process.exit } = {}
462
- ) {
463
- console.log();
464
- if (report.errors > 0) {
465
- console.log(color.red(`Summary: ${report.errors} error(s), ${report.warnings} warning(s)`));
466
- console.log(color.dim("Fix errors and re-run curdx-flow doctor"));
467
- exitImpl(1);
468
- return;
469
- }
470
-
471
- if (report.warnings > 0) {
472
- console.log(color.yellow(`Summary: ${report.warnings} warning(s). Usable, but worth addressing.`));
473
- return;
474
- }
475
-
476
- console.log(color.green("Summary: all healthy ✓"));
477
- }
478
-
479
- export function printVerboseDoctorDetails({ runSyncImpl = runSync } = {}) {
480
- console.log(`\n${color.bold("Details:")}`);
481
- console.log(color.dim(" Plugins raw:"));
482
- console.log(runSyncImpl("claude", ["plugin", "list"]).stdout);
483
- }