@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +2 -1
- package/cli/README.md +3 -1
- package/cli/doctor-workflow.js +14 -3
- package/cli/lib/doctor-claude-settings.js +165 -12
- package/cli/lib/doctor-report.js +139 -4
- package/knowledge/claude-code-runtime-contracts.md +1 -0
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
package/cli/doctor-workflow.js
CHANGED
|
@@ -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 =
|
|
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(
|
|
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 === "
|
|
321
|
-
? pluginOptionsState.
|
|
322
|
-
: scope === "
|
|
323
|
-
? pluginOptionsState.
|
|
324
|
-
:
|
|
325
|
-
|
|
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
|
|
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(
|
|
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;
|
package/cli/lib/doctor-report.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
:
|
|
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
|
|