@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +3 -1
- package/cli/README.md +3 -1
- package/cli/doctor-workflow.js +34 -0
- package/cli/doctor.js +43 -14
- package/cli/help.js +1 -0
- package/cli/lib/doctor-claude-settings.js +166 -13
- package/cli/lib/doctor-report.js +117 -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.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.
|
|
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.
|
|
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:
|
package/cli/doctor-workflow.js
CHANGED
|
@@ -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(
|
|
20
|
-
|
|
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
|
-
|
|
40
|
+
if (!context.json) {
|
|
41
|
+
logImpl.title("🏥 CurdX-Flow Health Check");
|
|
42
|
+
}
|
|
23
43
|
|
|
24
|
-
const doctorData = await
|
|
44
|
+
const doctorData = await collectDoctorDataImpl();
|
|
25
45
|
if (context.fix) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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 =
|
|
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
|
-
|
|
63
|
+
renderReportLinesImpl(report.lines, { logImpl });
|
|
35
64
|
for (const section of report.sections) {
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
consoleImpl.log(`\n${colorImpl.bold(section.title)}`);
|
|
66
|
+
renderReportLinesImpl(section.lines, { logImpl });
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
|
|
69
|
+
printDoctorSummaryImpl(report, { logImpl });
|
|
41
70
|
if (context.verbose && doctorData.claudeVersionValue) {
|
|
42
|
-
|
|
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(
|
|
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 [
|
|
@@ -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
|
-
|
|
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
|
-
:
|
|
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
|
|