@boyingliu01/xp-gate 0.19.2 → 0.20.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 (223) hide show
  1. package/adapters/gate-4.sh +83 -18
  2. package/adapters/gate-8.sh +49 -5
  3. package/adapters/iac.sh +1 -1
  4. package/adapters/typescript.sh +15 -60
  5. package/bin/xp-gate.js +15 -16
  6. package/gate-10.sh +35 -2
  7. package/gate-4.sh +110 -21
  8. package/gate-8.sh +49 -5
  9. package/hooks/gate-10.sh +35 -2
  10. package/hooks/gate-4.sh +110 -21
  11. package/hooks/gate-8.sh +49 -5
  12. package/hooks/lib/jscpd-run.sh +63 -0
  13. package/hooks/lib/sprint-gate-report.sh +58 -0
  14. package/hooks/lib/test-failure.sh +152 -0
  15. package/hooks/lib/typecheck.sh +141 -0
  16. package/hooks/lib/validate-code-walkthrough.cjs +43 -5
  17. package/hooks/post-merge +100 -0
  18. package/hooks/pre-commit +401 -155
  19. package/hooks/pre-push +75 -19
  20. package/lib/__tests__/bootstrap.test.js +23 -41
  21. package/lib/__tests__/check-version.test.js +8 -1
  22. package/lib/__tests__/detect-deps.test.js +16 -0
  23. package/lib/__tests__/doctor-drift-round2.test.js +212 -0
  24. package/lib/__tests__/doctor-executed-surface.test.js +209 -0
  25. package/lib/__tests__/doctor-hook-drift.test.js +329 -0
  26. package/lib/__tests__/doctor-machine-mutation.test.js +116 -0
  27. package/lib/__tests__/doctor-module-drift.test.js +206 -0
  28. package/lib/__tests__/doctor-module-resolution-parity.test.js +314 -0
  29. package/lib/__tests__/doctor-path-equivalence.test.js +59 -0
  30. package/lib/__tests__/doctor-repair-boundary.test.js +52 -0
  31. package/lib/__tests__/doctor-tui-skill-freshness.test.js +105 -0
  32. package/lib/__tests__/doctor.test.js +155 -0
  33. package/lib/__tests__/gate-runner.test.js +19 -3
  34. package/lib/__tests__/helpers/lib-sandbox.cjs +55 -0
  35. package/lib/__tests__/init.test.js +44 -17
  36. package/lib/__tests__/install-skill-full-dir.test.js +151 -0
  37. package/lib/__tests__/install-skill.test.js +51 -132
  38. package/lib/__tests__/language-tools-detect.test.js +158 -0
  39. package/lib/__tests__/phase-transition.test.js +15 -1
  40. package/lib/__tests__/qoder-delphi-agents.test.js +285 -0
  41. package/lib/__tests__/shared-paths.test.js +20 -0
  42. package/lib/__tests__/shared-utils.test.js +69 -0
  43. package/lib/__tests__/skill-flags.test.js +48 -0
  44. package/lib/__tests__/ui-detector.test.ts +6 -2
  45. package/lib/__tests__/uninstall.test.js +20 -0
  46. package/lib/__tests__/update-hooks.test.js +12 -0
  47. package/lib/__tests__/update-skill.test.js +7 -4
  48. package/lib/detect-deps.js +3 -20
  49. package/lib/doctor-tui.js +22 -5
  50. package/lib/doctor.js +1178 -31
  51. package/lib/init.js +184 -33
  52. package/lib/install-cmd.js +7 -4
  53. package/lib/install-skill.js +38 -95
  54. package/lib/language-tools.js +83 -4
  55. package/lib/mattpocock-license-verdict.json +1 -1
  56. package/lib/shared-paths.js +5 -2
  57. package/lib/shared-utils.js +47 -3
  58. package/lib/uninstall.js +12 -0
  59. package/lib/update-hooks.js +4 -4
  60. package/lib/update-skill.js +6 -7
  61. package/mock-policy/AGENTS.md +4 -4
  62. package/mutation/AGENTS.md +4 -4
  63. package/mutation/__tests__/gate-m.test.ts +66 -14
  64. package/mutation/__tests__/mutmut-runner.test.ts +91 -42
  65. package/mutation/__tests__/test-filter-parity.test.ts +37 -0
  66. package/mutation/gate-m.ts +21 -8
  67. package/package.json +1 -1
  68. package/plugins/claude-code/.claude-plugin/plugin.json +2 -2
  69. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  70. package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
  71. package/plugins/claude-code/skills/delphi-review/SKILL.md +98 -14
  72. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +36 -3
  73. package/plugins/claude-code/skills/delphi-review/references/requirements.md +6 -6
  74. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
  75. package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +3 -3
  76. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  77. package/plugins/dsh/README.md +169 -4
  78. package/plugins/dsh/dsh-overlay.patch.yml +67 -0
  79. package/plugins/dsh/package.json +1 -1
  80. package/plugins/dsh/scripts/prepack.cjs +24 -19
  81. package/plugins/dsh/setup-dsh.ps1 +160 -0
  82. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  83. package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
  84. package/plugins/dsh/skills/delphi-review/SKILL.md +98 -14
  85. package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +36 -3
  86. package/plugins/dsh/skills/delphi-review/references/requirements.md +6 -6
  87. package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
  88. package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +3 -3
  89. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
  90. package/plugins/dsh/src/command.test.ts +66 -2
  91. package/plugins/dsh/src/command.ts +36 -14
  92. package/plugins/dsh/src/contract.test.ts +31 -1
  93. package/plugins/dsh/src/delphi-config.test.ts +190 -0
  94. package/plugins/dsh/src/delphi-config.ts +223 -0
  95. package/plugins/dsh/src/delphi-run.test.ts +181 -0
  96. package/plugins/dsh/src/delphi-run.ts +129 -0
  97. package/plugins/dsh/src/delphi.test.ts +222 -0
  98. package/plugins/dsh/src/delphi.ts +247 -0
  99. package/plugins/dsh/src/dialect.test.ts +59 -0
  100. package/plugins/dsh/src/dialect.ts +40 -0
  101. package/plugins/dsh/src/index.ts +70 -4
  102. package/plugins/dsh/src/skills.test.ts +26 -19
  103. package/plugins/opencode/package.json +1 -1
  104. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  105. package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
  106. package/plugins/opencode/skills/delphi-review/SKILL.md +98 -14
  107. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +36 -3
  108. package/plugins/opencode/skills/delphi-review/references/requirements.md +6 -6
  109. package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
  110. package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +3 -3
  111. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  112. package/plugins/qoder/agents/delphi-architecture.md +1 -1
  113. package/plugins/qoder/agents/delphi-feasibility.md +1 -1
  114. package/plugins/qoder/agents/delphi-technical.md +1 -1
  115. package/plugins/qoder/plugin.json +2 -2
  116. package/plugins/qoder/skills/admin-template-guidelines/SKILL.md +6 -797
  117. package/plugins/qoder/skills/admin-template-guidelines/references/rule-1.md +91 -0
  118. package/plugins/qoder/skills/admin-template-guidelines/references/rule-2.md +155 -0
  119. package/plugins/qoder/skills/admin-template-guidelines/references/rule-3.md +70 -0
  120. package/plugins/qoder/skills/admin-template-guidelines/references/rule-4.md +154 -0
  121. package/plugins/qoder/skills/admin-template-guidelines/references/rule-5.md +126 -0
  122. package/plugins/qoder/skills/admin-template-guidelines/references/rule-6.md +180 -0
  123. package/plugins/qoder/skills/batch-grill-me/SKILL.md +15 -0
  124. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  125. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  126. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +161 -0
  127. package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +61 -28
  128. package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
  129. package/plugins/qoder/skills/delphi-review/INSTALL.md +97 -98
  130. package/plugins/qoder/skills/delphi-review/SKILL.md +170 -63
  131. package/plugins/qoder/skills/delphi-review/hooks.md +19 -0
  132. package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +6 -10
  133. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +36 -3
  134. package/plugins/qoder/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
  135. package/plugins/qoder/skills/delphi-review/references/requirements.md +402 -0
  136. package/plugins/qoder/skills/delphi-review/references/round-templates.md +134 -0
  137. package/plugins/qoder/skills/delphi-review/scripts/verify-consensus.sh +43 -0
  138. package/plugins/qoder/skills/delphi-review/tools.md +29 -0
  139. package/plugins/qoder/skills/domain-modeling/ADR-FORMAT.md +64 -0
  140. package/plugins/qoder/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  141. package/plugins/qoder/skills/domain-modeling/SKILL.md +74 -0
  142. package/plugins/qoder/skills/grill-with-docs/SKILL.md +7 -0
  143. package/plugins/qoder/skills/grilling/SKILL.md +12 -0
  144. package/plugins/qoder/skills/ralph-loop/SKILL.md +87 -428
  145. package/plugins/qoder/skills/ralph-loop/references/components.md +113 -0
  146. package/plugins/qoder/skills/ralph-loop/references/workflow-details.md +99 -0
  147. package/plugins/qoder/skills/sprint-flow/AGENTS.md +5 -5
  148. package/plugins/qoder/skills/sprint-flow/SKILL.md +255 -454
  149. package/plugins/qoder/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
  150. package/plugins/qoder/skills/sprint-flow/hooks.md +18 -0
  151. package/plugins/qoder/skills/sprint-flow/references/components/memory.md +1 -1
  152. package/plugins/qoder/skills/sprint-flow/references/components/middleware.md +1 -1
  153. package/plugins/qoder/skills/sprint-flow/references/components/skill-invocations.md +21 -20
  154. package/plugins/qoder/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
  155. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +0 -1
  156. package/plugins/qoder/skills/sprint-flow/references/orchestration-rules.md +34 -27
  157. package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
  158. package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +160 -123
  159. package/plugins/qoder/skills/sprint-flow/references/phase-3-build.md +203 -2
  160. package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +107 -34
  161. package/plugins/qoder/skills/sprint-flow/references/phase-5-ship.md +194 -12
  162. package/plugins/qoder/skills/sprint-flow/references/phase-6-close.md +139 -7
  163. package/plugins/qoder/skills/sprint-flow/references/phase-overview.md +486 -0
  164. package/plugins/qoder/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
  165. package/plugins/qoder/skills/sprint-flow/tools.md +29 -0
  166. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  167. package/plugins/qoder/skills/test-specification-alignment/SKILL.md +104 -601
  168. package/plugins/qoder/skills/to-issues/SKILL.md +1 -1
  169. package/principles/AGENTS.md +9 -8
  170. package/principles/__tests__/adapter-metrics.test.ts +292 -0
  171. package/principles/__tests__/analyzer.test.ts +29 -6
  172. package/principles/__tests__/baseline-prune-timeout.test.ts +71 -0
  173. package/principles/__tests__/baseline-prune.test.ts +338 -0
  174. package/principles/__tests__/boy-scout-init-baseline.test.ts +167 -0
  175. package/principles/__tests__/boy-scout-integration.test.ts +97 -6
  176. package/principles/__tests__/boy-scout.test.ts +253 -7
  177. package/principles/__tests__/config-enforcement.test.ts +469 -0
  178. package/principles/__tests__/config-git-timeout.test.ts +66 -0
  179. package/principles/__tests__/direct-execution.test.ts +187 -0
  180. package/principles/__tests__/index.test.ts +16 -3
  181. package/principles/__tests__/warnings-baseline-budgets.test.ts +74 -0
  182. package/principles/adapters/typescript.ts +613 -13
  183. package/principles/analyzer.ts +22 -0
  184. package/principles/baseline-prune.ts +217 -0
  185. package/principles/boy-scout.ts +481 -51
  186. package/principles/config.ts +362 -54
  187. package/principles/direct-execution.ts +60 -0
  188. package/principles/index.ts +40 -17
  189. package/principles/rules/__tests__/clean-code/many-exports.test.ts +16 -10
  190. package/principles/rules/clean-code/code-duplication.ts +8 -8
  191. package/principles/rules/clean-code/deep-nesting.ts +8 -8
  192. package/principles/rules/clean-code/god-class.ts +8 -8
  193. package/principles/rules/clean-code/large-file.ts +15 -11
  194. package/principles/rules/clean-code/long-function.ts +8 -8
  195. package/principles/rules/clean-code/magic-numbers.ts +10 -7
  196. package/principles/rules/clean-code/many-exports.ts +7 -7
  197. package/principles/rules/clean-code/missing-error-handling.ts +5 -5
  198. package/principles/rules/clean-code/too-many-params.ts +8 -8
  199. package/principles/rules/clean-code/unused-imports.ts +5 -5
  200. package/principles/rules/solid/dip.ts +18 -8
  201. package/principles/rules/solid/isp.ts +9 -8
  202. package/principles/rules/solid/lsp.ts +6 -6
  203. package/principles/rules/solid/ocp.ts +6 -5
  204. package/principles/rules/solid/srp.ts +7 -8
  205. package/principles/types.ts +15 -0
  206. package/scripts/__tests__/sync-package-content.test.js +28 -0
  207. package/scripts/delphi-external-review.cjs +193 -10
  208. package/scripts/sync-package-content.js +7 -4
  209. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
  210. package/skills/delphi-review/AGENTS.md +4 -4
  211. package/skills/delphi-review/SKILL.md +98 -14
  212. package/skills/delphi-review/references/code-walkthrough.md +36 -3
  213. package/skills/delphi-review/references/requirements.md +6 -6
  214. package/skills/sprint-flow/AGENTS.md +4 -4
  215. package/skills/sprint-flow/references/phase-2-design.md +3 -3
  216. package/skills/test-specification-alignment/AGENTS.md +4 -4
  217. package/lib/__tests__/download-skill.test.js +0 -281
  218. package/lib/download-skill.js +0 -96
  219. package/plugins/qoder/skills/ralph-loop/references/components/memory.md +0 -55
  220. package/plugins/qoder/skills/ralph-loop/references/components/middleware.md +0 -54
  221. package/plugins/qoder/skills/ralph-loop/references/components/skill-invocations.md +0 -39
  222. package/plugins/qoder/skills/ralph-loop/references/components/system-prompt.md +0 -24
  223. package/plugins/qoder/skills/ralph-loop/references/components/tool-descriptions.md +0 -32
@@ -242,7 +242,7 @@ Phase 1: PLAN → autoplan → delphi-review → specification.yaml
242
242
  → **/to-issues** → slices-manifest.json + Issue 列表
243
243
  Phase 2: BUILD → 按 execution_order 逐个切片执行
244
244
  → 每个切片 = 一个 REQ(ralph-loop 模式)
245
- → 或通过 dispatching-parallel-agents 并行(无依赖的 AFK 切片)
245
+ → 或通过原生 Task 并行派发(无依赖的 AFK 切片)
246
246
  Phase 3: REVIEW → **整体 review**(全部切片完成后统一 code-walkthrough)
247
247
  → browse / QA 验证端到端行为
248
248
  Phase 4: USER ACC → 人工验收(全部切片集成后的完整功能)
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
- **Generated:** 2026-09-22
4
- **Commit:** 3d1d1bf
5
- **Branch:** main
6
- **Version:** 0.19.2.0
3
+ **Generated:** 2026-10-07
4
+ **Commit:** ff55f35b
5
+ **Branch:** fix/batch-defects-20261007
6
+ **Version:** 0.20.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.
@@ -52,7 +52,8 @@ src/principles/
52
52
  | Output | reporter.ts | Emits Console, JSON, or SARIF 2.1.0 |
53
53
  | Thresholds | config.ts + .principlesrc | Defaults + project overrides |
54
54
  | Boy Scout enforcement | boy-scout.ts | classifyFiles → calculateDelta → enforceBoyScoutRule |
55
- | Baseline lifecycle | baseline.ts | loadBaseline, saveBaseline, initBaseline |
55
+ | Baseline lifecycle | boy-scout.ts | loadBaseline, saveBaseline, initBaseline |
56
+ | Baseline storage | baseline.ts | BaselineStorage (size/timeout/batch guards) + filterBaselineWarnings |
56
57
  | Adapter for new language | adapters/base.ts + add `<lang>.ts` | Subclass base; export via index |
57
58
  | C / C++ extraction | adapters/cpp.ts | Regex-based; handles both `.cpp` and `.c` |
58
59
  | Tests | __tests__/ | Adapter, rule, and core tests |
@@ -67,9 +68,9 @@ src/principles/
67
68
  | classifyFiles | Function | boy-scout.ts | new / modified / untouched bucket assignment |
68
69
  | calculateDelta | Function | boy-scout.ts | Diff vs `.warnings-baseline.json` |
69
70
  | enforceBoyScoutRule | Function | boy-scout.ts | Block when modified-file warnings increase |
70
- | loadBaseline | Function | baseline.ts | Read `.warnings-baseline.json` |
71
- | saveBaseline | Function | baseline.ts | Write updated baseline after successful commit |
72
- | initBaseline | Function | baseline.ts | First-time auto-snapshot (≤5 warnings ⇒ must clear to zero) |
71
+ | loadBaseline | Function | boy-scout.ts | Read `.warnings-baseline.json` |
72
+ | saveBaseline | Function | boy-scout.ts | Write updated baseline after successful commit |
73
+ | initBaseline | Function | boy-scout.ts | First-time auto-snapshot (≤5 warnings ⇒ must clear to zero) |
73
74
 
74
75
  ## CONVENTIONS
75
76
  - **Rule ID format**: `clean-code.long-function`, `solid.srp`. Stable, dot-namespaced.
@@ -0,0 +1,292 @@
1
+ /**
2
+ * @test REQ-DSH-015
3
+ * @intent 验证 TypeScriptAdapter 为各规则提供其所需度量字段:fix #446 之前,
4
+ * createCodeMatch() 只返回 {name,type,line,code},而规则读取
5
+ * length / paramCount / nestingDepth / methodCount / hasTryCatch,
6
+ * 导致 14 条规则里 12 条在 TypeScript 上结构性无法触发(Gate 4 形同虚设)
7
+ * @covers AC-DSH-015-01
8
+ *
9
+ * Mock-first per src/principles/AGENTS.md: inline fixture, no separate files.
10
+ */
11
+
12
+ import { describe, it, expect, vi, type Mock } from 'vitest';
13
+ import { TypeScriptAdapter } from '../adapters/typescript';
14
+
15
+ vi.mock('fs', () => ({
16
+ readFileSync: vi.fn(),
17
+ existsSync: vi.fn(() => true),
18
+ }));
19
+
20
+ import { readFileSync } from 'fs';
21
+
22
+ const LONG_BODY = Array.from({ length: 60 }, (_, i) => ` const v${i} = ${i};`).join('\n');
23
+
24
+ const SAMPLE = `
25
+ import { helper } from './helper';
26
+
27
+ export function eightParams(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) {
28
+ return a + b + c + d + e + f + g + h;
29
+ }
30
+
31
+ export function longOne() {
32
+ ${LONG_BODY}
33
+ return 0;
34
+ }
35
+
36
+ export function deeplyNested(a: number) {
37
+ if (a > 0) {
38
+ if (a > 1) {
39
+ if (a > 2) {
40
+ if (a > 3) {
41
+ if (a > 4) {
42
+ return 'deep';
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return 'shallow';
49
+ }
50
+
51
+ export function rawIo() {
52
+ return readFileSync('/tmp/x', 'utf-8');
53
+ }
54
+
55
+ export function guardedIo() {
56
+ try {
57
+ return readFileSync('/tmp/x', 'utf-8');
58
+ } catch (e) {
59
+ return '';
60
+ }
61
+ }
62
+
63
+ export const arrowFn = (x: number, y: number) => x + y;
64
+
65
+ export class MegaService {
66
+ aMethod() { return 1; }
67
+ m1() { return 1; } m2() { return 1; } m3() { return 1; } m4() { return 1; }
68
+ m5() { return 1; } m6() { return 1; } m7() { return 1; } m8() { return 1; }
69
+ m9() { return 1; } m10() { return 1; } m11() { return 1; } m12() { return 1; }
70
+ m13() { return 1; } m14() { return 1; } m15() { return 1; }
71
+ }
72
+ `;
73
+
74
+ interface FnInfo {
75
+ name: string;
76
+ startLine?: number;
77
+ line?: number;
78
+ length?: number;
79
+ paramCount?: number;
80
+ nestingDepth?: number;
81
+ hasTryCatch?: boolean;
82
+ ioOperations?: string[];
83
+ }
84
+
85
+ interface ClsInfo {
86
+ name: string;
87
+ methodCount?: number;
88
+ line?: number;
89
+ }
90
+
91
+ function adapterFor(content = SAMPLE): TypeScriptAdapter {
92
+ (readFileSync as Mock).mockReturnValue(content);
93
+ return new TypeScriptAdapter('sample.ts');
94
+ }
95
+
96
+ describe('TypeScriptAdapter metric extraction (#446)', () => {
97
+ describe('extractFunctions', () => {
98
+ it('reports paramCount for every function', () => {
99
+ const fns = adapterFor().extractFunctions() as FnInfo[];
100
+ const eight = fns.find(f => f.name === 'eightParams');
101
+ expect(eight).toBeDefined();
102
+ expect(eight?.paramCount).toBe(8);
103
+ });
104
+
105
+ it('reports length (body span) for every function', () => {
106
+ const fns = adapterFor().extractFunctions() as FnInfo[];
107
+ const long = fns.find(f => f.name === 'longOne');
108
+ expect(long).toBeDefined();
109
+ // > 50 lines is the long-function threshold; assert it is measured at all.
110
+ expect(long?.length).toBeGreaterThan(50);
111
+ });
112
+
113
+ it('reports nestingDepth for every function', () => {
114
+ const fns = adapterFor().extractFunctions() as FnInfo[];
115
+ const deep = fns.find(f => f.name === 'deeplyNested');
116
+ expect(deep).toBeDefined();
117
+ expect(deep?.nestingDepth).toBeGreaterThanOrEqual(5);
118
+ });
119
+
120
+ it('reports hasTryCatch for unguarded and guarded IO', () => {
121
+ const fns = adapterFor().extractFunctions() as FnInfo[];
122
+ const io = fns.find(f => f.name === 'rawIo');
123
+ expect(io).toBeDefined();
124
+ expect(io?.hasTryCatch).toBe(false);
125
+
126
+ const guarded = fns.find(f => f.name === 'guardedIo');
127
+ expect(guarded).toBeDefined();
128
+ expect(guarded?.hasTryCatch).toBe(true);
129
+ });
130
+
131
+ it('reports ioOperations for IO-performing functions', () => {
132
+ const fns = adapterFor().extractFunctions() as FnInfo[];
133
+ const io = fns.find(f => f.name === 'rawIo');
134
+ expect(io?.ioOperations?.length ?? 0).toBeGreaterThan(0);
135
+ });
136
+
137
+ it('still reports name / line for every function', () => {
138
+ const fns = adapterFor().extractFunctions() as FnInfo[];
139
+ expect(fns.length).toBeGreaterThan(0);
140
+ for (const f of fns) {
141
+ expect(f.name).toBeTruthy();
142
+ expect(f.line).toBeGreaterThan(0);
143
+ }
144
+ });
145
+
146
+ it('finds arrow functions and class methods, not only `function` declarations', () => {
147
+ const fns = adapterFor().extractFunctions() as FnInfo[];
148
+ const names = fns.map(f => f.name);
149
+ // Arrow consts and class methods are the dominant modern TS forms; the old
150
+ // regex only matched the literal `function` keyword.
151
+ expect(names).toContain('arrowFn');
152
+ expect(names).toContain('aMethod');
153
+ });
154
+ });
155
+
156
+ describe('extractClasses', () => {
157
+ it('reports methodCount per class', () => {
158
+ const classes = adapterFor().extractClasses() as ClsInfo[];
159
+ const mega = classes.find(c => c.name === 'MegaService');
160
+ expect(mega).toBeDefined();
161
+ expect(mega?.methodCount).toBeGreaterThanOrEqual(15);
162
+ });
163
+ });
164
+
165
+ describe('imports', () => {
166
+ interface ImportInfo {
167
+ name?: string;
168
+ line?: number;
169
+ used?: boolean;
170
+ type?: string;
171
+ }
172
+
173
+ it('returns objects, not bare strings (the rule reads .name/.used)', () => {
174
+ const imports = (adapterFor() as unknown as { imports: ImportInfo[] }).imports;
175
+ expect(Array.isArray(imports)).toBe(true);
176
+ expect(imports.length).toBeGreaterThan(0);
177
+ for (const imp of imports) {
178
+ expect(typeof imp).toBe('object');
179
+ // Never leak `undefined` as a name -- it renders straight into the message.
180
+ expect(imp.name).toBeTruthy();
181
+ expect(imp.name).not.toBe('undefined');
182
+ expect(imp.line).toBeGreaterThan(0);
183
+ }
184
+ });
185
+
186
+ it('marks a genuinely unused import as unused', () => {
187
+ const imports = (adapterFor() as unknown as { imports: ImportInfo[] }).imports;
188
+ const helper = imports.find(i => i.name === 'helper');
189
+ expect(helper).toBeDefined();
190
+ // `helper` is imported but never referenced in SAMPLE.
191
+ expect(helper?.used).toBe(false);
192
+ });
193
+
194
+ it('marks a referenced import as used', () => {
195
+ const content = `
196
+ import { readFileSync } from 'fs';
197
+ export function go() { return readFileSync('/tmp/x', 'utf-8'); }
198
+ `;
199
+ const imports = (adapterFor(content) as unknown as { imports: ImportInfo[] }).imports;
200
+ const rfs = imports.find(i => i.name === 'readFileSync');
201
+ expect(rfs).toBeDefined();
202
+ expect(rfs?.used).toBe(true);
203
+ });
204
+ });
205
+
206
+ describe('extract (magic numbers, #446)', () => {
207
+ it('returns {value,line} objects and ignores comments/strings', () => {
208
+ const content = `
209
+ // 999 should not count (comment)
210
+ const s = "1234 should not count (string)";
211
+ export function f() { return 12345; }
212
+ `;
213
+ const nums = (adapterFor(content) as unknown as {
214
+ extract: () => Array<{ value: number; line: number }>;
215
+ }).extract();
216
+ const values = nums.map(n => n.value);
217
+ expect(values).toContain(12345);
218
+ expect(values).not.toContain(999);
219
+ expect(values).not.toContain(1234);
220
+ for (const n of nums) expect(n.line).toBeGreaterThan(0);
221
+ });
222
+
223
+ /**
224
+ * @test REQ-DSH-018
225
+ * @intent 验证「命名常量的初始化值」不被当作 magic number:规则的修复建议
226
+ * 就是「改用命名常量」,若常量本身仍被标记,该违规永远无法消除
227
+ * @covers AC-DSH-018-01
228
+ */
229
+ it('skips the initializer of a named constant but still reports bare literals', () => {
230
+ // Fixtures are bare fragments: no `export`, so this file does not itself
231
+ // trip the many-exports rule that the adapter under test is meant to flag.
232
+ const content = `
233
+ const RENAME_PARTS = 3;
234
+ const BUDGET: number = 5;
235
+ function f(): number {
236
+ return 137;
237
+ }
238
+ `;
239
+ const nums = (adapterFor(content) as unknown as {
240
+ extract: () => Array<{ value: number; line: number }>;
241
+ }).extract();
242
+ const values = nums.map(n => n.value);
243
+
244
+ // The rule asks for named constants, so the constants must not be flagged.
245
+ expect(values).not.toContain(3);
246
+ expect(values).not.toContain(5);
247
+ // A bare literal in a body is the actual target and must still be reported.
248
+ expect(values).toContain(137);
249
+ });
250
+
251
+ it('still reports a number assigned to a non-constant binding', () => {
252
+ // Only `const`/`readonly` declarations are exempt; `let x = 99` is not.
253
+ const content = `
254
+ function f(): number {
255
+ let x = 99;
256
+ return x;
257
+ }
258
+ `;
259
+ const nums = (adapterFor(content) as unknown as {
260
+ extract: () => Array<{ value: number; line: number }>;
261
+ }).extract();
262
+ expect(nums.map(n => n.value)).toContain(99);
263
+ });
264
+ });
265
+
266
+ describe('extractInterfaces (ISP, #446)', () => {
267
+ it('reports interface names and methodCount', () => {
268
+ const content = `
269
+ export interface Worker {
270
+ run(): void;
271
+ stop(): void;
272
+ }
273
+ `;
274
+ const ifaces = (adapterFor(content) as unknown as {
275
+ extractInterfaces: () => Array<{ name: string; methodCount: number }>;
276
+ }).extractInterfaces();
277
+ const worker = ifaces.find(i => i.name === 'Worker');
278
+ expect(worker).toBeDefined();
279
+ expect(worker?.methodCount).toBe(2);
280
+ });
281
+ });
282
+
283
+ describe('no control-flow false positives', () => {
284
+ it('does not report `if` / `for` / `while` as functions', () => {
285
+ const fns = adapterFor().extractFunctions() as FnInfo[];
286
+ const names = fns.map(f => f.name);
287
+ for (const kw of ['if', 'for', 'while', 'switch', 'catch', 'return']) {
288
+ expect(names).not.toContain(kw);
289
+ }
290
+ });
291
+ });
292
+ });
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { describe, it, expect, beforeEach, vi } from 'vitest';
8
8
  import { analyze } from '../analyzer';
9
+ import { getDefaultConfig, resetActiveConfig, setActiveConfig } from '../config';
9
10
  import { Rule, Adapter, Severity } from '../types';
10
11
 
11
12
  describe('analyzer.ts - Rule Orchestration Engine', () => {
@@ -114,6 +115,26 @@ describe('analyzer.ts - Rule Orchestration Engine', () => {
114
115
  expect(disabledRule.check).not.toHaveBeenCalled();
115
116
  });
116
117
 
118
+ it('a rule disabled by config must not report files as checked (#457)', async () => {
119
+ // `filesChecked` used to be incremented before the `enabled` check, so a rule
120
+ // that never ran still claimed it had examined the file -- inflating per-rule
121
+ // coverage statistics for work the rule did not do.
122
+ const disabled = getDefaultConfig();
123
+ disabled.rules['clean-code']['long-function'].enabled = false;
124
+ try {
125
+ setActiveConfig(disabled);
126
+ const result = await analyze(['test.ts'], [mockRule], mockAdapter, {});
127
+ expect(mockRule.check).not.toHaveBeenCalled();
128
+ expect(result.ruleResults['clean-code.long-function'].filesChecked).toBe(0);
129
+ } finally {
130
+ // Restore the process-global config so later tests are unaffected.
131
+ // `resetActiveConfig()` rather than re-installing the captured config:
132
+ // replacing an already-installed config is exactly what the #457
133
+ // guardrail reports, and this suite never installs a non-default one.
134
+ resetActiveConfig();
135
+ }
136
+ });
137
+
117
138
  it('should return empty violations if all rules pass', async () => {
118
139
  const passingRule: Rule = {
119
140
  id: 'clean-code.passing',
@@ -228,6 +249,13 @@ it('should skip files that do not match adapter language', async () => {
228
249
  });
229
250
 
230
251
  describe('runRuleOnFile (via analyze) — verifies context-object parameter bundling', () => {
252
+ // Extracted from the test body: inlining this callback pushed the test past the
253
+ // 4-level nesting limit for no readability gain.
254
+ const ruleACheck = vi.fn().mockImplementation((file: string) => {
255
+ if (file !== 'a.ts') return [];
256
+ return [{ file: 'a.ts', line: 1, ruleId: 'test.rule-a', message: 'violation in a', severity: 'warning' as Severity }];
257
+ });
258
+
231
259
  it('should correctly aggregate violations per file and per rule', async () => {
232
260
  const files = ['a.ts', 'b.ts'];
233
261
  const ruleA = {
@@ -235,12 +263,7 @@ it('should skip files that do not match adapter language', async () => {
235
263
  name: 'Rule A',
236
264
  threshold: 10,
237
265
  severity: 'warning' as Severity,
238
- check: vi.fn().mockImplementation((file: string) => {
239
- if (file === 'a.ts') {
240
- return [{ file: 'a.ts', line: 1, ruleId: 'test.rule-a', message: 'violation in a', severity: 'warning' as Severity }];
241
- }
242
- return [];
243
- })
266
+ check: ruleACheck
244
267
  } as Rule;
245
268
  const ruleB = {
246
269
  id: 'test.rule-b',
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @test REQ-452 REQ-3
3
+ * @intent The baseline pruner runs inside a git hook, so its worst failure mode is
4
+ * not a wrong answer but a commit that never returns. Every git call it
5
+ * makes must carry a timeout, and the timeout must be short enough that a
6
+ * wedged git degrades to "keep the entry" rather than hanging the hook.
7
+ * @covers AC-452-14
8
+ *
9
+ * Mocked rather than stubbed with a fake binary: `execFileSync` resolves PATH
10
+ * entries without consulting PATHEXT unless `shell` is set, so a `git.cmd` shim on
11
+ * PATH is silently ignored in favour of the real binary -- a test built that way
12
+ * passes while proving nothing. Asserting the options handed to child_process
13
+ * tests the guard itself instead of Node's PATH resolution.
14
+ */
15
+
16
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
17
+
18
+ const execFileSyncMock = vi.fn();
19
+
20
+ vi.mock('child_process', () => ({
21
+ execFileSync: (...args: unknown[]) => execFileSyncMock(...args),
22
+ }));
23
+ vi.mock('fs', async () => {
24
+ const actual = await vi.importActual<typeof import('fs')>('fs');
25
+ return { ...actual, existsSync: vi.fn(() => false) };
26
+ });
27
+
28
+ import { pruneBaselineEntries } from '../baseline-prune';
29
+
30
+ /** Generous for a healthy `git ls-files`, short enough not to wedge a hook. */
31
+ const MAX_ACCEPTABLE_GIT_TIMEOUT_MS = 60_000;
32
+
33
+ describe('baseline pruning git invocation (#452)', () => {
34
+ beforeEach(() => {
35
+ execFileSyncMock.mockReset();
36
+ });
37
+
38
+ it('AC-452-14: bounds every git call with a timeout', () => {
39
+ execFileSyncMock.mockReturnValue('tracked.ts\n');
40
+
41
+ pruneBaselineEntries(JSON.stringify({ 'gone.ts': { 'clean-code.large-file': 1 } }), '/tmp/project');
42
+
43
+ expect(execFileSyncMock).toHaveBeenCalled();
44
+ for (const call of execFileSyncMock.mock.calls) {
45
+ const options = call[2] as { timeout?: number };
46
+ expect(typeof options.timeout).toBe('number');
47
+ expect(options.timeout).toBeGreaterThan(0);
48
+ expect(options.timeout).toBeLessThanOrEqual(MAX_ACCEPTABLE_GIT_TIMEOUT_MS);
49
+ }
50
+ });
51
+
52
+ it('AC-452-14: a git timeout keeps entries instead of failing the hook', () => {
53
+ // execFileSync reports a killed process as an error with no stdout, which is
54
+ // how a wedged git surfaces here.
55
+ execFileSyncMock.mockImplementation(() => {
56
+ const error = new Error('spawnSync git ETIMEDOUT') as Error & { signal?: string };
57
+ error.signal = 'SIGTERM';
58
+ throw error;
59
+ });
60
+
61
+ const result = pruneBaselineEntries(
62
+ JSON.stringify({ 'gone.ts': { 'clean-code.large-file': 1 } }),
63
+ '/tmp/project',
64
+ );
65
+
66
+ // Fail closed: an unreadable index must never justify deleting an entry.
67
+ expect(result.removed).toEqual([]);
68
+ expect(result.kept).toEqual(['gone.ts']);
69
+ expect(result.error).toBeDefined();
70
+ });
71
+ });