@curdx/flow 2.3.5 → 2.3.7

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.5"
9
+ "version": "2.3.7"
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.5",
19
+ "version": "2.3.7",
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.5",
3
+ "version": "2.3.7",
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.7
4
+
5
+ - taught `doctor` to detect source/package-vs-installed plugin version drift so local plugin development and release validation no longer silently run against stale Claude plugin cache state
6
+ - exposed bundled plugin body version in the runtime report and machine-readable doctor payload for automation consumers
7
+ - documented the reinstall/restart recovery path when Claude is still loading an older CurDX-Flow build
8
+
9
+ ## 2.3.6
10
+
11
+ - taught `doctor` to inspect file-based Claude Code managed settings and apply them at the correct highest-precedence layer for CurDX-Flow plugin options
12
+ - upgraded the `doctor --json` contract to v2 so wrappers can distinguish inspected `managed-file` scope from uninspected server-managed / MDM / CLI overrides
13
+ - added report and workflow coverage for managed-settings fragments, invalid managed JSON, and managed-vs-local/project/user precedence
14
+
3
15
  ## 2.3.5
4
16
 
5
17
  - added a versioned `doctor --json` contract for CI/wrappers, including applied-fix metadata and explicit settings-inspection scope metadata
package/README.md CHANGED
@@ -21,7 +21,8 @@ npx @curdx/flow install --all
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
23
  `npx @curdx/flow doctor` after install if anything looks off. For CI or wrapper
24
- automation, use `npx @curdx/flow doctor --json`.
24
+ automation, use `npx @curdx/flow doctor --json`, which now includes file-based
25
+ managed settings in its inspected precedence model.
25
26
 
26
27
  After restart, CurDX-Flow routes the main thread through `flow-orchestrator`
27
28
  by default and starts the bundled `.flow` progress monitor in interactive
package/cli/README.md CHANGED
@@ -42,7 +42,9 @@ External diagnostics: claude CLI / curdx-flow / required MCPs / recommended plug
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.
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.
46
48
 
47
49
  ### Project initialization (not a CLI command)
48
50
 
@@ -21,10 +21,10 @@ 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;
24
+ export const DOCTOR_JSON_CONTRACT_VERSION = 2;
25
25
  export const DOCTOR_SETTINGS_INSPECTION = Object.freeze({
26
- pluginOptionScopesInspected: ["user", "project", "local"],
27
- pluginOptionScopesNotInspected: ["managed", "cli"],
26
+ pluginOptionScopesInspected: ["managed-file", "user", "project", "local"],
27
+ pluginOptionScopesNotInspected: ["managed-server", "managed-mdm", "cli"],
28
28
  });
29
29
 
30
30
  export function createDoctorContext(args = []) {
@@ -163,9 +163,12 @@ export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROO
163
163
  const pluginSettingsPath = path.join(packageRoot, "settings.json");
164
164
  const pluginManifestPath = path.join(packageRoot, ".claude-plugin", "plugin.json");
165
165
  const monitorsPath = path.join(packageRoot, "monitors", "monitors.json");
166
+ const packageJsonPath = path.join(packageRoot, "package.json");
166
167
 
167
168
  const state = {
168
169
  exists: false,
170
+ packageVersion: null,
171
+ pluginVersion: null,
169
172
  defaultAgent: null,
170
173
  subagentStatusLineCommand: null,
171
174
  monitorCount: 0,
@@ -189,6 +192,13 @@ export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROO
189
192
  pluginManifest = null;
190
193
  }
191
194
 
195
+ try {
196
+ const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
197
+ state.packageVersion = typeof pkg?.version === "string" ? pkg.version : null;
198
+ } catch {
199
+ state.packageVersion = null;
200
+ }
201
+
192
202
  try {
193
203
  monitors = JSON.parse(await fs.readFile(monitorsPath, "utf-8"));
194
204
  } catch {
@@ -200,6 +210,7 @@ export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROO
200
210
  }
201
211
 
202
212
  state.exists = true;
213
+ state.pluginVersion = typeof pluginManifest?.version === "string" ? pluginManifest.version : null;
203
214
  state.defaultAgent = typeof pluginSettings?.agent === "string" ? pluginSettings.agent : null;
204
215
  state.subagentStatusLineCommand =
205
216
  typeof pluginSettings?.subagentStatusLine?.command === "string"
@@ -139,6 +139,22 @@ export 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 [
@@ -242,9 +263,23 @@ export function buildDoctorReport({
242
263
  pushLine(lines, "ok", `Node ${nodeVersion}`);
243
264
 
244
265
  const curdx = plugins.find((plugin) => plugin.name === "curdx-flow");
266
+ const bundledVersion =
267
+ bundledPluginRuntimeDefaults?.pluginVersion || bundledPluginRuntimeDefaults?.packageVersion || null;
245
268
  if (curdx) {
246
269
  if (curdx.status === "enabled") {
247
270
  pushLine(lines, "ok", `curdx-flow v${curdx.version} (enabled)`);
271
+ if (bundledVersion && curdx.version && curdx.version !== bundledVersion) {
272
+ pushLine(
273
+ lines,
274
+ "warn",
275
+ `curdx-flow source/body v${bundledVersion} differs from installed v${curdx.version}`,
276
+ [
277
+ "you are not validating the same CurDX-Flow build that Claude currently loads",
278
+ "reinstall the plugin from the current source/package, then restart Claude Code",
279
+ "run: npx @curdx/flow install --all",
280
+ ]
281
+ );
282
+ }
248
283
  } else {
249
284
  pushLine(
250
285
  lines,
@@ -421,6 +456,14 @@ export function buildDoctorReport({
421
456
  if (bundledPluginRuntimeDefaults?.exists) {
422
457
  const bundledRuntimeSection = createSection("CurDX-Flow bundled runtime:");
423
458
 
459
+ if (bundledVersion) {
460
+ pushSectionLine(
461
+ bundledRuntimeSection,
462
+ "info",
463
+ `Bundled plugin body v${bundledVersion}`
464
+ );
465
+ }
466
+
424
467
  if (bundledPluginRuntimeDefaults.defaultAgent) {
425
468
  pushSectionLine(
426
469
  bundledRuntimeSection,
@@ -481,6 +524,7 @@ export function buildDoctorReport({
481
524
  ) {
482
525
  const configuredOptionsSection = createSection("CurDX-Flow configured options:");
483
526
  const optionState = projectClaudeSettings?.pluginOptions;
527
+ const managedOverrides = optionState?.managed?.overrides || {};
484
528
  const userOverrides = optionState?.user?.overrides || {};
485
529
  const projectOverrides = optionState?.project?.overrides || {};
486
530
  const localOverrides = optionState?.local?.overrides || {};
@@ -492,13 +536,42 @@ export function buildDoctorReport({
492
536
  pushSectionLine(
493
537
  configuredOptionsSection,
494
538
  "info",
495
- "Scope precedence settings.local.json > settings.json > ~/.claude/settings.json > bundled default",
539
+ "Scope precedence file-managed > settings.local.json > settings.json > ~/.claude/settings.json > bundled default",
496
540
  [
497
- "managed settings and command-line overrides are not inspected here",
541
+ "server-managed settings, MDM/registry policies, and command-line overrides are not inspected here",
498
542
  "effective values below reflect this machine only",
499
543
  ]
500
544
  );
501
545
 
546
+ if (projectClaudeSettings?.managedSettings?.exists) {
547
+ if (Object.keys(managedOverrides).length > 0) {
548
+ const summary = Object.entries(managedOverrides)
549
+ .map(([key, value]) => `${key}=${formatInlineValue(value)}`)
550
+ .join(", ");
551
+ pushSectionLine(
552
+ configuredOptionsSection,
553
+ "info",
554
+ `file-managed settings ${Object.keys(managedOverrides).length} valid override(s)`,
555
+ [
556
+ summary,
557
+ projectClaudeSettings.managedSettings.rootPath,
558
+ ]
559
+ );
560
+ } else {
561
+ pushSectionLine(
562
+ configuredOptionsSection,
563
+ "info",
564
+ "file-managed settings present without valid CurDX-Flow overrides"
565
+ );
566
+ }
567
+ } else {
568
+ pushSectionLine(
569
+ configuredOptionsSection,
570
+ "info",
571
+ "file-managed settings not present"
572
+ );
573
+ }
574
+
502
575
  if (projectClaudeSettings?.userExists) {
503
576
  if (Object.keys(userOverrides).length > 0) {
504
577
  const summary = Object.entries(userOverrides)
@@ -589,7 +662,9 @@ export function buildDoctorReport({
589
662
  ? "project"
590
663
  : effective.source === "user"
591
664
  ? "user"
592
- : "bundled default";
665
+ : effective.source === "managed"
666
+ ? "managed"
667
+ : "bundled default";
593
668
  pushSectionLine(
594
669
  configuredOptionsSection,
595
670
  "ok",
@@ -608,7 +683,9 @@ export function buildDoctorReport({
608
683
  ? "project"
609
684
  : entry.source === "user"
610
685
  ? "user"
611
- : "bundled default";
686
+ : entry.source === "managed"
687
+ ? "managed"
688
+ : "bundled default";
612
689
  pushSectionLine(
613
690
  runtimeProjectionSection,
614
691
  "info",
@@ -709,8 +786,66 @@ export function buildDoctorReport({
709
786
  }
710
787
 
711
788
  const projectSettingsSection = createSection("Project Claude settings:");
789
+ const managedSettingsSection = createSection("Managed Claude settings:");
712
790
  const userSettingsSection = createSection("User Claude settings:");
713
791
 
792
+ if (projectClaudeSettings?.managedSettings?.supported) {
793
+ if (projectClaudeSettings.managedSettings.exists) {
794
+ pushSectionLine(
795
+ managedSettingsSection,
796
+ "info",
797
+ "File-based managed settings detected",
798
+ [projectClaudeSettings.managedSettings.rootPath]
799
+ );
800
+
801
+ for (const file of projectClaudeSettings.managedSettings.files || []) {
802
+ if (file.invalid) {
803
+ pushSectionLine(
804
+ managedSettingsSection,
805
+ "err",
806
+ `${file.label} invalid JSON`,
807
+ [file.parseError]
808
+ );
809
+ continue;
810
+ }
811
+
812
+ if (Object.keys(file.overrides || {}).length > 0) {
813
+ const summary = Object.entries(file.overrides)
814
+ .map(([key, value]) => `${key}=${formatInlineValue(value)}`)
815
+ .join(", ");
816
+ pushSectionLine(
817
+ managedSettingsSection,
818
+ "info",
819
+ `${file.label} ${Object.keys(file.overrides).length} CurDX-Flow override(s)`,
820
+ [summary]
821
+ );
822
+ } else {
823
+ pushSectionLine(
824
+ managedSettingsSection,
825
+ "ok",
826
+ `${file.label} present`
827
+ );
828
+ }
829
+ }
830
+
831
+ if ((projectClaudeSettings.managedWarnings || []).length > 0) {
832
+ pushSectionLine(managedSettingsSection, "warn", "Managed CurDX-Flow settings need review");
833
+ for (const warning of projectClaudeSettings.managedWarnings) {
834
+ pushSectionLine(
835
+ managedSettingsSection,
836
+ "warn",
837
+ warning.message,
838
+ projectSettingsWarningDetails(warning)
839
+ );
840
+ }
841
+ }
842
+ } else {
843
+ pushSectionLine(managedSettingsSection, "info", "File-based managed settings not present");
844
+ }
845
+ } else {
846
+ pushSectionLine(managedSettingsSection, "info", "File-based managed settings not supported on this platform");
847
+ }
848
+
714
849
  if (projectClaudeSettings?.userExists) {
715
850
  if (projectClaudeSettings.userInvalid) {
716
851
  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.5",
3
+ "version": "2.3.7",
4
4
  "description": "Skill-first discipline layer and CLI installer for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {