@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.
- package/bin/xp-gate.js +161 -28
- package/hooks/pre-commit +27 -10
- package/lib/__tests__/check-alignment-cli.test.mjs +77 -0
- package/lib/__tests__/doctor-469-hook-env.test.js +12 -7
- package/lib/__tests__/doctor-module-resolution-parity.test.js +15 -6
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +16 -4
- package/lib/__tests__/test-alignment.test.ts +37 -0
- package/lib/__tests__/windows-bash-probe.test.js +73 -0
- package/lib/check-alignment-runner.cjs +74 -0
- package/lib/detect-deps.js +82 -19
- package/lib/gate-runner.js +12 -5
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/sprint-status.js +5 -2
- package/lib/test-alignment.ts +136 -12
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/README.md +1 -1
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/prepack.cjs +2 -2
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/src/skills.test.ts +20 -7
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/scripts/prepack.cjs +0 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/principles/rules/__tests__/clean-code/code-duplication.test.ts +9 -8
- package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +3 -4
- package/principles/rules/__tests__/clean-code/god-class.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/large-file.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/long-function.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +2 -3
- package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/too-many-params.test.ts +2 -4
- package/principles/rules/__tests__/clean-code/unused-imports.test.ts +4 -4
- package/principles/rules/__tests__/solid/dip.test.ts +2 -4
- package/principles/rules/__tests__/solid/isp.test.ts +2 -4
- package/principles/rules/__tests__/solid/lsp.test.ts +2 -4
- package/principles/rules/__tests__/solid/ocp.test.ts +2 -4
- package/principles/rules/__tests__/solid/srp.test.ts +2 -4
- package/principles/rules/clean-code/code-duplication.ts +12 -14
- package/principles/rules/clean-code/deep-nesting.ts +18 -20
- package/principles/rules/clean-code/god-class.ts +32 -34
- package/principles/rules/clean-code/large-file.ts +14 -16
- package/principles/rules/clean-code/long-function.ts +26 -28
- package/principles/rules/clean-code/magic-numbers.ts +39 -38
- package/principles/rules/clean-code/many-exports.ts +21 -23
- package/principles/rules/clean-code/missing-error-handling.ts +13 -15
- package/principles/rules/clean-code/too-many-params.ts +15 -17
- package/principles/rules/clean-code/unused-imports.ts +13 -15
- package/principles/rules/solid/dip.ts +16 -18
- package/principles/rules/solid/isp.ts +17 -19
- package/principles/rules/solid/lsp.ts +4 -6
- package/principles/rules/solid/ocp.ts +17 -19
- package/principles/rules/solid/srp.ts +20 -22
- package/scripts/__tests__/sync-package-content.test.js +0 -1
- package/scripts/sync-package-content.js +0 -1
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/claude-code/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/plugins/dsh/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/plugins/opencode/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/plugins/qoder/skills/clipboard-vision/SKILL.md +0 -48
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
- package/skills/clipboard-vision/SKILL.md +0 -48
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
- package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
- package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
- package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
- package/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
- package/skills/clipboard-vision/describe-clipboard.sh +0 -161
- package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
- 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
|
|
17
|
+
* omitted one that prepack shipped, breaking parity (#448).
|
|
18
18
|
*/
|
|
19
19
|
function repoSkills(): string[] {
|
|
20
|
-
|
|
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
|
-
|
|
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,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-16
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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.
|
|
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:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-16
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 429f73c5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
}
|