weavatrix 0.3.14 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +231 -199
  3. package/bin/native/darwin-arm64/weavatrix +0 -0
  4. package/bin/native/darwin-x64/weavatrix +0 -0
  5. package/bin/native/linux-arm64/weavatrix +0 -0
  6. package/bin/native/linux-x64/weavatrix +0 -0
  7. package/bin/native/win32-arm64/weavatrix.exe +0 -0
  8. package/bin/native/win32-x64/weavatrix.exe +0 -0
  9. package/bin/resolve-binary.mjs +73 -0
  10. package/bin/run-native.mjs +26 -0
  11. package/bin/weavatrix-mcp.mjs +7 -6
  12. package/bin/weavatrix.mjs +5 -0
  13. package/package.json +8 -66
  14. package/skill/SKILL.md +62 -374
  15. package/skill/agents/openai.yaml +4 -0
  16. package/skill/references/tool-routing.md +45 -0
  17. package/SECURITY.md +0 -101
  18. package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
  19. package/docs/agent-task-benchmark.md +0 -58
  20. package/docs/releases/v0.2.19.md +0 -33
  21. package/docs/releases/v0.3.0.md +0 -43
  22. package/docs/releases/v0.3.1.md +0 -30
  23. package/docs/releases/v0.3.10.md +0 -76
  24. package/docs/releases/v0.3.11.md +0 -55
  25. package/docs/releases/v0.3.12.md +0 -44
  26. package/docs/releases/v0.3.13.md +0 -34
  27. package/docs/releases/v0.3.14.md +0 -37
  28. package/docs/releases/v0.3.2.md +0 -15
  29. package/docs/releases/v0.3.3.md +0 -25
  30. package/docs/releases/v0.3.4.md +0 -40
  31. package/docs/releases/v0.3.5.md +0 -50
  32. package/docs/releases/v0.3.6.md +0 -44
  33. package/docs/releases/v0.3.7.md +0 -37
  34. package/docs/releases/v0.3.8.md +0 -21
  35. package/docs/releases/v0.3.9.md +0 -36
  36. package/scripts/run-agent-task-benchmark.mjs +0 -118
  37. package/src/analysis/allowed-test-runner.js +0 -70
  38. package/src/analysis/architecture/contract-graph.js +0 -119
  39. package/src/analysis/architecture/contract-schema.js +0 -110
  40. package/src/analysis/architecture/contract-storage.js +0 -35
  41. package/src/analysis/architecture/contract-verification.js +0 -168
  42. package/src/analysis/architecture-contract.js +0 -16
  43. package/src/analysis/audit-debt.js +0 -94
  44. package/src/analysis/audit-extensions.js +0 -60
  45. package/src/analysis/cargo-dependency-evidence.js +0 -108
  46. package/src/analysis/cargo-manifests.js +0 -81
  47. package/src/analysis/change-classification/diff-parser.js +0 -103
  48. package/src/analysis/change-classification/options.js +0 -40
  49. package/src/analysis/change-classification/symbol-classifier.js +0 -177
  50. package/src/analysis/change-classification.js +0 -188
  51. package/src/analysis/coverage-reports-tarpaulin.js +0 -112
  52. package/src/analysis/coverage-reports.js +0 -246
  53. package/src/analysis/cycle-route.js +0 -14
  54. package/src/analysis/data-flow-evidence.js +0 -114
  55. package/src/analysis/dead-check.js +0 -300
  56. package/src/analysis/dead-code-review/candidates.js +0 -194
  57. package/src/analysis/dead-code-review/policy.js +0 -23
  58. package/src/analysis/dead-code-review.js +0 -119
  59. package/src/analysis/dep-check-ecosystems.js +0 -205
  60. package/src/analysis/dep-check.js +0 -290
  61. package/src/analysis/dep-rules.js +0 -10
  62. package/src/analysis/dependency/conventions.js +0 -16
  63. package/src/analysis/dependency/scoped-dependencies.js +0 -51
  64. package/src/analysis/dependency/source-references.js +0 -21
  65. package/src/analysis/duplicate-groups.js +0 -104
  66. package/src/analysis/duplicates-worker.js +0 -11
  67. package/src/analysis/duplicates.compute.js +0 -269
  68. package/src/analysis/duplicates.js +0 -15
  69. package/src/analysis/duplicates.run.js +0 -52
  70. package/src/analysis/duplicates.tokenize.js +0 -199
  71. package/src/analysis/endpoints/common.js +0 -79
  72. package/src/analysis/endpoints/extract.js +0 -120
  73. package/src/analysis/endpoints/inventory.js +0 -84
  74. package/src/analysis/endpoints/mounts.js +0 -129
  75. package/src/analysis/endpoints-java.js +0 -260
  76. package/src/analysis/endpoints-rust.js +0 -124
  77. package/src/analysis/endpoints.js +0 -3
  78. package/src/analysis/findings.js +0 -70
  79. package/src/analysis/git-history/analytics.js +0 -177
  80. package/src/analysis/git-history/collector.js +0 -67
  81. package/src/analysis/git-history/options.js +0 -66
  82. package/src/analysis/git-history.js +0 -143
  83. package/src/analysis/git-ref-graph.js +0 -76
  84. package/src/analysis/go-dependency-evidence.js +0 -62
  85. package/src/analysis/graph-analysis.aggregate.js +0 -296
  86. package/src/analysis/graph-analysis.edges.js +0 -24
  87. package/src/analysis/graph-analysis.js +0 -7
  88. package/src/analysis/graph-analysis.refs.js +0 -202
  89. package/src/analysis/graph-analysis.summaries.js +0 -65
  90. package/src/analysis/health-capabilities.js +0 -83
  91. package/src/analysis/hot-path-review.js +0 -266
  92. package/src/analysis/http-contract-wrappers.js +0 -241
  93. package/src/analysis/http-contracts/analysis.js +0 -179
  94. package/src/analysis/http-contracts/client-call-detection.js +0 -82
  95. package/src/analysis/http-contracts/client-call-parser.js +0 -273
  96. package/src/analysis/http-contracts/graph-context.js +0 -143
  97. package/src/analysis/http-contracts/matching.js +0 -61
  98. package/src/analysis/http-contracts/shared.js +0 -85
  99. package/src/analysis/http-contracts.js +0 -6
  100. package/src/analysis/internal-audit/dependency-health.js +0 -113
  101. package/src/analysis/internal-audit/python-manifests.js +0 -78
  102. package/src/analysis/internal-audit/repo-files.js +0 -50
  103. package/src/analysis/internal-audit/supply-chain.js +0 -149
  104. package/src/analysis/internal-audit.collect.js +0 -213
  105. package/src/analysis/internal-audit.js +0 -9
  106. package/src/analysis/internal-audit.reach.js +0 -201
  107. package/src/analysis/internal-audit.run.js +0 -256
  108. package/src/analysis/java-source.js +0 -36
  109. package/src/analysis/jvm-artifact-index.js +0 -87
  110. package/src/analysis/jvm-dependency-evidence.js +0 -138
  111. package/src/analysis/jvm-manifests.js +0 -114
  112. package/src/analysis/manifests.js +0 -123
  113. package/src/analysis/package-reachability.js +0 -39
  114. package/src/analysis/source-complexity.ast.js +0 -97
  115. package/src/analysis/source-complexity.constants.js +0 -55
  116. package/src/analysis/source-complexity.js +0 -14
  117. package/src/analysis/source-complexity.report.js +0 -81
  118. package/src/analysis/source-complexity.walk.js +0 -228
  119. package/src/analysis/source-correctness/retry-patterns.js +0 -93
  120. package/src/analysis/source-correctness.js +0 -222
  121. package/src/analysis/static-test-reachability.js +0 -150
  122. package/src/analysis/structure/dependency-graph.js +0 -151
  123. package/src/analysis/structure/findings.js +0 -162
  124. package/src/analysis/task-retrieval.js +0 -107
  125. package/src/analysis/transport-contracts.js +0 -224
  126. package/src/analysis/transport-runtime-evidence.js +0 -155
  127. package/src/analysis-kit.mjs +0 -17
  128. package/src/bounds.js +0 -4
  129. package/src/build-graph.js +0 -209
  130. package/src/child-env.js +0 -7
  131. package/src/config.js +0 -5
  132. package/src/extension/local-services.mjs +0 -75
  133. package/src/git-exec.js +0 -67
  134. package/src/graph/build-worker.js +0 -49
  135. package/src/graph/builder/go-receiver-resolution.js +0 -112
  136. package/src/graph/builder/js/dynamic-import.js +0 -26
  137. package/src/graph/builder/js/queries.js +0 -48
  138. package/src/graph/builder/lang-csharp.js +0 -40
  139. package/src/graph/builder/lang-css.js +0 -29
  140. package/src/graph/builder/lang-go.js +0 -140
  141. package/src/graph/builder/lang-html.js +0 -29
  142. package/src/graph/builder/lang-java-calls.js +0 -142
  143. package/src/graph/builder/lang-java.js +0 -215
  144. package/src/graph/builder/lang-js.js +0 -291
  145. package/src/graph/builder/lang-python.js +0 -235
  146. package/src/graph/builder/lang-rust-calls.js +0 -63
  147. package/src/graph/builder/lang-rust.js +0 -299
  148. package/src/graph/builder/lang-solidity.js +0 -126
  149. package/src/graph/builder/lang-sql.js +0 -263
  150. package/src/graph/builder/pass2-resolution.js +0 -81
  151. package/src/graph/builder/spec-pkg.js +0 -87
  152. package/src/graph/community.js +0 -27
  153. package/src/graph/edge-provenance.js +0 -31
  154. package/src/graph/file-lock.js +0 -69
  155. package/src/graph/freshness-probe.js +0 -142
  156. package/src/graph/graph-filter.js +0 -63
  157. package/src/graph/incremental-refresh.js +0 -235
  158. package/src/graph/internal-builder.barrels.js +0 -235
  159. package/src/graph/internal-builder.build.js +0 -300
  160. package/src/graph/internal-builder.java.js +0 -33
  161. package/src/graph/internal-builder.js +0 -12
  162. package/src/graph/internal-builder.langs.js +0 -157
  163. package/src/graph/internal-builder.pass2.js +0 -163
  164. package/src/graph/internal-builder.resolvers.js +0 -286
  165. package/src/graph/layout.js +0 -51
  166. package/src/graph/node-id.js +0 -7
  167. package/src/graph/parser-artifact-boundary.js +0 -55
  168. package/src/graph/relations.js +0 -4
  169. package/src/graph/repo-registry.js +0 -124
  170. package/src/graph/resolvers/rust.js +0 -117
  171. package/src/infra/infra-items.js +0 -255
  172. package/src/infra/infra-registry.js +0 -184
  173. package/src/infra/infra.detect.js +0 -119
  174. package/src/infra/infra.js +0 -38
  175. package/src/infra/infra.match.js +0 -102
  176. package/src/infra/infra.scan.js +0 -93
  177. package/src/mcp/actions/graph-lifecycle.mjs +0 -148
  178. package/src/mcp/architecture-bootstrap.mjs +0 -168
  179. package/src/mcp/architecture-starter.mjs +0 -234
  180. package/src/mcp/catalog.mjs +0 -233
  181. package/src/mcp/company-contract-page.mjs +0 -142
  182. package/src/mcp/company-contract-reconcile.mjs +0 -87
  183. package/src/mcp/company-contract-verdict.mjs +0 -42
  184. package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
  185. package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
  186. package/src/mcp/evidence/package-graph-common.mjs +0 -115
  187. package/src/mcp/evidence/package-lock-graph.mjs +0 -92
  188. package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
  189. package/src/mcp/evidence-snapshot.common.mjs +0 -158
  190. package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
  191. package/src/mcp/evidence-snapshot.health.mjs +0 -102
  192. package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
  193. package/src/mcp/evidence-snapshot.mjs +0 -50
  194. package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
  195. package/src/mcp/evidence-snapshot.structure.mjs +0 -84
  196. package/src/mcp/extension-api.mjs +0 -77
  197. package/src/mcp/git-output.mjs +0 -7
  198. package/src/mcp/graph/context-core.mjs +0 -111
  199. package/src/mcp/graph/context-seeds.mjs +0 -207
  200. package/src/mcp/graph/context-state.mjs +0 -85
  201. package/src/mcp/graph/reverse-reach.mjs +0 -42
  202. package/src/mcp/graph/tools-core.mjs +0 -143
  203. package/src/mcp/graph/tools-query.mjs +0 -276
  204. package/src/mcp/graph-context.mjs +0 -5
  205. package/src/mcp/graph-diff.mjs +0 -218
  206. package/src/mcp/health/audit-format.mjs +0 -199
  207. package/src/mcp/health/audit.mjs +0 -199
  208. package/src/mcp/health/dead-code.mjs +0 -110
  209. package/src/mcp/health/duplicates.mjs +0 -121
  210. package/src/mcp/health/endpoints.mjs +0 -71
  211. package/src/mcp/health/structure.mjs +0 -220
  212. package/src/mcp/runtime-version.mjs +0 -31
  213. package/src/mcp/server/auto-refresh.mjs +0 -66
  214. package/src/mcp/server/runtime-config.mjs +0 -43
  215. package/src/mcp/server/shutdown.mjs +0 -40
  216. package/src/mcp/staleness-notice.mjs +0 -20
  217. package/src/mcp/sync/evidence-architecture.mjs +0 -54
  218. package/src/mcp/sync/evidence-common.mjs +0 -88
  219. package/src/mcp/sync/evidence-duplicates.mjs +0 -97
  220. package/src/mcp/sync/evidence-health.mjs +0 -56
  221. package/src/mcp/sync/evidence-packages.mjs +0 -95
  222. package/src/mcp/sync/payload-common.mjs +0 -97
  223. package/src/mcp/sync/payload-v2.mjs +0 -54
  224. package/src/mcp/sync/payload-v3.mjs +0 -79
  225. package/src/mcp/sync-evidence.mjs +0 -23
  226. package/src/mcp/sync-payload.mjs +0 -70
  227. package/src/mcp/tool-result.mjs +0 -54
  228. package/src/mcp/tools-actions.mjs +0 -11
  229. package/src/mcp/tools-architecture.mjs +0 -177
  230. package/src/mcp/tools-company.mjs +0 -251
  231. package/src/mcp/tools-context.mjs +0 -222
  232. package/src/mcp/tools-endpoints.mjs +0 -207
  233. package/src/mcp/tools-graph-hubs.mjs +0 -78
  234. package/src/mcp/tools-graph.mjs +0 -17
  235. package/src/mcp/tools-health.mjs +0 -24
  236. package/src/mcp/tools-history.mjs +0 -28
  237. package/src/mcp/tools-impact-change.mjs +0 -220
  238. package/src/mcp/tools-impact-precision.mjs +0 -89
  239. package/src/mcp/tools-impact.mjs +0 -169
  240. package/src/mcp/tools-source.mjs +0 -238
  241. package/src/mcp/tools-verified-change.mjs +0 -200
  242. package/src/mcp-rg.mjs +0 -50
  243. package/src/mcp-runtime.mjs +0 -2
  244. package/src/mcp-server.mjs +0 -298
  245. package/src/mcp-source-tools.mjs +0 -163
  246. package/src/path-classification.js +0 -226
  247. package/src/path-ignore.js +0 -69
  248. package/src/precision/lsp-client/constants.js +0 -12
  249. package/src/precision/lsp-client/environment.js +0 -20
  250. package/src/precision/lsp-client/errors.js +0 -15
  251. package/src/precision/lsp-client/lifecycle.js +0 -114
  252. package/src/precision/lsp-client/message-parser.js +0 -81
  253. package/src/precision/lsp-client/protocol.js +0 -212
  254. package/src/precision/lsp-client/registry.js +0 -59
  255. package/src/precision/lsp-client/repo-uri.js +0 -60
  256. package/src/precision/lsp-client/stdio-client.js +0 -140
  257. package/src/precision/lsp-client.js +0 -10
  258. package/src/precision/lsp-overlay/budget-policy.js +0 -78
  259. package/src/precision/lsp-overlay/build.js +0 -261
  260. package/src/precision/lsp-overlay/contract.js +0 -61
  261. package/src/precision/lsp-overlay/reference-results.js +0 -108
  262. package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
  263. package/src/precision/lsp-overlay/source-session.js +0 -142
  264. package/src/precision/lsp-overlay/store.js +0 -150
  265. package/src/precision/lsp-overlay/target-index.js +0 -152
  266. package/src/precision/lsp-overlay/target-query.js +0 -55
  267. package/src/precision/lsp-overlay.js +0 -11
  268. package/src/precision/symbol-query.js +0 -224
  269. package/src/precision/typescript-lsp-provider.js +0 -12
  270. package/src/precision/typescript-provider/client.js +0 -89
  271. package/src/precision/typescript-provider/discovery.js +0 -124
  272. package/src/precision/typescript-provider/isolated-runtime.js +0 -39
  273. package/src/precision/typescript-provider/project-host.js +0 -254
  274. package/src/precision/typescript-provider/project-safety.js +0 -166
  275. package/src/precision/typescript-provider/reference-usage.js +0 -53
  276. package/src/process.js +0 -95
  277. package/src/repo-path.js +0 -53
  278. package/src/scan/discover.inventory.js +0 -78
  279. package/src/scan/discover.js +0 -5
  280. package/src/scan/discover.list.js +0 -79
  281. package/src/scan/discover.stack.js +0 -227
  282. package/src/scan/search.core.js +0 -24
  283. package/src/scan/search.git.js +0 -102
  284. package/src/scan/search.js +0 -9
  285. package/src/scan/search.node.js +0 -83
  286. package/src/scan/search.preview.js +0 -49
  287. package/src/scan/search.rg.js +0 -145
  288. package/src/security/advisory-store.js +0 -172
  289. package/src/security/installed-jvm-rust.js +0 -46
  290. package/src/security/installed.js +0 -280
  291. package/src/security/malware-file-heuristics.js +0 -121
  292. package/src/security/malware-heuristics.exclusions.js +0 -134
  293. package/src/security/malware-heuristics.js +0 -15
  294. package/src/security/malware-heuristics.roots.js +0 -145
  295. package/src/security/malware-heuristics.scan.js +0 -89
  296. package/src/security/malware-heuristics.sweep.js +0 -122
  297. package/src/security/malware-scoring.js +0 -105
  298. package/src/security/match.js +0 -85
  299. package/src/security/registry-sig.classify.js +0 -137
  300. package/src/security/registry-sig.js +0 -18
  301. package/src/security/registry-sig.rules.js +0 -192
  302. package/src/security/rust-advisory-report.js +0 -60
  303. package/src/security/typosquat.js +0 -81
  304. package/src/util.js +0 -40
  305. package/src/version.js +0 -7
@@ -1,290 +0,0 @@
1
- // Pure dependency checker (replaces depcheck + knip's dependency output) — set math over the graph's
2
- // externalImports vs package.json. NO filesystem here: the fs wrapper is internal-audit.js, so this is
3
- // fully unit-testable (same pattern as dead-check.js computeDead). See DEPS_SECURITY_PLAN.md (P1).
4
- //
5
- // Philosophy: bias to FALSE-NEGATIVES. A dep we can't prove unused stays unflagged (or drops to low
6
- // confidence) — knip's ~100 framework plugins know config conventions we don't, so we compensate with
7
- // script/config-text mention scanning + a config-ecosystem prefix rule, and we NEVER say "safe to
8
- // auto-remove", only "review".
9
- import { makeFinding } from "./findings.js";
10
- import {FRAMEWORK_RUNTIME_PEERS, IMPLICIT_STYLE_COMPILERS} from './dependency/conventions.js'
11
- import {createScopedDepFindings} from './dependency/scoped-dependencies.js'
12
- import {createPackageSourceMatcher} from './dependency/source-references.js'
13
-
14
- // Packages referenced by config CONVENTION, not imports (eslint extends "airbnb" → eslint-config-airbnb).
15
- // Flagged only at low confidence when nothing mentions them anywhere.
16
- const CONFIG_ECOSYSTEM_RE = /^(eslint-(config|plugin)-|@typescript-eslint\/|@eslint\/|prettier-plugin-|postcss-|autoprefixer$|tailwindcss$|babel-(plugin|preset)-|@babel\/(plugin|preset)-|stylelint-|@commitlint\/|commitlint-|remark-|rehype-|@semantic-release\/|karma-|grunt-|gulp-)/;
17
-
18
- // CLI name → package name, for script commands whose binary doesn't equal the package
19
- // (`tsc` comes from typescript, `depcruise` from dependency-cruiser, …).
20
- const BIN_PKG = {
21
- tsc: "typescript", depcruise: "dependency-cruiser", "vue-cli-service": "@vue/cli-service",
22
- ng: "@angular/cli", nest: "@nestjs/cli", sb: "storybook",
23
- "electron-rebuild": "@electron/rebuild", playwright: "@playwright/test",
24
- };
25
-
26
- const isStylesheetSpecifier = (spec) => /\.(?:css|scss|sass|less|styl(?:us)?)(?:[?#].*)?$/i.test(String(spec || ""));
27
- const escRe = (s) => String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
28
- // word-ish mention: the name not embedded inside a longer identifier/path segment
29
- const mentioned = (blob, name) => new RegExp(`(^|[^\\w@.-])${escRe(name)}($|[^\\w.-])`).test(blob);
30
-
31
- // computeDepFindings({ externalImports, pkg, workspacePkgNames, configTexts }) → { findings, usedPackages }
32
- // externalImports — graph.json's array (P0): {file, spec, pkg, builtin, kind, line, dynamic?, unresolved?}
33
- // pkg — parsed package.json ({} for non-JS repos → no dep findings)
34
- // workspacePkgNames — Set of monorepo-local package names (never "missing")
35
- // configTexts — Map<fileName, text> of root config files + CI workflows (mention scanning)
36
- // sourceTexts — Map<fileName, text> for narrow dynamic/package-path literal evidence
37
- export function computeDepFindings({
38
- externalImports = [], pkg = {}, workspacePkgNames = new Set(), configTexts = new Map(),
39
- sourceTexts = new Map(), aliases = [], scope = "", manifest = "package.json", nonRuntimeRoots = [], sourceFiles = [],
40
- } = {}) {
41
- const findings = [];
42
- const meta = { scope: scope || ".", manifest };
43
- const isAliasSpec = (spec) => aliases.some((a) => {
44
- const s = String(spec || "");
45
- const key = typeof a === "string" ? a : String(a?.key || "");
46
- const prefix = typeof a === "string" ? a.replace(/\*.*$/, "") : String(a?.prefix || "");
47
- const suffix = key.includes("*") ? key.slice(key.indexOf("*") + 1) : String(a?.suffix || "");
48
- return key.includes("*")
49
- ? !!prefix && s.startsWith(prefix) && (!suffix || s.endsWith(suffix))
50
- : !!key && s === key;
51
- });
52
- const sections = {
53
- dependencies: pkg.dependencies || {},
54
- devDependencies: pkg.devDependencies || {},
55
- peerDependencies: pkg.peerDependencies || {},
56
- optionalDependencies: pkg.optionalDependencies || {},
57
- };
58
- const allDeclared = new Set(Object.values(sections).flatMap((s) => Object.keys(s)));
59
- const selfName = String(pkg.name || "");
60
- const normRoots = (nonRuntimeRoots || []).map((root) => String(root || "").replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+|\/+$/g, "")).filter(Boolean);
61
- const isNonRuntimeFile = (file) => {
62
- const normalized = String(file || "").replace(/\\/g, "/").replace(/^\.\//, "");
63
- return normRoots.some((root) => normalized === root || normalized.startsWith(`${root}/`));
64
- };
65
- const npmNameParts = (name) => {
66
- const value = String(name || "");
67
- const slash = value.startsWith("@") ? value.indexOf("/") : -1;
68
- return slash > 0 ? { scope: value.slice(0, slash), base: value.slice(slash + 1) } : { scope: "", base: value };
69
- };
70
- const selfParts = npmNameParts(selfName);
71
- const configuredNapi = npmNameParts(pkg.napi?.name || selfName);
72
- const napiBinding = {
73
- scope: configuredNapi.scope || (configuredNapi.base === selfParts.base ? selfParts.scope : ""),
74
- base: configuredNapi.base,
75
- };
76
- const NAPI_PLATFORM_SUFFIX = /^(?:android-(?:arm64|arm-eabi)|win32-(?:x64|ia32|arm64)-msvc|darwin-(?:universal|x64|arm64)|freebsd-(?:x64|arm64)|linux-(?:x64|arm64|arm|riscv64|s390x|ppc64|loong64)-(?:gnu|musl|gnueabihf|musleabihf)|aix-ppc64|sunos-x64|wasm32-wasi(?:-preview1-threads)?)$/;
77
- const isGeneratedNapiPackage = (name, files) => {
78
- if (!pkg.napi || !napiBinding.base || !files.every((file) => /(^|\/)index\.[cm]?js$/i.test(String(file || "").replace(/\\/g, "/")))) return false;
79
- const candidate = npmNameParts(name);
80
- if (candidate.scope !== napiBinding.scope || !candidate.base.startsWith(`${napiBinding.base}-`)) return false;
81
- return NAPI_PLATFORM_SUFFIX.test(candidate.base.slice(napiBinding.base.length + 1));
82
- };
83
- const isGeneratedNapiBinary = (entry) => !!pkg.napi
84
- && /(^|\/)index\.[cm]?js$/i.test(String(entry.file || "").replace(/\\/g, "/"))
85
- && /^\.\/[^/]+\.node$/i.test(String(entry.spec || ""));
86
- // Some frameworks consume required peers internally, so application source legitimately never imports
87
- // them. Keep this deliberately narrow: react-dom is a required Next.js runtime peer in the same package
88
- // scope, not a general React exemption and not a repo-wide whitelist.
89
- const frameworkRuntime = new Set();
90
- for (const [provider, peers] of FRAMEWORK_RUNTIME_PEERS) {
91
- if (allDeclared.has(provider)) for (const peer of peers) frameworkRuntime.add(peer);
92
- }
93
- const implicitCompilerUsage = new Set();
94
- for (const [name, sourcePattern] of IMPLICIT_STYLE_COMPILERS) {
95
- if (allDeclared.has(name) && sourceFiles.some((file) => sourcePattern.test(String(file || "")))) {
96
- implicitCompilerUsage.add(name);
97
- }
98
- }
99
-
100
- // ---- usage index from the graph's recorded imports ----
101
- const usedPackages = new Map(); // pkgName -> { files:Set, lines:Map(file→first line), kinds/specs:Set, typeOnly:boolean }
102
- let builtinUsed = false;
103
- for (const e of externalImports) {
104
- if (e.ecosystem && e.ecosystem !== "npm") continue; // go/python imports have their own checkers
105
- if (isAliasSpec(e.spec)) continue;
106
- if (e.unresolved) continue;
107
- if (e.builtin) { builtinUsed = true; continue; }
108
- if (!e.pkg) continue;
109
- let u = usedPackages.get(e.pkg);
110
- if (!u) usedPackages.set(e.pkg, (u = { files: new Set(), lines: new Map(), kinds: new Set(), specs: new Set(), typeOnly: true }));
111
- u.files.add(e.file);
112
- if (!u.lines.has(e.file)) u.lines.set(e.file, e.line || 0);
113
- u.kinds.add(e.kind);
114
- u.specs.add(e.spec || e.pkg);
115
- if (!e.typeOnly) u.typeOnly = false;
116
- }
117
-
118
- const scriptBlob = Object.values(pkg.scripts || {}).join("\n");
119
- const configBlob = scriptBlob + "\n" + [...configTexts.values()].join("\n");
120
- const scriptTokens = new Set(scriptBlob.split(/[^\w@/.:-]+/).filter(Boolean));
121
- const binReferenced = (name) => {
122
- if (scriptTokens.has(name)) return true;
123
- for (const [bin, p] of Object.entries(BIN_PKG)) if (p === name && scriptTokens.has(bin)) return true;
124
- return false;
125
- };
126
- const typesBase = (name) => (name.startsWith("@types/") ? name.slice(7).replace(/^(.+?)__(.+)$/, "@$1/$2") : null); // @types/babel__core → @babel/core
127
- const sourceReferenced = createPackageSourceMatcher(sourceTexts);
128
-
129
- // ---- unused dependencies (per section; prod vs dev differ in severity/confidence) ----
130
- for (const [section, deps] of Object.entries(sections)) {
131
- if (section === "peerDependencies") continue; // peers are consumer-facing contracts, not usage
132
- for (const name of Object.keys(deps)) {
133
- if (name === selfName || usedPackages.has(name) || frameworkRuntime.has(name) || implicitCompilerUsage.has(name) || sourceReferenced(name)) continue;
134
- const tb = typesBase(name);
135
- if (tb) { // @types/x is used iff x is used (or it types the Node builtins)
136
- if (tb === "node" ? builtinUsed : usedPackages.has(tb) || frameworkRuntime.has(tb) || mentioned(configBlob, tb)) continue;
137
- }
138
- if (binReferenced(name) || mentioned(configBlob, name)) continue; // scripts/config keep it alive
139
- const ecosystem = CONFIG_ECOSYSTEM_RE.test(name);
140
- const dev = section !== "dependencies";
141
- if (ecosystem && dev) continue; // config-convention devDeps: too FP-prone to flag at all
142
- findings.push(makeFinding({
143
- category: "unused",
144
- rule: "unused-dep",
145
- severity: dev ? "info" : "low",
146
- confidence: dev || ecosystem ? "low" : "medium",
147
- title: `Unused ${section === "dependencies" ? "dependency" : section.replace(/ies$/, "y")}: ${name}`,
148
- reason: "No indexed package import, package-script command, recognized config mention, framework peer contract, or implicit style-compiler input uses this declaration.",
149
- detail: `"${name}" is declared in ${section}, but no usage was found in the indexed source scope, package scripts, or ${configTexts.size} known config file(s). Dynamic/plugin/config-convention usage is not proven absent — review before removing.`,
150
- file: manifest,
151
- package: name,
152
- evidence: [{ file: manifest, line: 0, snippet: `declared in ${section}` }],
153
- source: "internal",
154
- actionability: "MANIFEST_REVIEW_REQUIRED",
155
- autoRemove: false,
156
- verification: {
157
- evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE",
158
- manifestDeclaration: { status: "FOUND", file: manifest, section, version: String(deps[name] || "") },
159
- indexedSourceImports: { status: "ZERO_FOUND", completeness: "COMPLETE_FOR_GRAPH_SCOPE", count: 0 },
160
- packageScripts: { status: "CHECKED", matched: false },
161
- knownConfigs: { status: "CHECKED", filesScanned: configTexts.size, matched: false },
162
- frameworkPeerContract: "NOT_FOUND",
163
- implicitCompilerInput: "NOT_FOUND",
164
- dynamicOrPluginUsage: "NOT_PROVEN_ABSENT",
165
- decision: "REVIEW_REQUIRED",
166
- },
167
- fixHint: `Review manifest/config/plugin usage; if confirmed unused, remove ${name} with the package manager and run targeted tests`,
168
- ...meta,
169
- }));
170
- }
171
- }
172
-
173
- // ---- missing (phantom) dependencies: imported but declared nowhere ----
174
- for (const [name, use] of usedPackages) {
175
- if (allDeclared.has(name) || name === selfName || workspacePkgNames.has(name)) continue;
176
- const files = [...use.files];
177
- if (files.every(isNonRuntimeFile) || isGeneratedNapiPackage(name, files)) continue;
178
- const testOnly = files.every((f) => /(^|[/\\])(test|tests|__tests__|spec|e2e|__mocks__)([/\\]|$)|[._-](test|spec)\.[a-z]+$/i.test(f));
179
- const stylesheetOnly = [...use.specs].length > 0 && [...use.specs].every(isStylesheetSpecifier);
180
- const usageReason = stylesheetOnly
181
- ? `Direct stylesheet import(s) resolve through "${name}"; CSS-only imports are build/runtime inputs even without JavaScript bindings.`
182
- : use.typeOnly
183
- ? `Direct type-only import(s) resolve through "${name}"; the compiler still requires a declared package.`
184
- : `Direct source import(s) resolve through "${name}", but the nearest package manifest does not declare it.`;
185
- findings.push(makeFinding({
186
- category: "unused",
187
- rule: "missing-dep",
188
- severity: testOnly ? "low" : "medium",
189
- confidence: "high",
190
- title: `Missing dependency: ${name}`,
191
- reason: usageReason,
192
- detail: `"${name}" is imported by ${files.length} file(s) in scope ${meta.scope} but not declared in ${manifest} — it only works via a transitive install (phantom dependency) and can break on any lockfile change.`,
193
- package: name,
194
- file: files[0],
195
- line: use.lines.get(files[0]) || 0,
196
- evidence: files.slice(0, 5).map((f) => ({ file: f, line: use.lines.get(f) || 0, snippet: "" })),
197
- source: "internal",
198
- actionability: "STRONG_MANIFEST_MISMATCH",
199
- autoInstall: false,
200
- verification: {
201
- evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE",
202
- manifestDeclaration: { status: "NOT_FOUND", file: manifest, checkedSections: Object.keys(sections) },
203
- indexedSourceImports: {
204
- status: "FOUND",
205
- completeness: "COMPLETE_FOR_GRAPH_SCOPE",
206
- count: files.length,
207
- files: files.slice(0, 20),
208
- truncated: files.length > 20,
209
- typeOnly: use.typeOnly,
210
- stylesheetOnly,
211
- testOnly,
212
- },
213
- packageScope: meta.scope,
214
- transitiveResolutionRisk: "PRESENT",
215
- decision: "DECLARE_AFTER_SCOPE_REVIEW",
216
- },
217
- fixHint: `npm install ${testOnly ? "--save-dev " : ""}${name}`,
218
- ...meta,
219
- }));
220
- }
221
-
222
- // ---- duplicate declarations (same package in several sections) ----
223
- const seenIn = new Map();
224
- for (const [section, deps] of Object.entries(sections)) for (const name of Object.keys(deps)) (seenIn.get(name) || seenIn.set(name, []).get(name)).push(section);
225
- for (const [name, ss] of seenIn) {
226
- if (ss.length < 2) continue;
227
- if (ss.includes("peerDependencies") && ss.includes("devDependencies") && ss.length === 2) continue; // standard lib-author pattern
228
- findings.push(makeFinding({
229
- category: "unused",
230
- rule: "duplicate-dep",
231
- severity: "info",
232
- confidence: "high",
233
- title: `Duplicate declaration: ${name}`,
234
- reason: `The same package is declared in multiple manifest sections: ${ss.join(" + ")}.`,
235
- detail: `"${name}" is declared in ${ss.join(" + ")} — npm resolves one of them; keep a single section.`,
236
- package: name,
237
- source: "internal",
238
- actionability: "MANIFEST_REVIEW_REQUIRED",
239
- autoRemove: false,
240
- verification: {
241
- evidenceModel: "MANIFEST_ONLY",
242
- manifestDeclaration: { status: "DUPLICATE", file: manifest, sections: ss },
243
- decision: "REVIEW_REQUIRED",
244
- },
245
- ...meta,
246
- }));
247
- }
248
-
249
- // ---- unresolved local imports (broken relative/alias paths) ----
250
- const unresolvedSeen = new Set();
251
- let unresolvedCount = 0;
252
- for (const e of externalImports) {
253
- if (!e.unresolved) continue;
254
- if (isNonRuntimeFile(e.file) || isGeneratedNapiBinary(e)) continue;
255
- unresolvedCount++;
256
- const key = e.file + "|" + e.spec;
257
- if (unresolvedSeen.has(key) || unresolvedSeen.size >= 100) continue; // cap the findings, keep the count honest
258
- unresolvedSeen.add(key);
259
- findings.push(makeFinding({
260
- category: "structure",
261
- rule: "unresolved-import",
262
- severity: "low",
263
- confidence: "medium",
264
- title: `Unresolved import: ${e.spec}`,
265
- detail: `${e.file}:${e.line} imports "${e.spec}", which resolves to no file in the repo (broken path, missing alias target, or a file type the graph doesn't index).`,
266
- file: e.file,
267
- line: e.line || 0,
268
- source: "internal",
269
- ...meta,
270
- }));
271
- }
272
- if (unresolvedCount > unresolvedSeen.size) {
273
- findings.push(makeFinding({
274
- category: "structure",
275
- rule: "unresolved-import",
276
- severity: "info",
277
- confidence: "high",
278
- title: `…and ${unresolvedCount - unresolvedSeen.size} more unresolved imports`,
279
- detail: "Finding list capped at 100 unique unresolved imports; the count above is the true total.",
280
- source: "internal",
281
- ...meta,
282
- }));
283
- }
284
-
285
- return { findings, usedPackages, declared: allDeclared };
286
- }
287
-
288
- export const computeScopedDepFindings = createScopedDepFindings(computeDepFindings)
289
-
290
- export { computeGoDepFindings, computePyDepFindings } from "./dep-check-ecosystems.js";
@@ -1,10 +0,0 @@
1
- // Stable facade for structural dependency analysis.
2
- export {
3
- buildFileImportGraph,
4
- checkBoundaries,
5
- findOrphans,
6
- findSccs,
7
- globToRe,
8
- representativeCycle,
9
- } from './structure/dependency-graph.js'
10
- export {computeStructureFindings, isRustModuleTreeComponent} from './structure/findings.js'
@@ -1,16 +0,0 @@
1
- // Exact package contracts consumed by frameworks/build tools without a source
2
- // import. Keep them package-scope local; these are not repo-wide allowlists.
3
- export const FRAMEWORK_RUNTIME_PEERS = new Map([
4
- ['next', ['react-dom']],
5
- ['vinext', ['@vitejs/plugin-react', '@vitejs/plugin-rsc', 'react-server-dom-webpack', 'vite']],
6
- ['electron-vite', ['vite']],
7
- ['@cloudflare/vite-plugin', ['vite', 'wrangler']],
8
- ])
9
-
10
- // Style preprocessors are compiler inputs proven by source extensions.
11
- export const IMPLICIT_STYLE_COMPILERS = new Map([
12
- ['sass', /\.(?:scss|sass)$/i],
13
- ['sass-embedded', /\.(?:scss|sass)$/i],
14
- ['less', /\.less$/i],
15
- ['stylus', /\.styl(?:us)?$/i],
16
- ])
@@ -1,51 +0,0 @@
1
- import {posix} from 'node:path'
2
-
3
- export const normalizeDependencyScope = (root) => String(root || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+|\/+$/g, '')
4
- export const dependencyScopeRoot = (manifest) => {
5
- const root = posix.dirname(normalizeDependencyScope(manifest))
6
- return root === '.' ? '' : root
7
- }
8
- export const dependencyScopeOwnsFile = (scope, file) => !scope || file === scope || String(file || '').startsWith(`${scope}/`)
9
-
10
- // Bind the ecosystem-specific core without introducing a facade/helper import cycle.
11
- export function createScopedDepFindings(computeDepFindings) {
12
- return function computeScopedDepFindings({
13
- externalImports = [], packageScopes = [], workspacePkgNames = new Set(), configTexts = new Map(),
14
- sourceTexts = new Map(), nonRuntimeRoots = [], sourceFiles = [],
15
- } = {}) {
16
- const scopes = packageScopes.length
17
- ? packageScopes.map((scope) => ({...scope, root: normalizeDependencyScope(scope.root)})).sort((a, b) => b.root.length - a.root.length)
18
- : [{root: '', manifest: 'package.json', pkg: {}, aliases: []}]
19
- const importsByScope = new Map(scopes.map((scope) => [scope, []]))
20
- const sourceFilesByScope = new Map(scopes.map((scope) => [scope, []]))
21
- const sourceTextsByScope = new Map(scopes.map((scope) => [scope, new Map()]))
22
- for (const item of externalImports) {
23
- const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, item.file)) || scopes[scopes.length - 1]
24
- importsByScope.get(owner).push(item)
25
- }
26
- for (const file of sourceFiles) {
27
- const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, file)) || scopes[scopes.length - 1]
28
- sourceFilesByScope.get(owner).push(file)
29
- }
30
- for (const [file, text] of sourceTexts) {
31
- const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, file)) || scopes[scopes.length - 1]
32
- sourceTextsByScope.get(owner).set(file, text)
33
- }
34
- const configOwner = new Map()
35
- for (const [file] of configTexts) configOwner.set(file, scopes.find((scope) => dependencyScopeOwnsFile(scope.root, file)) || scopes[scopes.length - 1])
36
- const findings = [], usedPackages = new Map(), declared = new Set()
37
- for (const scope of scopes) {
38
- const scopeConfig = new Map([...configTexts].filter(([file]) => configOwner.get(file) === scope))
39
- const result = computeDepFindings({
40
- externalImports: importsByScope.get(scope), pkg: scope.pkg || {}, workspacePkgNames,
41
- configTexts: scopeConfig, sourceTexts: sourceTextsByScope.get(scope), aliases: scope.aliases || [], scope: scope.root,
42
- manifest: scope.manifest || (scope.root ? `${scope.root}/package.json` : 'package.json'),
43
- nonRuntimeRoots, sourceFiles: sourceFilesByScope.get(scope),
44
- })
45
- findings.push(...result.findings)
46
- for (const [name, use] of result.usedPackages) usedPackages.set(`${scope.root || '.'}:${name}`, use)
47
- for (const name of result.declared) declared.add(`${scope.root || '.'}:${name}`)
48
- }
49
- return {findings, usedPackages, declared}
50
- }
51
- }
@@ -1,21 +0,0 @@
1
- // Dynamic package loaders often construct a node_modules path instead of using
2
- // import/require, so they never become graph externalImports. Restrict this
3
- // fallback to executable npm source: manifests merely declare every dependency
4
- // and must never count as usage evidence.
5
- const NPM_SOURCE_RE = /\.(?:[cm]?[jt]sx?|vue|svelte)$/i
6
- const escRe = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
7
- const mentioned = (blob, name) => new RegExp(`(^|[^\\w@.-])${escRe(name)}($|[^\\w.-])`).test(blob)
8
-
9
- export function createPackageSourceMatcher(sourceTexts = new Map()) {
10
- const blob = [...sourceTexts]
11
- .filter(([file]) => NPM_SOURCE_RE.test(String(file || '')))
12
- .map(([, text]) => String(text || ''))
13
- .join('\n')
14
- return (name) => {
15
- if (mentioned(blob, name)) return true
16
- if (!name.startsWith('@') || !name.includes('/')) return false
17
- const [namespace, base] = name.split('/', 2)
18
- // join(root, "node_modules", "@scope", "package", ...)
19
- return new RegExp(`["'\x60]${escRe(namespace)}["'\x60][\\s\\S]{0,96}?["'\x60]${escRe(base)}["'\x60]`).test(blob)
20
- }
21
- }
@@ -1,104 +0,0 @@
1
- import {createHash} from 'node:crypto'
2
- import {writeFileSync} from 'node:fs'
3
- import {join} from 'node:path'
4
- import {computeDuplicates} from './duplicates.js'
5
- import {withGitRefCheckout} from './git-ref-graph.js'
6
- import {buildInternalGraph} from '../graph/internal-builder.js'
7
- import {filterGraphForMode} from '../graph/graph-filter.js'
8
-
9
- const NON_PRODUCT = new Set(['generated', 'vendored', 'mock', 'story', 'docs', 'benchmark', 'temp'])
10
- const eligible = (fragment, settings) => {
11
- const classes = new Set(fragment.classes || [])
12
- if (fragment.n < settings.tokMin) return false
13
- if (settings.skipTests && (fragment.test || classes.has('test') || classes.has('e2e'))) return false
14
- return settings.includeClassified || !(fragment.excluded || [...classes].some((name) => NON_PRODUCT.has(name)))
15
- }
16
-
17
- function groupPairs(data, settings) {
18
- const fragments = data.frags || []
19
- const pairs = (data.modes?.[settings.mode] || []).filter(([a, b, similarity]) => similarity >= settings.simMin && eligible(fragments[a], settings) && eligible(fragments[b], settings))
20
- const parent = new Map()
21
- const find = (value) => { let root = value; while (parent.get(root) !== root) root = parent.get(root); return root }
22
- for (const [a, b] of pairs) {
23
- if (!parent.has(a)) parent.set(a, a)
24
- if (!parent.has(b)) parent.set(b, b)
25
- parent.set(find(a), find(b))
26
- }
27
- const groups = new Map()
28
- for (const [a, b, similarity] of pairs) {
29
- const root = find(a)
30
- if (!groups.has(root)) groups.set(root, {members: new Set(), maxSim: 0})
31
- const group = groups.get(root)
32
- group.members.add(a); group.members.add(b); group.maxSim = Math.max(group.maxSim, similarity)
33
- }
34
- return [...groups.values()].map((group) => {
35
- const members = [...group.members].map((index) => fragments[index]).sort((a, b) => b.n - a.n)
36
- return {members, maxSim: group.maxSim, tokens: members.reduce((sum, item) => sum + item.n, 0)}
37
- }).sort((a, b) => b.tokens - a.tokens)
38
- }
39
-
40
- export function isFrameworkBoilerplateCloneGroup(group) {
41
- const members = Array.isArray(group?.members) ? group.members : []
42
- if (members.length < 2) return false
43
- return members.every((member) => /(?:^|\/)\w[^/]*\.router\.[cm]?[jt]s$/i.test(String(member.file || '').replace(/\\/g, '/'))
44
- && /^(?:router|routes)\(?\)?$/i.test(String(member.label || '').trim()))
45
- }
46
-
47
- export function isDeclarativeCatalogCloneGroup(group) {
48
- const members = Array.isArray(group?.members) ? group.members : []
49
- return members.length >= 2 && members.every((member) => member.declarative === true)
50
- }
51
-
52
- export function analyzeDuplicateGroups(repoRoot, graphPath, args = {}) {
53
- const settings = {
54
- simMin: Math.min(100, Math.max(50, Number(args.min_similarity) || 80)),
55
- tokMin: Math.min(400, Math.max(12, Number(args.min_tokens) || 50)),
56
- mode: args.mode === 'strict' ? 'strict' : 'renamed',
57
- skipTests: args.include_tests !== true,
58
- includeClassified: args.include_classified === true || args.include_non_product === true,
59
- includeBoilerplate: args.include_boilerplate === true,
60
- includeDeclarative: args.include_declarative === true,
61
- }
62
- const data = computeDuplicates(repoRoot, graphPath, {includeStrings: args.include_strings === true, minTokens: settings.tokMin})
63
- const allGroups = groupPairs(data, settings)
64
- const afterBoilerplate = settings.includeBoilerplate ? allGroups : allGroups.filter((group) => !isFrameworkBoilerplateCloneGroup(group))
65
- const groups = settings.includeDeclarative ? afterBoilerplate : afterBoilerplate.filter((group) => !isDeclarativeCatalogCloneGroup(group))
66
- const suppressed = (data.frags || []).filter((fragment) => !eligible(fragment, settings)).length
67
- return {
68
- settings, groups, suppressed,
69
- boilerplateSuppressed: allGroups.length - afterBoilerplate.length,
70
- declarativeSuppressed: afterBoilerplate.length - groups.length,
71
- }
72
- }
73
-
74
- const digest = (value) => createHash('sha256').update(value).digest('hex').slice(0, 20)
75
- function groupKey(group, mode) {
76
- const members = group.members.map((fragment) => {
77
- const fingerprints = Array.isArray(fragment.fp?.[mode]) ? [...fragment.fp[mode]].sort() : []
78
- return `${String(fragment.file).replace(/\\/g, '/')}\0${fragment.label || ''}\0${digest(JSON.stringify(fingerprints))}`
79
- }).sort()
80
- return digest(members.join('\n'))
81
- }
82
-
83
- export async function compareDuplicateGroups({repoRoot, graphPath, currentGraph, baseRef, changedFiles = [], args = {}}) {
84
- const current = analyzeDuplicateGroups(repoRoot, graphPath, args)
85
- const mode = ['full', 'no-tests', 'tests-only'].includes(currentGraph?.graphBuildMode) ? currentGraph.graphBuildMode : 'full'
86
- const baseline = await withGitRefCheckout(repoRoot, baseRef, async (checkout) => {
87
- let graph = await buildInternalGraph(checkout)
88
- if (mode !== 'full') graph = filterGraphForMode(graph, mode, {repoRoot: checkout})
89
- graph.graphBuildMode = mode
90
- const baselineGraphPath = join(checkout, '.weavatrix-verified-graph.json')
91
- writeFileSync(baselineGraphPath, JSON.stringify(graph))
92
- return analyzeDuplicateGroups(checkout, baselineGraphPath, args)
93
- })
94
- if (!baseline.ok) return {state: 'UNKNOWN', reason: baseline.error, currentGroups: current.groups.length}
95
- const baselineKeys = new Set(baseline.value.groups.map((group) => groupKey(group, current.settings.mode)))
96
- const changed = new Set(changedFiles.map((file) => String(file).replace(/\\/g, '/')))
97
- const added = current.groups.filter((group) => !baselineKeys.has(groupKey(group, current.settings.mode)))
98
- const scoped = added.filter((group) => group.members.some((member) => changed.has(String(member.file).replace(/\\/g, '/'))))
99
- return {
100
- state: scoped.length ? 'BLOCKED' : 'PASS', baseline: {ref: baseline.ref, commit: baseline.commit},
101
- currentGroups: current.groups.length, baselineGroups: baseline.value.groups.length, newGroups: added.length,
102
- scopedNewGroups: scoped.slice(0, 20).map((group) => ({similarity: group.maxSim, tokens: group.tokens, members: group.members.slice(0, 8).map((item) => ({file: item.file, start: item.start, end: item.end, label: item.label}))})),
103
- }
104
- }
@@ -1,11 +0,0 @@
1
- // duplicates-worker.js — runs clone detection OFF Electron's main thread (same rationale as
2
- // graph/build-worker.js: a blocked main event loop freezes every window on Windows). The fingerprint
3
- // sets never leave the worker; only slim fragment metadata + similarity pairs cross back.
4
- import { parentPort, workerData } from "node:worker_threads";
5
- import { computeDuplicates } from "./duplicates.js";
6
-
7
- try {
8
- parentPort.postMessage(computeDuplicates(workerData.repoPath, workerData.graphJsonPath));
9
- } catch (e) {
10
- parentPort.postMessage({ ok: false, error: (e && e.message) || String(e) });
11
- }