@boyingliu01/xp-gate 0.19.3 → 0.20.1

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.
Files changed (177) hide show
  1. package/adapters/gate-4.sh +83 -18
  2. package/adapters/gate-8.sh +115 -29
  3. package/adapters/iac.sh +1 -1
  4. package/adapters/powershell.sh +60 -5
  5. package/adapters/python.sh +38 -4
  6. package/adapters/typescript.sh +15 -60
  7. package/bin/xp-gate.js +15 -16
  8. package/build-integrity/__tests__/import-resolver.test.ts +59 -0
  9. package/build-integrity/gate-10.ts +17 -2
  10. package/gate-10.sh +35 -2
  11. package/gate-4.sh +110 -21
  12. package/gate-8.sh +115 -29
  13. package/hooks/gate-10.sh +35 -2
  14. package/hooks/gate-4.sh +110 -21
  15. package/hooks/gate-8.sh +115 -29
  16. package/hooks/lib/jscpd-run.sh +63 -0
  17. package/hooks/lib/sprint-gate-report.sh +58 -0
  18. package/hooks/lib/test-failure.sh +152 -0
  19. package/hooks/lib/typecheck.sh +141 -0
  20. package/hooks/lib/validate-code-walkthrough.cjs +43 -5
  21. package/hooks/post-merge +100 -0
  22. package/hooks/pre-commit +514 -184
  23. package/hooks/pre-push +75 -19
  24. package/lib/__tests__/bootstrap.test.js +23 -41
  25. package/lib/__tests__/check-version.test.js +8 -1
  26. package/lib/__tests__/detect-deps.test.js +16 -0
  27. package/lib/__tests__/doctor-drift-round2.test.js +212 -0
  28. package/lib/__tests__/doctor-executed-surface.test.js +209 -0
  29. package/lib/__tests__/doctor-hook-drift.test.js +329 -0
  30. package/lib/__tests__/doctor-machine-mutation.test.js +116 -0
  31. package/lib/__tests__/doctor-module-drift.test.js +206 -0
  32. package/lib/__tests__/doctor-module-resolution-parity.test.js +314 -0
  33. package/lib/__tests__/doctor-path-equivalence.test.js +59 -0
  34. package/lib/__tests__/doctor-repair-boundary.test.js +52 -0
  35. package/lib/__tests__/doctor-tui-skill-freshness.test.js +105 -0
  36. package/lib/__tests__/doctor.test.js +155 -0
  37. package/lib/__tests__/gate-runner.test.js +19 -3
  38. package/lib/__tests__/helpers/lib-sandbox.cjs +55 -0
  39. package/lib/__tests__/init.test.js +44 -17
  40. package/lib/__tests__/install-skill-full-dir.test.js +151 -0
  41. package/lib/__tests__/install-skill.test.js +51 -132
  42. package/lib/__tests__/language-tools-detect.test.js +158 -0
  43. package/lib/__tests__/phase-transition.test.js +15 -1
  44. package/lib/__tests__/qoder-delphi-agents.test.js +285 -0
  45. package/lib/__tests__/shared-paths.test.js +20 -0
  46. package/lib/__tests__/shared-utils.test.js +69 -0
  47. package/lib/__tests__/skill-flags.test.js +48 -0
  48. package/lib/__tests__/ui-detector.test.ts +6 -2
  49. package/lib/__tests__/uninstall.test.js +20 -0
  50. package/lib/__tests__/update-hooks.test.js +12 -0
  51. package/lib/__tests__/update-skill.test.js +7 -4
  52. package/lib/detect-deps.js +3 -20
  53. package/lib/doctor-tui.js +22 -5
  54. package/lib/doctor.js +1178 -31
  55. package/lib/init.js +184 -33
  56. package/lib/install-cmd.js +7 -4
  57. package/lib/install-skill.js +38 -95
  58. package/lib/language-tools.js +83 -4
  59. package/lib/mattpocock-license-verdict.json +1 -1
  60. package/lib/shared-paths.js +5 -2
  61. package/lib/shared-utils.js +47 -3
  62. package/lib/uninstall.js +12 -0
  63. package/lib/update-hooks.js +4 -4
  64. package/lib/update-skill.js +6 -7
  65. package/mock-policy/AGENTS.md +4 -4
  66. package/mutation/AGENTS.md +4 -4
  67. package/mutation/__tests__/gate-m.test.ts +66 -14
  68. package/mutation/__tests__/mutmut-runner.test.ts +91 -42
  69. package/mutation/__tests__/test-filter-parity.test.ts +37 -0
  70. package/mutation/gate-m.ts +21 -8
  71. package/package.json +2 -2
  72. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  73. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  74. package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
  75. package/plugins/claude-code/skills/delphi-review/SKILL.md +98 -14
  76. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +36 -3
  77. package/plugins/claude-code/skills/delphi-review/references/requirements.md +6 -6
  78. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
  79. package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +3 -3
  80. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  81. package/plugins/dsh/README.md +169 -4
  82. package/plugins/dsh/dsh-overlay.patch.yml +67 -0
  83. package/plugins/dsh/package.json +1 -1
  84. package/plugins/dsh/scripts/prepack.cjs +24 -19
  85. package/plugins/dsh/setup-dsh.ps1 +160 -0
  86. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  87. package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
  88. package/plugins/dsh/skills/delphi-review/SKILL.md +98 -14
  89. package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +36 -3
  90. package/plugins/dsh/skills/delphi-review/references/requirements.md +6 -6
  91. package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
  92. package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +3 -3
  93. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
  94. package/plugins/dsh/src/command.test.ts +66 -2
  95. package/plugins/dsh/src/command.ts +36 -14
  96. package/plugins/dsh/src/contract.test.ts +31 -1
  97. package/plugins/dsh/src/delphi-config.test.ts +190 -0
  98. package/plugins/dsh/src/delphi-config.ts +223 -0
  99. package/plugins/dsh/src/delphi-run.test.ts +181 -0
  100. package/plugins/dsh/src/delphi-run.ts +129 -0
  101. package/plugins/dsh/src/delphi.test.ts +222 -0
  102. package/plugins/dsh/src/delphi.ts +247 -0
  103. package/plugins/dsh/src/dialect.test.ts +59 -0
  104. package/plugins/dsh/src/dialect.ts +40 -0
  105. package/plugins/dsh/src/index.ts +70 -4
  106. package/plugins/dsh/src/skills.test.ts +26 -19
  107. package/plugins/opencode/package.json +1 -1
  108. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  109. package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
  110. package/plugins/opencode/skills/delphi-review/SKILL.md +98 -14
  111. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +36 -3
  112. package/plugins/opencode/skills/delphi-review/references/requirements.md +6 -6
  113. package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
  114. package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +3 -3
  115. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  116. package/plugins/qoder/agents/delphi-architecture.md +1 -1
  117. package/plugins/qoder/agents/delphi-feasibility.md +1 -1
  118. package/plugins/qoder/agents/delphi-technical.md +1 -1
  119. package/plugins/qoder/plugin.json +1 -1
  120. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  121. package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
  122. package/plugins/qoder/skills/delphi-review/SKILL.md +98 -14
  123. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +36 -3
  124. package/plugins/qoder/skills/delphi-review/references/requirements.md +6 -6
  125. package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
  126. package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +3 -3
  127. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  128. package/principles/AGENTS.md +9 -8
  129. package/principles/__tests__/adapter-metrics.test.ts +292 -0
  130. package/principles/__tests__/analyzer.test.ts +29 -6
  131. package/principles/__tests__/baseline-prune-timeout.test.ts +71 -0
  132. package/principles/__tests__/baseline-prune.test.ts +338 -0
  133. package/principles/__tests__/boy-scout-init-baseline.test.ts +167 -0
  134. package/principles/__tests__/boy-scout-integration.test.ts +97 -6
  135. package/principles/__tests__/boy-scout.test.ts +253 -7
  136. package/principles/__tests__/config-enforcement.test.ts +469 -0
  137. package/principles/__tests__/config-git-timeout.test.ts +66 -0
  138. package/principles/__tests__/direct-execution.test.ts +187 -0
  139. package/principles/__tests__/index.test.ts +16 -3
  140. package/principles/__tests__/warnings-baseline-budgets.test.ts +74 -0
  141. package/principles/adapters/typescript.ts +613 -13
  142. package/principles/analyzer.ts +22 -0
  143. package/principles/baseline-prune.ts +217 -0
  144. package/principles/boy-scout.ts +516 -56
  145. package/principles/config.ts +362 -54
  146. package/principles/direct-execution.ts +60 -0
  147. package/principles/index.ts +40 -17
  148. package/principles/rules/__tests__/clean-code/many-exports.test.ts +16 -10
  149. package/principles/rules/clean-code/code-duplication.ts +8 -8
  150. package/principles/rules/clean-code/deep-nesting.ts +8 -8
  151. package/principles/rules/clean-code/god-class.ts +8 -8
  152. package/principles/rules/clean-code/large-file.ts +15 -11
  153. package/principles/rules/clean-code/long-function.ts +8 -8
  154. package/principles/rules/clean-code/magic-numbers.ts +10 -7
  155. package/principles/rules/clean-code/many-exports.ts +7 -7
  156. package/principles/rules/clean-code/missing-error-handling.ts +5 -5
  157. package/principles/rules/clean-code/too-many-params.ts +8 -8
  158. package/principles/rules/clean-code/unused-imports.ts +5 -5
  159. package/principles/rules/solid/dip.ts +18 -8
  160. package/principles/rules/solid/isp.ts +9 -8
  161. package/principles/rules/solid/lsp.ts +6 -6
  162. package/principles/rules/solid/ocp.ts +6 -5
  163. package/principles/rules/solid/srp.ts +7 -8
  164. package/principles/types.ts +15 -0
  165. package/scripts/__tests__/sync-package-content.test.js +28 -0
  166. package/scripts/delphi-external-review.cjs +193 -10
  167. package/scripts/sync-package-content.js +20 -10
  168. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  169. package/skills/delphi-review/AGENTS.md +4 -4
  170. package/skills/delphi-review/SKILL.md +98 -14
  171. package/skills/delphi-review/references/code-walkthrough.md +36 -3
  172. package/skills/delphi-review/references/requirements.md +6 -6
  173. package/skills/sprint-flow/AGENTS.md +4 -4
  174. package/skills/sprint-flow/references/phase-2-design.md +3 -3
  175. package/skills/test-specification-alignment/AGENTS.md +4 -4
  176. package/lib/__tests__/download-skill.test.js +0 -281
  177. package/lib/download-skill.js +0 -96
@@ -1,15 +1,15 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const deepNestingRule: Rule = {
7
5
  id: 'clean-code.deep-nesting',
8
6
  name: 'Deep Nesting Rule',
9
- threshold: config.rules['clean-code']['deep-nesting'].threshold ?? 4,
10
- severity: config.rules['clean-code']['deep-nesting'].severity as Severity,
7
+ threshold: 4,
8
+ severity: 'warning',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['deep-nesting'];
13
13
 
14
14
  try {
15
15
  interface TypedAdapter {
@@ -19,15 +19,15 @@ export const deepNestingRule: Rule = {
19
19
  const functions = typedAdapter.extractFunctions?.() || [];
20
20
 
21
21
  for (const func of functions) {
22
- if (func.nestingDepth && func.nestingDepth > (config.rules['clean-code']['deep-nesting'].threshold as number)) {
22
+ if (func.nestingDepth && func.nestingDepth > (settings.threshold ?? 0)) {
23
23
  violations.push({
24
24
  file,
25
25
  line: func.startLine ?? func.line ?? 1,
26
26
  ruleId: 'clean-code.deep-nesting',
27
27
  message: `Function "${func.name}" has deep nesting: ${func.nestingDepth} levels (maximum: ${
28
- config.rules['clean-code']['deep-nesting'].threshold
28
+ settings.threshold
29
29
  })`,
30
- severity: config.rules['clean-code']['deep-nesting'].severity as Severity
30
+ severity: (settings.severity as Severity) ?? 'warning'
31
31
  });
32
32
  }
33
33
  }
@@ -1,15 +1,15 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const godClassRule: Rule = {
7
5
  id: 'clean-code.god-class',
8
6
  name: 'God Class Rule',
9
- threshold: config.rules['clean-code']['god-class'].threshold ?? 15,
10
- severity: config.rules['clean-code']['god-class'].severity as Severity,
7
+ threshold: 15,
8
+ severity: 'warning',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['god-class'];
13
13
 
14
14
  try {
15
15
  interface ClassObject {
@@ -35,15 +35,15 @@ export const godClassRule: Rule = {
35
35
  return true;
36
36
  }).length;
37
37
 
38
- if (methodCount > (config.rules['clean-code']['god-class'].threshold as number)) {
38
+ if (methodCount > (settings.threshold ?? 0)) {
39
39
  violations.push({
40
40
  file,
41
41
  line: cls.line,
42
42
  ruleId: 'clean-code.god-class',
43
43
  message: `Class "${cls.name}" has too many methods: ${methodCount} (maximum: ${
44
- config.rules['clean-code']['god-class'].threshold
44
+ settings.threshold
45
45
  })`,
46
- severity: config.rules['clean-code']['god-class'].severity as Severity
46
+ severity: (settings.severity as Severity) ?? 'warning'
47
47
  });
48
48
  }
49
49
  }
@@ -1,34 +1,38 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const largeFileRule: Rule = {
7
5
  id: 'clean-code.large-file',
8
6
  name: 'Large File Rule',
9
- threshold: config.rules['clean-code']['large-file'].threshold ?? 500,
10
- severity: config.rules['clean-code']['large-file'].severity as Severity,
7
+ // Static field mirrors the built-in default. The ENFORCED value comes from
8
+ // getActiveConfig() inside check(), so `.principlesrc` is honoured (#457).
9
+ threshold: 1150,
10
+ severity: 'warning',
11
11
  check: (file: string, adapter: unknown): Violation[] => {
12
12
  const violations: Violation[] = [];
13
-
13
+ // Read the ACTIVE config per invocation. Snapshotting it at module load was
14
+ // #457: a project's `.principlesrc` threshold was parsed but never applied.
15
+ const settings = getActiveConfig().rules['clean-code']['large-file'];
16
+ const threshold = settings.threshold ?? 500;
17
+
14
18
  try {
15
19
  interface TypedAdapter {
16
20
  countLines?: (file: string) => number | undefined;
17
21
  }
18
22
  const typedAdapter = adapter as TypedAdapter;
19
23
  const lineCount = typedAdapter.countLines?.(file) ?? 0;
20
-
21
- if (lineCount > (config.rules['clean-code']['large-file'].threshold as number)) {
24
+
25
+ if (lineCount > threshold) {
22
26
  violations.push({
23
27
  file,
24
28
  line: 1,
25
29
  ruleId: 'clean-code.large-file',
26
- message: `File is too large: ${lineCount} lines (maximum: ${config.rules['clean-code']['large-file'].threshold})`,
27
- severity: config.rules['clean-code']['large-file'].severity as Severity
30
+ message: `File is too large: ${lineCount} lines (maximum: ${threshold})`,
31
+ severity: (settings.severity as Severity) ?? 'warning'
28
32
  });
29
33
  }
30
34
  } catch { }
31
-
35
+
32
36
  return violations;
33
37
  }
34
38
  };
@@ -1,15 +1,15 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const longFunctionRule: Rule = {
7
5
  id: 'clean-code.long-function',
8
6
  name: 'Long Function Rule',
9
- threshold: config.rules['clean-code']['long-function'].threshold ?? 50,
10
- severity: config.rules['clean-code']['long-function'].severity as Severity,
7
+ threshold: 50,
8
+ severity: 'warning',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['long-function'];
13
13
 
14
14
  try {
15
15
  interface FunctionObj {
@@ -28,15 +28,15 @@ export const longFunctionRule: Rule = {
28
28
  for (const func of functions) {
29
29
  const { name, startLine, length } = func;
30
30
 
31
- if (length > (config.rules['clean-code']['long-function'].threshold as number)) {
31
+ if (length > (settings.threshold ?? 0)) {
32
32
  violations.push({
33
33
  file,
34
34
  line: startLine ?? 1,
35
35
  ruleId: 'clean-code.long-function',
36
36
  message: `Function "${name}" is too long: ${length} lines (maximum: ${
37
- config.rules['clean-code']['long-function'].threshold
37
+ settings.threshold
38
38
  })`,
39
- severity: config.rules['clean-code']['long-function'].severity as Severity
39
+ severity: (settings.severity as Severity) ?? 'warning'
40
40
  });
41
41
  }
42
42
  }
@@ -1,17 +1,19 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
2
+ import { getActiveConfig } from '../../config';
3
3
 
4
- const config = getDefaultConfig();
5
-
6
- const EXCLUDED_NUMBERS = config.rules['clean-code']['magic-numbers'].exclude || [0, 1, -1, 2, 10, 100, 1000, 60, 24, 7, 30, 365, 256, 1024];
4
+ const DEFAULT_EXCLUDED_NUMBERS = [0, 1, -1, 2, 10, 100, 1000, 60, 24, 7, 30, 365, 256, 1024];
7
5
 
8
6
  export const magicNumbersRule: Rule = {
9
7
  id: 'clean-code.magic-numbers',
10
8
  name: 'Magic Numbers Rule',
9
+ // Not config-derived: this rule reports every non-excluded literal, so the
10
+ // static field is an informational placeholder rather than an enforced bound.
11
11
  threshold: 10,
12
- severity: config.rules['clean-code']['magic-numbers'].severity as Severity,
12
+ severity: 'info',
13
13
  check: (file: string, adapter: unknown): Violation[] => {
14
14
  const violations: Violation[] = [];
15
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
16
+ const settings = getActiveConfig().rules['clean-code']['magic-numbers'];
15
17
 
16
18
  try {
17
19
  interface NumberObject {
@@ -37,9 +39,10 @@ export const magicNumbersRule: Rule = {
37
39
  }
38
40
  } catch { }
39
41
 
42
+ const excludedNumbers = settings.exclude ?? DEFAULT_EXCLUDED_NUMBERS;
40
43
  const filteredNumbers = magicNumbers.filter(numObj => {
41
44
  const numValue = numObj.value;
42
- return !EXCLUDED_NUMBERS.includes(numValue);
45
+ return !excludedNumbers.includes(numValue);
43
46
  });
44
47
 
45
48
  filteredNumbers.forEach(numObj => {
@@ -48,7 +51,7 @@ export const magicNumbersRule: Rule = {
48
51
  line: numObj.line,
49
52
  ruleId: 'clean-code.magic-numbers',
50
53
  message: `Potential magic number detected: ${numObj.value}. Consider using a named constant instead.`,
51
- severity: config.rules['clean-code']['magic-numbers'].severity as Severity
54
+ severity: (settings.severity as Severity) ?? 'info'
52
55
  });
53
56
  });
54
57
 
@@ -1,15 +1,15 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const manyExportsRule: Rule = {
7
5
  id: 'clean-code.many-exports',
8
6
  name: 'Many Exports Rule',
9
- threshold: config.rules['clean-code']['many-exports'].threshold ?? 10,
10
- severity: config.rules['clean-code']['many-exports'].severity as Severity,
7
+ threshold: 10,
8
+ severity: 'warning',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['many-exports'];
13
13
 
14
14
  try {
15
15
  interface ExportObj {
@@ -22,7 +22,7 @@ export const manyExportsRule: Rule = {
22
22
 
23
23
  const typedAdapter = adapter as TypedAdapter;
24
24
  const exports = typedAdapter.extractExports ? typedAdapter.extractExports() : [];
25
- const threshold = config.rules['clean-code']['many-exports'].threshold ?? 10;
25
+ const threshold = settings.threshold ?? 10;
26
26
 
27
27
  if (exports && exports.length > threshold) {
28
28
  violations.push({
@@ -30,7 +30,7 @@ export const manyExportsRule: Rule = {
30
30
  line: exports[0]?.line || 1,
31
31
  ruleId: 'clean-code.many-exports',
32
32
  message: `Module has too many exports: ${exports.length} (maximum: ${threshold}). Consider splitting into focused sub-modules.`,
33
- severity: config.rules['clean-code']['many-exports'].severity as Severity
33
+ severity: (settings.severity as Severity) ?? 'warning'
34
34
  });
35
35
  }
36
36
  } catch { }
@@ -1,15 +1,15 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const missingErrorHandlingRule: Rule = {
7
5
  id: 'clean-code.missing-error-handling',
8
6
  name: 'Missing Error Handling Rule',
9
7
  threshold: 1,
10
- severity: config.rules['clean-code']['missing-error-handling'].severity as Severity,
8
+ severity: 'warning',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['missing-error-handling'];
13
13
 
14
14
  try {
15
15
  const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; ioOperations?: string[]; hasTryCatch?: boolean;}> | undefined };
@@ -22,7 +22,7 @@ export const missingErrorHandlingRule: Rule = {
22
22
  line: func.startLine ?? func.line ?? 1,
23
23
  ruleId: 'clean-code.missing-error-handling',
24
24
  message: `Function "${func.name}" has IO operations (${func.ioOperations.join(', ')}) without error handling`,
25
- severity: config.rules['clean-code']['missing-error-handling'].severity as Severity
25
+ severity: (settings.severity as Severity) ?? 'warning'
26
26
  });
27
27
  }
28
28
  }
@@ -1,30 +1,30 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const tooManyParamsRule: Rule = {
7
5
  id: 'clean-code.too-many-params',
8
6
  name: 'Too Many Parameters Rule',
9
- threshold: config.rules['clean-code']['too-many-params'].threshold ?? 7,
10
- severity: config.rules['clean-code']['too-many-params'].severity as Severity,
7
+ threshold: 7,
8
+ severity: 'info',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['too-many-params'];
13
13
 
14
14
  try {
15
15
  const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; paramCount?: number;}> | undefined };
16
16
  const functions = typedAdapter.extractFunctions?.() || [];
17
17
 
18
18
  for (const func of functions) {
19
- if (func.paramCount && func.paramCount > (config.rules['clean-code']['too-many-params'].threshold as number)) {
19
+ if (func.paramCount && func.paramCount > (settings.threshold ?? 0)) {
20
20
  violations.push({
21
21
  file,
22
22
  line: func.startLine ?? func.line ?? 1,
23
23
  ruleId: 'clean-code.too-many-params',
24
24
  message: `Function "${func.name}" has too many parameters: ${func.paramCount} (maximum: ${
25
- config.rules['clean-code']['too-many-params'].threshold
25
+ settings.threshold
26
26
  })`,
27
- severity: config.rules['clean-code']['too-many-params'].severity as Severity
27
+ severity: (settings.severity as Severity) ?? 'info'
28
28
  });
29
29
  }
30
30
  }
@@ -1,15 +1,15 @@
1
1
  import { Rule, Violation, Severity } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const unusedImportsRule: Rule = {
7
5
  id: 'clean-code.unused-imports',
8
6
  name: 'Unused Imports Rule',
9
7
  threshold: 1,
10
- severity: config.rules['clean-code']['unused-imports'].severity as Severity,
8
+ severity: 'info',
11
9
  check: (file: string, adapter: unknown): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['clean-code']['unused-imports'];
13
13
 
14
14
  try {
15
15
  const typedAdapter = adapter as { imports?: Array<{name?: string; line?: number; used?: boolean; type?: string;}> | undefined };
@@ -22,7 +22,7 @@ export const unusedImportsRule: Rule = {
22
22
  line: imp.line ?? 1,
23
23
  ruleId: 'clean-code.unused-imports',
24
24
  message: `Unused import "${imp.name}" - consider removing`,
25
- severity: config.rules['clean-code']['unused-imports'].severity as Severity
25
+ severity: (settings.severity as Severity) ?? 'info'
26
26
  });
27
27
  }
28
28
  }
@@ -1,14 +1,22 @@
1
- import { Rule, Violation } from '../../types';
2
- import { getDefaultConfig } from '../../config';
1
+ import { Rule, Violation, Severity } from '../../types';
2
+ import { getActiveConfig } from '../../config';
3
3
 
4
- const config = getDefaultConfig();
5
- const { severity } = config.rules['solid']['dip'];
6
- const EXCLUDED_CLASSES = config.rules['solid']['dip'].exclude || [
4
+ const DEFAULT_EXCLUDED_CLASSES = [
7
5
  'Date', 'Map', 'Set', 'Error', 'Array', 'Object', 'Promise'
8
6
  ];
9
7
 
8
+ /**
9
+ * Read the excluded list from the ACTIVE config on each call.
10
+ *
11
+ * A module-load snapshot was #457: a project's `.principlesrc` override was
12
+ * parsed but never consulted.
13
+ */
14
+ function excludedClasses(): (string | number)[] {
15
+ return getActiveConfig().rules['solid']['dip'].exclude ?? DEFAULT_EXCLUDED_CLASSES;
16
+ }
17
+
10
18
  function shouldSkipInstantiation(className: string): boolean {
11
- if (EXCLUDED_CLASSES.includes(className)) return true;
19
+ if (excludedClasses().includes(className)) return true;
12
20
  if (className.endsWith('Factory') || className.endsWith('Builder')) return true;
13
21
  return false;
14
22
  }
@@ -17,9 +25,11 @@ export const dipRule: Rule = {
17
25
  id: 'solid.dip',
18
26
  name: 'Dependency Inversion Principle Rule',
19
27
  threshold: 0,
20
- severity: severity as 'error' | 'warning' | 'info',
28
+ severity: 'warning',
21
29
  check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
22
30
  const violations: Violation[] = [];
31
+ const settings = getActiveConfig().rules['solid']['dip'];
32
+ const severity = (settings.severity as Severity) ?? 'warning';
23
33
 
24
34
  try {
25
35
  for (const cls_any of adapter.extractClasses() || []) {
@@ -36,7 +46,7 @@ export const dipRule: Rule = {
36
46
  file,
37
47
  line: cls.line || 0,
38
48
  ruleId: 'solid.dip',
39
- severity: severity as 'error' | 'warning' | 'info',
49
+ severity,
40
50
  message: `Direct instantiation detected: new ${className}(). Prefer dependency injection for flexibility.`,
41
51
  });
42
52
  }
@@ -1,32 +1,33 @@
1
1
  import { Rule, Violation } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const ispRule: Rule = {
7
5
  id: 'solid.isp',
8
6
  name: 'Interface Segregation Principle Rule',
9
- threshold: config.rules['solid']['isp'].methodThreshold ?? 10,
10
- severity: config.rules['solid']['isp'].severity as 'error' | 'warning' | 'info',
7
+ threshold: 10,
8
+ severity: 'info',
11
9
  check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['solid']['isp'];
13
13
 
14
14
  try {
15
15
  const typedAdapter = adapter as { extractInterfaces?: () => Array<{name?: string; line?: number; methodCount?: number;}> | undefined };
16
16
  const interfaces = typedAdapter.extractInterfaces?.() || [];
17
+ const maxMethods = settings.methodThreshold ?? 10;
17
18
 
18
19
  for (const iface of interfaces) {
19
20
  const methodCount = iface.methodCount || 0;
20
21
 
21
- if (methodCount > (config.rules['solid']['isp'].methodThreshold as number)) {
22
+ if (methodCount > maxMethods) {
22
23
  violations.push({
23
24
  file,
24
25
  line: iface.line ?? 1,
25
26
  ruleId: 'solid.isp',
26
27
  message: `Interface "${iface.name}" has too many methods: ${methodCount} (maximum: ${
27
- config.rules['solid']['isp'].methodThreshold
28
+ maxMethods
28
29
  }). Consider splitting into focused interfaces.`,
29
- severity: config.rules['solid']['isp'].severity as "error" | "warning" | "info"
30
+ severity: (settings.severity as "error" | "warning" | "info") ?? 'info'
30
31
  });
31
32
  }
32
33
  }
@@ -1,8 +1,6 @@
1
- import { Rule, Violation, Adapter } from '../../types';
2
- import { getDefaultConfig } from '../../config';
1
+ import { Rule, Violation, Adapter, Severity } from '../../types';
2
+ import { getActiveConfig } from '../../config';
3
3
 
4
- const config = getDefaultConfig();
5
- const severity = config.rules['solid']['lsp'].severity as 'error' | 'warning' | 'info';
6
4
  const PRIMITIVE_TYPES = new Set([
7
5
  'string', 'number', 'boolean', 'any', 'void', 'null', 'undefined', 'Object', 'Array',
8
6
  ]);
@@ -37,12 +35,14 @@ function collectMethodParamTypes(methodSignature: string): string[] {
37
35
  }
38
36
 
39
37
  function buildViolation(file: string, cls: ClassInfo, paramType: string): Violation {
38
+ // Read the ACTIVE severity here rather than at module load (#457).
39
+ const settings = getActiveConfig().rules['solid']['lsp'];
40
40
  return {
41
41
  file,
42
42
  line: cls.line ?? 0,
43
43
  ruleId: 'solid.lsp',
44
44
  message: `Possible LSP violation in "${cls.name}". Parameter type "${paramType}" may not be compatible with base class contract.`,
45
- severity,
45
+ severity: (settings.severity as Severity) ?? 'info',
46
46
  };
47
47
  }
48
48
 
@@ -67,7 +67,7 @@ export const lspRule: Rule = {
67
67
  id: 'solid.lsp',
68
68
  name: 'Liskov Substitution Principle Rule',
69
69
  threshold: 0,
70
- severity,
70
+ severity: 'info',
71
71
  check: (file: string, adapter: Adapter): Violation[] => {
72
72
  try {
73
73
  const classes = (adapter.extractClasses() || []) as ClassInfo[];
@@ -1,15 +1,16 @@
1
1
  import { Rule, Violation } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
2
+ import { getActiveConfig } from '../../config';
5
3
 
6
4
  export const ocpRule: Rule = {
7
5
  id: 'solid.ocp',
8
6
  name: 'Open/Closed Principle Rule',
9
7
  threshold: 0,
10
- severity: config.rules['solid']['ocp'].severity as "error" | "warning" | "info",
8
+ severity: 'info',
11
9
  check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
12
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['solid']['ocp'];
13
+ const severity = (settings.severity as "error" | "warning" | "info") ?? 'info';
13
14
 
14
15
  try {
15
16
  const classes = adapter.extractClasses() || [];
@@ -28,7 +29,7 @@ export const ocpRule: Rule = {
28
29
  line: cls.line ?? 1,
29
30
  ruleId: 'solid.ocp',
30
31
  message: `Possible modification of base class "${baseClass}" while extending. Extension should not require modifying the base.`,
31
- severity: config.rules['solid']['ocp'].severity as "error" | "warning" | "info"
32
+ severity: severity
32
33
  });
33
34
  }
34
35
  }
@@ -1,21 +1,20 @@
1
1
  import { Rule, Violation } from '../../types';
2
- import { getDefaultConfig } from '../../config';
3
-
4
- const config = getDefaultConfig();
5
- const { methodThreshold, severity } = config.rules['solid']['srp'];
2
+ import { getActiveConfig } from '../../config';
6
3
 
7
4
  export const srpRule: Rule = {
8
5
  id: 'solid.srp',
9
6
  name: 'Single Responsibility Principle Rule',
10
- threshold: methodThreshold as number,
11
- severity: severity as "error" | "warning" | "info",
7
+ threshold: 15,
8
+ severity: 'warning',
12
9
  check: (file: string, adapter: import('../../types').Adapter): Violation[] => {
13
10
  const violations: Violation[] = [];
11
+ // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
+ const settings = getActiveConfig().rules['solid']['srp'];
14
13
 
15
14
  try {
16
15
  const classes = adapter.extractClasses() || [];
17
- const maxMethods = methodThreshold as number;
18
- const sev = severity as "error" | "warning" | "info";
16
+ const maxMethods = settings.methodThreshold as number;
17
+ const sev = (settings.severity as 'error' | 'warning' | 'info') ?? 'warning';
19
18
 
20
19
  for (const raw of classes) {
21
20
  const cls = raw as { code?: string; line?: number; name?: string; methodCount?: number; imports?: Record<string, unknown> };
@@ -3,7 +3,22 @@ export type Severity = 'error' | 'warning' | 'info';
3
3
  export interface Rule {
4
4
  id: string;
5
5
  name: string;
6
+ /**
7
+ * The rule's BUILT-IN default threshold.
8
+ *
9
+ * @deprecated This is NOT necessarily the value being enforced. Since #457 the
10
+ * effective threshold comes from the active `.principlesrc` and is read inside
11
+ * `check()`. Reading this field to decide whether a violation should occur will
12
+ * disagree with the checker whenever the project overrides the threshold. Use
13
+ * `getActiveConfig().rules[group][rule].threshold` for the enforced value.
14
+ */
6
15
  threshold: number;
16
+ /**
17
+ * The rule's BUILT-IN default severity.
18
+ *
19
+ * @deprecated See {@link Rule.threshold}: the enforced severity comes from the
20
+ * active config. Individual `Violation.severity` values are authoritative.
21
+ */
7
22
  severity: Severity;
8
23
  check: (file: string, adapter: Adapter) => Violation[];
9
24
  }
@@ -588,3 +588,31 @@ describe('sync-package-content subprocess invocation', () => {
588
588
  expect(Object.keys(pkgJsonAfter.dependencies || {}).length).toBe(0, 'dependencies should remain empty');
589
589
  }, 10000);
590
590
  });
591
+
592
+ /**
593
+ * @test REQ-TDD-005 load-time-no-sync
594
+ * @intent Requiring sync-package-content must not sync the repo — only executing it as a
595
+ * script may. The top-level main() used to run on every require(), so a test that
596
+ * merely imported checkDocsDrift regenerated the whole npm-package mirror mid-run
597
+ * and tripped vitest-worktree-guard on CI (missing mirror of an uncommitted
598
+ * canonical file was (re)created as a new untracked path).
599
+ * @covers AC-TDD-005-04
600
+ */
601
+ describe('load-time behavior', () => {
602
+ it('require() performs no filesystem sync; main() runs only when executed directly', () => {
603
+ const modulePath = require.resolve('../sync-package-content');
604
+ delete require.cache[modulePath];
605
+ const copies = [];
606
+ const originalCopy = fs.copyFileSync;
607
+ fs.copyFileSync = (...args) => { copies.push(args); };
608
+ try {
609
+ require('../sync-package-content');
610
+ } finally {
611
+ fs.copyFileSync = originalCopy;
612
+ }
613
+ expect(copies, `require() must not copy; got ${copies.length} copies`).toHaveLength(0);
614
+ const mod = require.cache[modulePath].exports;
615
+ expect(typeof mod.checkDocsDrift).toBe('function');
616
+ expect(typeof mod.checkAdapterDrift).toBe('function');
617
+ });
618
+ });