@curdx/flow 2.3.4 → 2.3.6

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.4"
9
+ "version": "2.3.6"
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.4",
19
+ "version": "2.3.6",
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.4",
3
+ "version": "2.3.6",
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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.6
4
+
5
+ - taught `doctor` to inspect file-based Claude Code managed settings and apply them at the correct highest-precedence layer for CurDX-Flow plugin options
6
+ - upgraded the `doctor --json` contract to v2 so wrappers can distinguish inspected `managed-file` scope from uninspected server-managed / MDM / CLI overrides
7
+ - added report and workflow coverage for managed-settings fragments, invalid managed JSON, and managed-vs-local/project/user precedence
8
+
9
+ ## 2.3.5
10
+
11
+ - added a versioned `doctor --json` contract for CI/wrappers, including applied-fix metadata and explicit settings-inspection scope metadata
12
+ - added CLI-level regression coverage so `doctor --json` stays silent except for JSON stdout and exit code
13
+ - locked runtime env consumer drift checks against manifest `userConfig` keys and the actual bundled hook/monitor scripts
14
+
3
15
  ## 2.3.1
4
16
 
5
17
  - expanded `doctor` to report CurDX-Flow’s bundled main-thread agent, monitor surface, and plugin option defaults
package/README.md CHANGED
@@ -20,7 +20,9 @@ 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`, which now includes file-based
25
+ managed settings in its inspected precedence model.
24
26
 
25
27
  After restart, CurDX-Flow routes the main thread through `flow-orchestrator`
26
28
  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, file-based managed settings, 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 = 2;
25
+ export const DOCTOR_SETTINGS_INSPECTION = Object.freeze({
26
+ pluginOptionScopesInspected: ["managed-file", "user", "project", "local"],
27
+ pluginOptionScopesNotInspected: ["managed-server", "managed-mdm", "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,7 +121,7 @@ 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
- const CURDX_FLOW_RUNTIME_CONSUMERS = {
124
+ export const CURDX_FLOW_RUNTIME_CONSUMERS = {
125
125
  autonomous_blocking: {
126
126
  envVar: "CLAUDE_PLUGIN_OPTION_AUTONOMOUS_BLOCKING",
127
127
  consumer: "hooks/scripts/stop-watcher.sh",
@@ -139,6 +139,22 @@ const CURDX_FLOW_RUNTIME_CONSUMERS = {
139
139
  },
140
140
  };
141
141
 
142
+ function resolveManagedSettingsRoot({ platform = process.platform, env = process.env } = {}) {
143
+ if (platform === "darwin") {
144
+ return "/Library/Application Support/ClaudeCode";
145
+ }
146
+
147
+ if (platform === "linux") {
148
+ return "/etc/claude-code";
149
+ }
150
+
151
+ if (platform === "win32") {
152
+ return path.join(env.ProgramFiles || "C:\\Program Files", "ClaudeCode");
153
+ }
154
+
155
+ return null;
156
+ }
157
+
142
158
  function envFlagEnabled(value) {
143
159
  if (value === true || value === 1) return true;
144
160
  if (typeof value !== "string") return false;
@@ -226,6 +242,10 @@ function createCurdxFlowPluginOptionsState() {
226
242
  return {
227
243
  pluginId: CURDX_FLOW_PLUGIN_ID,
228
244
  definitions,
245
+ managed: {
246
+ overrides: {},
247
+ files: [],
248
+ },
229
249
  user: {
230
250
  pluginConfigsPresent: false,
231
251
  pluginConfigPresent: false,
@@ -250,12 +270,14 @@ function createCurdxFlowPluginOptionsState() {
250
270
  }
251
271
 
252
272
  function pluginScopeLabel(scope) {
273
+ if (scope === "managed") return "managed settings";
253
274
  if (scope === "local") return "settings.local.json";
254
275
  if (scope === "user") return "~/.claude/settings.json";
255
276
  return "settings.json";
256
277
  }
257
278
 
258
279
  function invalidSettingKindForScope(scope) {
280
+ if (scope === "managed") return "invalid-managed-setting";
259
281
  if (scope === "local") return "invalid-local-setting";
260
282
  if (scope === "user") return "invalid-user-setting";
261
283
  return "invalid-project-setting";
@@ -312,17 +334,23 @@ function buildCurdxFlowRuntimeProjection(pluginOptionsState) {
312
334
  });
313
335
  }
314
336
 
315
- function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope = "project") {
337
+ function auditCurdxFlowPluginOptions(
338
+ parsed,
339
+ warnings,
340
+ pluginOptionsState,
341
+ { scope = "project", settingsLabel = pluginScopeLabel(scope), targetOverrides = null } = {}
342
+ ) {
316
343
  if (!isNonArrayObject(parsed) || !("pluginConfigs" in parsed)) {
317
344
  return;
318
345
  }
319
346
 
320
- const target = scope === "local"
321
- ? pluginOptionsState.local
322
- : scope === "user"
323
- ? pluginOptionsState.user
324
- : pluginOptionsState.project;
325
- const settingsLabel = pluginScopeLabel(scope);
347
+ const target = scope === "managed"
348
+ ? pluginOptionsState.managed
349
+ : scope === "local"
350
+ ? pluginOptionsState.local
351
+ : scope === "user"
352
+ ? pluginOptionsState.user
353
+ : pluginOptionsState.project;
326
354
  target.pluginConfigsPresent = true;
327
355
 
328
356
  if (!isNonArrayObject(parsed.pluginConfigs)) {
@@ -433,11 +461,107 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
433
461
  }
434
462
  }
435
463
 
436
- target.overrides[key] = value;
464
+ const overrideTarget = targetOverrides || target.overrides;
465
+ overrideTarget[key] = value;
466
+ if (scope === "managed") {
467
+ target.overrides[key] = value;
468
+ }
437
469
  applyCurdxFlowPluginOptionOverride(pluginOptionsState, key, value, scope);
438
470
  }
439
471
  }
440
472
 
473
+ async function readManagedClaudeSettings(
474
+ pluginOptionsState,
475
+ warnings,
476
+ {
477
+ platform = process.platform,
478
+ env = process.env,
479
+ managedSettingsDir = resolveManagedSettingsRoot({ platform, env }),
480
+ } = {}
481
+ ) {
482
+ const state = {
483
+ supported: Boolean(managedSettingsDir),
484
+ rootPath: managedSettingsDir,
485
+ basePath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.json") : null,
486
+ dropInPath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.d") : null,
487
+ exists: false,
488
+ baseExists: false,
489
+ dropInDirExists: false,
490
+ files: [],
491
+ };
492
+
493
+ if (!managedSettingsDir) {
494
+ return state;
495
+ }
496
+
497
+ const fileQueue = [];
498
+
499
+ try {
500
+ const stat = await fs.stat(state.basePath);
501
+ if (stat.isFile()) {
502
+ state.baseExists = true;
503
+ state.exists = true;
504
+ fileQueue.push({
505
+ label: "managed-settings.json",
506
+ path: state.basePath,
507
+ });
508
+ }
509
+ } catch {}
510
+
511
+ try {
512
+ const stat = await fs.stat(state.dropInPath);
513
+ if (stat.isDirectory()) {
514
+ state.dropInDirExists = true;
515
+ const entries = (await fs.readdir(state.dropInPath))
516
+ .filter((name) => !name.startsWith(".") && name.endsWith(".json"))
517
+ .sort();
518
+ for (const name of entries) {
519
+ const absPath = path.join(state.dropInPath, name);
520
+ const entryStat = await fs.stat(absPath);
521
+ if (!entryStat.isFile()) continue;
522
+ state.exists = true;
523
+ fileQueue.push({
524
+ label: `managed-settings.d/${name}`,
525
+ path: absPath,
526
+ });
527
+ }
528
+ }
529
+ } catch {}
530
+
531
+ for (const file of fileQueue) {
532
+ const entry = {
533
+ label: file.label,
534
+ path: file.path,
535
+ invalid: false,
536
+ parseError: null,
537
+ overrides: {},
538
+ };
539
+
540
+ try {
541
+ const parsed = JSON.parse(await fs.readFile(file.path, "utf-8"));
542
+ auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, {
543
+ scope: "managed",
544
+ settingsLabel: file.label,
545
+ targetOverrides: entry.overrides,
546
+ });
547
+ } catch (error) {
548
+ entry.invalid = true;
549
+ entry.parseError = error?.message || String(error);
550
+ }
551
+
552
+ state.files.push(entry);
553
+ pluginOptionsState.managed.files.push({
554
+ label: entry.label,
555
+ path: entry.path,
556
+ overrides: { ...entry.overrides },
557
+ invalid: entry.invalid,
558
+ parseError: entry.parseError,
559
+ });
560
+ }
561
+
562
+ return state;
563
+ }
564
+
441
565
  function auditLocalClaudeSettings(parsed, warnings) {
442
566
  const scope = "local";
443
567
  const permissions = parsed?.permissions && typeof parsed.permissions === "object"
@@ -629,11 +753,30 @@ function auditLocalClaudeSettings(parsed, warnings) {
629
753
  }
630
754
  }
631
755
 
632
- export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir = os.homedir() } = {}) {
756
+ export async function readProjectClaudeSettings(
757
+ cwd = process.cwd(),
758
+ {
759
+ homeDir = os.homedir(),
760
+ platform = process.platform,
761
+ env = process.env,
762
+ managedSettingsDir = resolveManagedSettingsRoot({ platform, env }),
763
+ } = {}
764
+ ) {
633
765
  const settingsPath = path.join(cwd, ".claude", "settings.json");
634
766
  const localSettingsPath = path.join(cwd, ".claude", "settings.local.json");
635
767
  const userSettingsPath = path.join(homeDir, ".claude", "settings.json");
636
768
  const state = {
769
+ managedSettings: {
770
+ supported: Boolean(managedSettingsDir),
771
+ rootPath: managedSettingsDir,
772
+ basePath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.json") : null,
773
+ dropInPath: managedSettingsDir ? path.join(managedSettingsDir, "managed-settings.d") : null,
774
+ exists: false,
775
+ baseExists: false,
776
+ dropInDirExists: false,
777
+ files: [],
778
+ },
779
+ managedWarnings: [],
637
780
  userExists: false,
638
781
  userInvalid: false,
639
782
  userParseError: null,
@@ -667,7 +810,7 @@ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir =
667
810
  if (state.userExists) {
668
811
  try {
669
812
  const userParsed = JSON.parse(await fs.readFile(userSettingsPath, "utf-8"));
670
- auditCurdxFlowPluginOptions(userParsed, state.userWarnings, state.pluginOptions, "user");
813
+ auditCurdxFlowPluginOptions(userParsed, state.userWarnings, state.pluginOptions, { scope: "user" });
671
814
  } catch (error) {
672
815
  state.userInvalid = true;
673
816
  state.userParseError = error?.message || String(error);
@@ -698,7 +841,7 @@ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir =
698
841
  const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
699
842
  const deny = Array.isArray(permissions.deny) ? permissions.deny : [];
700
843
 
701
- auditCurdxFlowPluginOptions(parsed, state.warnings, state.pluginOptions, "project");
844
+ auditCurdxFlowPluginOptions(parsed, state.warnings, state.pluginOptions, { scope: "project" });
702
845
 
703
846
  if ("enabledPlugins" in parsed && (
704
847
  !parsed.enabledPlugins ||
@@ -1019,7 +1162,7 @@ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir =
1019
1162
  if (state.localExists) {
1020
1163
  try {
1021
1164
  const localParsed = JSON.parse(await fs.readFile(localSettingsPath, "utf-8"));
1022
- auditCurdxFlowPluginOptions(localParsed, state.localWarnings, state.pluginOptions, "local");
1165
+ auditCurdxFlowPluginOptions(localParsed, state.localWarnings, state.pluginOptions, { scope: "local" });
1023
1166
  auditLocalClaudeSettings(localParsed, state.localWarnings);
1024
1167
  } catch (error) {
1025
1168
  state.localInvalid = true;
@@ -1027,6 +1170,16 @@ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir =
1027
1170
  }
1028
1171
  }
1029
1172
 
1173
+ state.managedSettings = await readManagedClaudeSettings(
1174
+ state.pluginOptions,
1175
+ state.managedWarnings,
1176
+ {
1177
+ platform,
1178
+ env,
1179
+ managedSettingsDir,
1180
+ }
1181
+ );
1182
+
1030
1183
  state.pluginRuntimeProjection = buildCurdxFlowRuntimeProjection(state.pluginOptions);
1031
1184
 
1032
1185
  return state;
@@ -13,6 +13,27 @@ function pluginErrorDetails(plugin) {
13
13
  }
14
14
 
15
15
  function projectSettingsWarningDetails(warning) {
16
+ if (warning?.scope === "managed") {
17
+ if (warning.kind === "unknown-plugin-option") {
18
+ return [
19
+ "file-based managed settings override user, project, and local CurDX-Flow plugin options on this machine",
20
+ "remove the unknown key or rename it to a supported CurDX-Flow plugin option under pluginConfigs[curdx-flow@curdx-flow-marketplace].options",
21
+ ];
22
+ }
23
+
24
+ if (warning.kind === "invalid-managed-setting") {
25
+ return [
26
+ "file-based managed settings are evaluated before user/project/local settings",
27
+ "fix the managed JSON value shape so CurDX-Flow runtime projection matches the intended policy",
28
+ ];
29
+ }
30
+
31
+ return [
32
+ "file-based managed settings override user, project, and local CurDX-Flow plugin options on this machine",
33
+ "fix the managed policy or move the value to a supported CurDX-Flow plugin option key",
34
+ ];
35
+ }
36
+
16
37
  if (warning?.scope === "user") {
17
38
  if (warning.kind === "unknown-plugin-option") {
18
39
  return [
@@ -481,6 +502,7 @@ export function buildDoctorReport({
481
502
  ) {
482
503
  const configuredOptionsSection = createSection("CurDX-Flow configured options:");
483
504
  const optionState = projectClaudeSettings?.pluginOptions;
505
+ const managedOverrides = optionState?.managed?.overrides || {};
484
506
  const userOverrides = optionState?.user?.overrides || {};
485
507
  const projectOverrides = optionState?.project?.overrides || {};
486
508
  const localOverrides = optionState?.local?.overrides || {};
@@ -492,13 +514,42 @@ export function buildDoctorReport({
492
514
  pushSectionLine(
493
515
  configuredOptionsSection,
494
516
  "info",
495
- "Scope precedence settings.local.json > settings.json > ~/.claude/settings.json > bundled default",
517
+ "Scope precedence file-managed > settings.local.json > settings.json > ~/.claude/settings.json > bundled default",
496
518
  [
497
- "managed settings and command-line overrides are not inspected here",
519
+ "server-managed settings, MDM/registry policies, and command-line overrides are not inspected here",
498
520
  "effective values below reflect this machine only",
499
521
  ]
500
522
  );
501
523
 
524
+ if (projectClaudeSettings?.managedSettings?.exists) {
525
+ if (Object.keys(managedOverrides).length > 0) {
526
+ const summary = Object.entries(managedOverrides)
527
+ .map(([key, value]) => `${key}=${formatInlineValue(value)}`)
528
+ .join(", ");
529
+ pushSectionLine(
530
+ configuredOptionsSection,
531
+ "info",
532
+ `file-managed settings ${Object.keys(managedOverrides).length} valid override(s)`,
533
+ [
534
+ summary,
535
+ projectClaudeSettings.managedSettings.rootPath,
536
+ ]
537
+ );
538
+ } else {
539
+ pushSectionLine(
540
+ configuredOptionsSection,
541
+ "info",
542
+ "file-managed settings present without valid CurDX-Flow overrides"
543
+ );
544
+ }
545
+ } else {
546
+ pushSectionLine(
547
+ configuredOptionsSection,
548
+ "info",
549
+ "file-managed settings not present"
550
+ );
551
+ }
552
+
502
553
  if (projectClaudeSettings?.userExists) {
503
554
  if (Object.keys(userOverrides).length > 0) {
504
555
  const summary = Object.entries(userOverrides)
@@ -589,7 +640,9 @@ export function buildDoctorReport({
589
640
  ? "project"
590
641
  : effective.source === "user"
591
642
  ? "user"
592
- : "bundled default";
643
+ : effective.source === "managed"
644
+ ? "managed"
645
+ : "bundled default";
593
646
  pushSectionLine(
594
647
  configuredOptionsSection,
595
648
  "ok",
@@ -608,7 +661,9 @@ export function buildDoctorReport({
608
661
  ? "project"
609
662
  : entry.source === "user"
610
663
  ? "user"
611
- : "bundled default";
664
+ : entry.source === "managed"
665
+ ? "managed"
666
+ : "bundled default";
612
667
  pushSectionLine(
613
668
  runtimeProjectionSection,
614
669
  "info",
@@ -709,8 +764,66 @@ export function buildDoctorReport({
709
764
  }
710
765
 
711
766
  const projectSettingsSection = createSection("Project Claude settings:");
767
+ const managedSettingsSection = createSection("Managed Claude settings:");
712
768
  const userSettingsSection = createSection("User Claude settings:");
713
769
 
770
+ if (projectClaudeSettings?.managedSettings?.supported) {
771
+ if (projectClaudeSettings.managedSettings.exists) {
772
+ pushSectionLine(
773
+ managedSettingsSection,
774
+ "info",
775
+ "File-based managed settings detected",
776
+ [projectClaudeSettings.managedSettings.rootPath]
777
+ );
778
+
779
+ for (const file of projectClaudeSettings.managedSettings.files || []) {
780
+ if (file.invalid) {
781
+ pushSectionLine(
782
+ managedSettingsSection,
783
+ "err",
784
+ `${file.label} invalid JSON`,
785
+ [file.parseError]
786
+ );
787
+ continue;
788
+ }
789
+
790
+ if (Object.keys(file.overrides || {}).length > 0) {
791
+ const summary = Object.entries(file.overrides)
792
+ .map(([key, value]) => `${key}=${formatInlineValue(value)}`)
793
+ .join(", ");
794
+ pushSectionLine(
795
+ managedSettingsSection,
796
+ "info",
797
+ `${file.label} ${Object.keys(file.overrides).length} CurDX-Flow override(s)`,
798
+ [summary]
799
+ );
800
+ } else {
801
+ pushSectionLine(
802
+ managedSettingsSection,
803
+ "ok",
804
+ `${file.label} present`
805
+ );
806
+ }
807
+ }
808
+
809
+ if ((projectClaudeSettings.managedWarnings || []).length > 0) {
810
+ pushSectionLine(managedSettingsSection, "warn", "Managed CurDX-Flow settings need review");
811
+ for (const warning of projectClaudeSettings.managedWarnings) {
812
+ pushSectionLine(
813
+ managedSettingsSection,
814
+ "warn",
815
+ warning.message,
816
+ projectSettingsWarningDetails(warning)
817
+ );
818
+ }
819
+ }
820
+ } else {
821
+ pushSectionLine(managedSettingsSection, "info", "File-based managed settings not present");
822
+ }
823
+ } else {
824
+ pushSectionLine(managedSettingsSection, "info", "File-based managed settings not supported on this platform");
825
+ }
826
+
714
827
  if (projectClaudeSettings?.userExists) {
715
828
  if (projectClaudeSettings.userInvalid) {
716
829
  pushSectionLine(
@@ -89,6 +89,7 @@ Guarded artifact targets:
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
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.
92
+ - Current `doctor` inspection includes file-based managed settings (`managed-settings.json` plus sorted `managed-settings.d/*.json` fragments) before local/project/user settings. It still cannot see server-managed settings, MDM/registry policy delivery, or one-off CLI overrides.
92
93
 
93
94
  ## Plugin Dependency Constraints
94
95
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "Skill-first discipline layer and CLI installer for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {