@curdx/flow 2.3.2 → 2.3.3

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.2"
9
+ "version": "2.3.3"
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.2",
19
+ "version": "2.3.3",
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.2",
3
+ "version": "2.3.3",
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",
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
 
4
5
  const PROJECT_ONLY_IGNORED_SETTINGS = [
@@ -208,6 +209,12 @@ function createCurdxFlowPluginOptionsState() {
208
209
  return {
209
210
  pluginId: CURDX_FLOW_PLUGIN_ID,
210
211
  definitions,
212
+ user: {
213
+ pluginConfigsPresent: false,
214
+ pluginConfigPresent: false,
215
+ optionsPresent: false,
216
+ overrides: {},
217
+ },
211
218
  project: {
212
219
  pluginConfigsPresent: false,
213
220
  pluginConfigPresent: false,
@@ -221,11 +228,34 @@ function createCurdxFlowPluginOptionsState() {
221
228
  overrides: {},
222
229
  },
223
230
  repoEffective,
231
+ machineEffective: structuredClone(repoEffective),
224
232
  };
225
233
  }
226
234
 
227
235
  function pluginScopeLabel(scope) {
228
- return scope === "local" ? "settings.local.json" : "settings.json";
236
+ if (scope === "local") return "settings.local.json";
237
+ if (scope === "user") return "~/.claude/settings.json";
238
+ return "settings.json";
239
+ }
240
+
241
+ function invalidSettingKindForScope(scope) {
242
+ if (scope === "local") return "invalid-local-setting";
243
+ if (scope === "user") return "invalid-user-setting";
244
+ return "invalid-project-setting";
245
+ }
246
+
247
+ function applyCurdxFlowPluginOptionOverride(pluginOptionsState, key, value, scope) {
248
+ pluginOptionsState.machineEffective[key] = {
249
+ value,
250
+ source: scope,
251
+ };
252
+
253
+ if (scope === "project" || scope === "local") {
254
+ pluginOptionsState.repoEffective[key] = {
255
+ value,
256
+ source: scope,
257
+ };
258
+ }
229
259
  }
230
260
 
231
261
  function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope = "project") {
@@ -233,14 +263,18 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
233
263
  return;
234
264
  }
235
265
 
236
- const target = scope === "local" ? pluginOptionsState.local : pluginOptionsState.project;
266
+ const target = scope === "local"
267
+ ? pluginOptionsState.local
268
+ : scope === "user"
269
+ ? pluginOptionsState.user
270
+ : pluginOptionsState.project;
237
271
  const settingsLabel = pluginScopeLabel(scope);
238
272
  target.pluginConfigsPresent = true;
239
273
 
240
274
  if (!isNonArrayObject(parsed.pluginConfigs)) {
241
275
  pushScopedWarning(
242
276
  warnings,
243
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
277
+ invalidSettingKindForScope(scope),
244
278
  `pluginConfigs in ${settingsLabel} must be an object keyed by plugin@marketplace id`,
245
279
  scope
246
280
  );
@@ -257,7 +291,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
257
291
  if (!isNonArrayObject(pluginConfig)) {
258
292
  pushScopedWarning(
259
293
  warnings,
260
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
294
+ invalidSettingKindForScope(scope),
261
295
  `pluginConfigs[${CURDX_FLOW_PLUGIN_ID}] in ${settingsLabel} must be an object when set`,
262
296
  scope
263
297
  );
@@ -273,7 +307,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
273
307
  if (!isNonArrayObject(pluginConfig.options)) {
274
308
  pushScopedWarning(
275
309
  warnings,
276
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
310
+ invalidSettingKindForScope(scope),
277
311
  `pluginConfigs[${CURDX_FLOW_PLUGIN_ID}].options in ${settingsLabel} must be an object when set`,
278
312
  scope
279
313
  );
@@ -296,7 +330,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
296
330
  if (definition.type === "boolean" && typeof value !== "boolean") {
297
331
  pushScopedWarning(
298
332
  warnings,
299
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
333
+ invalidSettingKindForScope(scope),
300
334
  `CurDX-Flow plugin option ${key} in ${settingsLabel} must be boolean`,
301
335
  scope
302
336
  );
@@ -307,7 +341,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
307
341
  if (typeof value !== "number" || Number.isNaN(value)) {
308
342
  pushScopedWarning(
309
343
  warnings,
310
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
344
+ invalidSettingKindForScope(scope),
311
345
  `CurDX-Flow plugin option ${key} in ${settingsLabel} must be a number`,
312
346
  scope
313
347
  );
@@ -317,7 +351,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
317
351
  if (definition.integer && !Number.isInteger(value)) {
318
352
  pushScopedWarning(
319
353
  warnings,
320
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
354
+ invalidSettingKindForScope(scope),
321
355
  `CurDX-Flow plugin option ${key} in ${settingsLabel} must be an integer`,
322
356
  scope
323
357
  );
@@ -327,7 +361,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
327
361
  if (typeof definition.min === "number" && value < definition.min) {
328
362
  pushScopedWarning(
329
363
  warnings,
330
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
364
+ invalidSettingKindForScope(scope),
331
365
  `CurDX-Flow plugin option ${key} in ${settingsLabel} must be between ${definition.min} and ${definition.max}`,
332
366
  scope
333
367
  );
@@ -337,7 +371,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
337
371
  if (typeof definition.max === "number" && value > definition.max) {
338
372
  pushScopedWarning(
339
373
  warnings,
340
- scope === "local" ? "invalid-local-setting" : "invalid-project-setting",
374
+ invalidSettingKindForScope(scope),
341
375
  `CurDX-Flow plugin option ${key} in ${settingsLabel} must be between ${definition.min} and ${definition.max}`,
342
376
  scope
343
377
  );
@@ -346,10 +380,7 @@ function auditCurdxFlowPluginOptions(parsed, warnings, pluginOptionsState, scope
346
380
  }
347
381
 
348
382
  target.overrides[key] = value;
349
- pluginOptionsState.repoEffective[key] = {
350
- value,
351
- source: scope,
352
- };
383
+ applyCurdxFlowPluginOptionOverride(pluginOptionsState, key, value, scope);
353
384
  }
354
385
  }
355
386
 
@@ -544,10 +575,15 @@ function auditLocalClaudeSettings(parsed, warnings) {
544
575
  }
545
576
  }
546
577
 
547
- export async function readProjectClaudeSettings(cwd = process.cwd()) {
578
+ export async function readProjectClaudeSettings(cwd = process.cwd(), { homeDir = os.homedir() } = {}) {
548
579
  const settingsPath = path.join(cwd, ".claude", "settings.json");
549
580
  const localSettingsPath = path.join(cwd, ".claude", "settings.local.json");
581
+ const userSettingsPath = path.join(homeDir, ".claude", "settings.json");
550
582
  const state = {
583
+ userExists: false,
584
+ userInvalid: false,
585
+ userParseError: null,
586
+ userWarnings: [],
551
587
  exists: false,
552
588
  localExists: false,
553
589
  invalid: false,
@@ -559,6 +595,13 @@ export async function readProjectClaudeSettings(cwd = process.cwd()) {
559
595
  pluginOptions: createCurdxFlowPluginOptionsState(),
560
596
  };
561
597
 
598
+ try {
599
+ const userStat = await fs.stat(userSettingsPath);
600
+ state.userExists = userStat.isFile();
601
+ } catch {
602
+ state.userExists = false;
603
+ }
604
+
562
605
  try {
563
606
  const localStat = await fs.stat(localSettingsPath);
564
607
  state.localExists = localStat.isFile();
@@ -566,6 +609,16 @@ export async function readProjectClaudeSettings(cwd = process.cwd()) {
566
609
  state.localExists = false;
567
610
  }
568
611
 
612
+ if (state.userExists) {
613
+ try {
614
+ const userParsed = JSON.parse(await fs.readFile(userSettingsPath, "utf-8"));
615
+ auditCurdxFlowPluginOptions(userParsed, state.userWarnings, state.pluginOptions, "user");
616
+ } catch (error) {
617
+ state.userInvalid = true;
618
+ state.userParseError = error?.message || String(error);
619
+ }
620
+ }
621
+
569
622
  let parsed;
570
623
  try {
571
624
  const stat = await fs.stat(settingsPath);
@@ -13,6 +13,33 @@ function pluginErrorDetails(plugin) {
13
13
  }
14
14
 
15
15
  function projectSettingsWarningDetails(warning) {
16
+ if (warning?.scope === "user") {
17
+ if (warning.kind === "unknown-plugin-option") {
18
+ return [
19
+ "~/.claude/settings.json applies across all your repositories 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 (
25
+ warning.kind === "invalid-user-setting" &&
26
+ (
27
+ warning.message?.includes("pluginConfigs[curdx-flow@curdx-flow-marketplace]") ||
28
+ warning.message?.includes("CurDX-Flow plugin option")
29
+ )
30
+ ) {
31
+ return [
32
+ "CurDX-Flow plugin options in ~/.claude/settings.json apply across all local repositories unless a project or local override wins",
33
+ "use boolean values for autonomous_blocking and daily_dependency_check, and keep monitor_interval_seconds as an integer between 3 and 60",
34
+ ];
35
+ }
36
+
37
+ return [
38
+ "~/.claude/settings.json applies across all your repositories on this machine",
39
+ "fix or remove the user-scoped override if CurDX-Flow behaves unexpectedly everywhere",
40
+ ];
41
+ }
42
+
16
43
  if (warning?.scope === "local") {
17
44
  if (warning.kind === "unknown-plugin-option") {
18
45
  return [
@@ -454,9 +481,10 @@ export function buildDoctorReport({
454
481
  ) {
455
482
  const configuredOptionsSection = createSection("CurDX-Flow configured options:");
456
483
  const optionState = projectClaudeSettings?.pluginOptions;
484
+ const userOverrides = optionState?.user?.overrides || {};
457
485
  const projectOverrides = optionState?.project?.overrides || {};
458
486
  const localOverrides = optionState?.local?.overrides || {};
459
- const repoEffective = optionState?.repoEffective || {};
487
+ const machineEffective = optionState?.machineEffective || optionState?.repoEffective || {};
460
488
  const definitions = bundledPluginRuntimeDefaults?.userConfig?.length
461
489
  ? bundledPluginRuntimeDefaults.userConfig
462
490
  : (optionState?.definitions || []);
@@ -464,13 +492,42 @@ export function buildDoctorReport({
464
492
  pushSectionLine(
465
493
  configuredOptionsSection,
466
494
  "info",
467
- "Scope precedence settings.local.json > settings.json > bundled default",
495
+ "Scope precedence settings.local.json > settings.json > ~/.claude/settings.json > bundled default",
468
496
  [
469
- "this section audits repo-scoped CurDX-Flow plugin options only",
470
- "user-level ~/.claude/settings.json is not inspected here",
497
+ "managed settings and command-line overrides are not inspected here",
498
+ "effective values below reflect this machine only",
471
499
  ]
472
500
  );
473
501
 
502
+ if (projectClaudeSettings?.userExists) {
503
+ if (Object.keys(userOverrides).length > 0) {
504
+ const summary = Object.entries(userOverrides)
505
+ .map(([key, value]) => `${key}=${formatInlineValue(value)}`)
506
+ .join(", ");
507
+ pushSectionLine(
508
+ configuredOptionsSection,
509
+ "info",
510
+ `~/.claude/settings.json ${Object.keys(userOverrides).length} valid override(s)`,
511
+ [
512
+ summary,
513
+ "user-scoped overrides apply across repositories unless project/local settings win",
514
+ ]
515
+ );
516
+ } else {
517
+ pushSectionLine(
518
+ configuredOptionsSection,
519
+ "info",
520
+ "~/.claude/settings.json present without valid CurDX-Flow overrides"
521
+ );
522
+ }
523
+ } else {
524
+ pushSectionLine(
525
+ configuredOptionsSection,
526
+ "info",
527
+ "~/.claude/settings.json not present"
528
+ );
529
+ }
530
+
474
531
  if (Object.keys(projectOverrides).length > 0) {
475
532
  const summary = Object.entries(projectOverrides)
476
533
  .map(([key, value]) => `${key}=${formatInlineValue(value)}`)
@@ -522,7 +579,7 @@ export function buildDoctorReport({
522
579
  }
523
580
 
524
581
  for (const definition of definitions) {
525
- const effective = repoEffective[definition.key] || {
582
+ const effective = machineEffective[definition.key] || {
526
583
  value: definition.default,
527
584
  source: "default",
528
585
  };
@@ -530,6 +587,8 @@ export function buildDoctorReport({
530
587
  ? "local"
531
588
  : effective.source === "project"
532
589
  ? "project"
590
+ : effective.source === "user"
591
+ ? "user"
533
592
  : "bundled default";
534
593
  pushSectionLine(
535
594
  configuredOptionsSection,
@@ -626,6 +685,33 @@ export function buildDoctorReport({
626
685
  }
627
686
 
628
687
  const projectSettingsSection = createSection("Project Claude settings:");
688
+ const userSettingsSection = createSection("User Claude settings:");
689
+
690
+ if (projectClaudeSettings?.userExists) {
691
+ if (projectClaudeSettings.userInvalid) {
692
+ pushSectionLine(
693
+ userSettingsSection,
694
+ "err",
695
+ "~/.claude/settings.json invalid JSON",
696
+ [projectClaudeSettings.userParseError]
697
+ );
698
+ } else if ((projectClaudeSettings.userWarnings || []).length > 0) {
699
+ pushSectionLine(userSettingsSection, "warn", "~/.claude/settings.json affects CurDX-Flow globally");
700
+ for (const warning of projectClaudeSettings.userWarnings) {
701
+ pushSectionLine(
702
+ userSettingsSection,
703
+ "warn",
704
+ warning.message,
705
+ projectSettingsWarningDetails(warning)
706
+ );
707
+ }
708
+ } else {
709
+ pushSectionLine(userSettingsSection, "ok", "~/.claude/settings.json present");
710
+ }
711
+ } else {
712
+ pushSectionLine(userSettingsSection, "info", "~/.claude/settings.json not present");
713
+ }
714
+
629
715
  if (projectClaudeSettings?.exists) {
630
716
  if (projectClaudeSettings.invalid) {
631
717
  pushSectionLine(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "2.3.2",
3
+ "version": "2.3.3",
4
4
  "description": "Skill-first discipline layer and CLI installer for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {