@opensip-cli/graph 0.1.10 → 0.1.12

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 (211) hide show
  1. package/README.md +4 -2
  2. package/dist/__tests__/cache/engine-version.test.js +4 -4
  3. package/dist/__tests__/cache/engine-version.test.js.map +1 -1
  4. package/dist/__tests__/cli/graph.test.d.ts.map +1 -1
  5. package/dist/__tests__/cli/graph.test.js +0 -1
  6. package/dist/__tests__/cli/graph.test.js.map +1 -1
  7. package/dist/__tests__/internal-surface.test.js +1 -1
  8. package/dist/__tests__/internal-surface.test.js.map +1 -1
  9. package/dist/__tests__/public-api.test.js +14 -0
  10. package/dist/__tests__/public-api.test.js.map +1 -1
  11. package/dist/__tests__/render/catalog-json.test.d.ts.map +1 -1
  12. package/dist/__tests__/render/catalog-json.test.js +0 -1
  13. package/dist/__tests__/render/catalog-json.test.js.map +1 -1
  14. package/dist/__tests__/rules/registry.test.js +1 -0
  15. package/dist/__tests__/rules/registry.test.js.map +1 -1
  16. package/dist/cache/engine-version.d.ts.map +1 -1
  17. package/dist/cache/engine-version.js +5 -1
  18. package/dist/cache/engine-version.js.map +1 -1
  19. package/dist/cli/__tests__/equivalence-diagnostic.test.d.ts +7 -0
  20. package/dist/cli/__tests__/equivalence-diagnostic.test.d.ts.map +1 -0
  21. package/dist/cli/__tests__/equivalence-diagnostic.test.js +136 -0
  22. package/dist/cli/__tests__/equivalence-diagnostic.test.js.map +1 -0
  23. package/dist/cli/__tests__/graph-envelope-view.test.js +9 -9
  24. package/dist/cli/__tests__/graph-envelope-view.test.js.map +1 -1
  25. package/dist/cli/__tests__/graph-runner.test.d.ts +5 -0
  26. package/dist/cli/__tests__/graph-runner.test.d.ts.map +1 -0
  27. package/dist/cli/__tests__/graph-runner.test.js +100 -0
  28. package/dist/cli/__tests__/graph-runner.test.js.map +1 -0
  29. package/dist/cli/equivalence-check-command.d.ts.map +1 -1
  30. package/dist/cli/equivalence-check-command.js +35 -6
  31. package/dist/cli/equivalence-check-command.js.map +1 -1
  32. package/dist/cli/equivalence-diagnostic.d.ts +100 -0
  33. package/dist/cli/equivalence-diagnostic.d.ts.map +1 -0
  34. package/dist/cli/equivalence-diagnostic.js +138 -0
  35. package/dist/cli/equivalence-diagnostic.js.map +1 -0
  36. package/dist/cli/graph/graph-aux-command-specs.d.ts +0 -25
  37. package/dist/cli/graph/graph-aux-command-specs.d.ts.map +1 -1
  38. package/dist/cli/graph/graph-aux-command-specs.js +6 -12
  39. package/dist/cli/graph/graph-aux-command-specs.js.map +1 -1
  40. package/dist/cli/graph/graph-command-spec.d.ts +3 -3
  41. package/dist/cli/graph/graph-command-spec.d.ts.map +1 -1
  42. package/dist/cli/graph/graph-command-spec.js +7 -4
  43. package/dist/cli/graph/graph-command-spec.js.map +1 -1
  44. package/dist/cli/graph-config-schema.d.ts +3 -0
  45. package/dist/cli/graph-config-schema.d.ts.map +1 -1
  46. package/dist/cli/graph-config-schema.js +11 -1
  47. package/dist/cli/graph-config-schema.js.map +1 -1
  48. package/dist/cli/graph-config.d.ts.map +1 -1
  49. package/dist/cli/graph-config.js +5 -10
  50. package/dist/cli/graph-config.js.map +1 -1
  51. package/dist/cli/graph-envelope-view.d.ts +7 -36
  52. package/dist/cli/graph-envelope-view.d.ts.map +1 -1
  53. package/dist/cli/graph-envelope-view.js +7 -102
  54. package/dist/cli/graph-envelope-view.js.map +1 -1
  55. package/dist/cli/graph-modes.d.ts.map +1 -1
  56. package/dist/cli/graph-modes.js +6 -5
  57. package/dist/cli/graph-modes.js.map +1 -1
  58. package/dist/cli/graph-report.d.ts +4 -6
  59. package/dist/cli/graph-report.d.ts.map +1 -1
  60. package/dist/cli/graph-report.js +2 -2
  61. package/dist/cli/graph-report.js.map +1 -1
  62. package/dist/cli/graph-result-delivery.d.ts +30 -0
  63. package/dist/cli/graph-result-delivery.d.ts.map +1 -0
  64. package/dist/cli/graph-result-delivery.js +231 -0
  65. package/dist/cli/graph-result-delivery.js.map +1 -0
  66. package/dist/cli/graph-runner.d.ts +2 -83
  67. package/dist/cli/graph-runner.d.ts.map +1 -1
  68. package/dist/cli/graph-runner.js +71 -215
  69. package/dist/cli/graph-runner.js.map +1 -1
  70. package/dist/cli/graph-session-contribution.d.ts.map +1 -1
  71. package/dist/cli/graph-session-contribution.js +6 -2
  72. package/dist/cli/graph-session-contribution.js.map +1 -1
  73. package/dist/cli/graph-sharded-engine.d.ts.map +1 -1
  74. package/dist/cli/graph-sharded-engine.js +0 -1
  75. package/dist/cli/graph-sharded-engine.js.map +1 -1
  76. package/dist/cli/graph-single-run-mode.d.ts.map +1 -1
  77. package/dist/cli/graph-single-run-mode.js +0 -4
  78. package/dist/cli/graph-single-run-mode.js.map +1 -1
  79. package/dist/cli/graph-worker.d.ts.map +1 -1
  80. package/dist/cli/graph-worker.js +0 -1
  81. package/dist/cli/graph-worker.js.map +1 -1
  82. package/dist/cli/graph.d.ts +2 -22
  83. package/dist/cli/graph.d.ts.map +1 -1
  84. package/dist/cli/graph.js +3 -256
  85. package/dist/cli/graph.js.map +1 -1
  86. package/dist/cli/list-files.d.ts.map +1 -1
  87. package/dist/cli/list-files.js +0 -2
  88. package/dist/cli/list-files.js.map +1 -1
  89. package/dist/cli/orchestrate/__tests__/cross-shard-resolve.test.js +76 -0
  90. package/dist/cli/orchestrate/__tests__/cross-shard-resolve.test.js.map +1 -1
  91. package/dist/cli/orchestrate/cache-orchestrator.d.ts.map +1 -1
  92. package/dist/cli/orchestrate/cache-orchestrator.js +0 -1
  93. package/dist/cli/orchestrate/cache-orchestrator.js.map +1 -1
  94. package/dist/cli/orchestrate/catalog-stats.d.ts.map +1 -1
  95. package/dist/cli/orchestrate/catalog-stats.js +0 -1
  96. package/dist/cli/orchestrate/catalog-stats.js.map +1 -1
  97. package/dist/cli/orchestrate/cross-shard-resolve.d.ts.map +1 -1
  98. package/dist/cli/orchestrate/cross-shard-resolve.js +77 -2
  99. package/dist/cli/orchestrate/cross-shard-resolve.js.map +1 -1
  100. package/dist/cli/orchestrate/flat-monorepo-strategy.d.ts.map +1 -1
  101. package/dist/cli/orchestrate/flat-monorepo-strategy.js +0 -1
  102. package/dist/cli/orchestrate/flat-monorepo-strategy.js.map +1 -1
  103. package/dist/cli/orchestrate/sharded-graph.js +0 -4
  104. package/dist/cli/orchestrate/sharded-graph.js.map +1 -1
  105. package/dist/cli/orchestrate.d.ts.map +1 -1
  106. package/dist/cli/orchestrate.js +0 -1
  107. package/dist/cli/orchestrate.js.map +1 -1
  108. package/dist/cli/pressure-monitor.d.ts +2 -0
  109. package/dist/cli/pressure-monitor.d.ts.map +1 -1
  110. package/dist/cli/pressure-monitor.js +15 -1
  111. package/dist/cli/pressure-monitor.js.map +1 -1
  112. package/dist/cli/report-data.d.ts.map +1 -1
  113. package/dist/cli/report-data.js +0 -1
  114. package/dist/cli/report-data.js.map +1 -1
  115. package/dist/cli/workspace-runner.d.ts.map +1 -1
  116. package/dist/cli/workspace-runner.js +0 -1
  117. package/dist/cli/workspace-runner.js.map +1 -1
  118. package/dist/identity.d.ts +5 -0
  119. package/dist/identity.d.ts.map +1 -0
  120. package/dist/identity.js +6 -0
  121. package/dist/identity.js.map +1 -0
  122. package/dist/index.d.ts +8 -2
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +14 -3
  125. package/dist/index.js.map +1 -1
  126. package/dist/internal.d.ts +1 -1
  127. package/dist/internal.d.ts.map +1 -1
  128. package/dist/internal.js +4 -6
  129. package/dist/internal.js.map +1 -1
  130. package/dist/lang-adapter/__tests__/language-of-file.test.d.ts +2 -0
  131. package/dist/lang-adapter/__tests__/language-of-file.test.d.ts.map +1 -0
  132. package/dist/lang-adapter/__tests__/language-of-file.test.js +36 -0
  133. package/dist/lang-adapter/__tests__/language-of-file.test.js.map +1 -0
  134. package/dist/lang-adapter/__tests__/near-duplicate-signature.test.d.ts +2 -0
  135. package/dist/lang-adapter/__tests__/near-duplicate-signature.test.d.ts.map +1 -0
  136. package/dist/lang-adapter/__tests__/near-duplicate-signature.test.js +78 -0
  137. package/dist/lang-adapter/__tests__/near-duplicate-signature.test.js.map +1 -0
  138. package/dist/lang-adapter/body-digest.d.ts +9 -41
  139. package/dist/lang-adapter/body-digest.d.ts.map +1 -1
  140. package/dist/lang-adapter/body-digest.js +9 -44
  141. package/dist/lang-adapter/body-digest.js.map +1 -1
  142. package/dist/lang-adapter/language-of-file.d.ts +15 -0
  143. package/dist/lang-adapter/language-of-file.d.ts.map +1 -0
  144. package/dist/lang-adapter/language-of-file.js +43 -0
  145. package/dist/lang-adapter/language-of-file.js.map +1 -0
  146. package/dist/lang-adapter/near-duplicate-signature.d.ts +12 -0
  147. package/dist/lang-adapter/near-duplicate-signature.d.ts.map +1 -0
  148. package/dist/lang-adapter/near-duplicate-signature.js +12 -0
  149. package/dist/lang-adapter/near-duplicate-signature.js.map +1 -0
  150. package/dist/lang-adapter/read-source.d.ts +20 -0
  151. package/dist/lang-adapter/read-source.d.ts.map +1 -0
  152. package/dist/lang-adapter/read-source.js +48 -0
  153. package/dist/lang-adapter/read-source.js.map +1 -0
  154. package/dist/lang-adapter/read-source.test.d.ts +2 -0
  155. package/dist/lang-adapter/read-source.test.d.ts.map +1 -0
  156. package/dist/lang-adapter/read-source.test.js +41 -0
  157. package/dist/lang-adapter/read-source.test.js.map +1 -0
  158. package/dist/lang-adapter/registry.d.ts.map +1 -1
  159. package/dist/lang-adapter/registry.js +0 -1
  160. package/dist/lang-adapter/registry.js.map +1 -1
  161. package/dist/persistence/catalog-repo.d.ts.map +1 -1
  162. package/dist/persistence/catalog-repo.js +1 -0
  163. package/dist/persistence/catalog-repo.js.map +1 -1
  164. package/dist/persistence/session-payload.d.ts.map +1 -1
  165. package/dist/persistence/session-payload.js +0 -1
  166. package/dist/persistence/session-payload.js.map +1 -1
  167. package/dist/persistence/session-replay.d.ts.map +1 -1
  168. package/dist/persistence/session-replay.js +10 -25
  169. package/dist/persistence/session-replay.js.map +1 -1
  170. package/dist/pipeline/feature-deps.d.ts.map +1 -1
  171. package/dist/pipeline/feature-deps.js +0 -1
  172. package/dist/pipeline/feature-deps.js.map +1 -1
  173. package/dist/pipeline/features.d.ts.map +1 -1
  174. package/dist/pipeline/features.js +0 -1
  175. package/dist/pipeline/features.js.map +1 -1
  176. package/dist/pipeline/indexes.d.ts.map +1 -1
  177. package/dist/pipeline/indexes.js +0 -1
  178. package/dist/pipeline/indexes.js.map +1 -1
  179. package/dist/recipes/resolve.d.ts.map +1 -1
  180. package/dist/recipes/resolve.js +0 -1
  181. package/dist/recipes/resolve.js.map +1 -1
  182. package/dist/render/rule-id-mapping.d.ts.map +1 -1
  183. package/dist/render/rule-id-mapping.js +1 -0
  184. package/dist/render/rule-id-mapping.js.map +1 -1
  185. package/dist/rules/__tests__/near-duplicate-function-body.test.d.ts +2 -0
  186. package/dist/rules/__tests__/near-duplicate-function-body.test.d.ts.map +1 -0
  187. package/dist/rules/__tests__/near-duplicate-function-body.test.js +142 -0
  188. package/dist/rules/__tests__/near-duplicate-function-body.test.js.map +1 -0
  189. package/dist/rules/_entry-points.d.ts.map +1 -1
  190. package/dist/rules/_entry-points.js +0 -1
  191. package/dist/rules/_entry-points.js.map +1 -1
  192. package/dist/rules/define-rule.d.ts.map +1 -1
  193. package/dist/rules/define-rule.js +0 -3
  194. package/dist/rules/define-rule.js.map +1 -1
  195. package/dist/rules/duplicated-function-body.d.ts +12 -34
  196. package/dist/rules/duplicated-function-body.d.ts.map +1 -1
  197. package/dist/rules/duplicated-function-body.js +83 -233
  198. package/dist/rules/duplicated-function-body.js.map +1 -1
  199. package/dist/rules/near-duplicate-function-body.d.ts +11 -0
  200. package/dist/rules/near-duplicate-function-body.d.ts.map +1 -0
  201. package/dist/rules/near-duplicate-function-body.js +74 -0
  202. package/dist/rules/near-duplicate-function-body.js.map +1 -0
  203. package/dist/rules/registry.d.ts.map +1 -1
  204. package/dist/rules/registry.js +2 -1
  205. package/dist/rules/registry.js.map +1 -1
  206. package/dist/tool.d.ts.map +1 -1
  207. package/dist/tool.js +8 -4
  208. package/dist/tool.js.map +1 -1
  209. package/dist/types.d.ts +21 -1
  210. package/dist/types.d.ts.map +1 -1
  211. package/package.json +16 -10
@@ -34,19 +34,16 @@ export function defineRule(config) {
34
34
  if (typeof config.slug !== 'string' ||
35
35
  config.slug.length === 0 ||
36
36
  !config.slug.startsWith('graph:')) {
37
- // @fitness-ignore-next-line result-pattern-consistency -- authoring-time programmer error: invalid rule metadata
38
37
  throw new ValidationError(`defineRule: 'slug' must be a non-empty string starting with 'graph:' (got ${JSON.stringify(config.slug)})`, {
39
38
  code: VALIDATION_CODE,
40
39
  });
41
40
  }
42
41
  if (config.defaultSeverity !== 'error' && config.defaultSeverity !== 'warning') {
43
- // @fitness-ignore-next-line result-pattern-consistency -- authoring-time programmer error: invalid rule metadata
44
42
  throw new ValidationError(`defineRule: 'defaultSeverity' must be 'error' or 'warning' (got ${JSON.stringify(config.defaultSeverity)})`, {
45
43
  code: VALIDATION_CODE,
46
44
  });
47
45
  }
48
46
  if (typeof config.evaluate !== 'function') {
49
- // @fitness-ignore-next-line result-pattern-consistency -- authoring-time programmer error: invalid rule metadata
50
47
  throw new ValidationError(`defineRule: 'evaluate' must be a function for rule '${config.slug}'`, {
51
48
  code: VALIDATION_CODE,
52
49
  });
@@ -1 +1 @@
1
- {"version":3,"file":"define-rule.js","sourceRoot":"","sources":["../../src/rules/define-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAiDpD,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,IACE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;QAC/B,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QACxB,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EACjC,CAAC;QACD,iHAAiH;QACjH,MAAM,IAAI,eAAe,CACvB,6EAA6E,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAC3G;YACE,IAAI,EAAE,eAAe;SACtB,CACF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,eAAe,KAAK,OAAO,IAAI,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC/E,iHAAiH;QACjH,MAAM,IAAI,eAAe,CACvB,mEAAmE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,EAC5G;YACE,IAAI,EAAE,eAAe;SACtB,CACF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC1C,iHAAiH;QACjH,MAAM,IAAI,eAAe,CACvB,uDAAuD,MAAM,CAAC,IAAI,GAAG,EACrE;YACE,IAAI,EAAE,eAAe;SACtB,CACF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAS;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,QAAQ,CACN,OAAgB,EAChB,OAAgB,EAChB,UAAuB,EACvB,KAAiB,EACjB,QAAuB;YAEvB,uEAAuE;YACvE,0EAA0E;YAC1E,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpF,CAAC;KACF,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"define-rule.js","sourceRoot":"","sources":["../../src/rules/define-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAiDpD,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAAwB;IACjD,IACE,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;QAC/B,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;QACxB,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EACjC,CAAC;QACD,MAAM,IAAI,eAAe,CACvB,6EAA6E,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAC3G;YACE,IAAI,EAAE,eAAe;SACtB,CACF,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,eAAe,KAAK,OAAO,IAAI,MAAM,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QAC/E,MAAM,IAAI,eAAe,CACvB,mEAAmE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,EAC5G;YACE,IAAI,EAAE,eAAe;SACtB,CACF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,eAAe,CACvB,uDAAuD,MAAM,CAAC,IAAI,GAAG,EACrE;YACE,IAAI,EAAE,eAAe;SACtB,CACF,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAS;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,QAAQ,CACN,OAAgB,EAChB,OAAgB,EAChB,UAAuB,EACvB,KAAiB,EACjB,QAAuB;YAEvB,uEAAuE;YACvE,0EAA0E;YAC1E,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpF,CAAC;KACF,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
@@ -1,40 +1,18 @@
1
1
  /**
2
- * graph:duplicated-function-body — group catalog entries whose body
3
- * normalizes to the same hash and report duplicate bodies. The rule has
4
- * two complementary code paths under one slug:
2
+ * graph:duplicated-function-body — group catalog entries whose body normalizes to the
3
+ * same hash and report duplicate bodies. Two complementary paths under one slug:
5
4
  *
6
- * 1. **Per-instance (size-gated).** A "duplicate" is two or more
7
- * functions whose normalized body is byte-identical
8
- * (whitespace/comment normalization handled by hashFunctionBody).
9
- * Two thresholds filter out non-actionable hits:
5
+ * 1. Per-instance (size-gated): N-1 signals per group of ≥2 byte-identical normalized
6
+ * bodies passing a line floor (default 5) AND a normalized-char floor (default 200,
7
+ * which drops thin `defineCheck`/pass-through wrappers).
8
+ * 2. Aggregate (cross-package): one signal per body hash in ≥ 3 distinct packages,
9
+ * suppressing that hash's per-instance signals (lighter body-size-only floor, 80).
10
10
  *
11
- * - minDuplicateBodyLines (default 5): the source span must be at
12
- * least this many lines.
13
- * - minDuplicateBodySize (default 200): the *normalized* body
14
- * (post comment-strip + whitespace-collapse) must be at least
15
- * this many characters. This catches the common case of a
16
- * `defineCheck({ ... analyze(content) { return analyzeFile(...) } })`
17
- * wrapper where the source spans 8+ lines but the executable
18
- * body is only ~80 characters once normalized — structurally
19
- * identical to every other thin wrapper, but never an actionable
20
- * refactor target.
21
- *
22
- * Emits N-1 signals per group (one per non-primary occurrence).
23
- *
24
- * 2. **Aggregate (cross-package).** For each body hash present in
25
- * ≥ minCrossPackageDuplicatePackages (default 3) DISTINCT packages
26
- * (via `pkgOf`), the rule emits ONE aggregate signal naming the
27
- * packages and SUPPRESSES the per-instance signals for that same hash
28
- * (no double-reporting). Bodies that don't reach N packages flow
29
- * through path (1) unchanged.
30
- *
31
- * This path applies a LIGHTER, body-size-only floor
32
- * (`minCrossPackageDuplicateBodySize`, default 80 chars — no line floor)
33
- * rather than the per-instance floor: a *small* body copied across
34
- * packages is exactly what this path exists to catch, so the floor is
35
- * tuned only to drop TRIVIAL bodies (empty DI-constructor shims,
36
- * one-line getters, thin delegators) that are never consolidation
37
- * targets, while keeping genuinely-small shared utilities visible.
11
+ * The detection algorithm + curation policy now live in `@opensip-cli/clone-detection`
12
+ * (ADR-0064) so graph and yagni single-source them. This rule is a thin adapter: it maps
13
+ * the catalog to `CloneCandidate[]` (pre-resolving `bodyLines` from the feature table and
14
+ * `package` from `pkgOf`), calls `findDuplicateBodies`, and wraps each finding into a
15
+ * graph `Signal` with the unchanged message/severity/metadata so output is byte-stable.
38
16
  */
39
17
  export declare const duplicatedFunctionBodyRule: import("../types.js").Rule;
40
18
  //# sourceMappingURL=duplicated-function-body.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"duplicated-function-body.d.ts","sourceRoot":"","sources":["../../src/rules/duplicated-function-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAoBH,eAAO,MAAM,0BAA0B,4BA0DrC,CAAC"}
1
+ {"version":3,"file":"duplicated-function-body.d.ts","sourceRoot":"","sources":["../../src/rules/duplicated-function-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAeH,eAAO,MAAM,0BAA0B,4BAoBrC,CAAC"}
@@ -1,262 +1,112 @@
1
1
  /**
2
- * graph:duplicated-function-body — group catalog entries whose body
3
- * normalizes to the same hash and report duplicate bodies. The rule has
4
- * two complementary code paths under one slug:
2
+ * graph:duplicated-function-body — group catalog entries whose body normalizes to the
3
+ * same hash and report duplicate bodies. Two complementary paths under one slug:
5
4
  *
6
- * 1. **Per-instance (size-gated).** A "duplicate" is two or more
7
- * functions whose normalized body is byte-identical
8
- * (whitespace/comment normalization handled by hashFunctionBody).
9
- * Two thresholds filter out non-actionable hits:
5
+ * 1. Per-instance (size-gated): N-1 signals per group of ≥2 byte-identical normalized
6
+ * bodies passing a line floor (default 5) AND a normalized-char floor (default 200,
7
+ * which drops thin `defineCheck`/pass-through wrappers).
8
+ * 2. Aggregate (cross-package): one signal per body hash in ≥ 3 distinct packages,
9
+ * suppressing that hash's per-instance signals (lighter body-size-only floor, 80).
10
10
  *
11
- * - minDuplicateBodyLines (default 5): the source span must be at
12
- * least this many lines.
13
- * - minDuplicateBodySize (default 200): the *normalized* body
14
- * (post comment-strip + whitespace-collapse) must be at least
15
- * this many characters. This catches the common case of a
16
- * `defineCheck({ ... analyze(content) { return analyzeFile(...) } })`
17
- * wrapper where the source spans 8+ lines but the executable
18
- * body is only ~80 characters once normalized — structurally
19
- * identical to every other thin wrapper, but never an actionable
20
- * refactor target.
21
- *
22
- * Emits N-1 signals per group (one per non-primary occurrence).
23
- *
24
- * 2. **Aggregate (cross-package).** For each body hash present in
25
- * ≥ minCrossPackageDuplicatePackages (default 3) DISTINCT packages
26
- * (via `pkgOf`), the rule emits ONE aggregate signal naming the
27
- * packages and SUPPRESSES the per-instance signals for that same hash
28
- * (no double-reporting). Bodies that don't reach N packages flow
29
- * through path (1) unchanged.
30
- *
31
- * This path applies a LIGHTER, body-size-only floor
32
- * (`minCrossPackageDuplicateBodySize`, default 80 chars — no line floor)
33
- * rather than the per-instance floor: a *small* body copied across
34
- * packages is exactly what this path exists to catch, so the floor is
35
- * tuned only to drop TRIVIAL bodies (empty DI-constructor shims,
36
- * one-line getters, thin delegators) that are never consolidation
37
- * targets, while keeping genuinely-small shared utilities visible.
11
+ * The detection algorithm + curation policy now live in `@opensip-cli/clone-detection`
12
+ * (ADR-0064) so graph and yagni single-source them. This rule is a thin adapter: it maps
13
+ * the catalog to `CloneCandidate[]` (pre-resolving `bodyLines` from the feature table and
14
+ * `package` from `pkgOf`), calls `findDuplicateBodies`, and wraps each finding into a
15
+ * graph `Signal` with the unchanged message/severity/metadata so output is byte-stable.
38
16
  */
17
+ import { findDuplicateBodies } from '@opensip-cli/clone-detection';
39
18
  import { pkgOf } from '../resolve-callee.js';
40
19
  import { createGraphSignal } from './create-graph-signal.js';
41
20
  import { defineRule } from './define-rule.js';
42
- const DEFAULT_MIN_LINES = 5;
43
- const DEFAULT_MIN_BODY_SIZE = 200;
44
- const DEFAULT_MIN_CROSS_PACKAGE_PACKAGES = 3;
45
- // Lighter than DEFAULT_MIN_BODY_SIZE (the per-instance floor) and applied with
46
- // NO line floor: the aggregate path is meant to catch genuinely-small shared
47
- // utilities copied across packages, so its floor only drops trivial bodies
48
- // (empty DI shims, one-line getters/delegators).
49
- const DEFAULT_MIN_CROSS_PACKAGE_BODY_SIZE = 80;
50
21
  const SLUG = 'graph:duplicated-function-body';
51
22
  export const duplicatedFunctionBodyRule = defineRule({
52
23
  slug: SLUG,
53
24
  defaultSeverity: 'warning',
54
25
  featureDeps: ['bodyLines'],
55
26
  evaluate({ catalog, config, features }) {
56
- const minLines = config.minDuplicateBodyLines ?? DEFAULT_MIN_LINES;
57
- const minBodySize = config.minDuplicateBodySize ?? DEFAULT_MIN_BODY_SIZE;
58
- const minPackages = config.minCrossPackageDuplicatePackages ?? DEFAULT_MIN_CROSS_PACKAGE_PACKAGES;
59
- const minCrossPackageBodySize = config.minCrossPackageDuplicateBodySize ?? DEFAULT_MIN_CROSS_PACKAGE_BODY_SIZE;
60
- const signals = [];
61
- // Aggregate path first: group every kind/test-eligible occurrence by
62
- // body hash (no line floor) so we can detect cross-package spread and
63
- // decide which hashes to suppress on the per-instance path below.
64
- const aggregateBuckets = groupByHashUnfloored(catalog);
65
- const suppressedHashes = new Set();
66
- for (const [bodyHash, occs] of aggregateBuckets) {
67
- const packages = [...new Set(occs.map((o) => pkgOf(o)))].sort();
68
- if (packages.length < minPackages)
69
- continue;
70
- const anchor = lowestByQualifiedName(occs);
71
- // Lighter, body-size-only floor (NO line floor): keeps the aggregate
72
- // path catching genuinely-small shared utilities copied across packages,
73
- // while dropping trivial bodies — empty DI-constructor shims, one-line
74
- // getters, thin delegators — that are not consolidation targets. A body
75
- // that fails this won't surface on the per-instance path either (its
76
- // 200-char floor is stricter), so there is nothing to suppress.
77
- if (anchor.bodySize !== undefined && anchor.bodySize < minCrossPackageBodySize)
78
- continue;
79
- // This hash is owned by the aggregate path; suppress its per-instance
80
- // signals so a single duplicate group never double-reports.
81
- suppressedHashes.add(bodyHash);
82
- signals.push(createGraphSignal(SLUG, config, {
83
- severity: 'low',
84
- category: 'quality',
85
- message: `This body is duplicated across ${String(packages.length)} packages (${packages.join(', ')}) in ${String(occs.length)} occurrences — hoist it into a shared package.`,
86
- code: { file: anchor.filePath, line: anchor.line, column: anchor.column },
87
- suggestion: 'Hoist the shared body into a single shared package and have every copy import it.',
88
- metadata: {
89
- packages,
90
- packageCount: packages.length,
91
- occurrenceCount: occs.length,
92
- bodyHash,
93
- },
94
- }));
95
- }
96
- // Per-instance path: size-gated groups, skipping any hash already
97
- // claimed by an aggregate signal so a group never double-reports.
98
- const groups = groupByHash(catalog, minLines, minBodySize, features);
99
- signals.push(...emitPerInstanceSignals(groups, suppressedHashes, config));
100
- return signals;
27
+ const candidates = toCandidates(catalog, features);
28
+ const { aggregates, groups } = findDuplicateBodies(candidates, {
29
+ minLines: config.minDuplicateBodyLines,
30
+ minBodySize: config.minDuplicateBodySize,
31
+ minCrossPackagePackages: config.minCrossPackageDuplicatePackages,
32
+ minCrossPackageBodySize: config.minCrossPackageDuplicateBodySize,
33
+ });
34
+ // Aggregate (cross-package) signals first, then per-instance signals — same order
35
+ // as the prior loops, but built as bounded in-memory projections over rule findings.
36
+ return [
37
+ ...aggregates.map((agg) => aggregateSignal(agg, config)),
38
+ ...groups.flatMap((group) => groupSignals(group, config)),
39
+ ];
101
40
  },
102
41
  });
103
- /**
104
- * Per-instance duplicate signals: N-1 per size-gated group, skipping any hash
105
- * already claimed by an aggregate signal (no double-reporting). Extracted from
106
- * evaluate() to keep its cognitive complexity within budget.
107
- */
108
- function emitPerInstanceSignals(groups, suppressedHashes, config) {
109
- const signals = [];
110
- for (const group of groups) {
111
- if (group.length < 2)
112
- continue;
113
- const primary = group[0];
114
- /* v8 ignore next */
115
- if (!primary)
116
- continue;
117
- if (suppressedHashes.has(primary.bodyHash))
118
- continue;
119
- for (let i = 1; i < group.length; i++) {
120
- const occ = group[i];
121
- /* v8 ignore next */
122
- if (!occ)
123
- continue;
124
- signals.push(createGraphSignal(SLUG, config, {
125
- severity: 'low',
126
- category: 'quality',
127
- message: `${occ.simpleName} has the same body as ${primary.qualifiedName} (${primary.filePath}:${String(primary.line)}).`,
128
- code: { file: occ.filePath, line: occ.line, column: occ.column },
129
- suggestion: 'Extract the shared body to a single function and have both call sites import it.',
130
- metadata: {
131
- primary: primary.qualifiedName,
132
- duplicate: occ.qualifiedName,
133
- groupSize: group.length,
134
- },
135
- }));
136
- }
137
- }
138
- return signals;
139
- }
140
- /**
141
- * A function's stable physical identity: where it is declared. The catalog's
142
- * `functions` record is keyed by simple name, and the same physical function
143
- * can surface more than once across that index (e.g. indexed under both its
144
- * simple and qualified name, or a recursive function whose self-reference is
145
- * walked as an extra occurrence). Two entries that share this identity are the
146
- * SAME function — they must never count as a duplicate of each other. Keying on
147
- * file + declaration position + simple name keeps genuinely-distinct functions
148
- * (same body, different file/line) in separate identities while collapsing
149
- * self-matches.
150
- */
151
- function identityOf(occ) {
152
- return `${occ.filePath}:${String(occ.line)}:${String(occ.column)}:${occ.simpleName}`;
42
+ function aggregateSignal(agg, config) {
43
+ return createGraphSignal(SLUG, config, {
44
+ severity: 'low',
45
+ category: 'quality',
46
+ message: `This body is duplicated across ${String(agg.packages.length)} packages (${agg.packages.join(', ')}) in ${String(agg.occurrenceCount)} occurrences — hoist it into a shared package.`,
47
+ code: { file: agg.anchor.filePath, line: agg.anchor.line, column: agg.anchor.column },
48
+ suggestion: 'Hoist the shared body into a single shared package and have every copy import it.',
49
+ metadata: {
50
+ packages: agg.packages,
51
+ packageCount: agg.packages.length,
52
+ occurrenceCount: agg.occurrenceCount,
53
+ bodyHash: agg.bodyHash,
54
+ },
55
+ });
153
56
  }
154
- /**
155
- * Add an occurrence to its body-hash bucket, deduping by physical identity so a
156
- * function never appears twice in its own bucket. Without this, a function that
157
- * surfaces more than once in the catalog index (self-reference, dual name
158
- * indexing) would form a phantom 2-member group and self-report as a duplicate.
159
- */
160
- function pushDeduped(buckets, seenByHash, occ) {
161
- let bucket = buckets.get(occ.bodyHash);
162
- let seen = seenByHash.get(occ.bodyHash);
163
- if (!bucket) {
164
- bucket = [];
165
- buckets.set(occ.bodyHash, bucket);
166
- seen = new Set();
167
- seenByHash.set(occ.bodyHash, seen);
168
- }
57
+ function groupSignals(group, config) {
58
+ const primary = group.members[0];
169
59
  /* v8 ignore next */
170
- if (!seen)
171
- return;
172
- const id = identityOf(occ);
173
- if (seen.has(id))
174
- return;
175
- seen.add(id);
176
- bucket.push(occ);
177
- }
178
- function groupByHash(catalog, minLines, minBodySize, features) {
179
- // Walk the catalog directly; the byBodyHash index dedupes by hash,
180
- // which is exactly what we need to NOT do here.
181
- const buckets = new Map();
182
- const seenByHash = new Map();
183
- for (const name of Object.keys(catalog.functions)) {
184
- const occs = catalog.functions[name];
185
- /* v8 ignore next */
186
- if (!occs)
187
- continue;
188
- for (const occ of occs) {
189
- if (!isInterestingForDup(occ, minLines, minBodySize, features))
190
- continue;
191
- pushDeduped(buckets, seenByHash, occ);
192
- }
193
- }
194
- return [...buckets.values()];
60
+ if (!primary)
61
+ return [];
62
+ return group.members.slice(1).map((occ) => createGraphSignal(SLUG, config, {
63
+ severity: 'low',
64
+ category: 'quality',
65
+ message: `${occ.simpleName} has the same body as ${primary.qualifiedName} (${primary.filePath}:${String(primary.line)}).`,
66
+ code: { file: occ.filePath, line: occ.line, column: occ.column },
67
+ suggestion: 'Extract the shared body to a single function and have both call sites import it.',
68
+ metadata: {
69
+ primary: primary.qualifiedName,
70
+ duplicate: occ.qualifiedName,
71
+ groupSize: group.members.length,
72
+ },
73
+ }));
195
74
  }
196
75
  /**
197
- * Group occurrences by body hash applying ONLY the kind/test-file
198
- * exclusions (no size/line floor) the grouping the aggregate
199
- * cross-package path consumes. Returns a Map so callers keep the body
200
- * hash key for suppression bookkeeping.
76
+ * Map the catalog to `CloneCandidate[]` in the SAME iteration order the prior in-rule
77
+ * grouping used (`Object.keys(catalog.functions)` each name's occurrences), so the
78
+ * substrate's bucket/primary selection and emission order are byte-identical.
79
+ * `bodyLines` is pre-resolved from the feature table (canonical span) with the inline
80
+ * `endLine − line + 1` fallback handled by the substrate; `package` is `pkgOf` (the
81
+ * nearest-package.json resolution the aggregate path keys on).
201
82
  */
202
- function groupByHashUnfloored(catalog) {
203
- const buckets = new Map();
204
- const seenByHash = new Map();
83
+ function toCandidates(catalog, features) {
84
+ const candidates = [];
205
85
  for (const name of Object.keys(catalog.functions)) {
206
86
  const occs = catalog.functions[name];
207
87
  /* v8 ignore next */
208
88
  if (!occs)
209
89
  continue;
210
- for (const occ of occs) {
211
- if (!isEligibleKind(occ))
212
- continue;
213
- pushDeduped(buckets, seenByHash, occ);
214
- }
90
+ for (const occ of occs)
91
+ candidates.push(toCandidate(occ, features));
215
92
  }
216
- return buckets;
217
- }
218
- function lowestByQualifiedName(occs) {
219
- return occs.reduce((lo, c) => (c.qualifiedName < lo.qualifiedName ? c : lo));
93
+ return candidates;
220
94
  }
221
- /**
222
- * The kind/test-file exclusions shared by both code paths. Inline arrows
223
- * / function expressions / module-init occurrences are never an
224
- * extract/hoist target:
225
- * - test-suite arrows with identical 5-line bodies are common boilerplate
226
- * - module-inits hash whatever is at top level; identical hashes mean
227
- * twin files, not a dup-extraction opportunity
228
- * - constructor/getter/setter/method dups are interesting; keep those
229
- *
230
- * Test-file occurrences are excluded on both paths.
231
- */
232
- function isEligibleKind(occ) {
233
- if (occ.kind === 'arrow' || occ.kind === 'function-expression' || occ.kind === 'module-init') {
234
- return false;
235
- }
236
- if (occ.inTestFile)
237
- return false;
238
- return true;
239
- }
240
- /**
241
- * The per-instance dup-body filter: the shared kind/test exclusions plus
242
- * the size/line floor. The bodySize threshold drops trivial wrappers — a
243
- * `defineCheck` or pass-through analyze that contains an `if`-guard plus a
244
- * delegating call. These have identical normalized bodies across many
245
- * files but are never the right refactor target. Catalogs from older runs
246
- * that lack `bodySize` skip the size check (treated as "passes").
247
- */
248
- function isInterestingForDup(occ, minLines, minBodySize, features) {
249
- if (!isEligibleKind(occ))
250
- return false;
251
- // The bodyLines feature column is the canonical span (computed once in
252
- // pipeline/features.ts). The inline `endLine − line + 1` here is the single
253
- // sanctioned graceful-degrade fallback for features-absent calls (3/4-arg
254
- // test evaluate), not a duplicate of the engine derivation.
255
- const span = features?.function.get(occ.bodyHash)?.bodyLines ?? occ.endLine - occ.line + 1;
256
- if (span < minLines)
257
- return false;
258
- if (occ.bodySize !== undefined && occ.bodySize < minBodySize)
259
- return false;
260
- return true;
95
+ function toCandidate(occ, features) {
96
+ const bodyLines = features?.function.get(occ.bodyHash)?.bodyLines;
97
+ return {
98
+ bodyHash: occ.bodyHash,
99
+ kind: occ.kind,
100
+ inTestFile: occ.inTestFile,
101
+ filePath: occ.filePath,
102
+ line: occ.line,
103
+ column: occ.column,
104
+ endLine: occ.endLine,
105
+ simpleName: occ.simpleName,
106
+ qualifiedName: occ.qualifiedName,
107
+ package: pkgOf(occ),
108
+ ...(occ.bodySize === undefined ? {} : { bodySize: occ.bodySize }),
109
+ ...(bodyLines === undefined ? {} : { bodyLines }),
110
+ };
261
111
  }
262
112
  //# sourceMappingURL=duplicated-function-body.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"duplicated-function-body.js","sourceRoot":"","sources":["../../src/rules/duplicated-function-body.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"duplicated-function-body.js","sourceRoot":"","sources":["../../src/rules/duplicated-function-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAM9C,MAAM,IAAI,GAAG,gCAAgC,CAAC;AAE9C,MAAM,CAAC,MAAM,0BAA0B,GAAG,UAAU,CAAC;IACnD,IAAI,EAAE,IAAI;IACV,eAAe,EAAE,SAAS;IAC1B,WAAW,EAAE,CAAC,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE;QACpC,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAAC,UAAU,EAAE;YAC7D,QAAQ,EAAE,MAAM,CAAC,qBAAqB;YACtC,WAAW,EAAE,MAAM,CAAC,oBAAoB;YACxC,uBAAuB,EAAE,MAAM,CAAC,gCAAgC;YAChE,uBAAuB,EAAE,MAAM,CAAC,gCAAgC;SACjE,CAAC,CAAC;QAEH,kFAAkF;QAClF,qFAAqF;QACrF,OAAO;YACL,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxD,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;SAC1D,CAAC;IACJ,CAAC;CACF,CAAC,CAAC;AAEH,SAAS,eAAe,CACtB,GAAiE,EACjE,MAAmB;IAEnB,OAAO,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE;QACrC,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,kCAAkC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,gDAAgD;QAC9L,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE;QACrF,UAAU,EAAE,mFAAmF;QAC/F,QAAQ,EAAE;YACR,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,YAAY,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM;YACjC,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,QAAQ,EAAE,GAAG,CAAC,QAAQ;SACvB;KACF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CACnB,KAA+D,EAC/D,MAAmB;IAEnB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,oBAAoB;IACpB,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACxC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE;QAC9B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,SAAS;QACnB,OAAO,EAAE,GAAG,GAAG,CAAC,UAAU,yBAAyB,OAAO,CAAC,aAAa,KAAK,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI;QACzH,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;QAChE,UAAU,EACR,kFAAkF;QACpF,QAAQ,EAAE;YACR,OAAO,EAAE,OAAO,CAAC,aAAa;YAC9B,SAAS,EAAE,GAAG,CAAC,aAAa;YAC5B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;SAChC;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,OAAgB,EAAE,QAAkC;IACxE,MAAM,UAAU,GAAqB,EAAE,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACrC,oBAAoB;QACpB,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,WAAW,CAAC,GAAuB,EAAE,QAAkC;IAC9E,MAAM,SAAS,GAAG,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAClE,OAAO;QACL,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC;QACnB,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjE,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;KAClD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * graph:near-duplicate-function-body — LSH-banded MinHash near-clone detection.
3
+ *
4
+ * Complements `graph:duplicated-function-body` (exact hash). The detection algorithm now
5
+ * lives in `@opensip-cli/clone-detection` (ADR-0064); this rule is a thin adapter that
6
+ * maps the catalog to `CloneCandidate[]` (stamping `language` from `languageOfFile` for
7
+ * the same-language gate, which stays in graph), calls `findNearDuplicates`, and wraps
8
+ * each cluster into a graph `Signal` with the unchanged message/severity/metadata.
9
+ */
10
+ export declare const nearDuplicateFunctionBodyRule: import("../types.js").Rule;
11
+ //# sourceMappingURL=near-duplicate-function-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"near-duplicate-function-body.d.ts","sourceRoot":"","sources":["../../src/rules/near-duplicate-function-body.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,eAAO,MAAM,6BAA6B,4BA4BxC,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * graph:near-duplicate-function-body — LSH-banded MinHash near-clone detection.
3
+ *
4
+ * Complements `graph:duplicated-function-body` (exact hash). The detection algorithm now
5
+ * lives in `@opensip-cli/clone-detection` (ADR-0064); this rule is a thin adapter that
6
+ * maps the catalog to `CloneCandidate[]` (stamping `language` from `languageOfFile` for
7
+ * the same-language gate, which stays in graph), calls `findNearDuplicates`, and wraps
8
+ * each cluster into a graph `Signal` with the unchanged message/severity/metadata.
9
+ */
10
+ import { findNearDuplicates } from '@opensip-cli/clone-detection';
11
+ import { languageOfFile } from '../lang-adapter/language-of-file.js';
12
+ import { createGraphSignal } from './create-graph-signal.js';
13
+ import { defineRule } from './define-rule.js';
14
+ const SLUG = 'graph:near-duplicate-function-body';
15
+ export const nearDuplicateFunctionBodyRule = defineRule({
16
+ slug: SLUG,
17
+ defaultSeverity: 'warning',
18
+ evaluate({ catalog, config }) {
19
+ const candidates = toCandidates(catalog);
20
+ const clusters = findNearDuplicates(candidates, {
21
+ minSimilarity: config.minNearDuplicateSimilarity,
22
+ minBodySize: config.minNearDuplicateBodySize,
23
+ lshBands: config.nearDuplicateLshBands,
24
+ });
25
+ return clusters.map((c) => createGraphSignal(SLUG, config, {
26
+ severity: 'low',
27
+ category: 'quality',
28
+ message: `${String(c.nearMembers.length)} near-duplicate function bodies cluster around ${c.anchor.qualifiedName} (estimated Jaccard ≥ ${c.estimatedSimilarity.toFixed(2)}).`,
29
+ code: { file: c.anchor.filePath, line: c.anchor.line, column: c.anchor.column },
30
+ suggestion: 'Extract the shared logic into one function and replace the near-clone copies with calls to it.',
31
+ metadata: {
32
+ nearMembers: c.nearMembers,
33
+ exactMembers: c.exactMembers,
34
+ estimatedSimilarity: c.estimatedSimilarity,
35
+ clusterSize: c.clusterSize,
36
+ },
37
+ }));
38
+ },
39
+ });
40
+ /**
41
+ * Map the catalog to `CloneCandidate[]` in catalog iteration order (so the substrate's
42
+ * `collectEligible` index identity — which the union-find clustering depends on — matches
43
+ * the prior in-rule behavior). `language` is the caller-resolved same-language gate
44
+ * (`languageOfFile`, which stays in graph by the rule of three).
45
+ */
46
+ function toCandidates(catalog) {
47
+ const candidates = [];
48
+ for (const name of Object.keys(catalog.functions)) {
49
+ const occs = catalog.functions[name];
50
+ if (!occs)
51
+ continue;
52
+ for (const occ of occs)
53
+ candidates.push(toCandidate(occ));
54
+ }
55
+ return candidates;
56
+ }
57
+ function toCandidate(occ) {
58
+ const language = languageOfFile(occ.filePath);
59
+ return {
60
+ bodyHash: occ.bodyHash,
61
+ kind: occ.kind,
62
+ inTestFile: occ.inTestFile,
63
+ filePath: occ.filePath,
64
+ line: occ.line,
65
+ column: occ.column,
66
+ endLine: occ.endLine,
67
+ simpleName: occ.simpleName,
68
+ qualifiedName: occ.qualifiedName,
69
+ ...(occ.bodySignature === undefined ? {} : { bodySignature: occ.bodySignature }),
70
+ ...(occ.bodySize === undefined ? {} : { bodySize: occ.bodySize }),
71
+ ...(language === undefined ? {} : { language }),
72
+ };
73
+ }
74
+ //# sourceMappingURL=near-duplicate-function-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"near-duplicate-function-body.js","sourceRoot":"","sources":["../../src/rules/near-duplicate-function-body.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;GAgBG;AAeH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAyBxC;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAInB;;IAQH,MAAM,IAAI,SAAS,IAAI,EAAE;CAG1B;AAED,+DAA+D;AAC/D,wBAAgB,mBAAmB,IAAI,kBAAkB,CAExD;AA2BD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,SAAS,IAAI,EAAE,CAE9C"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAgBH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AA0BxC;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAInB;;IAQH,MAAM,IAAI,SAAS,IAAI,EAAE;CAG1B;AAED,+DAA+D;AAC/D,wBAAgB,mBAAmB,IAAI,kBAAkB,CAExD;AA2BD;;;GAGG;AACH,wBAAgB,YAAY,IAAI,SAAS,IAAI,EAAE,CAE9C"}
@@ -1,4 +1,3 @@
1
- // @fitness-ignore-file batch-operation-limits -- iterates bounded collection (registered rules + getAll() returning the rule list per RunScope)
2
1
  /**
3
2
  * Rule registry — per-RunScope.
4
3
  *
@@ -22,6 +21,7 @@ import { cycleRule } from './cycle.js';
22
21
  import { duplicatedFunctionBodyRule } from './duplicated-function-body.js';
23
22
  import { highBlastUntestedRule } from './high-blast-untested.js';
24
23
  import { largeFunctionRule } from './large-function.js';
24
+ import { nearDuplicateFunctionBodyRule } from './near-duplicate-function-body.js';
25
25
  import { noSideEffectPathRule } from './no-side-effect-path.js';
26
26
  import { orphanSubtreeRule } from './orphan-subtree.js';
27
27
  import { testOnlyReachableRule } from './test-only-reachable.js';
@@ -33,6 +33,7 @@ import { wideFunctionRule } from './wide-function.js';
33
33
  const BUILT_IN_RULES = [
34
34
  orphanSubtreeRule,
35
35
  duplicatedFunctionBodyRule,
36
+ nearDuplicateFunctionBodyRule,
36
37
  noSideEffectPathRule,
37
38
  testOnlyReachableRule,
38
39
  alwaysThrowsBranchRule,
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"AAAA,gJAAgJ;AAChJ;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AAE9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD;;uBAEuB;AACvB,MAAM,cAAc,GAAoB;IACtC,iBAAiB;IACjB,0BAA0B;IAC1B,oBAAoB;IACpB,qBAAqB;IACrB,sBAAsB;IACtB,4CAA4C;IAC5C,iBAAiB;IACjB,gBAAgB;IAChB,qBAAqB;IACrB,SAAS;IACT,sBAAsB;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACZ,KAAK,GAAG,IAAI,QAAQ,CAAmB;QACtD,MAAM,EAAE,aAAa;QACrB,eAAe,EAAE,iBAAiB;QAClC,SAAS,EAAE,qBAAqB;KACjC,CAAC,CAAC;IAEH;QACE,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,kBAAkB,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB;IAC3B,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;IAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,2DAA2D;YACzD,0EAA0E;YAC1E,8DAA8D,CACjE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,uEAAuE;YACrE,sDAAsD,CACzD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,oBAAoB,EAAE,CAAC,MAAM,EAAE,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/rules/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AAE9E,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,6BAA6B,EAAE,MAAM,mCAAmC,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAUtD;;uBAEuB;AACvB,MAAM,cAAc,GAAoB;IACtC,iBAAiB;IACjB,0BAA0B;IAC1B,6BAA6B;IAC7B,oBAAoB;IACpB,qBAAqB;IACrB,sBAAsB;IACtB,4CAA4C;IAC5C,iBAAiB;IACjB,gBAAgB;IAChB,qBAAqB;IACrB,SAAS;IACT,sBAAsB;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACZ,KAAK,GAAG,IAAI,QAAQ,CAAmB;QACtD,MAAM,EAAE,aAAa;QACrB,eAAe,EAAE,iBAAiB;QAClC,SAAS,EAAE,qBAAqB;KACjC,CAAC,CAAC;IAEH;QACE,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,kBAAkB,EAAE,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB;IAC3B,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;IAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,2DAA2D;YACzD,0EAA0E;YAC1E,8DAA8D,CACjE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,uEAAuE;YACrE,sDAAsD,CACzD,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,OAAO,oBAAoB,EAAE,CAAC,MAAM,EAAE,CAAC;AACzC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;GAmBG;AA8BH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAGV,IAAI,EAGL,MAAM,mBAAmB,CAAC;AAmG3B;;GAEG;AACH,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAE9C,eAAO,MAAM,eAAe,yCAAyC,CAAC;AAEtE,eAAO,MAAM,SAAS,EAAE,IAoBtB,CAAC"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/tool.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;;;GAmBG;AA+BH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAuB,IAAI,EAAa,MAAM,mBAAmB,CAAC;AAmG9E;;GAEG;AACH,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAE9C,eAAO,MAAM,eAAe,yCAAyC,CAAC;AAEtE,eAAO,MAAM,SAAS,EAAE,IAuBtB,CAAC"}