@curdx/flow 2.3.3 → 2.3.5

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.3"
9
+ "version": "2.3.5"
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.3",
19
+ "version": "2.3.5",
20
20
  "author": {
21
21
  "name": "wdx",
22
22
  "email": "bydongxin@gmail.com"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "curdx-flow",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
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
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.5
4
+
5
+ - added a versioned `doctor --json` contract for CI/wrappers, including applied-fix metadata and explicit settings-inspection scope metadata
6
+ - added CLI-level regression coverage so `doctor --json` stays silent except for JSON stdout and exit code
7
+ - locked runtime env consumer drift checks against manifest `userConfig` keys and the actual bundled hook/monitor scripts
8
+
3
9
  ## 2.3.1
4
10
 
5
11
  - expanded `doctor` to report CurDX-Flow’s bundled main-thread agent, monitor surface, and plugin option defaults
package/README.md CHANGED
@@ -20,7 +20,8 @@ npx @curdx/flow install --all
20
20
 
21
21
  Requires modern Claude Code and Node 18+. The install flow registers the plugin,
22
22
  required reasoning/doc tools, and recommended companion plugins. Run
23
- `npx @curdx/flow doctor` after install if anything looks off.
23
+ `npx @curdx/flow doctor` after install if anything looks off. For CI or wrapper
24
+ automation, use `npx @curdx/flow doctor --json`.
24
25
 
25
26
  After restart, CurDX-Flow routes the main thread through `flow-orchestrator`
26
27
  by default and starts the bundled `.flow` progress monitor in interactive
package/cli/README.md CHANGED
@@ -36,12 +36,14 @@ Steps:
36
36
  | `--all` | Install all recommended plugins, no prompt |
37
37
  | `--no-deps` | Install only curdx-flow itself |
38
38
 
39
- ### `doctor [--verbose] [--fix]`
39
+ ### `doctor [--verbose] [--fix] [--json]`
40
40
 
41
41
  External diagnostics: claude CLI / curdx-flow / required MCPs / recommended plugins / current directory `.flow/` state.
42
42
 
43
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
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 and runtime env projection.
46
+
45
47
  ### Project initialization (not a CLI command)
46
48
 
47
49
  Project initialization is a Claude Code slash command, not a CLI one. After `install`, open your project in Claude Code and run:
@@ -21,10 +21,16 @@ export { readProjectClaudeSettings };
21
21
  export { inspectRuntimeEnvironment };
22
22
 
23
23
  const PACKAGE_ROOT = fileURLToPath(new URL("../", import.meta.url));
24
+ export const DOCTOR_JSON_CONTRACT_VERSION = 1;
25
+ export const DOCTOR_SETTINGS_INSPECTION = Object.freeze({
26
+ pluginOptionScopesInspected: ["user", "project", "local"],
27
+ pluginOptionScopesNotInspected: ["managed", "cli"],
28
+ });
24
29
 
25
30
  export function createDoctorContext(args = []) {
26
31
  return {
27
32
  fix: args.includes("--fix"),
33
+ json: args.includes("--json"),
28
34
  verbose: args.includes("--verbose") || args.includes("-v"),
29
35
  };
30
36
  }
@@ -334,6 +340,34 @@ export function renderReportLines(lines, { logImpl = log } = {}) {
334
340
  }
335
341
  }
336
342
 
343
+ export function buildDoctorJsonPayload({
344
+ context = {},
345
+ doctorData,
346
+ fixes = [],
347
+ report,
348
+ } = {}) {
349
+ return {
350
+ contractVersion: DOCTOR_JSON_CONTRACT_VERSION,
351
+ generatedAt: new Date().toISOString(),
352
+ context: {
353
+ fix: context.fix === true,
354
+ json: context.json === true,
355
+ verbose: context.verbose === true,
356
+ },
357
+ summary: {
358
+ errors: report?.errors || 0,
359
+ warnings: report?.warnings || 0,
360
+ healthy: (report?.errors || 0) === 0,
361
+ },
362
+ metadata: {
363
+ appliedFixes: fixes,
364
+ settingsInspection: DOCTOR_SETTINGS_INSPECTION,
365
+ },
366
+ doctorData,
367
+ report,
368
+ };
369
+ }
370
+
337
371
  export function printDoctorSummary(
338
372
  report,
339
373
  { logImpl = log, exitImpl = process.exit } = {}
package/cli/doctor.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  import { buildDoctorReport } from "./lib/doctor-report.js";
10
10
  import {
11
11
  applyDoctorFixes,
12
+ buildDoctorJsonPayload,
12
13
  collectDoctorData,
13
14
  createDoctorContext,
14
15
  printDoctorSummary,
@@ -16,29 +17,57 @@ import {
16
17
  renderReportLines,
17
18
  } from "./doctor-workflow.js";
18
19
 
19
- export async function doctor(args = []) {
20
- const context = createDoctorContext(args);
20
+ export async function doctor(
21
+ args = [],
22
+ {
23
+ applyDoctorFixesImpl = applyDoctorFixes,
24
+ buildDoctorJsonPayloadImpl = buildDoctorJsonPayload,
25
+ buildDoctorReportImpl = buildDoctorReport,
26
+ collectDoctorDataImpl = collectDoctorData,
27
+ createDoctorContextImpl = createDoctorContext,
28
+ printDoctorSummaryImpl = printDoctorSummary,
29
+ printVerboseDoctorDetailsImpl = printVerboseDoctorDetails,
30
+ renderReportLinesImpl = renderReportLines,
31
+ logImpl = log,
32
+ colorImpl = color,
33
+ consoleImpl = console,
34
+ processImpl = process,
35
+ } = {}
36
+ ) {
37
+ const context = createDoctorContextImpl(args);
38
+ let fixes = [];
21
39
 
22
- log.title("🏥 CurdX-Flow Health Check");
40
+ if (!context.json) {
41
+ logImpl.title("🏥 CurdX-Flow Health Check");
42
+ }
23
43
 
24
- const doctorData = await collectDoctorData();
44
+ const doctorData = await collectDoctorDataImpl();
25
45
  if (context.fix) {
26
- log.info("Applying safe fixes...");
27
- const fixes = await applyDoctorFixes(doctorData);
28
- if (fixes.length === 0) {
29
- log.info("No automatic fixes available for the current environment");
46
+ if (!context.json) {
47
+ logImpl.info("Applying safe fixes...");
48
+ }
49
+ fixes = await applyDoctorFixesImpl(doctorData);
50
+ if (fixes.length === 0 && !context.json) {
51
+ logImpl.info("No automatic fixes available for the current environment");
30
52
  }
31
53
  }
32
- const report = buildDoctorReport(doctorData);
54
+ const report = buildDoctorReportImpl(doctorData);
55
+
56
+ if (context.json) {
57
+ const payload = buildDoctorJsonPayloadImpl({ context, doctorData, fixes, report });
58
+ consoleImpl.log(JSON.stringify(payload, null, 2));
59
+ processImpl.exitCode = report.errors > 0 ? 1 : 0;
60
+ return;
61
+ }
33
62
 
34
- renderReportLines(report.lines);
63
+ renderReportLinesImpl(report.lines, { logImpl });
35
64
  for (const section of report.sections) {
36
- console.log(`\n${color.bold(section.title)}`);
37
- renderReportLines(section.lines);
65
+ consoleImpl.log(`\n${colorImpl.bold(section.title)}`);
66
+ renderReportLinesImpl(section.lines, { logImpl });
38
67
  }
39
68
 
40
- printDoctorSummary(report);
69
+ printDoctorSummaryImpl(report, { logImpl });
41
70
  if (context.verbose && doctorData.claudeVersionValue) {
42
- printVerboseDoctorDetails();
71
+ printVerboseDoctorDetailsImpl();
43
72
  }
44
73
  }
package/cli/help.js CHANGED
@@ -18,6 +18,7 @@ ${color.bold("COMMANDS")}
18
18
 
19
19
  ${color.cyan("doctor")} Check health (claude CLI, plugin, MCPs, recommended)
20
20
  --fix Apply safe runtime fixes (bun/uv PATH symlinks)
21
+ --json Emit machine-readable health report JSON
21
22
  --verbose Show raw plugin list details
22
23
 
23
24
  ${color.cyan("upgrade")} Update curdx-flow and recommended plugins to latest
@@ -121,6 +121,23 @@ const CURDX_FLOW_REQUIRED_PLUGIN_IDS = ["context7-plugin@context7-marketplace"];
121
121
  const HTTP_HOOK_SETTINGS = ["allowedHttpHookUrls", "httpHookAllowedEnvVars"];
122
122
  const PERSISTED_EFFORT_LEVELS = ["low", "medium", "high", "xhigh"];
123
123
  const ENV_EFFORT_LEVELS = [...PERSISTED_EFFORT_LEVELS, "max", "auto"];
124
+ export const CURDX_FLOW_RUNTIME_CONSUMERS = {
125
+ autonomous_blocking: {
126
+ envVar: "CLAUDE_PLUGIN_OPTION_AUTONOMOUS_BLOCKING",
127
+ consumer: "hooks/scripts/stop-watcher.sh",
128
+ summary: "controls whether the Stop hook blocks Claude from stopping while execute work remains",
129
+ },
130
+ daily_dependency_check: {
131
+ envVar: "CLAUDE_PLUGIN_OPTION_DAILY_DEPENDENCY_CHECK",
132
+ consumer: "hooks/scripts/session-start.sh",
133
+ summary: "controls the once-per-day recommended companion plugin reminder",
134
+ },
135
+ monitor_interval_seconds: {
136
+ envVar: "CLAUDE_PLUGIN_OPTION_MONITOR_INTERVAL_SECONDS",
137
+ consumer: "monitors/scripts/flow-state-monitor.sh",
138
+ summary: "controls the polling interval for the interactive flow-state monitor",
139
+ },
140
+ };
124
141
 
125
142
  function envFlagEnabled(value) {
126
143
  if (value === true || value === 1) return true;
@@ -258,6 +275,43 @@ function applyCurdxFlowPluginOptionOverride(pluginOptionsState, key, value, scop
258
275
  }
259
276
  }
260
277
 
278
+ function buildCurdxFlowRuntimeProjection(pluginOptionsState) {
279
+ return pluginOptionsState.definitions.map((definition) => {
280
+ const effective = pluginOptionsState.machineEffective[definition.key] || {
281
+ value: definition.default,
282
+ source: "default",
283
+ };
284
+ const runtime = CURDX_FLOW_RUNTIME_CONSUMERS[definition.key] || {};
285
+ const details = [];
286
+
287
+ if (definition.key === "autonomous_blocking") {
288
+ details.push(
289
+ effective.value === false
290
+ ? "stop-hook continuation is disabled; Claude may stop at turn end even when execute tasks remain"
291
+ : "stop-hook continuation is enabled; CurDX-Flow can block turn end while execute tasks remain"
292
+ );
293
+ } else if (definition.key === "daily_dependency_check") {
294
+ details.push(
295
+ effective.value === false
296
+ ? "SessionStart plugin reminder is disabled on this machine"
297
+ : "SessionStart plugin reminder runs at most once per day on this machine"
298
+ );
299
+ } else if (definition.key === "monitor_interval_seconds") {
300
+ details.push(`flow-state monitor polls every ${effective.value} second(s) when Monitor is available`);
301
+ }
302
+
303
+ return {
304
+ key: definition.key,
305
+ envVar: runtime.envVar || `CLAUDE_PLUGIN_OPTION_${definition.key.toUpperCase()}`,
306
+ consumer: runtime.consumer || "plugin subprocess",
307
+ summary: runtime.summary || "runtime consumer",
308
+ value: effective.value,
309
+ source: effective.source,
310
+ details,
311
+ };
312
+ });
313
+ }
314
+
261
315
  function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope = "project") {
262
316
  if (!isNonArrayObject(parsed) || !("pluginConfigs" in parsed)) {
263
317
  return;
@@ -593,6 +647,7 @@ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir =
593
647
  localParseError: null,
594
648
  localWarnings: [],
595
649
  pluginOptions: createCurdxFlowPluginOptionsState(),
650
+ pluginRuntimeProjection: [],
596
651
  };
597
652
 
598
653
  try {
@@ -972,5 +1027,7 @@ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir =
972
1027
  }
973
1028
  }
974
1029
 
1030
+ state.pluginRuntimeProjection = buildCurdxFlowRuntimeProjection(state.pluginOptions);
1031
+
975
1032
  return state;
976
1033
  }
@@ -598,6 +598,30 @@ export function buildDoctorReport({
598
598
  }
599
599
  }
600
600
 
601
+ if ((projectClaudeSettings?.pluginRuntimeProjection || []).length > 0) {
602
+ const runtimeProjectionSection = createSection("CurDX-Flow runtime projection:");
603
+
604
+ for (const entry of projectClaudeSettings.pluginRuntimeProjection) {
605
+ const sourceLabel = entry.source === "local"
606
+ ? "local"
607
+ : entry.source === "project"
608
+ ? "project"
609
+ : entry.source === "user"
610
+ ? "user"
611
+ : "bundled default";
612
+ pushSectionLine(
613
+ runtimeProjectionSection,
614
+ "info",
615
+ `${entry.envVar.padEnd(36)} ${formatInlineValue(entry.value)} (${sourceLabel})`,
616
+ [
617
+ `consumer: ${entry.consumer}`,
618
+ entry.summary,
619
+ ...(entry.details || []),
620
+ ]
621
+ );
622
+ }
623
+ }
624
+
601
625
  const localProjectSection = createSection("Local project:");
602
626
  if (projectState?.exists) {
603
627
  pushSectionLine(localProjectSection, "ok", `.flow/ ${cwd}`);
@@ -88,6 +88,7 @@ Guarded artifact targets:
88
88
  - `autonomous_blocking`: lets users disable stop-hook continuation without editing plugin files.
89
89
  - `daily_dependency_check`: silences or enables the once-per-day recommended-plugin reminder.
90
90
  - `monitor_interval_seconds`: controls plugin monitor polling cadence.
91
+ - `doctor` should explain both the machine-effective config value and the projected plugin subprocess env var for these knobs, since hook/monitor behavior depends on the env projection rather than direct JSON parsing.
91
92
 
92
93
  ## Plugin Dependency Constraints
93
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "Skill-first discipline layer and CLI installer for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {