code-auditor-mcp 3.4.11 → 3.4.13

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 (254) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +119 -1
  3. package/dist/analyzers/__verify_self_probe.d.ts +2 -0
  4. package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
  5. package/dist/analyzers/__verify_self_probe.js +5 -0
  6. package/dist/analyzers/__verify_self_probe.js.map +1 -0
  7. package/dist/analyzers/analyzerUtils.d.ts +34 -1
  8. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  9. package/dist/analyzers/analyzerUtils.js +61 -22
  10. package/dist/analyzers/analyzerUtils.js.map +1 -1
  11. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
  12. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  13. package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
  14. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  15. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
  16. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  17. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
  18. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  19. package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
  20. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  21. package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
  22. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  23. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
  24. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  25. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
  26. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  27. package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
  28. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  29. package/dist/analyzers/documentationAnalyzer.js +259 -198
  30. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  31. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
  32. package/dist/analyzers/invariantsAnalyzer.js +59 -46
  33. package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
  34. package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
  35. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
  36. package/dist/analyzers/orm/adapterRegistry.js +10 -0
  37. package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
  38. package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
  39. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
  40. package/dist/analyzers/orm/drizzleAdapter.js +42 -44
  41. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
  42. package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
  43. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
  44. package/dist/analyzers/orm/prismaAdapter.js +30 -0
  45. package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
  46. package/dist/analyzers/provenance.d.ts +72 -2
  47. package/dist/analyzers/provenance.d.ts.map +1 -1
  48. package/dist/analyzers/provenance.js +462 -338
  49. package/dist/analyzers/provenance.js.map +1 -1
  50. package/dist/analyzers/reactAnalyzer.d.ts +11 -0
  51. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  52. package/dist/analyzers/reactAnalyzer.js +188 -125
  53. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  54. package/dist/analyzers/ruleRegistry.d.ts +118 -1
  55. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  56. package/dist/analyzers/ruleRegistry.js +1717 -104
  57. package/dist/analyzers/ruleRegistry.js.map +1 -1
  58. package/dist/analyzers/ruleTiming.d.ts +54 -0
  59. package/dist/analyzers/ruleTiming.d.ts.map +1 -0
  60. package/dist/analyzers/ruleTiming.js +71 -0
  61. package/dist/analyzers/ruleTiming.js.map +1 -0
  62. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
  63. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  64. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
  65. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  66. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
  67. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  68. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
  69. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  70. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +24 -186
  71. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  72. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1081 -1061
  73. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  74. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
  75. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  76. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
  77. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  78. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
  79. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  80. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
  81. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  82. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -212
  83. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  84. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1518
  85. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  86. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
  87. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  88. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -472
  89. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  90. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
  91. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
  92. package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
  93. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
  94. package/dist/analyzers/universal/schema/config.d.ts +31 -0
  95. package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
  96. package/dist/analyzers/universal/schema/config.js +57 -0
  97. package/dist/analyzers/universal/schema/config.js.map +1 -0
  98. package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
  99. package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
  100. package/dist/analyzers/universal/schema/discovery.js +616 -0
  101. package/dist/analyzers/universal/schema/discovery.js.map +1 -0
  102. package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
  103. package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
  104. package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
  105. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
  106. package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
  107. package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
  108. package/dist/analyzers/universal/schema/migrations.js +238 -0
  109. package/dist/analyzers/universal/schema/migrations.js.map +1 -0
  110. package/dist/analyzers/universal/schema/types.d.ts +131 -0
  111. package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
  112. package/dist/analyzers/universal/schema/types.js +10 -0
  113. package/dist/analyzers/universal/schema/types.js.map +1 -0
  114. package/dist/analyzers/universal/schema/violations.d.ts +44 -0
  115. package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
  116. package/dist/analyzers/universal/schema/violations.js +54 -0
  117. package/dist/analyzers/universal/schema/violations.js.map +1 -0
  118. package/dist/auditRunner.d.ts.map +1 -1
  119. package/dist/auditRunner.js +101 -39
  120. package/dist/auditRunner.js.map +1 -1
  121. package/dist/cli.js +348 -40
  122. package/dist/cli.js.map +1 -1
  123. package/dist/config/configLoader.d.ts.map +1 -1
  124. package/dist/config/configLoader.js +9 -7
  125. package/dist/config/configLoader.js.map +1 -1
  126. package/dist/config/defaults.d.ts +12 -3
  127. package/dist/config/defaults.d.ts.map +1 -1
  128. package/dist/config/defaults.js +15 -5
  129. package/dist/config/defaults.js.map +1 -1
  130. package/dist/config/effectiveConfig.d.ts +75 -0
  131. package/dist/config/effectiveConfig.d.ts.map +1 -0
  132. package/dist/config/effectiveConfig.js +213 -0
  133. package/dist/config/effectiveConfig.js.map +1 -0
  134. package/dist/config/pathProfiles.d.ts +7 -4
  135. package/dist/config/pathProfiles.d.ts.map +1 -1
  136. package/dist/config/pathProfiles.js +8 -10
  137. package/dist/config/pathProfiles.js.map +1 -1
  138. package/dist/config/thresholdRationales.d.ts +45 -0
  139. package/dist/config/thresholdRationales.d.ts.map +1 -0
  140. package/dist/config/thresholdRationales.js +67 -0
  141. package/dist/config/thresholdRationales.js.map +1 -0
  142. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  143. package/dist/conventions/conventionMiner.js +6 -2
  144. package/dist/conventions/conventionMiner.js.map +1 -1
  145. package/dist/enforcement/diffGate.d.ts +55 -0
  146. package/dist/enforcement/diffGate.d.ts.map +1 -0
  147. package/dist/enforcement/diffGate.js +136 -0
  148. package/dist/enforcement/diffGate.js.map +1 -0
  149. package/dist/enforcement/gate.d.ts +36 -0
  150. package/dist/enforcement/gate.d.ts.map +1 -0
  151. package/dist/enforcement/gate.js +72 -0
  152. package/dist/enforcement/gate.js.map +1 -0
  153. package/dist/enforcement/suppressions.d.ts +41 -0
  154. package/dist/enforcement/suppressions.d.ts.map +1 -0
  155. package/dist/enforcement/suppressions.js +170 -0
  156. package/dist/enforcement/suppressions.js.map +1 -0
  157. package/dist/fingerprint.d.ts.map +1 -1
  158. package/dist/fingerprint.js +4 -1
  159. package/dist/fingerprint.js.map +1 -1
  160. package/dist/grammars/tree-sitter-scss.wasm +0 -0
  161. package/dist/invariants/ruleEngine.d.ts +3 -4
  162. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  163. package/dist/invariants/ruleEngine.js +2 -2
  164. package/dist/invariants/ruleEngine.js.map +1 -1
  165. package/dist/languages/LanguageOrchestrator.d.ts +14 -0
  166. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  167. package/dist/languages/LanguageOrchestrator.js +93 -55
  168. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  169. package/dist/languages/LanguageRegistry.d.ts +8 -0
  170. package/dist/languages/LanguageRegistry.d.ts.map +1 -1
  171. package/dist/languages/LanguageRegistry.js +8 -0
  172. package/dist/languages/LanguageRegistry.js.map +1 -1
  173. package/dist/languages/RuntimeManager.d.ts +82 -60
  174. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  175. package/dist/languages/RuntimeManager.js +404 -349
  176. package/dist/languages/RuntimeManager.js.map +1 -1
  177. package/dist/languages/UniversalAnalyzer.d.ts +51 -6
  178. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  179. package/dist/languages/UniversalAnalyzer.js +127 -88
  180. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  181. package/dist/languages/adapterBridge.d.ts +36 -0
  182. package/dist/languages/adapterBridge.d.ts.map +1 -1
  183. package/dist/languages/adapterBridge.js +89 -35
  184. package/dist/languages/adapterBridge.js.map +1 -1
  185. package/dist/languages/go/GoAdapter.d.ts +66 -23
  186. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  187. package/dist/languages/go/GoAdapter.js +553 -474
  188. package/dist/languages/go/GoAdapter.js.map +1 -1
  189. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +38 -9
  190. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
  191. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +111 -74
  192. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
  193. package/dist/languages/tree-sitter/converter.d.ts +18 -0
  194. package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
  195. package/dist/languages/tree-sitter/converter.js +18 -0
  196. package/dist/languages/tree-sitter/converter.js.map +1 -1
  197. package/dist/languages/tree-sitter/parser.d.ts +39 -2
  198. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  199. package/dist/languages/tree-sitter/parser.js +95 -12
  200. package/dist/languages/tree-sitter/parser.js.map +1 -1
  201. package/dist/languages/types.d.ts +67 -2
  202. package/dist/languages/types.d.ts.map +1 -1
  203. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -62
  204. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  205. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1461 -791
  206. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  207. package/dist/mcp-tools/whitelistTools.d.ts +1 -1
  208. package/dist/mcp-tools-shared.d.ts.map +1 -1
  209. package/dist/mcp-tools-shared.js +1 -0
  210. package/dist/mcp-tools-shared.js.map +1 -1
  211. package/dist/mcpAuditJobs.d.ts.map +1 -1
  212. package/dist/mcpAuditJobs.js +1 -0
  213. package/dist/mcpAuditJobs.js.map +1 -1
  214. package/dist/pipeline.d.ts +30 -1
  215. package/dist/pipeline.d.ts.map +1 -1
  216. package/dist/pipeline.js +527 -203
  217. package/dist/pipeline.js.map +1 -1
  218. package/dist/pipelineAdapters.d.ts +5 -12
  219. package/dist/pipelineAdapters.d.ts.map +1 -1
  220. package/dist/pipelineAdapters.js +175 -100
  221. package/dist/pipelineAdapters.js.map +1 -1
  222. package/dist/presets/presets.d.ts +54 -0
  223. package/dist/presets/presets.d.ts.map +1 -0
  224. package/dist/presets/presets.js +181 -0
  225. package/dist/presets/presets.js.map +1 -0
  226. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  227. package/dist/reporting/sarifReportGenerator.js +0 -2
  228. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  229. package/dist/ruleAliases.d.ts +67 -0
  230. package/dist/ruleAliases.d.ts.map +1 -0
  231. package/dist/ruleAliases.js +79 -0
  232. package/dist/ruleAliases.js.map +1 -0
  233. package/dist/styles/cssAstExtractor.d.ts +20 -5
  234. package/dist/styles/cssAstExtractor.d.ts.map +1 -1
  235. package/dist/styles/cssAstExtractor.js +247 -9
  236. package/dist/styles/cssAstExtractor.js.map +1 -1
  237. package/dist/styles/styleIndexer.js +13 -4
  238. package/dist/styles/styleIndexer.js.map +1 -1
  239. package/dist/styles/tailwindConfigLoader.d.ts +8 -6
  240. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
  241. package/dist/styles/tailwindConfigLoader.js +61 -15
  242. package/dist/styles/tailwindConfigLoader.js.map +1 -1
  243. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  244. package/dist/styles/tailwindUtilityExpander.js +13 -1
  245. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  246. package/dist/types.d.ts +201 -1
  247. package/dist/types.d.ts.map +1 -1
  248. package/dist/types.js +8 -0
  249. package/dist/types.js.map +1 -1
  250. package/package.json +7 -4
  251. package/plugin/.claude-plugin/plugin.json +1 -1
  252. package/plugin/hooks/hooks.json +1 -1
  253. package/plugin/scripts/hook-audit.sh +25 -7
  254. package/plugin/skills/code-auditor/SKILL.md +5 -5
@@ -1,26 +1,3 @@
1
- /**
2
- * Rule Registry — canonical mapping of every emitted rule/violation-type ID
3
- * to its emitting analyzer.
4
- *
5
- * This is the ONE place that asserts "each rule ID has exactly one emitter."
6
- * If you add a new emitted ID to an analyzer, add it here. The enforcement
7
- * test (`registry enforces one emitter per rule ID`) fails the suite on
8
- * duplicate entries — preventing the `severityOverrides` collision class
9
- * where two analyzers emit the same string.
10
- *
11
- * Rider 2, Spec 19: the `type-mismatch` duplicate between
12
- * UniversalSchemaAnalyzer (rule) and SchemaValidator (violationType) was
13
- * discovered during ground-truth enumeration. SchemaValidator's copy was
14
- * renamed to `schema-field-mismatch`.
15
- *
16
- * NOTE: `contractType` is NOT in the buildFingerprintInput rule-ID chain.
17
- * APIContractAnalyzer violations currently resolve rule='' in fingerprints.
18
- * They are listed here for collision detection nonetheless, because
19
- * severityOverrides may use a different resolution path.
20
- *
21
- * NOTE: invariants analyzer rule IDs are user-defined and variable —
22
- * `config-error` and `engine-error` are the only fixed, internal ones.
23
- */
24
1
  /**
25
2
  * Every known rule/violation-type ID → analyzer.
26
3
  *
@@ -30,95 +7,1731 @@
30
7
  */
31
8
  export const RULE_REGISTRY = {
32
9
  // ── solid (UniversalSOLIDAnalyzer) ──────────────────────────────────────
33
- 'solid/class-size': { analyzer: 'solid', field: 'rule' },
34
- 'solid/method-complexity': { analyzer: 'solid', field: 'rule' },
35
- 'open-closed': { analyzer: 'solid', field: 'rule' },
36
- 'single-responsibility': { analyzer: 'solid', field: 'rule' },
37
- 'interface-segregation': { analyzer: 'solid', field: 'rule' },
38
- 'liskov-substitution': { analyzer: 'solid', field: 'rule' },
39
- 'dependency-inversion': { analyzer: 'solid', field: 'rule' },
10
+ 'solid/class-size': {
11
+ analyzer: 'solid',
12
+ field: 'rule',
13
+ input: ['files'],
14
+ gating: true,
15
+ resolvable: true,
16
+ message: 'Class "{name}" has {methods} methods, exceeding the maximum of {max}. Consider splitting responsibilities.',
17
+ docs: 'solid/class-size',
18
+ thresholds: ['maxMethodsPerClass', 'classMethodsThreshold', 'classAggregateComplexity'],
19
+ samples: {
20
+ valid: [
21
+ { code: 'class Small {\n load() { return this.fetch(); }\n save() { return this.persist(); }\n}', nearMiss: true },
22
+ ],
23
+ invalid: [
24
+ {
25
+ code: 'class Big {\n m1() {} m2() {} m3() {} m4() {} m5() {} m6() {} m7() {} m8() {}\n m9() {} m10() {} m11() {} m12() {} m13() {} m14() {} m15() {} m16() {}\n}',
26
+ resolution: { action: 'extract-methods', summary: 'Extract the methods that touch only migration state into a separate class and delegate to it.', symbols: ['Big'] },
27
+ },
28
+ ],
29
+ },
30
+ },
31
+ 'solid/method-complexity': {
32
+ analyzer: 'solid',
33
+ field: 'rule',
34
+ input: ['files'],
35
+ gating: false,
36
+ resolvable: false,
37
+ message: 'Method "{name}" has cyclomatic complexity {complexity}, exceeding the maximum of {max}.',
38
+ docs: 'solid/method-complexity',
39
+ thresholds: ['maxMethodComplexity'],
40
+ samples: {
41
+ valid: [
42
+ { code: 'function simple(x) {\n if (x > 0) return x;\n return -x;\n}', nearMiss: true },
43
+ ],
44
+ invalid: [
45
+ { code: 'function complex(x) {\n if (a && b) { if (c) { while (d) { if (e) return 1; } } }\n if (f || g) { for (;;) { if (h) break; } }\n return 0;\n}' },
46
+ ],
47
+ },
48
+ },
49
+ 'open-closed': {
50
+ analyzer: 'solid',
51
+ field: 'rule',
52
+ input: ['files'],
53
+ gating: false,
54
+ resolvable: false,
55
+ message: 'Class "{name}" violates the Open/Closed Principle.',
56
+ docs: 'open-closed',
57
+ thresholds: [],
58
+ samples: {
59
+ valid: [
60
+ { code: 'class Circle extends Shape {\n area() { return Math.PI * this.r ** 2; }\n}', nearMiss: true },
61
+ ],
62
+ invalid: [
63
+ { code: 'function area(shape) {\n if (shape.type === "circle") return Math.PI * shape.r ** 2;\n else if (shape.type === "square") return shape.s ** 2;\n}' },
64
+ ],
65
+ },
66
+ },
67
+ 'single-responsibility': {
68
+ analyzer: 'solid',
69
+ field: 'rule',
70
+ input: ['files'],
71
+ gating: true,
72
+ resolvable: true,
73
+ message: 'Function "{name}" has {count} {metric}, exceeding the maximum of {max}. Break it down.',
74
+ docs: 'single-responsibility',
75
+ thresholds: ['maxParametersPerMethod', 'maxLinesPerMethod'],
76
+ samples: {
77
+ valid: [
78
+ { code: 'function parse(input) {\n return input.trim().split(",");\n}', nearMiss: true },
79
+ ],
80
+ invalid: [
81
+ {
82
+ code: 'function handler(req) {\n const user = db.find(req.id);\n sendEmail(user);\n logEvent(req);\n render(user);\n audit(user);\n notify(user);\n}',
83
+ resolution: { action: 'split-function', summary: 'Split handler into one function per responsibility and compose them at the call site.', symbols: ['handler'] },
84
+ },
85
+ ],
86
+ },
87
+ },
88
+ 'interface-segregation': {
89
+ analyzer: 'solid',
90
+ field: 'rule',
91
+ input: ['files'],
92
+ gating: false,
93
+ resolvable: false,
94
+ message: 'Interface "{name}" violates the Interface Segregation Principle.',
95
+ docs: 'interface-segregation',
96
+ thresholds: [],
97
+ samples: {
98
+ valid: [
99
+ { code: 'interface Printer {\n print(doc) { return doc; }\n}', nearMiss: true },
100
+ ],
101
+ invalid: [
102
+ { code: 'interface Machine {\n print() {}\n scan() {}\n fax() {}\n staple() {}\n}' },
103
+ ],
104
+ },
105
+ },
106
+ 'liskov-substitution': {
107
+ analyzer: 'solid',
108
+ field: 'rule',
109
+ input: ['files'],
110
+ gating: false,
111
+ resolvable: false,
112
+ message: 'Class "{name}" violates the Liskov Substitution Principle.',
113
+ docs: 'liskov-substitution',
114
+ thresholds: [],
115
+ samples: {
116
+ valid: [
117
+ { code: 'class Bird {\n fly() { return "flying"; }\n}', nearMiss: true },
118
+ ],
119
+ invalid: [
120
+ { code: 'class Ostrich extends Bird {\n fly() { throw new Error("cannot fly"); }\n}' },
121
+ ],
122
+ },
123
+ },
124
+ 'dependency-inversion': {
125
+ analyzer: 'solid',
126
+ field: 'rule',
127
+ input: ['files'],
128
+ gating: false,
129
+ resolvable: false,
130
+ message: 'Module "{name}" violates the Dependency Inversion Principle.',
131
+ docs: 'dependency-inversion',
132
+ thresholds: [],
133
+ samples: {
134
+ valid: [
135
+ { code: 'class Service {\n constructor(repo) { this.repo = repo; }\n}', nearMiss: true },
136
+ ],
137
+ invalid: [
138
+ { code: 'class Service {\n constructor() { this.repo = new PostgresRepo(); }\n}' },
139
+ ],
140
+ },
141
+ },
40
142
  // ── dry (UniversalDRYAnalyzer) ──────────────────────────────────────────
41
- 'dry/duplicate': { analyzer: 'dry', field: 'rule' },
42
- 'dry/structural-similarity': { analyzer: 'dry', field: 'rule' },
43
- 'duplicate-string-literal': { analyzer: 'dry', field: 'rule' },
44
- 'duplicate-import': { analyzer: 'dry', field: 'rule' },
143
+ 'dry/duplicate': {
144
+ analyzer: 'dry',
145
+ field: 'rule',
146
+ input: ['files'],
147
+ gating: true,
148
+ resolvable: true,
149
+ message: 'Duplicate code block detected ({lines} lines). First occurrence at {file}:{line}.',
150
+ docs: 'dry/duplicate',
151
+ thresholds: ['minLineThreshold'],
152
+ samples: {
153
+ valid: [
154
+ { code: 'function a() {\n return foo();\n}\nfunction b() {\n return bar();\n}', nearMiss: true },
155
+ ],
156
+ invalid: [
157
+ {
158
+ code: 'function a() {\n const x = compute(1);\n const y = compute(2);\n return x + y;\n}\nfunction b() {\n const x = compute(1);\n const y = compute(2);\n return x + y;\n}',
159
+ resolution: { action: 'extract-shared', summary: 'Extract the duplicated block into a shared helper and call it from both sites.', symbols: ['a', 'b'] },
160
+ },
161
+ ],
162
+ },
163
+ },
164
+ 'dry/structural-similarity': {
165
+ analyzer: 'dry',
166
+ field: 'rule',
167
+ configGate: 'checkStructuralSimilarity',
168
+ input: ['files'],
169
+ gating: false,
170
+ resolvable: false,
171
+ message: 'Structurally similar code block detected ({similarity}% similar to {file}:{line}).',
172
+ docs: 'dry/structural-similarity',
173
+ thresholds: ['similarityThreshold'],
174
+ samples: {
175
+ valid: [
176
+ { code: 'function a(x) {\n return x + 1;\n}\nfunction b(x) {\n return x * 2;\n}', nearMiss: true },
177
+ ],
178
+ invalid: [
179
+ { code: 'function a(x) {\n return fetch(x).then((r) => r.json());\n}\nfunction b(y) {\n return fetch(y).then((r) => r.json());\n}' },
180
+ ],
181
+ },
182
+ },
183
+ 'duplicate-string-literal': {
184
+ analyzer: 'dry',
185
+ field: 'rule',
186
+ configGate: 'checkStrings',
187
+ input: ['files'],
188
+ gating: false,
189
+ resolvable: false,
190
+ message: 'String literal "{text}" is duplicated {count} times.',
191
+ docs: 'duplicate-string-literal',
192
+ thresholds: [],
193
+ samples: {
194
+ valid: [
195
+ { code: 'const a = "one";\nconst b = "two";', nearMiss: true },
196
+ ],
197
+ invalid: [
198
+ { code: 'const a = "connection-timeout";\nconst b = "connection-timeout";\nconst c = "connection-timeout";' },
199
+ ],
200
+ },
201
+ },
202
+ 'duplicate-import': {
203
+ analyzer: 'dry',
204
+ field: 'rule',
205
+ configGate: 'checkImports',
206
+ input: ['files'],
207
+ gating: false,
208
+ resolvable: false,
209
+ message: 'Duplicate import of "{module}".',
210
+ docs: 'duplicate-import',
211
+ thresholds: [],
212
+ samples: {
213
+ valid: [
214
+ { code: 'import { a } from "./mod";\nimport { b } from "./other";', nearMiss: true },
215
+ ],
216
+ invalid: [
217
+ { code: 'import { a } from "./mod";\nimport { b } from "./mod";' },
218
+ ],
219
+ },
220
+ },
45
221
  // ── data-access (UniversalDataAccessAnalyzer) ───────────────────────────
46
- 'sql-injection-risk': { analyzer: 'data-access', field: 'rule' },
47
- 'missing-org-filter': { analyzer: 'data-access', field: 'rule' },
48
- 'complex-query': { analyzer: 'data-access', field: 'rule' },
49
- 'unfiltered-query': { analyzer: 'data-access', field: 'rule' },
50
- 'hardcoded-connection': { analyzer: 'data-access', field: 'rule' },
51
- 'loop-query': { analyzer: 'data-access', field: 'rule' },
222
+ 'sql-injection-risk': {
223
+ analyzer: 'data-access',
224
+ field: 'rule',
225
+ input: ['files'],
226
+ gating: true,
227
+ resolvable: true,
228
+ message: 'Potential SQL injection risk in {method}. Use parameterized queries.',
229
+ docs: 'sql-injection-risk',
230
+ thresholds: [],
231
+ samples: {
232
+ valid: [
233
+ { code: 'db.query("SELECT * FROM users WHERE id = ?", [userId])', nearMiss: true },
234
+ ],
235
+ invalid: [
236
+ {
237
+ code: 'db.query("SELECT * FROM users WHERE id = " + userId)',
238
+ resolution: { action: 'parameterize', summary: 'Replace the string-concatenated SQL with a parameterized query using the driver\'s placeholder form.', symbols: ['query'] },
239
+ },
240
+ ],
241
+ },
242
+ },
243
+ 'missing-org-filter': {
244
+ analyzer: 'data-access',
245
+ field: 'rule',
246
+ input: ['files'],
247
+ gating: false,
248
+ resolvable: false,
249
+ message: 'Query on {tables} is missing an organization/tenant filter.',
250
+ docs: 'missing-org-filter',
251
+ thresholds: [],
252
+ samples: {
253
+ valid: [
254
+ { code: 'db.query("SELECT * FROM projects WHERE org_id = ?", [orgId])', nearMiss: true },
255
+ ],
256
+ invalid: [
257
+ { code: 'db.query("SELECT * FROM projects WHERE id = ?", [id])' },
258
+ ],
259
+ },
260
+ },
261
+ 'complex-query': {
262
+ analyzer: 'data-access',
263
+ field: 'rule',
264
+ input: ['files'],
265
+ gating: false,
266
+ resolvable: false,
267
+ message: 'Complex query with {tables} tables may have performance issues.',
268
+ docs: 'complex-query',
269
+ thresholds: ['performanceThresholds.joinedTableCount'],
270
+ samples: {
271
+ valid: [
272
+ { code: 'db.query("SELECT COUNT(*) FROM users")', nearMiss: true },
273
+ ],
274
+ invalid: [
275
+ { code: 'db.query("SELECT * FROM a JOIN b JOIN c JOIN d JOIN e JOIN f JOIN g JOIN h JOIN i")' },
276
+ ],
277
+ },
278
+ },
279
+ 'unfiltered-query': {
280
+ analyzer: 'data-access',
281
+ field: 'rule',
282
+ input: ['files'],
283
+ gating: false,
284
+ resolvable: false,
285
+ message: 'Unfiltered query on {tables} may cause performance issues.',
286
+ docs: 'unfiltered-query',
287
+ thresholds: [],
288
+ samples: {
289
+ valid: [
290
+ { code: 'db.query("SELECT * FROM users WHERE id = ?", [id])', nearMiss: true },
291
+ ],
292
+ invalid: [
293
+ { code: 'db.query("SELECT * FROM users")' },
294
+ ],
295
+ },
296
+ },
297
+ 'hardcoded-connection': {
298
+ analyzer: 'data-access',
299
+ field: 'rule',
300
+ input: ['files'],
301
+ gating: false,
302
+ resolvable: false,
303
+ message: 'Hardcoded database connection string detected.',
304
+ docs: 'hardcoded-connection',
305
+ thresholds: [],
306
+ samples: {
307
+ valid: [
308
+ { code: 'const conn = connect(process.env.DATABASE_URL)', nearMiss: true },
309
+ ],
310
+ invalid: [
311
+ { code: 'const conn = connect("postgres://user:pass@localhost:5432/db")' },
312
+ ],
313
+ },
314
+ },
315
+ 'loop-query': {
316
+ analyzer: 'data-access',
317
+ field: 'rule',
318
+ input: ['files'],
319
+ gating: false,
320
+ resolvable: false,
321
+ message: 'Database query inside a loop detected in {method}.',
322
+ docs: 'loop-query',
323
+ thresholds: [],
324
+ samples: {
325
+ valid: [
326
+ { code: 'for (const id of ids) {\n cache.set(id, lookup(id));\n}', nearMiss: true },
327
+ ],
328
+ invalid: [
329
+ { code: 'for (const id of ids) {\n db.query("SELECT * FROM users WHERE id = ?", [id]);\n}' },
330
+ ],
331
+ },
332
+ },
52
333
  // ── documentation (UniversalDocumentationAnalyzer) ─────────────────────
53
- 'file-documentation': { analyzer: 'documentation', field: 'rule' },
54
- 'function-documentation': { analyzer: 'documentation', field: 'rule' },
55
- 'parameter-documentation': { analyzer: 'documentation', field: 'rule' },
56
- 'return-documentation': { analyzer: 'documentation', field: 'rule' },
57
- 'class-documentation': { analyzer: 'documentation', field: 'rule' },
58
- 'method-documentation': { analyzer: 'documentation', field: 'rule' },
334
+ 'file-documentation': {
335
+ analyzer: 'documentation',
336
+ field: 'rule',
337
+ input: ['files'],
338
+ gating: false,
339
+ resolvable: false,
340
+ message: 'File is missing a documentation header comment.',
341
+ docs: 'file-documentation',
342
+ thresholds: ['minDescriptionLength'],
343
+ samples: {
344
+ valid: [
345
+ { code: '/** Core utilities for this module. */\nexport const a = 1;', nearMiss: true },
346
+ ],
347
+ invalid: [
348
+ { code: 'export const a = 1;' },
349
+ ],
350
+ },
351
+ },
352
+ 'function-documentation': {
353
+ analyzer: 'documentation',
354
+ field: 'rule',
355
+ input: ['files'],
356
+ gating: false,
357
+ resolvable: false,
358
+ message: 'Function "{name}" is missing a JSDoc comment.',
359
+ docs: 'function-documentation',
360
+ thresholds: ['minDescriptionLength'],
361
+ samples: {
362
+ valid: [
363
+ { code: '/** Does the thing. */\nfunction foo() {}', nearMiss: true },
364
+ ],
365
+ invalid: [
366
+ { code: 'function foo() {}' },
367
+ ],
368
+ },
369
+ },
370
+ 'parameter-documentation': {
371
+ analyzer: 'documentation',
372
+ field: 'rule',
373
+ input: ['files'],
374
+ gating: false,
375
+ resolvable: false,
376
+ message: 'Parameter "{name}" in function "{func}" is missing a @param tag.',
377
+ docs: 'parameter-documentation',
378
+ thresholds: [],
379
+ samples: {
380
+ valid: [
381
+ { code: '/**\n * Greets.\n * @param name the name\n */\nfunction greet(name) {}', nearMiss: true },
382
+ ],
383
+ invalid: [
384
+ { code: '/** Greets. */\nfunction greet(name) {}' },
385
+ ],
386
+ },
387
+ },
388
+ 'return-documentation': {
389
+ analyzer: 'documentation',
390
+ field: 'rule',
391
+ input: ['files'],
392
+ gating: false,
393
+ resolvable: false,
394
+ message: 'Function "{name}" is missing a @returns tag.',
395
+ docs: 'return-documentation',
396
+ thresholds: [],
397
+ samples: {
398
+ valid: [
399
+ { code: '/**\n * Computes.\n * @returns the result\n */\nfunction compute() {}', nearMiss: true },
400
+ ],
401
+ invalid: [
402
+ { code: '/** Computes. */\nfunction compute() {}' },
403
+ ],
404
+ },
405
+ },
406
+ 'class-documentation': {
407
+ analyzer: 'documentation',
408
+ field: 'rule',
409
+ input: ['files'],
410
+ gating: false,
411
+ resolvable: false,
412
+ message: 'Class "{name}" is missing a JSDoc comment.',
413
+ docs: 'class-documentation',
414
+ thresholds: ['minDescriptionLength', 'docsMinLines'],
415
+ samples: {
416
+ valid: [
417
+ { code: '/** A widget. */\nclass Widget {}', nearMiss: true },
418
+ ],
419
+ invalid: [
420
+ { code: 'class Widget {}' },
421
+ ],
422
+ },
423
+ },
424
+ 'method-documentation': {
425
+ analyzer: 'documentation',
426
+ field: 'rule',
427
+ input: ['files'],
428
+ gating: false,
429
+ resolvable: false,
430
+ message: 'Method "{name}" is missing a JSDoc comment.',
431
+ docs: 'method-documentation',
432
+ thresholds: ['minDescriptionLength'],
433
+ samples: {
434
+ valid: [
435
+ { code: 'class W {\n /** Renders. */\n render() {}\n}', nearMiss: true },
436
+ ],
437
+ invalid: [
438
+ { code: 'class W {\n render() {}\n}' },
439
+ ],
440
+ },
441
+ },
59
442
  // ── schema (UniversalSchemaAnalyzer) ────────────────────────────────────
60
- 'invalid-json': { analyzer: 'schema', field: 'rule' },
61
- 'missing-schema-declaration': { analyzer: 'schema', field: 'rule' },
62
- 'undefined-required-field': { analyzer: 'schema', field: 'rule' },
63
- 'invalid-type': { analyzer: 'schema', field: 'rule' },
64
- 'invalid-range': { analyzer: 'schema', field: 'rule' },
65
- 'file-error': { analyzer: 'schema', field: 'rule' },
66
- 'type-mismatch': { analyzer: 'schema', field: 'rule' },
67
- 'string-too-short': { analyzer: 'schema', field: 'rule' },
68
- 'string-too-long': { analyzer: 'schema', field: 'rule' },
69
- 'pattern-mismatch': { analyzer: 'schema', field: 'rule' },
70
- 'invalid-format': { analyzer: 'schema', field: 'rule' },
71
- 'below-minimum': { analyzer: 'schema', field: 'rule' },
72
- 'above-maximum': { analyzer: 'schema', field: 'rule' },
73
- 'too-few-items': { analyzer: 'schema', field: 'rule' },
74
- 'too-many-items': { analyzer: 'schema', field: 'rule' },
75
- 'missing-required-field': { analyzer: 'schema', field: 'rule' },
76
- 'unexpected-property': { analyzer: 'schema', field: 'rule' },
77
- 'enum-mismatch': { analyzer: 'schema', field: 'rule' },
78
- 'sql-injection': { analyzer: 'schema', field: 'rule' },
443
+ // JSON validation rules → emitted by the schema-json visitor path.
444
+ 'invalid-json': {
445
+ analyzer: 'schema',
446
+ field: 'rule',
447
+ input: ['schema-json'],
448
+ gating: false,
449
+ resolvable: false,
450
+ message: 'Invalid JSON: {error}.',
451
+ docs: 'invalid-json',
452
+ thresholds: [],
453
+ samples: {
454
+ valid: [
455
+ { code: '{"a": 1}', nearMiss: true },
456
+ ],
457
+ invalid: [
458
+ { code: '{"a": }' },
459
+ ],
460
+ },
461
+ },
462
+ 'missing-schema-declaration': {
463
+ analyzer: 'schema',
464
+ field: 'rule',
465
+ input: ['schema-json'],
466
+ gating: false,
467
+ resolvable: false,
468
+ message: 'Missing JSON schema declaration.',
469
+ docs: 'missing-schema-declaration',
470
+ thresholds: [],
471
+ samples: {
472
+ valid: [
473
+ { code: '{"$schema": "http://json-schema.org/draft-07/schema#"}', nearMiss: true },
474
+ ],
475
+ invalid: [
476
+ { code: '{"type": "object"}' },
477
+ ],
478
+ },
479
+ },
480
+ 'undefined-required-field': {
481
+ analyzer: 'schema',
482
+ field: 'rule',
483
+ input: ['schema-json'],
484
+ gating: false,
485
+ resolvable: false,
486
+ message: 'Undefined required field "{field}".',
487
+ docs: 'undefined-required-field',
488
+ thresholds: [],
489
+ samples: {
490
+ valid: [
491
+ { code: '{"field": "id"}', nearMiss: true },
492
+ ],
493
+ invalid: [
494
+ { code: '{"field": "unknown_col"}' },
495
+ ],
496
+ },
497
+ },
498
+ 'invalid-type': {
499
+ analyzer: 'schema',
500
+ field: 'rule',
501
+ input: ['schema-json'],
502
+ gating: false,
503
+ resolvable: false,
504
+ message: 'Invalid type for field "{field}".',
505
+ docs: 'invalid-type',
506
+ thresholds: [],
507
+ samples: {
508
+ valid: [
509
+ { code: '{"field": "age", "type": "integer"}', nearMiss: true },
510
+ ],
511
+ invalid: [
512
+ { code: '{"field": "age", "type": "intger"}' },
513
+ ],
514
+ },
515
+ },
516
+ 'invalid-range': {
517
+ analyzer: 'schema',
518
+ field: 'rule',
519
+ input: ['schema-json'],
520
+ gating: false,
521
+ resolvable: false,
522
+ message: 'Invalid range for field "{field}".',
523
+ docs: 'invalid-range',
524
+ thresholds: [],
525
+ samples: {
526
+ valid: [
527
+ { code: '{"field": "age", "minimum": 0, "maximum": 120}', nearMiss: true },
528
+ ],
529
+ invalid: [
530
+ { code: '{"field": "age", "minimum": 200, "maximum": 100}' },
531
+ ],
532
+ },
533
+ },
534
+ 'file-error': {
535
+ analyzer: 'schema',
536
+ field: 'rule',
537
+ input: ['schema-json'],
538
+ gating: false,
539
+ resolvable: false,
540
+ message: 'Error processing schema file: {error}.',
541
+ docs: 'file-error',
542
+ thresholds: [],
543
+ samples: {
544
+ valid: [
545
+ { code: '{"valid": true}', nearMiss: true },
546
+ ],
547
+ invalid: [
548
+ { code: 'not a readable schema' },
549
+ ],
550
+ },
551
+ },
552
+ 'type-mismatch': {
553
+ analyzer: 'schema',
554
+ field: 'rule',
555
+ input: ['schema-json'],
556
+ gating: false,
557
+ resolvable: false,
558
+ message: 'Type mismatch for field "{field}".',
559
+ docs: 'type-mismatch',
560
+ thresholds: [],
561
+ samples: {
562
+ valid: [
563
+ { code: '{"age": 30}', nearMiss: true },
564
+ ],
565
+ invalid: [
566
+ { code: '{"age": "thirty"}' },
567
+ ],
568
+ },
569
+ },
570
+ 'string-too-short': {
571
+ analyzer: 'schema',
572
+ field: 'rule',
573
+ input: ['schema-json'],
574
+ gating: false,
575
+ resolvable: false,
576
+ message: 'String value too short for field "{field}".',
577
+ docs: 'string-too-short',
578
+ thresholds: [],
579
+ samples: {
580
+ valid: [
581
+ { code: '{"name": "ab"}', nearMiss: true },
582
+ ],
583
+ invalid: [
584
+ { code: '{"name": "a"}' },
585
+ ],
586
+ },
587
+ },
588
+ 'string-too-long': {
589
+ analyzer: 'schema',
590
+ field: 'rule',
591
+ input: ['schema-json'],
592
+ gating: false,
593
+ resolvable: false,
594
+ message: 'String value too long for field "{field}".',
595
+ docs: 'string-too-long',
596
+ thresholds: [],
597
+ samples: {
598
+ valid: [
599
+ { code: '{"name": "ab"}', nearMiss: true },
600
+ ],
601
+ invalid: [
602
+ { code: '{"name": "abcdefghij"}' },
603
+ ],
604
+ },
605
+ },
606
+ 'pattern-mismatch': {
607
+ analyzer: 'schema',
608
+ field: 'rule',
609
+ input: ['schema-json'],
610
+ gating: false,
611
+ resolvable: false,
612
+ message: 'Value does not match pattern for field "{field}".',
613
+ docs: 'pattern-mismatch',
614
+ thresholds: [],
615
+ samples: {
616
+ valid: [
617
+ { code: '{"email": "a@b.com"}', nearMiss: true },
618
+ ],
619
+ invalid: [
620
+ { code: '{"email": "not-an-email"}' },
621
+ ],
622
+ },
623
+ },
624
+ 'invalid-format': {
625
+ analyzer: 'schema',
626
+ field: 'rule',
627
+ input: ['schema-json'],
628
+ gating: false,
629
+ resolvable: false,
630
+ message: 'Invalid format for field "{field}".',
631
+ docs: 'invalid-format',
632
+ thresholds: [],
633
+ samples: {
634
+ valid: [
635
+ { code: '{"date": "2026-01-01"}', nearMiss: true },
636
+ ],
637
+ invalid: [
638
+ { code: '{"date": "not-a-date"}' },
639
+ ],
640
+ },
641
+ },
642
+ 'below-minimum': {
643
+ analyzer: 'schema',
644
+ field: 'rule',
645
+ input: ['schema-json'],
646
+ gating: false,
647
+ resolvable: false,
648
+ message: 'Value below minimum for field "{field}".',
649
+ docs: 'below-minimum',
650
+ thresholds: [],
651
+ samples: {
652
+ valid: [
653
+ { code: '{"count": 5}', nearMiss: true },
654
+ ],
655
+ invalid: [
656
+ { code: '{"count": 0}' },
657
+ ],
658
+ },
659
+ },
660
+ 'above-maximum': {
661
+ analyzer: 'schema',
662
+ field: 'rule',
663
+ input: ['schema-json'],
664
+ gating: false,
665
+ resolvable: false,
666
+ message: 'Value above maximum for field "{field}".',
667
+ docs: 'above-maximum',
668
+ thresholds: [],
669
+ samples: {
670
+ valid: [
671
+ { code: '{"count": 5}', nearMiss: true },
672
+ ],
673
+ invalid: [
674
+ { code: '{"count": 100}' },
675
+ ],
676
+ },
677
+ },
678
+ 'too-few-items': {
679
+ analyzer: 'schema',
680
+ field: 'rule',
681
+ input: ['schema-json'],
682
+ gating: false,
683
+ resolvable: false,
684
+ message: 'Too few items for field "{field}".',
685
+ docs: 'too-few-items',
686
+ thresholds: [],
687
+ samples: {
688
+ valid: [
689
+ { code: '{"tags": ["a", "b"]}', nearMiss: true },
690
+ ],
691
+ invalid: [
692
+ { code: '{"tags": []}' },
693
+ ],
694
+ },
695
+ },
696
+ 'too-many-items': {
697
+ analyzer: 'schema',
698
+ field: 'rule',
699
+ input: ['schema-json'],
700
+ gating: false,
701
+ resolvable: false,
702
+ message: 'Too many items for field "{field}".',
703
+ docs: 'too-many-items',
704
+ thresholds: [],
705
+ samples: {
706
+ valid: [
707
+ { code: '{"tags": ["a"]}', nearMiss: true },
708
+ ],
709
+ invalid: [
710
+ { code: '{"tags": ["a", "b", "c", "d"]}' },
711
+ ],
712
+ },
713
+ },
714
+ 'missing-required-field': {
715
+ analyzer: 'schema',
716
+ field: 'rule',
717
+ input: ['schema-json'],
718
+ gating: false,
719
+ resolvable: false,
720
+ message: 'Missing required field "{field}".',
721
+ docs: 'missing-required-field',
722
+ thresholds: [],
723
+ samples: {
724
+ valid: [
725
+ { code: '{"id": 1}', nearMiss: true },
726
+ ],
727
+ invalid: [
728
+ { code: '{}' },
729
+ ],
730
+ },
731
+ },
732
+ 'unexpected-property': {
733
+ analyzer: 'schema',
734
+ field: 'rule',
735
+ input: ['schema-json'],
736
+ gating: false,
737
+ resolvable: false,
738
+ message: 'Unexpected property "{property}".',
739
+ docs: 'unexpected-property',
740
+ thresholds: [],
741
+ samples: {
742
+ valid: [
743
+ { code: '{"id": 1}', nearMiss: true },
744
+ ],
745
+ invalid: [
746
+ { code: '{"id": 1, "extra": true}' },
747
+ ],
748
+ },
749
+ },
750
+ 'enum-mismatch': {
751
+ analyzer: 'schema',
752
+ field: 'rule',
753
+ input: ['schema-json'],
754
+ gating: false,
755
+ resolvable: false,
756
+ message: 'Value does not match any enum value for field "{field}".',
757
+ docs: 'enum-mismatch',
758
+ thresholds: [],
759
+ samples: {
760
+ valid: [
761
+ { code: '{"status": "active"}', nearMiss: true },
762
+ ],
763
+ invalid: [
764
+ { code: '{"status": "actiev"}' },
765
+ ],
766
+ },
767
+ },
768
+ // SQL-injection → emitted by the schema-code visitor over TS/JS source.
769
+ 'sql-injection': {
770
+ analyzer: 'schema',
771
+ field: 'rule',
772
+ input: ['schema-code'],
773
+ gating: false,
774
+ resolvable: false,
775
+ message: 'Potential SQL injection via string interpolation in {method}.',
776
+ docs: 'sql-injection',
777
+ thresholds: [],
778
+ samples: {
779
+ valid: [
780
+ { code: 'db.query("SELECT * FROM t WHERE id = ?", [id])', nearMiss: true },
781
+ ],
782
+ invalid: [
783
+ { code: 'db.query("SELECT * FROM t WHERE id = " + id)' },
784
+ ],
785
+ },
786
+ },
787
+ // Table naming-convention check → emitted by the schema-code visitor.
788
+ // Spec 38 R5: renamed from `naming-convention` (see src/ruleAliases.ts).
789
+ 'table-naming-convention': {
790
+ analyzer: 'schema',
791
+ field: 'rule',
792
+ configGate: 'checkNamingConventions',
793
+ input: ['schema-code'],
794
+ gating: false,
795
+ resolvable: false,
796
+ message: 'Table name "{table}" should use snake_case convention.',
797
+ docs: 'table-naming-convention',
798
+ thresholds: [],
799
+ samples: {
800
+ valid: [
801
+ { code: 'const t = sql`SELECT * FROM user_profiles`', nearMiss: true },
802
+ ],
803
+ invalid: [
804
+ { code: 'const t = sql`SELECT * FROM UserProfiles`' },
805
+ ],
806
+ },
807
+ },
808
+ // Cross-file unknown-table detection → emitted by the schema Stage 3 reducer.
809
+ 'unknown-table': {
810
+ analyzer: 'schema',
811
+ field: 'rule',
812
+ input: ['schema-code'],
813
+ gating: true,
814
+ resolvable: true,
815
+ message: 'Reference to unknown table "{table}" ({type}). Did you mean: {suggestions}?',
816
+ docs: 'unknown-table',
817
+ thresholds: [],
818
+ samples: {
819
+ valid: [
820
+ { code: 'db.query("SELECT * FROM users")', nearMiss: true },
821
+ ],
822
+ invalid: [
823
+ {
824
+ code: 'db.query("SELECT * FROM user")',
825
+ resolution: { action: 'use-known-table', summary: 'Rename the table reference "user" to the nearest known table "users".', symbols: ['users'] },
826
+ },
827
+ ],
828
+ },
829
+ },
79
830
  // ── react (reactAnalyzer) ───────────────────────────────────────────────
80
- 'hooks-naming': { analyzer: 'react', field: 'rule' },
81
- 'complexity': { analyzer: 'react', field: 'rule' },
82
- 'missing-props': { analyzer: 'react', field: 'rule' },
83
- 'no-error-boundary': { analyzer: 'react', field: 'rule' },
84
- 'performance': { analyzer: 'react', field: 'rule' },
85
- 'accessibility': { analyzer: 'react', field: 'rule' },
86
- 'raw-element': { analyzer: 'react', field: 'rule' },
831
+ 'hooks-naming': {
832
+ analyzer: 'react',
833
+ field: 'rule',
834
+ input: ['files'],
835
+ gating: false,
836
+ resolvable: false,
837
+ message: 'React hook "{name}" does not follow the "use*" naming convention.',
838
+ docs: 'hooks-naming',
839
+ thresholds: [],
840
+ samples: {
841
+ valid: [
842
+ { code: 'function useFetch() {\n return useState(null);\n}', nearMiss: true },
843
+ ],
844
+ invalid: [
845
+ { code: 'function fetchData() {\n return useState(null);\n}' },
846
+ ],
847
+ },
848
+ },
849
+ 'complexity': {
850
+ analyzer: 'react',
851
+ field: 'rule',
852
+ input: ['files'],
853
+ gating: false,
854
+ resolvable: false,
855
+ message: 'React component "{name}" has complexity {complexity}, exceeding the maximum.',
856
+ docs: 'complexity',
857
+ thresholds: [],
858
+ samples: {
859
+ valid: [
860
+ { code: 'function Simple({ x }) {\n return <div>{x}</div>;\n}', nearMiss: true },
861
+ ],
862
+ invalid: [
863
+ { code: 'function Complex(props) {\n if (a) { if (b) { if (c) { if (d) { return <X/>; } } } }\n if (e) { if (f) { return <Y/>; } }\n return <Z/>;\n}' },
864
+ ],
865
+ },
866
+ },
867
+ 'missing-props': {
868
+ analyzer: 'react',
869
+ field: 'rule',
870
+ configGate: 'requirePropTypes',
871
+ input: ['files'],
872
+ gating: false,
873
+ resolvable: false,
874
+ message: 'React component "{name}" is missing prop-types.',
875
+ docs: 'missing-props',
876
+ thresholds: [],
877
+ samples: {
878
+ valid: [
879
+ { code: 'function P({ x }) {\n return <div>{x}</div>;\n}\nP.propTypes = { x: PropTypes.number };', nearMiss: true },
880
+ ],
881
+ invalid: [
882
+ { code: 'function P({ x }) {\n return <div>{x}</div>;\n}' },
883
+ ],
884
+ },
885
+ },
886
+ 'no-error-boundary': {
887
+ analyzer: 'react',
888
+ field: 'rule',
889
+ input: ['files'],
890
+ gating: false,
891
+ resolvable: false,
892
+ message: 'React component tree is missing an error boundary.',
893
+ docs: 'no-error-boundary',
894
+ thresholds: [],
895
+ samples: {
896
+ valid: [
897
+ { code: '<ErrorBoundary>\n <App />\n</ErrorBoundary>', nearMiss: true },
898
+ ],
899
+ invalid: [
900
+ { code: '<App />' },
901
+ ],
902
+ },
903
+ },
904
+ 'performance': {
905
+ analyzer: 'react',
906
+ field: 'rule',
907
+ configGate: 'requireMemoization',
908
+ input: ['files'],
909
+ gating: false,
910
+ resolvable: false,
911
+ message: 'React component "{name}" is missing memoization.',
912
+ docs: 'performance',
913
+ thresholds: [],
914
+ samples: {
915
+ valid: [
916
+ { code: 'const List = memo(function List({ items }) {\n return <ul>{items}</ul>;\n});', nearMiss: true },
917
+ ],
918
+ invalid: [
919
+ { code: 'function List({ items }) {\n return <ul>{items}</ul>;\n}' },
920
+ ],
921
+ },
922
+ },
923
+ 'accessibility': {
924
+ analyzer: 'react',
925
+ field: 'rule',
926
+ input: ['files'],
927
+ gating: false,
928
+ resolvable: false,
929
+ message: 'Accessibility issue in component "{name}": {issue}.',
930
+ docs: 'accessibility',
931
+ thresholds: [],
932
+ samples: {
933
+ valid: [
934
+ { code: '<img src="x.png" alt="description" />', nearMiss: true },
935
+ ],
936
+ invalid: [
937
+ { code: '<img src="x.png" />' },
938
+ ],
939
+ },
940
+ },
941
+ 'raw-element': {
942
+ analyzer: 'react',
943
+ field: 'rule',
944
+ input: ['files'],
945
+ gating: true,
946
+ resolvable: true,
947
+ message: 'raw `<{element}>` — this project uses `<{wrapper}>` ({file}).',
948
+ docs: 'raw-element',
949
+ thresholds: [],
950
+ samples: {
951
+ valid: [
952
+ { code: 'return <Button onClick={fn}>Save</Button>;', nearMiss: true },
953
+ { code: 'return React.createElement(Button, null, "Save");', nearMiss: true },
954
+ ],
955
+ invalid: [
956
+ {
957
+ code: 'return <button onClick={fn}>Save</button>;',
958
+ resolution: { action: 'use-wrapper', summary: 'Replace the raw <button> with the project\'s <Button> component.', symbols: ['Button'] },
959
+ },
960
+ ],
961
+ },
962
+ },
87
963
  // ── invariants (invariantsAnalyzer) — fixed internal IDs only ──────────
88
- 'config-error': { analyzer: 'invariants', field: 'rule' },
89
- 'engine-error': { analyzer: 'invariants', field: 'rule' },
90
- // ── cross-language-solid (CrossLanguageSOLIDAnalyzer) ───────────────────
91
- 'SRP': { analyzer: 'cross-language-solid', field: 'rule' },
92
- 'OCP': { analyzer: 'cross-language-solid', field: 'rule' },
93
- 'ISP': { analyzer: 'cross-language-solid', field: 'rule' },
94
- 'DIP': { analyzer: 'cross-language-solid', field: 'rule' },
95
- 'LSP': { analyzer: 'cross-language-solid', field: 'rule' },
964
+ 'config-error': {
965
+ analyzer: 'invariants',
966
+ field: 'rule',
967
+ input: ['files'],
968
+ gating: false,
969
+ resolvable: false,
970
+ message: 'Invalid invariant config: {error}.',
971
+ docs: 'config-error',
972
+ thresholds: [],
973
+ samples: {
974
+ valid: [
975
+ { code: 'export default { rules: [] };', nearMiss: true },
976
+ ],
977
+ invalid: [
978
+ { code: 'export default { rules: [{ kind: "not-a-kind" }] };' },
979
+ ],
980
+ },
981
+ },
982
+ 'engine-error': {
983
+ analyzer: 'invariants',
984
+ field: 'rule',
985
+ input: ['files'],
986
+ gating: false,
987
+ resolvable: false,
988
+ message: 'Invariant rule engine error: {error}.',
989
+ docs: 'engine-error',
990
+ thresholds: [],
991
+ samples: {
992
+ valid: [
993
+ { code: 'runRules([], { rules: [] });', nearMiss: true },
994
+ ],
995
+ invalid: [
996
+ { code: 'runRules(null, { rules: [] });' },
997
+ ],
998
+ },
999
+ },
96
1000
  // ── schema-validator (SchemaValidator) ──────────────────────────────────
97
- 'field-mismatch': { analyzer: 'schema-validator', field: 'rule' },
98
- 'schema-field-mismatch': { analyzer: 'schema-validator', field: 'rule' },
99
- 'missing-field': { analyzer: 'schema-validator', field: 'rule' },
100
- 'extra-field': { analyzer: 'schema-validator', field: 'rule' },
101
- 'constraint-mismatch': { analyzer: 'schema-validator', field: 'rule' },
102
- 'version-mismatch': { analyzer: 'schema-validator', field: 'rule' },
1001
+ 'field-mismatch': {
1002
+ analyzer: 'schema-validator',
1003
+ field: 'rule',
1004
+ gating: false,
1005
+ resolvable: false,
1006
+ message: 'Field mismatch: {detail}.',
1007
+ docs: 'field-mismatch',
1008
+ thresholds: [],
1009
+ samples: {
1010
+ valid: [
1011
+ { code: 'model User { id Int @id }', nearMiss: true },
1012
+ ],
1013
+ invalid: [
1014
+ { code: 'model User { id String @id }' },
1015
+ ],
1016
+ },
1017
+ },
1018
+ 'schema-field-mismatch': {
1019
+ analyzer: 'schema-validator',
1020
+ field: 'rule',
1021
+ gating: false,
1022
+ resolvable: false,
1023
+ message: 'Schema field mismatch: {detail}.',
1024
+ docs: 'schema-field-mismatch',
1025
+ thresholds: [],
1026
+ samples: {
1027
+ valid: [
1028
+ { code: 'CREATE TABLE users (id INTEGER);', nearMiss: true },
1029
+ ],
1030
+ invalid: [
1031
+ { code: 'CREATE TABLE users (id INTEGER);\nINSERT INTO users (id, name) VALUES (1, "x");' },
1032
+ ],
1033
+ },
1034
+ },
1035
+ 'missing-field': {
1036
+ analyzer: 'schema-validator',
1037
+ field: 'rule',
1038
+ gating: false,
1039
+ resolvable: false,
1040
+ message: 'Missing field: {field}.',
1041
+ docs: 'missing-field',
1042
+ thresholds: [],
1043
+ samples: {
1044
+ valid: [
1045
+ { code: 'INSERT INTO users (id, name) VALUES (?, ?);', nearMiss: true },
1046
+ ],
1047
+ invalid: [
1048
+ { code: 'INSERT INTO users (id) VALUES (?);' },
1049
+ ],
1050
+ },
1051
+ },
1052
+ 'extra-field': {
1053
+ analyzer: 'schema-validator',
1054
+ field: 'rule',
1055
+ gating: false,
1056
+ resolvable: false,
1057
+ message: 'Extra field: {field}.',
1058
+ docs: 'extra-field',
1059
+ thresholds: [],
1060
+ samples: {
1061
+ valid: [
1062
+ { code: 'SELECT id, name FROM users;', nearMiss: true },
1063
+ ],
1064
+ invalid: [
1065
+ { code: 'SELECT id, name, bogus FROM users;' },
1066
+ ],
1067
+ },
1068
+ },
1069
+ 'constraint-mismatch': {
1070
+ analyzer: 'schema-validator',
1071
+ field: 'rule',
1072
+ gating: false,
1073
+ resolvable: false,
1074
+ message: 'Constraint mismatch: {detail}.',
1075
+ docs: 'constraint-mismatch',
1076
+ thresholds: [],
1077
+ samples: {
1078
+ valid: [
1079
+ { code: 'model User { id Int @id @default(autoincrement()) }', nearMiss: true },
1080
+ ],
1081
+ invalid: [
1082
+ { code: 'model User { id Int @id }' },
1083
+ ],
1084
+ },
1085
+ },
1086
+ 'version-mismatch': {
1087
+ analyzer: 'schema-validator',
1088
+ field: 'rule',
1089
+ gating: false,
1090
+ resolvable: false,
1091
+ message: 'Version mismatch: {detail}.',
1092
+ docs: 'version-mismatch',
1093
+ thresholds: [],
1094
+ samples: {
1095
+ valid: [
1096
+ { code: 'export const version = "1.2.3";', nearMiss: true },
1097
+ ],
1098
+ invalid: [
1099
+ { code: 'export const version = "0.0.1";' },
1100
+ ],
1101
+ },
1102
+ },
103
1103
  // ── api-contract (APIContractAnalyzer) ──────────────────────────────────
104
- 'api-type-mismatch': { analyzer: 'api-contract', field: 'rule' },
105
- 'missing-endpoint': { analyzer: 'api-contract', field: 'rule' },
106
- 'api-extra-field': { analyzer: 'api-contract', field: 'rule' },
107
- 'api-missing-field': { analyzer: 'api-contract', field: 'rule' },
108
- 'method-mismatch': { analyzer: 'api-contract', field: 'rule' },
109
- 'auth-mismatch': { analyzer: 'api-contract', field: 'rule' },
110
- // ── schema-parser (SchemaParser) ────────────────────────────────────────
111
- 'naming-convention': { analyzer: 'schema-parser', field: 'rule' },
112
- 'missing-index': { analyzer: 'schema-parser', field: 'rule' },
113
- 'missing-reference': { analyzer: 'schema-parser', field: 'rule' },
1104
+ 'api-type-mismatch': {
1105
+ analyzer: 'api-contract',
1106
+ field: 'rule',
1107
+ gating: false,
1108
+ resolvable: false,
1109
+ message: 'API type mismatch: {detail}.',
1110
+ docs: 'api-type-mismatch',
1111
+ thresholds: [],
1112
+ samples: {
1113
+ valid: [
1114
+ { code: 'function getUser(id: number): User { return {} as User; }', nearMiss: true },
1115
+ ],
1116
+ invalid: [
1117
+ { code: 'function getUser(id: number): string { return ""; }' },
1118
+ ],
1119
+ },
1120
+ },
1121
+ 'missing-endpoint': {
1122
+ analyzer: 'api-contract',
1123
+ field: 'rule',
1124
+ gating: false,
1125
+ resolvable: false,
1126
+ message: 'Missing API endpoint: {endpoint}.',
1127
+ docs: 'missing-endpoint',
1128
+ thresholds: [],
1129
+ samples: {
1130
+ valid: [
1131
+ { code: 'app.get("/users", handler);', nearMiss: true },
1132
+ ],
1133
+ invalid: [
1134
+ { code: 'app.get("/unlisted", handler);' },
1135
+ ],
1136
+ },
1137
+ },
1138
+ 'api-extra-field': {
1139
+ analyzer: 'api-contract',
1140
+ field: 'rule',
1141
+ gating: false,
1142
+ resolvable: false,
1143
+ message: 'Extra field in API response: {field}.',
1144
+ docs: 'api-extra-field',
1145
+ thresholds: [],
1146
+ samples: {
1147
+ valid: [
1148
+ { code: 'return res.json({ id, name });', nearMiss: true },
1149
+ ],
1150
+ invalid: [
1151
+ { code: 'return res.json({ id, name, internal });' },
1152
+ ],
1153
+ },
1154
+ },
1155
+ 'api-missing-field': {
1156
+ analyzer: 'api-contract',
1157
+ field: 'rule',
1158
+ gating: false,
1159
+ resolvable: false,
1160
+ message: 'Missing field in API response: {field}.',
1161
+ docs: 'api-missing-field',
1162
+ thresholds: [],
1163
+ samples: {
1164
+ valid: [
1165
+ { code: 'return res.json({ id, name });', nearMiss: true },
1166
+ ],
1167
+ invalid: [
1168
+ { code: 'return res.json({ id });' },
1169
+ ],
1170
+ },
1171
+ },
1172
+ 'method-mismatch': {
1173
+ analyzer: 'api-contract',
1174
+ field: 'rule',
1175
+ gating: false,
1176
+ resolvable: false,
1177
+ message: 'HTTP method mismatch: {detail}.',
1178
+ docs: 'method-mismatch',
1179
+ thresholds: [],
1180
+ samples: {
1181
+ valid: [
1182
+ { code: 'app.post("/users", createUser);', nearMiss: true },
1183
+ ],
1184
+ invalid: [
1185
+ { code: 'app.get("/users", createUser);' },
1186
+ ],
1187
+ },
1188
+ },
1189
+ 'auth-mismatch': {
1190
+ analyzer: 'api-contract',
1191
+ field: 'rule',
1192
+ gating: false,
1193
+ resolvable: false,
1194
+ message: 'Authentication mismatch: {detail}.',
1195
+ docs: 'auth-mismatch',
1196
+ thresholds: [],
1197
+ samples: {
1198
+ valid: [
1199
+ { code: 'app.get("/users", auth, handler);', nearMiss: true },
1200
+ ],
1201
+ invalid: [
1202
+ { code: 'app.get("/users", handler);' },
1203
+ ],
1204
+ },
1205
+ },
114
1206
  // ── dependency-graph (DependencyGraphBuilder) ───────────────────────────
115
- 'circular-dependency': { analyzer: 'dependency-graph', field: 'type' },
116
- 'break-cycles': { analyzer: 'dependency-graph', field: 'type' },
117
- 'tight-coupling': { analyzer: 'dependency-graph', field: 'type' },
118
- 'reduce-coupling': { analyzer: 'dependency-graph', field: 'type' },
119
- 'hub-nodes': { analyzer: 'dependency-graph', field: 'type' },
120
- 'split-responsibilities': { analyzer: 'dependency-graph', field: 'type' },
121
- 'orphaned-nodes': { analyzer: 'dependency-graph', field: 'type' },
122
- 'review-orphans': { analyzer: 'dependency-graph', field: 'type' },
1207
+ 'circular-dependency': {
1208
+ analyzer: 'dependency-graph',
1209
+ field: 'type',
1210
+ gating: false,
1211
+ resolvable: false,
1212
+ message: 'Circular dependency detected: {cycle}.',
1213
+ docs: 'circular-dependency',
1214
+ thresholds: [],
1215
+ samples: {
1216
+ valid: [
1217
+ { code: 'import { b } from "./b";\n// a.ts imports b.ts only', nearMiss: true },
1218
+ ],
1219
+ invalid: [
1220
+ { code: 'import { b } from "./b";\n// b.ts also imports a.ts' },
1221
+ ],
1222
+ },
1223
+ },
1224
+ 'break-cycles': {
1225
+ analyzer: 'dependency-graph',
1226
+ field: 'type',
1227
+ gating: false,
1228
+ resolvable: false,
1229
+ message: 'Break dependency cycle: {cycle}.',
1230
+ docs: 'break-cycles',
1231
+ thresholds: [],
1232
+ samples: {
1233
+ valid: [
1234
+ { code: '// a.ts -> b.ts -> c.ts (no back edge)', nearMiss: true },
1235
+ ],
1236
+ invalid: [
1237
+ { code: '// a.ts -> b.ts -> a.ts' },
1238
+ ],
1239
+ },
1240
+ },
1241
+ 'tight-coupling': {
1242
+ analyzer: 'dependency-graph',
1243
+ field: 'type',
1244
+ gating: false,
1245
+ resolvable: false,
1246
+ message: 'Tight coupling detected between {a} and {b}.',
1247
+ docs: 'tight-coupling',
1248
+ thresholds: [],
1249
+ samples: {
1250
+ valid: [
1251
+ { code: 'import { one } from "./m";', nearMiss: true },
1252
+ ],
1253
+ invalid: [
1254
+ { code: 'import * as m from "./m";\nm.a(); m.b(); m.c(); m.d(); m.e();' },
1255
+ ],
1256
+ },
1257
+ },
1258
+ 'reduce-coupling': {
1259
+ analyzer: 'dependency-graph',
1260
+ field: 'type',
1261
+ gating: false,
1262
+ resolvable: false,
1263
+ message: 'Reduce coupling between {a} and {b}.',
1264
+ docs: 'reduce-coupling',
1265
+ thresholds: [],
1266
+ samples: {
1267
+ valid: [
1268
+ { code: 'import { f } from "./u";', nearMiss: true },
1269
+ ],
1270
+ invalid: [
1271
+ { code: 'import { a, b, c, d, e, f, g, h } from "./u";' },
1272
+ ],
1273
+ },
1274
+ },
1275
+ 'hub-nodes': {
1276
+ analyzer: 'dependency-graph',
1277
+ field: 'type',
1278
+ gating: false,
1279
+ resolvable: false,
1280
+ message: 'Hub node "{node}" has {count} dependencies.',
1281
+ docs: 'hub-nodes',
1282
+ thresholds: [],
1283
+ samples: {
1284
+ valid: [
1285
+ { code: 'export function small() {}', nearMiss: true },
1286
+ ],
1287
+ invalid: [
1288
+ { code: 'export function hub() {\n a(); b(); c(); d(); e(); f(); g(); h(); i(); j();\n}' },
1289
+ ],
1290
+ },
1291
+ },
1292
+ 'split-responsibilities': {
1293
+ analyzer: 'dependency-graph',
1294
+ field: 'type',
1295
+ gating: false,
1296
+ resolvable: false,
1297
+ message: 'Split responsibilities of node "{node}".',
1298
+ docs: 'split-responsibilities',
1299
+ thresholds: [],
1300
+ samples: {
1301
+ valid: [
1302
+ { code: 'export function focused() { return a(); }', nearMiss: true },
1303
+ ],
1304
+ invalid: [
1305
+ { code: 'export function multi() {\n readDb(); writeLog(); renderUi(); sendMail();\n}' },
1306
+ ],
1307
+ },
1308
+ },
1309
+ 'orphaned-nodes': {
1310
+ analyzer: 'dependency-graph',
1311
+ field: 'type',
1312
+ gating: false,
1313
+ resolvable: false,
1314
+ message: 'Orphaned node "{node}" has no connections.',
1315
+ docs: 'orphaned-nodes',
1316
+ thresholds: [],
1317
+ samples: {
1318
+ valid: [
1319
+ { code: 'export function used() {}', nearMiss: true },
1320
+ ],
1321
+ invalid: [
1322
+ { code: 'export function neverImported() {}' },
1323
+ ],
1324
+ },
1325
+ },
1326
+ 'review-orphans': {
1327
+ analyzer: 'dependency-graph',
1328
+ field: 'type',
1329
+ gating: false,
1330
+ resolvable: false,
1331
+ message: 'Review orphaned nodes: {nodes}.',
1332
+ docs: 'review-orphans',
1333
+ thresholds: [],
1334
+ samples: {
1335
+ valid: [
1336
+ { code: '// all exports are imported elsewhere', nearMiss: true },
1337
+ ],
1338
+ invalid: [
1339
+ { code: 'export const dead = 1;' },
1340
+ ],
1341
+ },
1342
+ },
1343
+ // ── styles (UniversalStylesAnalyzer) ─────────────────────────────────────
1344
+ 'styles/value-drift': {
1345
+ analyzer: 'styles',
1346
+ field: 'rule',
1347
+ input: ['styles-css'],
1348
+ gating: false,
1349
+ resolvable: false,
1350
+ message: 'Color value "{value}" drifts from the dominant token.',
1351
+ docs: 'styles/value-drift',
1352
+ thresholds: ['colorDeltaE', 'outlierMaxShare'],
1353
+ samples: {
1354
+ valid: [
1355
+ { code: '.btn { color: var(--brand); }', nearMiss: true },
1356
+ ],
1357
+ invalid: [
1358
+ { code: '.btn { color: #123456; }' },
1359
+ ],
1360
+ },
1361
+ },
1362
+ 'styles/off-scale': {
1363
+ analyzer: 'styles',
1364
+ field: 'rule',
1365
+ input: ['styles-css'],
1366
+ gating: false,
1367
+ resolvable: false,
1368
+ message: 'Value "{value}" is off the project\'s design scale.',
1369
+ docs: 'styles/off-scale',
1370
+ thresholds: ['minCorpus'],
1371
+ samples: {
1372
+ valid: [
1373
+ { code: '.x { padding: 8px; }', nearMiss: true },
1374
+ ],
1375
+ invalid: [
1376
+ { code: '.x { padding: 13px; }' },
1377
+ ],
1378
+ },
1379
+ },
1380
+ 'styles/undefined-class': {
1381
+ analyzer: 'styles',
1382
+ field: 'rule',
1383
+ input: ['styles-css'],
1384
+ gating: true,
1385
+ resolvable: true,
1386
+ message: 'Undefined CSS class: "{class}" has no matching definition.',
1387
+ docs: 'styles/undefined-class',
1388
+ thresholds: [],
1389
+ samples: {
1390
+ valid: [
1391
+ { code: '.card { display: flex; }\n// used in markup: <div class="card">', nearMiss: true },
1392
+ ],
1393
+ invalid: [
1394
+ {
1395
+ code: '// markup uses <div class="missing">\n.card { display: flex; }',
1396
+ resolution: { action: 'define-class', summary: 'Define the .missing class in the stylesheet that this markup imports.', symbols: ['missing'] },
1397
+ },
1398
+ ],
1399
+ },
1400
+ },
1401
+ 'styles/undefined-class-disabled': {
1402
+ analyzer: 'styles',
1403
+ field: 'rule',
1404
+ input: ['styles-css'],
1405
+ gating: false,
1406
+ resolvable: false,
1407
+ message: 'Undefined-class detection skipped: {reason}.',
1408
+ docs: 'styles/undefined-class-disabled',
1409
+ thresholds: [],
1410
+ samples: {
1411
+ valid: [
1412
+ { code: '// detection enabled, corpus present', nearMiss: true },
1413
+ ],
1414
+ invalid: [
1415
+ { code: '// no stylesheet corpus available' },
1416
+ ],
1417
+ },
1418
+ },
1419
+ 'styles/token-bypass': {
1420
+ analyzer: 'styles',
1421
+ field: 'rule',
1422
+ input: ['styles-css'],
1423
+ gating: false,
1424
+ resolvable: false,
1425
+ message: 'Token bypass: raw value "{value}" used instead of a design token.',
1426
+ docs: 'styles/token-bypass',
1427
+ thresholds: [],
1428
+ samples: {
1429
+ valid: [
1430
+ { code: '.btn { background: var(--color-bg); }', nearMiss: true },
1431
+ ],
1432
+ invalid: [
1433
+ { code: '.btn { background: #fff; }' },
1434
+ ],
1435
+ },
1436
+ },
1437
+ 'styles/mechanism-fragmentation': {
1438
+ analyzer: 'styles',
1439
+ field: 'rule',
1440
+ input: ['styles-css'],
1441
+ gating: false,
1442
+ resolvable: false,
1443
+ message: 'Styling mechanism fragmented across {count} mechanisms.',
1444
+ docs: 'styles/mechanism-fragmentation',
1445
+ thresholds: ['mechanismFragmentationMinMechanisms'],
1446
+ samples: {
1447
+ valid: [
1448
+ { code: '// one mechanism: css-modules only', nearMiss: true },
1449
+ ],
1450
+ invalid: [
1451
+ { code: '// inline styles + css-modules + tailwind + styled-components' },
1452
+ ],
1453
+ },
1454
+ },
1455
+ 'styles/mechanism-mixing': {
1456
+ analyzer: 'styles',
1457
+ field: 'rule',
1458
+ input: ['styles-css'],
1459
+ gating: false,
1460
+ resolvable: false,
1461
+ message: 'Mixing styling mechanisms in one file.',
1462
+ docs: 'styles/mechanism-mixing',
1463
+ thresholds: [],
1464
+ samples: {
1465
+ valid: [
1466
+ { code: '.x { color: var(--brand); }', nearMiss: true },
1467
+ ],
1468
+ invalid: [
1469
+ { code: 'const s = { color: "#fff" };\n<div style={s} className="x" />' },
1470
+ ],
1471
+ },
1472
+ },
1473
+ 'styles/declaration-set-similarity': {
1474
+ analyzer: 'styles',
1475
+ field: 'rule',
1476
+ input: ['styles-css'],
1477
+ gating: false,
1478
+ resolvable: false,
1479
+ message: 'Declaration set similar to another block ({similarity}%).',
1480
+ docs: 'styles/declaration-set-similarity',
1481
+ thresholds: ['declarationSetMinDeclarations', 'declarationSetSimilarityThreshold'],
1482
+ samples: {
1483
+ valid: [
1484
+ { code: '.a { color: red; margin: 0; padding: 1px; border: 0; }', nearMiss: true },
1485
+ ],
1486
+ invalid: [
1487
+ { code: '.a { color: red; margin: 0; padding: 1px; border: 0; }\n.b { color: red; margin: 0; padding: 1px; border: 0; }' },
1488
+ ],
1489
+ },
1490
+ },
1491
+ 'styles/z-index-sprawl': {
1492
+ analyzer: 'styles',
1493
+ field: 'rule',
1494
+ input: ['styles-css'],
1495
+ gating: false,
1496
+ resolvable: false,
1497
+ message: 'Z-index sprawl: {count} distinct z-index values.',
1498
+ docs: 'styles/z-index-sprawl',
1499
+ thresholds: ['zIndexMaxDistinct'],
1500
+ samples: {
1501
+ valid: [
1502
+ { code: '.m { z-index: 1; }', nearMiss: true },
1503
+ ],
1504
+ invalid: [
1505
+ { code: '.a { z-index: 1; } .b { z-index: 2; } .c { z-index: 3; } .d { z-index: 4; } .e { z-index: 5; }' },
1506
+ ],
1507
+ },
1508
+ },
1509
+ 'styles/z-index-singleton': {
1510
+ analyzer: 'styles',
1511
+ field: 'rule',
1512
+ input: ['styles-css'],
1513
+ gating: false,
1514
+ resolvable: false,
1515
+ message: 'Z-index value "{value}" appears only once.',
1516
+ docs: 'styles/z-index-singleton',
1517
+ thresholds: [],
1518
+ samples: {
1519
+ valid: [
1520
+ { code: '.m { z-index: 2; } .n { z-index: 2; }', nearMiss: true },
1521
+ ],
1522
+ invalid: [
1523
+ { code: '.m { z-index: 99; }' },
1524
+ ],
1525
+ },
1526
+ },
1527
+ // ── conventions (UniversalConventionsAnalyzer) ───────────────────────────
1528
+ 'conventions/usage-pair': {
1529
+ analyzer: 'conventions',
1530
+ field: 'rule',
1531
+ input: ['function-index'],
1532
+ gating: true,
1533
+ resolvable: true,
1534
+ message: '{pct}% of `{antecedent}` callers also call `{consequent}`.',
1535
+ docs: 'conventions/usage-pair',
1536
+ thresholds: ['pairConfidence'],
1537
+ samples: {
1538
+ valid: [
1539
+ { code: '// calls to openDb are independently distributed', nearMiss: true },
1540
+ ],
1541
+ invalid: [
1542
+ {
1543
+ code: 'function a() { openDb(); closeDb(); }\nfunction b() { openDb(); }\nfunction c() { openDb(); }\nfunction d() { openDb(); }\nfunction e() { openDb(); }',
1544
+ resolution: { action: 'pair-call', summary: 'Add the companion call to closeDb() wherever openDb() is called, matching the dominant usage pair.', symbols: ['closeDb', 'openDb'] },
1545
+ },
1546
+ ],
1547
+ },
1548
+ },
1549
+ 'conventions/import-form': {
1550
+ analyzer: 'conventions',
1551
+ field: 'rule',
1552
+ input: ['function-index'],
1553
+ gating: false,
1554
+ resolvable: false,
1555
+ message: 'Import form mismatch: use {form} for "{source}".',
1556
+ docs: 'conventions/import-form',
1557
+ thresholds: ['modeShare', 'minCorpus'],
1558
+ samples: {
1559
+ valid: [
1560
+ { code: 'import { x } from "./m";', nearMiss: true },
1561
+ ],
1562
+ invalid: [
1563
+ { code: 'import * as x from "./m";' },
1564
+ ],
1565
+ },
1566
+ },
1567
+ 'conventions/error-handling': {
1568
+ analyzer: 'conventions',
1569
+ field: 'rule',
1570
+ input: ['function-index'],
1571
+ gating: false,
1572
+ resolvable: false,
1573
+ message: 'Error-handling convention mismatch: {detail}.',
1574
+ docs: 'conventions/error-handling',
1575
+ thresholds: ['modeShare', 'minCorpus'],
1576
+ samples: {
1577
+ valid: [
1578
+ { code: 'const [v, err] = await tryRead();', nearMiss: true },
1579
+ ],
1580
+ invalid: [
1581
+ { code: 'const v = await tryRead();' },
1582
+ ],
1583
+ },
1584
+ },
1585
+ 'conventions/export-shape': {
1586
+ analyzer: 'conventions',
1587
+ field: 'rule',
1588
+ input: ['function-index'],
1589
+ gating: false,
1590
+ resolvable: false,
1591
+ message: 'Export shape mismatch: {detail}.',
1592
+ docs: 'conventions/export-shape',
1593
+ thresholds: ['modeShare', 'minCorpus'],
1594
+ samples: {
1595
+ valid: [
1596
+ { code: 'export default function f() {}', nearMiss: true },
1597
+ ],
1598
+ invalid: [
1599
+ { code: 'module.exports = { f };' },
1600
+ ],
1601
+ },
1602
+ },
1603
+ 'conventions/naming': {
1604
+ analyzer: 'conventions',
1605
+ field: 'rule',
1606
+ input: ['function-index'],
1607
+ gating: false,
1608
+ resolvable: false,
1609
+ message: 'Naming convention mismatch: {detail}.',
1610
+ docs: 'conventions/naming',
1611
+ thresholds: ['modeShare', 'minCorpus'],
1612
+ samples: {
1613
+ valid: [
1614
+ { code: 'function fetchUser() {}', nearMiss: true },
1615
+ ],
1616
+ invalid: [
1617
+ { code: 'function getData() {}' },
1618
+ ],
1619
+ },
1620
+ },
1621
+ // ── cross-domain (CrossDomainAnalyzer) ────────────────────────────────────
1622
+ 'cross-domain/written-never-read': {
1623
+ analyzer: 'cross-domain',
1624
+ field: 'rule',
1625
+ input: ['schema_usage', 'functions'],
1626
+ gating: false,
1627
+ resolvable: false,
1628
+ message: 'Table "{table}" is written but never read.',
1629
+ docs: 'cross-domain/written-never-read',
1630
+ thresholds: [],
1631
+ samples: {
1632
+ valid: [
1633
+ { code: 'INSERT INTO logs ...;\nSELECT * FROM logs ...;', nearMiss: true },
1634
+ ],
1635
+ invalid: [
1636
+ { code: 'INSERT INTO logs ...;' },
1637
+ ],
1638
+ },
1639
+ },
1640
+ 'cross-domain/read-never-written': {
1641
+ analyzer: 'cross-domain',
1642
+ field: 'rule',
1643
+ input: ['schema_usage', 'functions'],
1644
+ gating: false,
1645
+ resolvable: false,
1646
+ message: 'Table "{table}" is read but never written.',
1647
+ docs: 'cross-domain/read-never-written',
1648
+ thresholds: [],
1649
+ samples: {
1650
+ valid: [
1651
+ { code: 'INSERT INTO cfg ...;\nSELECT * FROM cfg ...;', nearMiss: true },
1652
+ ],
1653
+ invalid: [
1654
+ { code: 'SELECT * FROM cfg ...;' },
1655
+ ],
1656
+ },
1657
+ },
1658
+ 'cross-domain/transaction-boundary': {
1659
+ analyzer: 'cross-domain',
1660
+ field: 'rule',
1661
+ input: ['schema_usage', 'functions'],
1662
+ gating: false,
1663
+ resolvable: false,
1664
+ message: 'Transaction boundary spans {count} tables.',
1665
+ docs: 'cross-domain/transaction-boundary',
1666
+ thresholds: ['schemaLifecycle.txnTableMax'],
1667
+ samples: {
1668
+ valid: [
1669
+ { code: 'BEGIN; UPDATE users ...; COMMIT;', nearMiss: true },
1670
+ ],
1671
+ invalid: [
1672
+ { code: 'BEGIN; UPDATE a ...; UPDATE b ...; UPDATE c ...; UPDATE d ...; COMMIT;' },
1673
+ ],
1674
+ },
1675
+ },
1676
+ 'cross-domain/validation-bypass': {
1677
+ analyzer: 'cross-domain',
1678
+ field: 'rule',
1679
+ input: ['schema_usage', 'functions'],
1680
+ gating: false,
1681
+ resolvable: false,
1682
+ message: 'Validation bypass: {detail}.',
1683
+ docs: 'cross-domain/validation-bypass',
1684
+ thresholds: [],
1685
+ samples: {
1686
+ valid: [
1687
+ { code: 'const v = validate(input);\nuse(v);', nearMiss: true },
1688
+ ],
1689
+ invalid: [
1690
+ { code: 'use(input);' },
1691
+ ],
1692
+ },
1693
+ },
1694
+ 'cross-domain/uncovered-risk': {
1695
+ analyzer: 'cross-domain',
1696
+ field: 'rule',
1697
+ input: ['schema_usage', 'functions'],
1698
+ gating: false,
1699
+ resolvable: false,
1700
+ message: 'Uncovered risk: {detail}.',
1701
+ docs: 'cross-domain/uncovered-risk',
1702
+ thresholds: [],
1703
+ samples: {
1704
+ valid: [
1705
+ { code: '// risk mitigated by guard', nearMiss: true },
1706
+ ],
1707
+ invalid: [
1708
+ { code: '// risk present with no mitigation' },
1709
+ ],
1710
+ },
1711
+ },
123
1712
  };
1713
+ /**
1714
+ * Resolve a violation to its canonical {@link RuleRegistryEntry}, if any.
1715
+ *
1716
+ * The registry records which field on the Violation carries the rule ID
1717
+ * (`field` — `rule` for almost every analyzer, `type` for dependency-graph).
1718
+ * This helper reads that field off the violation and matches it against the
1719
+ * registry key, so gate decisions (Spec 36 R4/R6) never hard-code a field.
1720
+ *
1721
+ * Invariant violations (user-defined rule IDs) have no registry entry and
1722
+ * return `undefined` — they are always "new" and never gating (their gate
1723
+ * decision is made by the invariant rule engine, not this map).
1724
+ */
1725
+ export function getViolationRuleEntry(v) {
1726
+ if (!v.analyzer)
1727
+ return undefined;
1728
+ for (const [ruleId, entry] of Object.entries(RULE_REGISTRY)) {
1729
+ if (entry.analyzer !== v.analyzer)
1730
+ continue;
1731
+ const value = v[entry.field];
1732
+ if (value === ruleId)
1733
+ return entry;
1734
+ }
1735
+ return undefined;
1736
+ }
124
1737
  //# sourceMappingURL=ruleRegistry.js.map