@boyingliu01/xp-gate 0.26.0 → 0.27.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 (119) hide show
  1. package/bin/xp-gate.js +161 -28
  2. package/hooks/pre-commit +27 -10
  3. package/lib/__tests__/check-alignment-cli.test.mjs +77 -0
  4. package/lib/__tests__/doctor-469-hook-env.test.js +12 -7
  5. package/lib/__tests__/doctor-exec-retry.test.js +152 -0
  6. package/lib/__tests__/doctor-module-resolution-parity.test.js +15 -6
  7. package/lib/__tests__/doctor-tui-skill-freshness.test.js +16 -4
  8. package/lib/__tests__/test-alignment.test.ts +37 -0
  9. package/lib/__tests__/windows-bash-probe.test.js +123 -0
  10. package/lib/check-alignment-runner.cjs +74 -0
  11. package/lib/detect-deps.js +139 -20
  12. package/lib/doctor.js +45 -11
  13. package/lib/gate-runner.js +12 -5
  14. package/lib/mattpocock-license-verdict.json +1 -1
  15. package/lib/sprint-status.js +5 -2
  16. package/lib/test-alignment.ts +136 -12
  17. package/mock-policy/AGENTS.md +2 -2
  18. package/mutation/AGENTS.md +2 -2
  19. package/package.json +1 -1
  20. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  21. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  22. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  23. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  24. package/plugins/dsh/README.md +1 -1
  25. package/plugins/dsh/package.json +1 -1
  26. package/plugins/dsh/scripts/prepack.cjs +2 -2
  27. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  28. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  29. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  30. package/plugins/dsh/src/skills.test.ts +20 -7
  31. package/plugins/opencode/package.json +1 -1
  32. package/plugins/opencode/scripts/prepack.cjs +0 -1
  33. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  34. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  35. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  36. package/plugins/qoder/plugin.json +1 -1
  37. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  38. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  39. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  40. package/principles/AGENTS.md +2 -2
  41. package/principles/rules/__tests__/clean-code/code-duplication.test.ts +9 -8
  42. package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +3 -4
  43. package/principles/rules/__tests__/clean-code/god-class.test.ts +2 -4
  44. package/principles/rules/__tests__/clean-code/large-file.test.ts +2 -4
  45. package/principles/rules/__tests__/clean-code/long-function.test.ts +2 -4
  46. package/principles/rules/__tests__/clean-code/many-exports.test.ts +2 -3
  47. package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +2 -4
  48. package/principles/rules/__tests__/clean-code/too-many-params.test.ts +2 -4
  49. package/principles/rules/__tests__/clean-code/unused-imports.test.ts +4 -4
  50. package/principles/rules/__tests__/solid/dip.test.ts +2 -4
  51. package/principles/rules/__tests__/solid/isp.test.ts +2 -4
  52. package/principles/rules/__tests__/solid/lsp.test.ts +2 -4
  53. package/principles/rules/__tests__/solid/ocp.test.ts +2 -4
  54. package/principles/rules/__tests__/solid/srp.test.ts +2 -4
  55. package/principles/rules/clean-code/code-duplication.ts +12 -14
  56. package/principles/rules/clean-code/deep-nesting.ts +18 -20
  57. package/principles/rules/clean-code/god-class.ts +32 -34
  58. package/principles/rules/clean-code/large-file.ts +14 -16
  59. package/principles/rules/clean-code/long-function.ts +26 -28
  60. package/principles/rules/clean-code/magic-numbers.ts +39 -38
  61. package/principles/rules/clean-code/many-exports.ts +21 -23
  62. package/principles/rules/clean-code/missing-error-handling.ts +13 -15
  63. package/principles/rules/clean-code/too-many-params.ts +15 -17
  64. package/principles/rules/clean-code/unused-imports.ts +13 -15
  65. package/principles/rules/solid/dip.ts +16 -18
  66. package/principles/rules/solid/isp.ts +17 -19
  67. package/principles/rules/solid/lsp.ts +4 -6
  68. package/principles/rules/solid/ocp.ts +17 -19
  69. package/principles/rules/solid/srp.ts +20 -22
  70. package/scripts/__tests__/sync-package-content.test.js +0 -1
  71. package/scripts/sync-package-content.js +0 -1
  72. package/skills/delphi-review/AGENTS.md +2 -2
  73. package/skills/sprint-flow/AGENTS.md +2 -2
  74. package/skills/test-specification-alignment/AGENTS.md +2 -2
  75. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +0 -48
  76. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  77. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  78. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  79. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  80. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  81. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +0 -161
  82. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  83. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  84. package/plugins/dsh/skills/clipboard-vision/SKILL.md +0 -48
  85. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  86. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  87. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  88. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  89. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  90. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +0 -161
  91. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  92. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  93. package/plugins/opencode/skills/clipboard-vision/SKILL.md +0 -48
  94. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  95. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  96. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  97. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  98. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  99. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +0 -161
  100. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  101. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  102. package/plugins/qoder/skills/clipboard-vision/SKILL.md +0 -48
  103. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  104. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  105. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  106. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  107. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  108. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +0 -161
  109. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  110. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  111. package/skills/clipboard-vision/SKILL.md +0 -48
  112. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  113. package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  114. package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  115. package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  116. package/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  117. package/skills/clipboard-vision/describe-clipboard.sh +0 -161
  118. package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  119. package/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
@@ -111,14 +111,13 @@ describe('manyExportsRule', () => {
111
111
  expect(violations).toHaveLength(0);
112
112
  });
113
113
 
114
- it('returns empty violations when adapter throws (catch branch)', () => {
114
+ it('propagates adapter errors (no empty catch, #392)', () => {
115
115
  const adapter: never = {
116
116
  ...baseAdapter,
117
117
  extractExports: () => {
118
118
  throw new Error('boom');
119
119
  },
120
120
  } as never;
121
- const violations = manyExportsRule.check('file.ts', adapter);
122
- expect(violations).toHaveLength(0);
121
+ expect(() => manyExportsRule.check('file.ts', adapter)).toThrow('boom');
123
122
  });
124
123
  });
@@ -134,15 +134,13 @@ async function fetchUser(id: string) {
134
134
  expect(missingErrorHandlingRule.severity).toBe('warning');
135
135
  });
136
136
 
137
- it('should return empty array when adapter.extractFunctions throws', () => {
137
+ it('should propagate adapter errors (no empty catch, #392)', () => {
138
138
  const mockAdapterThrowing = {
139
139
  ...mockAdapter,
140
140
  extractFunctions: () => { throw new Error('Parse error'); }
141
141
  };
142
142
 
143
- const violations = missingErrorHandlingRule.check('test.ts', mockAdapterThrowing as never);
144
-
145
- expect(violations).toEqual([]);
143
+ expect(() => missingErrorHandlingRule.check('test.ts', mockAdapterThrowing as never)).toThrow('Parse error');
146
144
  });
147
145
 
148
146
  it('should detect multiple IO operations in one function', () => {
@@ -47,14 +47,12 @@ describe('too-many-params.ts - Too Many Parameters Rule', () => {
47
47
  expect(tooManyParamsRule.severity).toBe('info');
48
48
  });
49
49
 
50
- it('should return empty violations when adapter throws error', () => {
50
+ it('should propagate adapter errors (no empty catch, #392)', () => {
51
51
  const mockAdapterThatThrows = {
52
52
  ...mockAdapter,
53
53
  extractFunctions: () => { throw new Error('Adapter failed'); }
54
54
  };
55
55
 
56
- const violations = tooManyParamsRule.check('test.ts', mockAdapterThatThrows as never);
57
-
58
- expect(violations.length).toBe(0);
56
+ expect(() => tooManyParamsRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
59
57
  });
60
58
  });
@@ -79,15 +79,15 @@ describe('unused-imports.ts - Unused Imports Rule', () => {
79
79
  expect(violations).toEqual([]);
80
80
  });
81
81
 
82
- it('should return empty array when adapter throws', () => {
82
+ it('should propagate adapter errors (no empty catch, #392)', () => {
83
83
  const mockAdapterThrowing = {
84
84
  ...mockAdapter,
85
+ // Violate the adapter contract: `imports` must be an array. With the old
86
+ // empty catch this was swallowed; now it must bubble up to the analyzer.
85
87
  imports: () => { throw new Error('Parse error'); }
86
88
  };
87
89
 
88
- const violations = unusedImportsRule.check('test.ts', mockAdapterThrowing as never);
89
-
90
- expect(violations).toEqual([]);
90
+ expect(() => unusedImportsRule.check('test.ts', mockAdapterThrowing as never)).toThrow();
91
91
  });
92
92
 
93
93
  it('should handle namespace imports', () => {
@@ -82,15 +82,13 @@ class DateService {
82
82
  expect(dipRule.severity).toBe('warning');
83
83
  });
84
84
 
85
- it('should return empty violations when adapter throws error', () => {
85
+ it('should propagate adapter errors (no empty catch, #392)', () => {
86
86
  const mockAdapterThatThrows = {
87
87
  ...mockAdapter,
88
88
  extractClasses: () => { throw new Error('Adapter failed'); }
89
89
  };
90
90
 
91
- const violations = dipRule.check('test.ts', mockAdapterThatThrows as never);
92
-
93
- expect(violations.length).toBe(0);
91
+ expect(() => dipRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
94
92
  });
95
93
 
96
94
  it('should exclude built-in types (Date, Map, Set, etc.)', () => {
@@ -47,14 +47,12 @@ describe('isp.ts - Interface Segregation Principle Rule', () => {
47
47
  expect(ispRule.severity).toBe('info');
48
48
  });
49
49
 
50
- it('should return empty violations when adapter throws error', () => {
50
+ it('should propagate adapter errors (no empty catch, #392)', () => {
51
51
  const mockAdapterThatThrows = {
52
52
  ...mockAdapter,
53
53
  extractInterfaces: () => { throw new Error('Adapter failed'); }
54
54
  };
55
55
 
56
- const violations = ispRule.check('test.ts', mockAdapterThatThrows as never);
57
-
58
- expect(violations.length).toBe(0);
56
+ expect(() => ispRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
59
57
  });
60
58
  });
@@ -49,15 +49,13 @@ class DerivedRepo extends BaseRepo {
49
49
  expect(lspRule.severity).toBe('info');
50
50
  });
51
51
 
52
- it('should return empty violations when adapter throws error', () => {
52
+ it('should propagate adapter errors (no empty catch, #392)', () => {
53
53
  const mockAdapterThatThrows = {
54
54
  ...mockAdapter,
55
55
  extractClasses: () => { throw new Error('Adapter failed'); }
56
56
  };
57
57
 
58
- const violations = lspRule.check('test.ts', mockAdapterThatThrows as never);
59
-
60
- expect(violations.length).toBe(0);
58
+ expect(() => lspRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
61
59
  });
62
60
 
63
61
  /**
@@ -56,14 +56,12 @@ class DerivedClass extends BaseClass {
56
56
  expect(ocpRule.severity).toBe('info');
57
57
  });
58
58
 
59
- it('should return empty violations when adapter throws error', () => {
59
+ it('should propagate adapter errors (no empty catch, #392)', () => {
60
60
  const mockAdapterThatThrows = {
61
61
  ...mockAdapter,
62
62
  extractClasses: () => { throw new Error('Adapter failed'); }
63
63
  };
64
64
 
65
- const violations = ocpRule.check('test.ts', mockAdapterThatThrows as never);
66
-
67
- expect(violations.length).toBe(0);
65
+ expect(() => ocpRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
68
66
  });
69
67
  });
@@ -76,14 +76,12 @@ describe('srp.ts - Single Responsibility Principle Rule', () => {
76
76
  expect(srpRule.severity).toBe('warning');
77
77
  });
78
78
 
79
- it('should return empty violations when adapter throws error', () => {
79
+ it('should propagate adapter errors (no empty catch, #392)', () => {
80
80
  const mockAdapterThatThrows = {
81
81
  ...mockAdapter,
82
82
  extractClasses: () => { throw new Error('Adapter failed'); }
83
83
  };
84
84
 
85
- const violations = srpRule.check('test.ts', mockAdapterThatThrows as never);
86
-
87
- expect(violations.length).toBe(0);
85
+ expect(() => srpRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
88
86
  });
89
87
  });
@@ -11,20 +11,18 @@ export const codeDuplicationRule: Rule = {
11
11
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
12
  const settings = getActiveConfig().rules['clean-code']['code-duplication'];
13
13
 
14
- try {
15
- const typedAdapter = adapter as { duplicationPercentage?: number };
16
- const duplicationPercentage = typedAdapter.duplicationPercentage;
17
-
18
- if (duplicationPercentage && duplicationPercentage > (settings.threshold ?? 0)) {
19
- violations.push({
20
- file,
21
- line: 1,
22
- ruleId: 'clean-code.code-duplication',
23
- message: `Code duplication detected: ${duplicationPercentage}% (threshold: ${settings.threshold}%). Consider refactoring duplicated code.`,
24
- severity: (settings.severity as Severity) ?? 'warning'
25
- });
26
- }
27
- } catch { }
14
+ const typedAdapter = adapter as { duplicationPercentage?: number };
15
+ const duplicationPercentage = typedAdapter.duplicationPercentage;
16
+
17
+ if (duplicationPercentage && duplicationPercentage > (settings.threshold ?? 0)) {
18
+ violations.push({
19
+ file,
20
+ line: 1,
21
+ ruleId: 'clean-code.code-duplication',
22
+ message: `Code duplication detected: ${duplicationPercentage}% (threshold: ${settings.threshold}%). Consider refactoring duplicated code.`,
23
+ severity: (settings.severity as Severity) ?? 'warning'
24
+ });
25
+ }
28
26
 
29
27
  return violations;
30
28
  }
@@ -11,27 +11,25 @@ export const deepNestingRule: Rule = {
11
11
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
12
  const settings = getActiveConfig().rules['clean-code']['deep-nesting'];
13
13
 
14
- try {
15
- interface TypedAdapter {
16
- extractFunctions?: () => Array<{name?: string, nestingDepth?: number, startLine?: number, line?: number}> | undefined;
17
- }
18
- const typedAdapter = adapter as TypedAdapter;
19
- const functions = typedAdapter.extractFunctions?.() || [];
20
-
21
- for (const func of functions) {
22
- if (func.nestingDepth && func.nestingDepth > (settings.threshold ?? 0)) {
23
- violations.push({
24
- file,
25
- line: func.startLine ?? func.line ?? 1,
26
- ruleId: 'clean-code.deep-nesting',
27
- message: `Function "${func.name}" has deep nesting: ${func.nestingDepth} levels (maximum: ${
28
- settings.threshold
29
- })`,
30
- severity: (settings.severity as Severity) ?? 'warning'
31
- });
32
- }
14
+ interface TypedAdapter {
15
+ extractFunctions?: () => Array<{name?: string, nestingDepth?: number, startLine?: number, line?: number}> | undefined;
16
+ }
17
+ const typedAdapter = adapter as TypedAdapter;
18
+ const functions = typedAdapter.extractFunctions?.() || [];
19
+
20
+ for (const func of functions) {
21
+ if (func.nestingDepth && func.nestingDepth > (settings.threshold ?? 0)) {
22
+ violations.push({
23
+ file,
24
+ line: func.startLine ?? func.line ?? 1,
25
+ ruleId: 'clean-code.deep-nesting',
26
+ message: `Function "${func.name}" has deep nesting: ${func.nestingDepth} levels (maximum: ${
27
+ settings.threshold
28
+ })`,
29
+ severity: (settings.severity as Severity) ?? 'warning'
30
+ });
33
31
  }
34
- } catch { }
32
+ }
35
33
 
36
34
  return violations;
37
35
  }
@@ -11,43 +11,41 @@ export const godClassRule: Rule = {
11
11
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
12
  const settings = getActiveConfig().rules['clean-code']['god-class'];
13
13
 
14
- try {
15
- interface ClassObject {
16
- code?: string;
17
- line: number;
18
- name: string;
19
- }
20
-
21
- interface TypedAdapter {
22
- extractClasses?: () => ClassObject[] | undefined;
23
- }
24
-
25
- const typedAdapter = adapter as TypedAdapter;
26
- const classes = typedAdapter.extractClasses?.() || [];
14
+ interface ClassObject {
15
+ code?: string;
16
+ line: number;
17
+ name: string;
18
+ }
19
+
20
+ interface TypedAdapter {
21
+ extractClasses?: () => ClassObject[] | undefined;
22
+ }
23
+
24
+ const typedAdapter = adapter as TypedAdapter;
25
+ const classes = typedAdapter.extractClasses?.() || [];
26
+
27
+ for (const cls of classes) {
28
+ const methodMatches = cls.code?.match(/(get|set)\s+\w+\s*\(|\w+\s*\([^)]*\)\s*{/g) || [];
27
29
 
28
- for (const cls of classes) {
29
- const methodMatches = cls.code?.match(/(get|set)\s+\w+\s*\(|\w+\s*\([^)]*\)\s*{/g) || [];
30
-
31
- const methodCount = methodMatches.filter((m: string) => {
32
- if (m.startsWith('get ') || m.startsWith('set ')) {
33
- return false;
34
- }
35
- return true;
36
- }).length;
37
-
38
- if (methodCount > (settings.threshold ?? 0)) {
39
- violations.push({
40
- file,
41
- line: cls.line,
42
- ruleId: 'clean-code.god-class',
43
- message: `Class "${cls.name}" has too many methods: ${methodCount} (maximum: ${
44
- settings.threshold
45
- })`,
46
- severity: (settings.severity as Severity) ?? 'warning'
47
- });
30
+ const methodCount = methodMatches.filter((m: string) => {
31
+ if (m.startsWith('get ') || m.startsWith('set ')) {
32
+ return false;
48
33
  }
34
+ return true;
35
+ }).length;
36
+
37
+ if (methodCount > (settings.threshold ?? 0)) {
38
+ violations.push({
39
+ file,
40
+ line: cls.line,
41
+ ruleId: 'clean-code.god-class',
42
+ message: `Class "${cls.name}" has too many methods: ${methodCount} (maximum: ${
43
+ settings.threshold
44
+ })`,
45
+ severity: (settings.severity as Severity) ?? 'warning'
46
+ });
49
47
  }
50
- } catch { }
48
+ }
51
49
 
52
50
  return violations;
53
51
  }
@@ -15,23 +15,21 @@ export const largeFileRule: Rule = {
15
15
  const settings = getActiveConfig().rules['clean-code']['large-file'];
16
16
  const threshold = settings.threshold ?? 500;
17
17
 
18
- try {
19
- interface TypedAdapter {
20
- countLines?: (file: string) => number | undefined;
21
- }
22
- const typedAdapter = adapter as TypedAdapter;
23
- const lineCount = typedAdapter.countLines?.(file) ?? 0;
18
+ interface TypedAdapter {
19
+ countLines?: (file: string) => number | undefined;
20
+ }
21
+ const typedAdapter = adapter as TypedAdapter;
22
+ const lineCount = typedAdapter.countLines?.(file) ?? 0;
24
23
 
25
- if (lineCount > threshold) {
26
- violations.push({
27
- file,
28
- line: 1,
29
- ruleId: 'clean-code.large-file',
30
- message: `File is too large: ${lineCount} lines (maximum: ${threshold})`,
31
- severity: (settings.severity as Severity) ?? 'warning'
32
- });
33
- }
34
- } catch { }
24
+ if (lineCount > threshold) {
25
+ violations.push({
26
+ file,
27
+ line: 1,
28
+ ruleId: 'clean-code.large-file',
29
+ message: `File is too large: ${lineCount} lines (maximum: ${threshold})`,
30
+ severity: (settings.severity as Severity) ?? 'warning'
31
+ });
32
+ }
35
33
 
36
34
  return violations;
37
35
  }
@@ -11,36 +11,34 @@ export const longFunctionRule: Rule = {
11
11
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
12
  const settings = getActiveConfig().rules['clean-code']['long-function'];
13
13
 
14
- try {
15
- interface FunctionObj {
16
- name: string;
17
- startLine: number;
18
- length: number;
19
- }
20
-
21
- interface TypedAdapter {
22
- extractFunctions?: () => FunctionObj[] | undefined;
23
- }
24
-
25
- const typedAdapter = adapter as TypedAdapter;
26
- const functions = typedAdapter.extractFunctions?.() || [];
14
+ interface FunctionObj {
15
+ name: string;
16
+ startLine: number;
17
+ length: number;
18
+ }
19
+
20
+ interface TypedAdapter {
21
+ extractFunctions?: () => FunctionObj[] | undefined;
22
+ }
23
+
24
+ const typedAdapter = adapter as TypedAdapter;
25
+ const functions = typedAdapter.extractFunctions?.() || [];
26
+
27
+ for (const func of functions) {
28
+ const { name, startLine, length } = func;
27
29
 
28
- for (const func of functions) {
29
- const { name, startLine, length } = func;
30
-
31
- if (length > (settings.threshold ?? 0)) {
32
- violations.push({
33
- file,
34
- line: startLine ?? 1,
35
- ruleId: 'clean-code.long-function',
36
- message: `Function "${name}" is too long: ${length} lines (maximum: ${
37
- settings.threshold
38
- })`,
39
- severity: (settings.severity as Severity) ?? 'warning'
40
- });
41
- }
30
+ if (length > (settings.threshold ?? 0)) {
31
+ violations.push({
32
+ file,
33
+ line: startLine ?? 1,
34
+ ruleId: 'clean-code.long-function',
35
+ message: `Function "${name}" is too long: ${length} lines (maximum: ${
36
+ settings.threshold
37
+ })`,
38
+ severity: (settings.severity as Severity) ?? 'warning'
39
+ });
42
40
  }
43
- } catch { }
41
+ }
44
42
 
45
43
  return violations;
46
44
  }
@@ -15,48 +15,49 @@ export const magicNumbersRule: Rule = {
15
15
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
16
16
  const settings = getActiveConfig().rules['clean-code']['magic-numbers'];
17
17
 
18
+ // #392-#4: rules must not swallow their own errors. The outer try/catch was
19
+ // removed so a genuine rule bug bubbles up to analyzer.runRuleOnFile() and
20
+ // surfaces as a gate error. The inner try/catch below is kept deliberately:
21
+ // an adapter that cannot extract AST data is a legitimate "no data" case.
22
+ interface NumberObject {
23
+ value: number;
24
+ line: number;
25
+ }
26
+
27
+ interface TypedAdapter {
28
+ parseAST?: () => unknown | undefined;
29
+ extract?: () => NumberObject[] | undefined;
30
+ }
31
+
32
+ const typedAdapter = adapter as TypedAdapter;
33
+
34
+ let magicNumbers: NumberObject[] = [];
35
+
18
36
  try {
19
- interface NumberObject {
20
- value: number;
21
- line: number;
22
- }
23
-
24
- interface TypedAdapter {
25
- parseAST?: () => unknown | undefined;
26
- extract?: () => NumberObject[] | undefined;
27
- }
28
-
29
- const typedAdapter = adapter as TypedAdapter;
30
-
31
- let magicNumbers: NumberObject[] = [];
32
-
33
- try {
34
- if (typeof typedAdapter.extract !== 'undefined') {
35
- const literals = typedAdapter.extract?.();
36
- if (literals && Array.isArray(literals)) {
37
- magicNumbers = literals;
38
- }
37
+ if (typeof typedAdapter.extract !== 'undefined') {
38
+ const literals = typedAdapter.extract?.();
39
+ if (literals && Array.isArray(literals)) {
40
+ magicNumbers = literals;
39
41
  }
40
- } catch { }
41
-
42
- const excludedNumbers = settings.exclude ?? DEFAULT_EXCLUDED_NUMBERS;
43
- const filteredNumbers = magicNumbers.filter(numObj => {
44
- const numValue = numObj.value;
45
- return !excludedNumbers.includes(numValue);
46
- });
47
-
48
- filteredNumbers.forEach(numObj => {
49
- violations.push({
50
- file,
51
- line: numObj.line,
52
- ruleId: 'clean-code.magic-numbers',
53
- message: `Potential magic number detected: ${numObj.value}. Consider using a named constant instead.`,
54
- severity: (settings.severity as Severity) ?? 'info'
55
- });
56
- });
57
-
42
+ }
58
43
  } catch { }
59
44
 
45
+ const excludedNumbers = settings.exclude ?? DEFAULT_EXCLUDED_NUMBERS;
46
+ const filteredNumbers = magicNumbers.filter(numObj => {
47
+ const numValue = numObj.value;
48
+ return !excludedNumbers.includes(numValue);
49
+ });
50
+
51
+ filteredNumbers.forEach(numObj => {
52
+ violations.push({
53
+ file,
54
+ line: numObj.line,
55
+ ruleId: 'clean-code.magic-numbers',
56
+ message: `Potential magic number detected: ${numObj.value}. Consider using a named constant instead.`,
57
+ severity: (settings.severity as Severity) ?? 'info'
58
+ });
59
+ });
60
+
60
61
  return violations;
61
62
  }
62
63
  };
@@ -11,29 +11,27 @@ export const manyExportsRule: Rule = {
11
11
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
12
  const settings = getActiveConfig().rules['clean-code']['many-exports'];
13
13
 
14
- try {
15
- interface ExportObj {
16
- line: number;
17
- }
18
-
19
- interface TypedAdapter {
20
- extractExports?: () => ExportObj[] | undefined;
21
- }
22
-
23
- const typedAdapter = adapter as TypedAdapter;
24
- const exports = typedAdapter.extractExports ? typedAdapter.extractExports() : [];
25
- const threshold = settings.threshold ?? 10;
26
-
27
- if (exports && exports.length > threshold) {
28
- violations.push({
29
- file,
30
- line: exports[0]?.line || 1,
31
- ruleId: 'clean-code.many-exports',
32
- message: `Module has too many exports: ${exports.length} (maximum: ${threshold}). Consider splitting into focused sub-modules.`,
33
- severity: (settings.severity as Severity) ?? 'warning'
34
- });
35
- }
36
- } catch { }
14
+ interface ExportObj {
15
+ line: number;
16
+ }
17
+
18
+ interface TypedAdapter {
19
+ extractExports?: () => ExportObj[] | undefined;
20
+ }
21
+
22
+ const typedAdapter = adapter as TypedAdapter;
23
+ const exports = typedAdapter.extractExports ? typedAdapter.extractExports() : [];
24
+ const threshold = settings.threshold ?? 10;
25
+
26
+ if (exports && exports.length > threshold) {
27
+ violations.push({
28
+ file,
29
+ line: exports[0]?.line || 1,
30
+ ruleId: 'clean-code.many-exports',
31
+ message: `Module has too many exports: ${exports.length} (maximum: ${threshold}). Consider splitting into focused sub-modules.`,
32
+ severity: (settings.severity as Severity) ?? 'warning'
33
+ });
34
+ }
37
35
 
38
36
  return violations;
39
37
  }
@@ -11,22 +11,20 @@ export const missingErrorHandlingRule: Rule = {
11
11
  // Read the ACTIVE config per invocation; a module-load snapshot was #457.
12
12
  const settings = getActiveConfig().rules['clean-code']['missing-error-handling'];
13
13
 
14
- try {
15
- const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; ioOperations?: string[]; hasTryCatch?: boolean;}> | undefined };
16
- const functions = typedAdapter.extractFunctions?.() || [];
17
-
18
- for (const func of functions) {
19
- if (func.ioOperations && func.ioOperations.length > 0 && !func.hasTryCatch) {
20
- violations.push({
21
- file,
22
- line: func.startLine ?? func.line ?? 1,
23
- ruleId: 'clean-code.missing-error-handling',
24
- message: `Function "${func.name}" has IO operations (${func.ioOperations.join(', ')}) without error handling`,
25
- severity: (settings.severity as Severity) ?? 'warning'
26
- });
27
- }
14
+ const typedAdapter = adapter as { extractFunctions?: () => Array<{name?: string; startLine?: number; line?: number; ioOperations?: string[]; hasTryCatch?: boolean;}> | undefined };
15
+ const functions = typedAdapter.extractFunctions?.() || [];
16
+
17
+ for (const func of functions) {
18
+ if (func.ioOperations && func.ioOperations.length > 0 && !func.hasTryCatch) {
19
+ violations.push({
20
+ file,
21
+ line: func.startLine ?? func.line ?? 1,
22
+ ruleId: 'clean-code.missing-error-handling',
23
+ message: `Function "${func.name}" has IO operations (${func.ioOperations.join(', ')}) without error handling`,
24
+ severity: (settings.severity as Severity) ?? 'warning'
25
+ });
28
26
  }
29
- } catch { }
27
+ }
30
28
 
31
29
  return violations;
32
30
  }