@boyingliu01/xp-gate 0.26.0 → 0.27.0

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 (117) 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-module-resolution-parity.test.js +15 -6
  6. package/lib/__tests__/doctor-tui-skill-freshness.test.js +16 -4
  7. package/lib/__tests__/test-alignment.test.ts +37 -0
  8. package/lib/__tests__/windows-bash-probe.test.js +73 -0
  9. package/lib/check-alignment-runner.cjs +74 -0
  10. package/lib/detect-deps.js +82 -19
  11. package/lib/gate-runner.js +12 -5
  12. package/lib/mattpocock-license-verdict.json +1 -1
  13. package/lib/sprint-status.js +5 -2
  14. package/lib/test-alignment.ts +136 -12
  15. package/mock-policy/AGENTS.md +2 -2
  16. package/mutation/AGENTS.md +2 -2
  17. package/package.json +1 -1
  18. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  19. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  20. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  21. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  22. package/plugins/dsh/README.md +1 -1
  23. package/plugins/dsh/package.json +1 -1
  24. package/plugins/dsh/scripts/prepack.cjs +2 -2
  25. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  26. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  27. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  28. package/plugins/dsh/src/skills.test.ts +20 -7
  29. package/plugins/opencode/package.json +1 -1
  30. package/plugins/opencode/scripts/prepack.cjs +0 -1
  31. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  32. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  33. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  34. package/plugins/qoder/plugin.json +1 -1
  35. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  36. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  37. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  38. package/principles/AGENTS.md +2 -2
  39. package/principles/rules/__tests__/clean-code/code-duplication.test.ts +9 -8
  40. package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +3 -4
  41. package/principles/rules/__tests__/clean-code/god-class.test.ts +2 -4
  42. package/principles/rules/__tests__/clean-code/large-file.test.ts +2 -4
  43. package/principles/rules/__tests__/clean-code/long-function.test.ts +2 -4
  44. package/principles/rules/__tests__/clean-code/many-exports.test.ts +2 -3
  45. package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +2 -4
  46. package/principles/rules/__tests__/clean-code/too-many-params.test.ts +2 -4
  47. package/principles/rules/__tests__/clean-code/unused-imports.test.ts +4 -4
  48. package/principles/rules/__tests__/solid/dip.test.ts +2 -4
  49. package/principles/rules/__tests__/solid/isp.test.ts +2 -4
  50. package/principles/rules/__tests__/solid/lsp.test.ts +2 -4
  51. package/principles/rules/__tests__/solid/ocp.test.ts +2 -4
  52. package/principles/rules/__tests__/solid/srp.test.ts +2 -4
  53. package/principles/rules/clean-code/code-duplication.ts +12 -14
  54. package/principles/rules/clean-code/deep-nesting.ts +18 -20
  55. package/principles/rules/clean-code/god-class.ts +32 -34
  56. package/principles/rules/clean-code/large-file.ts +14 -16
  57. package/principles/rules/clean-code/long-function.ts +26 -28
  58. package/principles/rules/clean-code/magic-numbers.ts +39 -38
  59. package/principles/rules/clean-code/many-exports.ts +21 -23
  60. package/principles/rules/clean-code/missing-error-handling.ts +13 -15
  61. package/principles/rules/clean-code/too-many-params.ts +15 -17
  62. package/principles/rules/clean-code/unused-imports.ts +13 -15
  63. package/principles/rules/solid/dip.ts +16 -18
  64. package/principles/rules/solid/isp.ts +17 -19
  65. package/principles/rules/solid/lsp.ts +4 -6
  66. package/principles/rules/solid/ocp.ts +17 -19
  67. package/principles/rules/solid/srp.ts +20 -22
  68. package/scripts/__tests__/sync-package-content.test.js +0 -1
  69. package/scripts/sync-package-content.js +0 -1
  70. package/skills/delphi-review/AGENTS.md +2 -2
  71. package/skills/sprint-flow/AGENTS.md +2 -2
  72. package/skills/test-specification-alignment/AGENTS.md +2 -2
  73. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +0 -48
  74. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  75. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  76. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  77. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  78. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  79. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +0 -161
  80. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  81. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  82. package/plugins/dsh/skills/clipboard-vision/SKILL.md +0 -48
  83. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  84. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  85. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  86. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  87. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  88. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +0 -161
  89. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  90. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  91. package/plugins/opencode/skills/clipboard-vision/SKILL.md +0 -48
  92. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  93. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  94. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  95. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  96. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  97. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +0 -161
  98. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  99. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  100. package/plugins/qoder/skills/clipboard-vision/SKILL.md +0 -48
  101. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  102. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  103. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  104. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  105. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  106. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +0 -161
  107. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  108. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  109. package/skills/clipboard-vision/SKILL.md +0 -48
  110. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  111. package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  112. package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  113. package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  114. package/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  115. package/skills/clipboard-vision/describe-clipboard.sh +0 -161
  116. package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  117. package/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
@@ -14,10 +14,20 @@ const REPO_SKILLS_DIR = resolve(PLUGIN_ROOT, "..", "..", "skills")
14
14
  /**
15
15
  * The expected set is the REPO's skill directories, discovered rather than
16
16
  * hardcoded. A hardcoded list silently drifts: it previously named 12 skills and
17
- * omitted clipboard-vision, while prepack shipped 12 of 13 (#448).
17
+ * omitted one that prepack shipped, breaking parity (#448).
18
18
  */
19
19
  function repoSkills(): string[] {
20
- return readdirSync(REPO_SKILLS_DIR, { withFileTypes: true })
20
+ // Guarded IO: a missing/unreadable repo skills dir is a test-environment
21
+ // failure we surface loudly, not a silent empty set (clean-code.missing-error-handling).
22
+ let entries;
23
+ try {
24
+ entries = readdirSync(REPO_SKILLS_DIR, { withFileTypes: true })
25
+ } catch (err) {
26
+ throw new Error(
27
+ `Cannot read repo skills dir ${REPO_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}`
28
+ )
29
+ }
30
+ return entries
21
31
  .filter((d) => d.isDirectory())
22
32
  .map((d) => d.name)
23
33
  .filter((name) => existsSync(join(REPO_SKILLS_DIR, name, "SKILL.md")))
@@ -25,7 +35,14 @@ function repoSkills(): string[] {
25
35
  }
26
36
 
27
37
  function readFrontmatter(file: string): Record<string, unknown> {
28
- const raw = readFileSync(file, "utf8")
38
+ // Guarded IO: re-throw with context so a broken SKILL.md names the file
39
+ // (clean-code.missing-error-handling).
40
+ let raw;
41
+ try {
42
+ raw = readFileSync(file, "utf8")
43
+ } catch (err) {
44
+ throw new Error(`${file}: cannot read file (${err instanceof Error ? err.message : String(err)})`)
45
+ }
29
46
  const m = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n/)
30
47
  if (!m) throw new Error(`${file}: missing YAML frontmatter`)
31
48
  return parse(m[1]) as Record<string, unknown>
@@ -58,8 +75,4 @@ describe("bundled skills", () => {
58
75
  expect((fm.description as string).trim().length, `${name}: non-empty description`).toBeGreaterThan(0)
59
76
  }
60
77
  })
61
-
62
- it("bundles clipboard-vision, which the old hardcoded list omitted (#448)", () => {
63
- expect(existsSync(join(SKILLS_DIR, "clipboard-vision", "SKILL.md"))).toBe(true)
64
- })
65
78
  })
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -16,7 +16,6 @@ const REPO_ROOT = path.resolve(PLUGIN_ROOT, '..', '..');
16
16
 
17
17
  const CORE_SKILLS = [
18
18
  'admin-template-guidelines',
19
- 'clipboard-vision',
20
19
  'delphi-review',
21
20
  'improve-codebase-architecture',
22
21
  'ralph-loop',
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
3
  **Generated:** 2026-10-16
4
- **Commit:** 98096abe
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.26.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
@@ -56,15 +56,16 @@ describe('code-duplication.ts - Code Duplication Rule', () => {
56
56
  expect(violations).toEqual([]);
57
57
  });
58
58
 
59
- it('should return empty array when adapter throws', () => {
60
- const mockAdapterThrowing = {
61
- ...mockAdapter,
62
- duplicationPercentage: () => { throw new Error('jscpd error'); }
63
- };
59
+ it('should propagate adapter errors (no empty catch, #392)', () => {
60
+ // #392-#4: the rule no longer swallows adapter errors in an empty catch, so
61
+ // a throwing adapter getter must bubble up to analyzer.runRuleOnFile().
62
+ const mockAdapterThrowing = Object.defineProperty(
63
+ { ...mockAdapter },
64
+ 'duplicationPercentage',
65
+ { get: () => { throw new Error('jscpd error'); } }
66
+ );
64
67
 
65
- const violations = codeDuplicationRule.check('test.ts', mockAdapterThrowing as never);
66
-
67
- expect(violations).toEqual([]);
68
+ expect(() => codeDuplicationRule.check('test.ts', mockAdapterThrowing as never)).toThrow('jscpd error');
68
69
  });
69
70
 
70
71
  it('should handle multiple files with duplication', () => {
@@ -61,14 +61,13 @@ function deeplyNested() {
61
61
  expect(deepNestingRule.severity).toBe('warning');
62
62
  });
63
63
 
64
- it('should return empty violations when adapter throws error', () => {
64
+ it('should propagate adapter errors (no empty catch, #392)', () => {
65
65
  const mockAdapterThatThrows = {
66
66
  ...mockAdapter,
67
67
  extractFunctions: () => { throw new Error('Adapter failed'); }
68
68
  };
69
69
 
70
- const violations = deepNestingRule.check('test.ts', mockAdapterThatThrows as never);
71
-
72
- expect(violations.length).toBe(0);
70
+ // Rule errors must bubble up to analyzer.runRuleOnFile(), not be swallowed.
71
+ expect(() => deepNestingRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
73
72
  });
74
73
  });
@@ -98,14 +98,12 @@ class DataClass {
98
98
  expect(godClassRule.severity).toBe('warning');
99
99
  });
100
100
 
101
- it('should return empty violations when adapter throws error', () => {
101
+ it('should propagate adapter errors (no empty catch, #392)', () => {
102
102
  const mockAdapterThatThrows = {
103
103
  ...mockAdapter,
104
104
  extractClasses: () => { throw new Error('Adapter failed'); }
105
105
  };
106
106
 
107
- const violations = godClassRule.check('test.ts', mockAdapterThatThrows as never);
108
-
109
- expect(violations.length).toBe(0);
107
+ expect(() => godClassRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
110
108
  });
111
109
  });
@@ -61,14 +61,12 @@ describe('largeFileRule', () => {
61
61
  expect(largeFileRule.threshold).toEqual(1150);
62
62
  });
63
63
 
64
- it('should return empty violations when adapter throws error', () => {
64
+ it('should propagate adapter errors (no empty catch, #392)', () => {
65
65
  const mockAdapterThatThrows = {
66
66
  ...mockAdapter,
67
67
  countLines: () => { throw new Error('Adapter failed'); }
68
68
  };
69
69
 
70
- const violations = largeFileRule.check('test.ts', mockAdapterThatThrows);
71
-
72
- expect(violations).toHaveLength(0);
70
+ expect(() => largeFileRule.check('test.ts', mockAdapterThatThrows)).toThrow('Adapter failed');
73
71
  });
74
72
  });
@@ -70,14 +70,12 @@ describe('longFunctionRule', () => {
70
70
  expect(longFunctionRule.threshold).toEqual(50);
71
71
  });
72
72
 
73
- it('should return empty violations when adapter throws error', () => {
73
+ it('should propagate adapter errors (no empty catch, #392)', () => {
74
74
  const mockAdapterThatThrows = {
75
75
  ...mockAdapter,
76
76
  extractFunctions: () => { throw new Error('Adapter failed'); }
77
77
  };
78
78
 
79
- const violations = longFunctionRule.check('test.ts', mockAdapterThatThrows as never);
80
-
81
- expect(violations).toHaveLength(0);
79
+ expect(() => longFunctionRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
82
80
  });
83
81
  });
@@ -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
  }