weavatrix 0.3.14 → 1.1.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 (306) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +132 -200
  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 +10 -67
  14. package/server.json +38 -0
  15. package/skill/SKILL.md +62 -374
  16. package/skill/agents/openai.yaml +4 -0
  17. package/skill/references/tool-routing.md +45 -0
  18. package/SECURITY.md +0 -101
  19. package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
  20. package/docs/agent-task-benchmark.md +0 -58
  21. package/docs/releases/v0.2.19.md +0 -33
  22. package/docs/releases/v0.3.0.md +0 -43
  23. package/docs/releases/v0.3.1.md +0 -30
  24. package/docs/releases/v0.3.10.md +0 -76
  25. package/docs/releases/v0.3.11.md +0 -55
  26. package/docs/releases/v0.3.12.md +0 -44
  27. package/docs/releases/v0.3.13.md +0 -34
  28. package/docs/releases/v0.3.14.md +0 -37
  29. package/docs/releases/v0.3.2.md +0 -15
  30. package/docs/releases/v0.3.3.md +0 -25
  31. package/docs/releases/v0.3.4.md +0 -40
  32. package/docs/releases/v0.3.5.md +0 -50
  33. package/docs/releases/v0.3.6.md +0 -44
  34. package/docs/releases/v0.3.7.md +0 -37
  35. package/docs/releases/v0.3.8.md +0 -21
  36. package/docs/releases/v0.3.9.md +0 -36
  37. package/scripts/run-agent-task-benchmark.mjs +0 -118
  38. package/src/analysis/allowed-test-runner.js +0 -70
  39. package/src/analysis/architecture/contract-graph.js +0 -119
  40. package/src/analysis/architecture/contract-schema.js +0 -110
  41. package/src/analysis/architecture/contract-storage.js +0 -35
  42. package/src/analysis/architecture/contract-verification.js +0 -168
  43. package/src/analysis/architecture-contract.js +0 -16
  44. package/src/analysis/audit-debt.js +0 -94
  45. package/src/analysis/audit-extensions.js +0 -60
  46. package/src/analysis/cargo-dependency-evidence.js +0 -108
  47. package/src/analysis/cargo-manifests.js +0 -81
  48. package/src/analysis/change-classification/diff-parser.js +0 -103
  49. package/src/analysis/change-classification/options.js +0 -40
  50. package/src/analysis/change-classification/symbol-classifier.js +0 -177
  51. package/src/analysis/change-classification.js +0 -188
  52. package/src/analysis/coverage-reports-tarpaulin.js +0 -112
  53. package/src/analysis/coverage-reports.js +0 -246
  54. package/src/analysis/cycle-route.js +0 -14
  55. package/src/analysis/data-flow-evidence.js +0 -114
  56. package/src/analysis/dead-check.js +0 -300
  57. package/src/analysis/dead-code-review/candidates.js +0 -194
  58. package/src/analysis/dead-code-review/policy.js +0 -23
  59. package/src/analysis/dead-code-review.js +0 -119
  60. package/src/analysis/dep-check-ecosystems.js +0 -205
  61. package/src/analysis/dep-check.js +0 -290
  62. package/src/analysis/dep-rules.js +0 -10
  63. package/src/analysis/dependency/conventions.js +0 -16
  64. package/src/analysis/dependency/scoped-dependencies.js +0 -51
  65. package/src/analysis/dependency/source-references.js +0 -21
  66. package/src/analysis/duplicate-groups.js +0 -104
  67. package/src/analysis/duplicates-worker.js +0 -11
  68. package/src/analysis/duplicates.compute.js +0 -269
  69. package/src/analysis/duplicates.js +0 -15
  70. package/src/analysis/duplicates.run.js +0 -52
  71. package/src/analysis/duplicates.tokenize.js +0 -199
  72. package/src/analysis/endpoints/common.js +0 -79
  73. package/src/analysis/endpoints/extract.js +0 -120
  74. package/src/analysis/endpoints/inventory.js +0 -84
  75. package/src/analysis/endpoints/mounts.js +0 -129
  76. package/src/analysis/endpoints-java.js +0 -260
  77. package/src/analysis/endpoints-rust.js +0 -124
  78. package/src/analysis/endpoints.js +0 -3
  79. package/src/analysis/findings.js +0 -70
  80. package/src/analysis/git-history/analytics.js +0 -177
  81. package/src/analysis/git-history/collector.js +0 -67
  82. package/src/analysis/git-history/options.js +0 -66
  83. package/src/analysis/git-history.js +0 -143
  84. package/src/analysis/git-ref-graph.js +0 -76
  85. package/src/analysis/go-dependency-evidence.js +0 -62
  86. package/src/analysis/graph-analysis.aggregate.js +0 -296
  87. package/src/analysis/graph-analysis.edges.js +0 -24
  88. package/src/analysis/graph-analysis.js +0 -7
  89. package/src/analysis/graph-analysis.refs.js +0 -202
  90. package/src/analysis/graph-analysis.summaries.js +0 -65
  91. package/src/analysis/health-capabilities.js +0 -83
  92. package/src/analysis/hot-path-review.js +0 -266
  93. package/src/analysis/http-contract-wrappers.js +0 -241
  94. package/src/analysis/http-contracts/analysis.js +0 -179
  95. package/src/analysis/http-contracts/client-call-detection.js +0 -82
  96. package/src/analysis/http-contracts/client-call-parser.js +0 -273
  97. package/src/analysis/http-contracts/graph-context.js +0 -143
  98. package/src/analysis/http-contracts/matching.js +0 -61
  99. package/src/analysis/http-contracts/shared.js +0 -85
  100. package/src/analysis/http-contracts.js +0 -6
  101. package/src/analysis/internal-audit/dependency-health.js +0 -113
  102. package/src/analysis/internal-audit/python-manifests.js +0 -78
  103. package/src/analysis/internal-audit/repo-files.js +0 -50
  104. package/src/analysis/internal-audit/supply-chain.js +0 -149
  105. package/src/analysis/internal-audit.collect.js +0 -213
  106. package/src/analysis/internal-audit.js +0 -9
  107. package/src/analysis/internal-audit.reach.js +0 -201
  108. package/src/analysis/internal-audit.run.js +0 -256
  109. package/src/analysis/java-source.js +0 -36
  110. package/src/analysis/jvm-artifact-index.js +0 -87
  111. package/src/analysis/jvm-dependency-evidence.js +0 -138
  112. package/src/analysis/jvm-manifests.js +0 -114
  113. package/src/analysis/manifests.js +0 -123
  114. package/src/analysis/package-reachability.js +0 -39
  115. package/src/analysis/source-complexity.ast.js +0 -97
  116. package/src/analysis/source-complexity.constants.js +0 -55
  117. package/src/analysis/source-complexity.js +0 -14
  118. package/src/analysis/source-complexity.report.js +0 -81
  119. package/src/analysis/source-complexity.walk.js +0 -228
  120. package/src/analysis/source-correctness/retry-patterns.js +0 -93
  121. package/src/analysis/source-correctness.js +0 -222
  122. package/src/analysis/static-test-reachability.js +0 -150
  123. package/src/analysis/structure/dependency-graph.js +0 -151
  124. package/src/analysis/structure/findings.js +0 -162
  125. package/src/analysis/task-retrieval.js +0 -107
  126. package/src/analysis/transport-contracts.js +0 -224
  127. package/src/analysis/transport-runtime-evidence.js +0 -155
  128. package/src/analysis-kit.mjs +0 -17
  129. package/src/bounds.js +0 -4
  130. package/src/build-graph.js +0 -209
  131. package/src/child-env.js +0 -7
  132. package/src/config.js +0 -5
  133. package/src/extension/local-services.mjs +0 -75
  134. package/src/git-exec.js +0 -67
  135. package/src/graph/build-worker.js +0 -49
  136. package/src/graph/builder/go-receiver-resolution.js +0 -112
  137. package/src/graph/builder/js/dynamic-import.js +0 -26
  138. package/src/graph/builder/js/queries.js +0 -48
  139. package/src/graph/builder/lang-csharp.js +0 -40
  140. package/src/graph/builder/lang-css.js +0 -29
  141. package/src/graph/builder/lang-go.js +0 -140
  142. package/src/graph/builder/lang-html.js +0 -29
  143. package/src/graph/builder/lang-java-calls.js +0 -142
  144. package/src/graph/builder/lang-java.js +0 -215
  145. package/src/graph/builder/lang-js.js +0 -291
  146. package/src/graph/builder/lang-python.js +0 -235
  147. package/src/graph/builder/lang-rust-calls.js +0 -63
  148. package/src/graph/builder/lang-rust.js +0 -299
  149. package/src/graph/builder/lang-solidity.js +0 -126
  150. package/src/graph/builder/lang-sql.js +0 -263
  151. package/src/graph/builder/pass2-resolution.js +0 -81
  152. package/src/graph/builder/spec-pkg.js +0 -87
  153. package/src/graph/community.js +0 -27
  154. package/src/graph/edge-provenance.js +0 -31
  155. package/src/graph/file-lock.js +0 -69
  156. package/src/graph/freshness-probe.js +0 -142
  157. package/src/graph/graph-filter.js +0 -63
  158. package/src/graph/incremental-refresh.js +0 -235
  159. package/src/graph/internal-builder.barrels.js +0 -235
  160. package/src/graph/internal-builder.build.js +0 -300
  161. package/src/graph/internal-builder.java.js +0 -33
  162. package/src/graph/internal-builder.js +0 -12
  163. package/src/graph/internal-builder.langs.js +0 -157
  164. package/src/graph/internal-builder.pass2.js +0 -163
  165. package/src/graph/internal-builder.resolvers.js +0 -286
  166. package/src/graph/layout.js +0 -51
  167. package/src/graph/node-id.js +0 -7
  168. package/src/graph/parser-artifact-boundary.js +0 -55
  169. package/src/graph/relations.js +0 -4
  170. package/src/graph/repo-registry.js +0 -124
  171. package/src/graph/resolvers/rust.js +0 -117
  172. package/src/infra/infra-items.js +0 -255
  173. package/src/infra/infra-registry.js +0 -184
  174. package/src/infra/infra.detect.js +0 -119
  175. package/src/infra/infra.js +0 -38
  176. package/src/infra/infra.match.js +0 -102
  177. package/src/infra/infra.scan.js +0 -93
  178. package/src/mcp/actions/graph-lifecycle.mjs +0 -148
  179. package/src/mcp/architecture-bootstrap.mjs +0 -168
  180. package/src/mcp/architecture-starter.mjs +0 -234
  181. package/src/mcp/catalog.mjs +0 -233
  182. package/src/mcp/company-contract-page.mjs +0 -142
  183. package/src/mcp/company-contract-reconcile.mjs +0 -87
  184. package/src/mcp/company-contract-verdict.mjs +0 -42
  185. package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
  186. package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
  187. package/src/mcp/evidence/package-graph-common.mjs +0 -115
  188. package/src/mcp/evidence/package-lock-graph.mjs +0 -92
  189. package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
  190. package/src/mcp/evidence-snapshot.common.mjs +0 -158
  191. package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
  192. package/src/mcp/evidence-snapshot.health.mjs +0 -102
  193. package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
  194. package/src/mcp/evidence-snapshot.mjs +0 -50
  195. package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
  196. package/src/mcp/evidence-snapshot.structure.mjs +0 -84
  197. package/src/mcp/extension-api.mjs +0 -77
  198. package/src/mcp/git-output.mjs +0 -7
  199. package/src/mcp/graph/context-core.mjs +0 -111
  200. package/src/mcp/graph/context-seeds.mjs +0 -207
  201. package/src/mcp/graph/context-state.mjs +0 -85
  202. package/src/mcp/graph/reverse-reach.mjs +0 -42
  203. package/src/mcp/graph/tools-core.mjs +0 -143
  204. package/src/mcp/graph/tools-query.mjs +0 -276
  205. package/src/mcp/graph-context.mjs +0 -5
  206. package/src/mcp/graph-diff.mjs +0 -218
  207. package/src/mcp/health/audit-format.mjs +0 -199
  208. package/src/mcp/health/audit.mjs +0 -199
  209. package/src/mcp/health/dead-code.mjs +0 -110
  210. package/src/mcp/health/duplicates.mjs +0 -121
  211. package/src/mcp/health/endpoints.mjs +0 -71
  212. package/src/mcp/health/structure.mjs +0 -220
  213. package/src/mcp/runtime-version.mjs +0 -31
  214. package/src/mcp/server/auto-refresh.mjs +0 -66
  215. package/src/mcp/server/runtime-config.mjs +0 -43
  216. package/src/mcp/server/shutdown.mjs +0 -40
  217. package/src/mcp/staleness-notice.mjs +0 -20
  218. package/src/mcp/sync/evidence-architecture.mjs +0 -54
  219. package/src/mcp/sync/evidence-common.mjs +0 -88
  220. package/src/mcp/sync/evidence-duplicates.mjs +0 -97
  221. package/src/mcp/sync/evidence-health.mjs +0 -56
  222. package/src/mcp/sync/evidence-packages.mjs +0 -95
  223. package/src/mcp/sync/payload-common.mjs +0 -97
  224. package/src/mcp/sync/payload-v2.mjs +0 -54
  225. package/src/mcp/sync/payload-v3.mjs +0 -79
  226. package/src/mcp/sync-evidence.mjs +0 -23
  227. package/src/mcp/sync-payload.mjs +0 -70
  228. package/src/mcp/tool-result.mjs +0 -54
  229. package/src/mcp/tools-actions.mjs +0 -11
  230. package/src/mcp/tools-architecture.mjs +0 -177
  231. package/src/mcp/tools-company.mjs +0 -251
  232. package/src/mcp/tools-context.mjs +0 -222
  233. package/src/mcp/tools-endpoints.mjs +0 -207
  234. package/src/mcp/tools-graph-hubs.mjs +0 -78
  235. package/src/mcp/tools-graph.mjs +0 -17
  236. package/src/mcp/tools-health.mjs +0 -24
  237. package/src/mcp/tools-history.mjs +0 -28
  238. package/src/mcp/tools-impact-change.mjs +0 -220
  239. package/src/mcp/tools-impact-precision.mjs +0 -89
  240. package/src/mcp/tools-impact.mjs +0 -169
  241. package/src/mcp/tools-source.mjs +0 -238
  242. package/src/mcp/tools-verified-change.mjs +0 -200
  243. package/src/mcp-rg.mjs +0 -50
  244. package/src/mcp-runtime.mjs +0 -2
  245. package/src/mcp-server.mjs +0 -298
  246. package/src/mcp-source-tools.mjs +0 -163
  247. package/src/path-classification.js +0 -226
  248. package/src/path-ignore.js +0 -69
  249. package/src/precision/lsp-client/constants.js +0 -12
  250. package/src/precision/lsp-client/environment.js +0 -20
  251. package/src/precision/lsp-client/errors.js +0 -15
  252. package/src/precision/lsp-client/lifecycle.js +0 -114
  253. package/src/precision/lsp-client/message-parser.js +0 -81
  254. package/src/precision/lsp-client/protocol.js +0 -212
  255. package/src/precision/lsp-client/registry.js +0 -59
  256. package/src/precision/lsp-client/repo-uri.js +0 -60
  257. package/src/precision/lsp-client/stdio-client.js +0 -140
  258. package/src/precision/lsp-client.js +0 -10
  259. package/src/precision/lsp-overlay/budget-policy.js +0 -78
  260. package/src/precision/lsp-overlay/build.js +0 -261
  261. package/src/precision/lsp-overlay/contract.js +0 -61
  262. package/src/precision/lsp-overlay/reference-results.js +0 -108
  263. package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
  264. package/src/precision/lsp-overlay/source-session.js +0 -142
  265. package/src/precision/lsp-overlay/store.js +0 -150
  266. package/src/precision/lsp-overlay/target-index.js +0 -152
  267. package/src/precision/lsp-overlay/target-query.js +0 -55
  268. package/src/precision/lsp-overlay.js +0 -11
  269. package/src/precision/symbol-query.js +0 -224
  270. package/src/precision/typescript-lsp-provider.js +0 -12
  271. package/src/precision/typescript-provider/client.js +0 -89
  272. package/src/precision/typescript-provider/discovery.js +0 -124
  273. package/src/precision/typescript-provider/isolated-runtime.js +0 -39
  274. package/src/precision/typescript-provider/project-host.js +0 -254
  275. package/src/precision/typescript-provider/project-safety.js +0 -166
  276. package/src/precision/typescript-provider/reference-usage.js +0 -53
  277. package/src/process.js +0 -95
  278. package/src/repo-path.js +0 -53
  279. package/src/scan/discover.inventory.js +0 -78
  280. package/src/scan/discover.js +0 -5
  281. package/src/scan/discover.list.js +0 -79
  282. package/src/scan/discover.stack.js +0 -227
  283. package/src/scan/search.core.js +0 -24
  284. package/src/scan/search.git.js +0 -102
  285. package/src/scan/search.js +0 -9
  286. package/src/scan/search.node.js +0 -83
  287. package/src/scan/search.preview.js +0 -49
  288. package/src/scan/search.rg.js +0 -145
  289. package/src/security/advisory-store.js +0 -172
  290. package/src/security/installed-jvm-rust.js +0 -46
  291. package/src/security/installed.js +0 -280
  292. package/src/security/malware-file-heuristics.js +0 -121
  293. package/src/security/malware-heuristics.exclusions.js +0 -134
  294. package/src/security/malware-heuristics.js +0 -15
  295. package/src/security/malware-heuristics.roots.js +0 -145
  296. package/src/security/malware-heuristics.scan.js +0 -89
  297. package/src/security/malware-heuristics.sweep.js +0 -122
  298. package/src/security/malware-scoring.js +0 -105
  299. package/src/security/match.js +0 -85
  300. package/src/security/registry-sig.classify.js +0 -137
  301. package/src/security/registry-sig.js +0 -18
  302. package/src/security/registry-sig.rules.js +0 -192
  303. package/src/security/rust-advisory-report.js +0 -60
  304. package/src/security/typosquat.js +0 -81
  305. package/src/util.js +0 -40
  306. 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
- }