weavatrix 0.3.13 → 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 (303) 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 -65
  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.2.md +0 -15
  28. package/docs/releases/v0.3.3.md +0 -25
  29. package/docs/releases/v0.3.4.md +0 -40
  30. package/docs/releases/v0.3.5.md +0 -50
  31. package/docs/releases/v0.3.6.md +0 -44
  32. package/docs/releases/v0.3.7.md +0 -37
  33. package/docs/releases/v0.3.8.md +0 -21
  34. package/docs/releases/v0.3.9.md +0 -36
  35. package/scripts/run-agent-task-benchmark.mjs +0 -118
  36. package/src/analysis/allowed-test-runner.js +0 -70
  37. package/src/analysis/architecture/contract-graph.js +0 -119
  38. package/src/analysis/architecture/contract-schema.js +0 -110
  39. package/src/analysis/architecture/contract-storage.js +0 -35
  40. package/src/analysis/architecture/contract-verification.js +0 -168
  41. package/src/analysis/architecture-contract.js +0 -16
  42. package/src/analysis/audit-debt.js +0 -94
  43. package/src/analysis/audit-extensions.js +0 -60
  44. package/src/analysis/cargo-dependency-evidence.js +0 -108
  45. package/src/analysis/cargo-manifests.js +0 -81
  46. package/src/analysis/change-classification/diff-parser.js +0 -103
  47. package/src/analysis/change-classification/options.js +0 -40
  48. package/src/analysis/change-classification/symbol-classifier.js +0 -177
  49. package/src/analysis/change-classification.js +0 -188
  50. package/src/analysis/coverage-reports.js +0 -235
  51. package/src/analysis/cycle-route.js +0 -14
  52. package/src/analysis/data-flow-evidence.js +0 -114
  53. package/src/analysis/dead-check.js +0 -300
  54. package/src/analysis/dead-code-review/candidates.js +0 -194
  55. package/src/analysis/dead-code-review/policy.js +0 -23
  56. package/src/analysis/dead-code-review.js +0 -119
  57. package/src/analysis/dep-check-ecosystems.js +0 -205
  58. package/src/analysis/dep-check.js +0 -290
  59. package/src/analysis/dep-rules.js +0 -10
  60. package/src/analysis/dependency/conventions.js +0 -16
  61. package/src/analysis/dependency/scoped-dependencies.js +0 -51
  62. package/src/analysis/dependency/source-references.js +0 -21
  63. package/src/analysis/duplicate-groups.js +0 -104
  64. package/src/analysis/duplicates-worker.js +0 -11
  65. package/src/analysis/duplicates.compute.js +0 -269
  66. package/src/analysis/duplicates.js +0 -15
  67. package/src/analysis/duplicates.run.js +0 -52
  68. package/src/analysis/duplicates.tokenize.js +0 -199
  69. package/src/analysis/endpoints/common.js +0 -62
  70. package/src/analysis/endpoints/extract.js +0 -107
  71. package/src/analysis/endpoints/inventory.js +0 -84
  72. package/src/analysis/endpoints/mounts.js +0 -129
  73. package/src/analysis/endpoints-java.js +0 -260
  74. package/src/analysis/endpoints-rust.js +0 -124
  75. package/src/analysis/endpoints.js +0 -3
  76. package/src/analysis/findings.js +0 -70
  77. package/src/analysis/git-history/analytics.js +0 -177
  78. package/src/analysis/git-history/collector.js +0 -67
  79. package/src/analysis/git-history/options.js +0 -66
  80. package/src/analysis/git-history.js +0 -143
  81. package/src/analysis/git-ref-graph.js +0 -76
  82. package/src/analysis/go-dependency-evidence.js +0 -62
  83. package/src/analysis/graph-analysis.aggregate.js +0 -295
  84. package/src/analysis/graph-analysis.edges.js +0 -24
  85. package/src/analysis/graph-analysis.js +0 -7
  86. package/src/analysis/graph-analysis.refs.js +0 -154
  87. package/src/analysis/graph-analysis.summaries.js +0 -65
  88. package/src/analysis/health-capabilities.js +0 -83
  89. package/src/analysis/hot-path-review.js +0 -266
  90. package/src/analysis/http-contract-wrappers.js +0 -241
  91. package/src/analysis/http-contracts/analysis.js +0 -179
  92. package/src/analysis/http-contracts/client-call-detection.js +0 -82
  93. package/src/analysis/http-contracts/client-call-parser.js +0 -273
  94. package/src/analysis/http-contracts/graph-context.js +0 -143
  95. package/src/analysis/http-contracts/matching.js +0 -61
  96. package/src/analysis/http-contracts/shared.js +0 -85
  97. package/src/analysis/http-contracts.js +0 -6
  98. package/src/analysis/internal-audit/dependency-health.js +0 -113
  99. package/src/analysis/internal-audit/python-manifests.js +0 -78
  100. package/src/analysis/internal-audit/repo-files.js +0 -50
  101. package/src/analysis/internal-audit/supply-chain.js +0 -149
  102. package/src/analysis/internal-audit.collect.js +0 -213
  103. package/src/analysis/internal-audit.js +0 -9
  104. package/src/analysis/internal-audit.reach.js +0 -173
  105. package/src/analysis/internal-audit.run.js +0 -256
  106. package/src/analysis/java-source.js +0 -36
  107. package/src/analysis/jvm-artifact-index.js +0 -87
  108. package/src/analysis/jvm-dependency-evidence.js +0 -138
  109. package/src/analysis/jvm-manifests.js +0 -114
  110. package/src/analysis/manifests.js +0 -123
  111. package/src/analysis/package-reachability.js +0 -39
  112. package/src/analysis/source-complexity.ast.js +0 -97
  113. package/src/analysis/source-complexity.constants.js +0 -55
  114. package/src/analysis/source-complexity.js +0 -14
  115. package/src/analysis/source-complexity.report.js +0 -81
  116. package/src/analysis/source-complexity.walk.js +0 -228
  117. package/src/analysis/source-correctness/retry-patterns.js +0 -93
  118. package/src/analysis/source-correctness.js +0 -222
  119. package/src/analysis/static-test-reachability.js +0 -150
  120. package/src/analysis/structure/dependency-graph.js +0 -151
  121. package/src/analysis/structure/findings.js +0 -162
  122. package/src/analysis/task-retrieval.js +0 -107
  123. package/src/analysis/transport-contracts.js +0 -224
  124. package/src/analysis/transport-runtime-evidence.js +0 -155
  125. package/src/analysis-kit.mjs +0 -17
  126. package/src/bounds.js +0 -4
  127. package/src/build-graph.js +0 -209
  128. package/src/child-env.js +0 -7
  129. package/src/config.js +0 -5
  130. package/src/extension/local-services.mjs +0 -75
  131. package/src/git-exec.js +0 -67
  132. package/src/graph/build-worker.js +0 -49
  133. package/src/graph/builder/go-receiver-resolution.js +0 -112
  134. package/src/graph/builder/js/dynamic-import.js +0 -26
  135. package/src/graph/builder/js/queries.js +0 -48
  136. package/src/graph/builder/lang-csharp.js +0 -40
  137. package/src/graph/builder/lang-css.js +0 -29
  138. package/src/graph/builder/lang-go.js +0 -140
  139. package/src/graph/builder/lang-html.js +0 -29
  140. package/src/graph/builder/lang-java-calls.js +0 -142
  141. package/src/graph/builder/lang-java.js +0 -215
  142. package/src/graph/builder/lang-js.js +0 -291
  143. package/src/graph/builder/lang-python.js +0 -235
  144. package/src/graph/builder/lang-rust-calls.js +0 -63
  145. package/src/graph/builder/lang-rust.js +0 -299
  146. package/src/graph/builder/lang-solidity.js +0 -126
  147. package/src/graph/builder/lang-sql.js +0 -263
  148. package/src/graph/builder/pass2-resolution.js +0 -81
  149. package/src/graph/builder/spec-pkg.js +0 -87
  150. package/src/graph/community.js +0 -27
  151. package/src/graph/edge-provenance.js +0 -31
  152. package/src/graph/file-lock.js +0 -69
  153. package/src/graph/freshness-probe.js +0 -142
  154. package/src/graph/graph-filter.js +0 -63
  155. package/src/graph/incremental-refresh.js +0 -235
  156. package/src/graph/internal-builder.barrels.js +0 -235
  157. package/src/graph/internal-builder.build.js +0 -300
  158. package/src/graph/internal-builder.java.js +0 -33
  159. package/src/graph/internal-builder.js +0 -12
  160. package/src/graph/internal-builder.langs.js +0 -157
  161. package/src/graph/internal-builder.pass2.js +0 -163
  162. package/src/graph/internal-builder.resolvers.js +0 -286
  163. package/src/graph/layout.js +0 -51
  164. package/src/graph/node-id.js +0 -7
  165. package/src/graph/parser-artifact-boundary.js +0 -55
  166. package/src/graph/relations.js +0 -4
  167. package/src/graph/repo-registry.js +0 -124
  168. package/src/graph/resolvers/rust.js +0 -117
  169. package/src/infra/infra-items.js +0 -255
  170. package/src/infra/infra-registry.js +0 -184
  171. package/src/infra/infra.detect.js +0 -119
  172. package/src/infra/infra.js +0 -38
  173. package/src/infra/infra.match.js +0 -102
  174. package/src/infra/infra.scan.js +0 -93
  175. package/src/mcp/actions/graph-lifecycle.mjs +0 -148
  176. package/src/mcp/architecture-bootstrap.mjs +0 -168
  177. package/src/mcp/architecture-starter.mjs +0 -234
  178. package/src/mcp/catalog.mjs +0 -233
  179. package/src/mcp/company-contract-page.mjs +0 -142
  180. package/src/mcp/company-contract-reconcile.mjs +0 -87
  181. package/src/mcp/company-contract-verdict.mjs +0 -42
  182. package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
  183. package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
  184. package/src/mcp/evidence/package-graph-common.mjs +0 -115
  185. package/src/mcp/evidence/package-lock-graph.mjs +0 -92
  186. package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
  187. package/src/mcp/evidence-snapshot.common.mjs +0 -158
  188. package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
  189. package/src/mcp/evidence-snapshot.health.mjs +0 -102
  190. package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
  191. package/src/mcp/evidence-snapshot.mjs +0 -50
  192. package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
  193. package/src/mcp/evidence-snapshot.structure.mjs +0 -84
  194. package/src/mcp/extension-api.mjs +0 -77
  195. package/src/mcp/git-output.mjs +0 -7
  196. package/src/mcp/graph/context-core.mjs +0 -111
  197. package/src/mcp/graph/context-seeds.mjs +0 -207
  198. package/src/mcp/graph/context-state.mjs +0 -85
  199. package/src/mcp/graph/reverse-reach.mjs +0 -42
  200. package/src/mcp/graph/tools-core.mjs +0 -143
  201. package/src/mcp/graph/tools-query.mjs +0 -276
  202. package/src/mcp/graph-context.mjs +0 -5
  203. package/src/mcp/graph-diff.mjs +0 -218
  204. package/src/mcp/health/audit-format.mjs +0 -199
  205. package/src/mcp/health/audit.mjs +0 -174
  206. package/src/mcp/health/dead-code.mjs +0 -110
  207. package/src/mcp/health/duplicates.mjs +0 -85
  208. package/src/mcp/health/endpoints.mjs +0 -71
  209. package/src/mcp/health/structure.mjs +0 -219
  210. package/src/mcp/runtime-version.mjs +0 -31
  211. package/src/mcp/server/auto-refresh.mjs +0 -66
  212. package/src/mcp/server/runtime-config.mjs +0 -43
  213. package/src/mcp/server/shutdown.mjs +0 -40
  214. package/src/mcp/staleness-notice.mjs +0 -20
  215. package/src/mcp/sync/evidence-architecture.mjs +0 -54
  216. package/src/mcp/sync/evidence-common.mjs +0 -88
  217. package/src/mcp/sync/evidence-duplicates.mjs +0 -97
  218. package/src/mcp/sync/evidence-health.mjs +0 -56
  219. package/src/mcp/sync/evidence-packages.mjs +0 -95
  220. package/src/mcp/sync/payload-common.mjs +0 -97
  221. package/src/mcp/sync/payload-v2.mjs +0 -54
  222. package/src/mcp/sync/payload-v3.mjs +0 -79
  223. package/src/mcp/sync-evidence.mjs +0 -23
  224. package/src/mcp/sync-payload.mjs +0 -70
  225. package/src/mcp/tool-result.mjs +0 -54
  226. package/src/mcp/tools-actions.mjs +0 -11
  227. package/src/mcp/tools-architecture.mjs +0 -177
  228. package/src/mcp/tools-company.mjs +0 -251
  229. package/src/mcp/tools-context.mjs +0 -222
  230. package/src/mcp/tools-endpoints.mjs +0 -207
  231. package/src/mcp/tools-graph-hubs.mjs +0 -78
  232. package/src/mcp/tools-graph.mjs +0 -17
  233. package/src/mcp/tools-health.mjs +0 -24
  234. package/src/mcp/tools-history.mjs +0 -28
  235. package/src/mcp/tools-impact-change.mjs +0 -220
  236. package/src/mcp/tools-impact-precision.mjs +0 -89
  237. package/src/mcp/tools-impact.mjs +0 -169
  238. package/src/mcp/tools-source.mjs +0 -238
  239. package/src/mcp/tools-verified-change.mjs +0 -200
  240. package/src/mcp-rg.mjs +0 -50
  241. package/src/mcp-runtime.mjs +0 -2
  242. package/src/mcp-server.mjs +0 -298
  243. package/src/mcp-source-tools.mjs +0 -163
  244. package/src/path-classification.js +0 -220
  245. package/src/path-ignore.js +0 -69
  246. package/src/precision/lsp-client/constants.js +0 -12
  247. package/src/precision/lsp-client/environment.js +0 -20
  248. package/src/precision/lsp-client/errors.js +0 -15
  249. package/src/precision/lsp-client/lifecycle.js +0 -114
  250. package/src/precision/lsp-client/message-parser.js +0 -81
  251. package/src/precision/lsp-client/protocol.js +0 -212
  252. package/src/precision/lsp-client/registry.js +0 -59
  253. package/src/precision/lsp-client/repo-uri.js +0 -60
  254. package/src/precision/lsp-client/stdio-client.js +0 -140
  255. package/src/precision/lsp-client.js +0 -10
  256. package/src/precision/lsp-overlay/budget-policy.js +0 -78
  257. package/src/precision/lsp-overlay/build.js +0 -261
  258. package/src/precision/lsp-overlay/contract.js +0 -61
  259. package/src/precision/lsp-overlay/reference-results.js +0 -108
  260. package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
  261. package/src/precision/lsp-overlay/source-session.js +0 -142
  262. package/src/precision/lsp-overlay/store.js +0 -150
  263. package/src/precision/lsp-overlay/target-index.js +0 -152
  264. package/src/precision/lsp-overlay/target-query.js +0 -55
  265. package/src/precision/lsp-overlay.js +0 -11
  266. package/src/precision/symbol-query.js +0 -224
  267. package/src/precision/typescript-lsp-provider.js +0 -12
  268. package/src/precision/typescript-provider/client.js +0 -89
  269. package/src/precision/typescript-provider/discovery.js +0 -124
  270. package/src/precision/typescript-provider/isolated-runtime.js +0 -39
  271. package/src/precision/typescript-provider/project-host.js +0 -254
  272. package/src/precision/typescript-provider/project-safety.js +0 -166
  273. package/src/precision/typescript-provider/reference-usage.js +0 -53
  274. package/src/process.js +0 -95
  275. package/src/repo-path.js +0 -53
  276. package/src/scan/discover.inventory.js +0 -78
  277. package/src/scan/discover.js +0 -5
  278. package/src/scan/discover.list.js +0 -79
  279. package/src/scan/discover.stack.js +0 -227
  280. package/src/scan/search.core.js +0 -24
  281. package/src/scan/search.git.js +0 -102
  282. package/src/scan/search.js +0 -9
  283. package/src/scan/search.node.js +0 -83
  284. package/src/scan/search.preview.js +0 -49
  285. package/src/scan/search.rg.js +0 -145
  286. package/src/security/advisory-store.js +0 -172
  287. package/src/security/installed-jvm-rust.js +0 -46
  288. package/src/security/installed.js +0 -280
  289. package/src/security/malware-file-heuristics.js +0 -121
  290. package/src/security/malware-heuristics.exclusions.js +0 -134
  291. package/src/security/malware-heuristics.js +0 -15
  292. package/src/security/malware-heuristics.roots.js +0 -145
  293. package/src/security/malware-heuristics.scan.js +0 -89
  294. package/src/security/malware-heuristics.sweep.js +0 -122
  295. package/src/security/malware-scoring.js +0 -105
  296. package/src/security/match.js +0 -85
  297. package/src/security/registry-sig.classify.js +0 -137
  298. package/src/security/registry-sig.js +0 -18
  299. package/src/security/registry-sig.rules.js +0 -192
  300. package/src/security/rust-advisory-report.js +0 -60
  301. package/src/security/typosquat.js +0 -81
  302. package/src/util.js +0 -40
  303. package/src/version.js +0 -7
@@ -1,260 +0,0 @@
1
- // Spring MVC/WebFlux endpoint extraction. This stays source-only and deliberately resolves only
2
- // literal annotation paths: inventing values for constants would make an architecture inventory look
3
- // more complete than the evidence permits.
4
- import { maskJavaNonCode } from "./java-source.js";
5
- import { lineNumberAt } from "../util.js";
6
-
7
- const SPRING_MAPPING = /@(?:org\.springframework\.web\.bind\.annotation\.)?(RequestMapping|GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping)\b/g;
8
- const SPRING_CONDITION = /@(?:org\.springframework\.boot\.autoconfigure\.condition\.)?(ConditionalOnExpression|ConditionalOnProperty)\b/g;
9
- const REQUEST_METHOD = /\bRequestMethod\s*\.\s*(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE)\b/g;
10
-
11
- const lineAt = lineNumberAt;
12
-
13
- function skipTrivia(text, start) {
14
- let i = start;
15
- while (i < text.length) {
16
- if (/\s/.test(text[i])) { i++; continue; }
17
- if (text.startsWith("//", i)) {
18
- const nl = text.indexOf("\n", i + 2);
19
- i = nl < 0 ? text.length : nl + 1;
20
- continue;
21
- }
22
- if (text.startsWith("/*", i)) {
23
- const end = text.indexOf("*/", i + 2);
24
- i = end < 0 ? text.length : end + 2;
25
- continue;
26
- }
27
- break;
28
- }
29
- return i;
30
- }
31
-
32
- function balancedEnd(text, start, open = "(", close = ")") {
33
- if (text[start] !== open) return start;
34
- let depth = 0;
35
- let quote = "";
36
- let escaped = false;
37
- for (let i = start; i < text.length; i++) {
38
- const ch = text[i];
39
- if (quote) {
40
- if (escaped) escaped = false;
41
- else if (ch === "\\") escaped = true;
42
- else if (ch === quote) quote = "";
43
- continue;
44
- }
45
- if (ch === '"' || ch === "'") { quote = ch; continue; }
46
- if (ch === open) depth++;
47
- else if (ch === close && --depth === 0) return i + 1;
48
- }
49
- return text.length;
50
- }
51
-
52
- function annotationInvocation(text, nameEnd) {
53
- const start = skipTrivia(text, nameEnd);
54
- if (text[start] !== "(") return { args: "", end: start };
55
- const end = balancedEnd(text, start);
56
- return { args: text.slice(start + 1, Math.max(start + 1, end - 1)), end };
57
- }
58
-
59
- function skipAnnotations(text, start) {
60
- let i = skipTrivia(text, start);
61
- while (text[i] === "@") {
62
- const name = /^@[A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*/.exec(text.slice(i));
63
- if (!name) break;
64
- i += name[0].length;
65
- i = annotationInvocation(text, i).end;
66
- i = skipTrivia(text, i);
67
- }
68
- return i;
69
- }
70
-
71
- function stringLiterals(value) {
72
- const out = [];
73
- const re = /"((?:\\.|[^"\\])*)"/g;
74
- let match;
75
- while ((match = re.exec(String(value || "")))) {
76
- out.push(match[1].replace(/\\"/g, '"').replace(/\\\\/g, "\\"));
77
- }
78
- return out;
79
- }
80
-
81
- function initializer(args, equalsAt) {
82
- let i = skipTrivia(args, equalsAt + 1);
83
- if (args[i] === "{") {
84
- const end = balancedEnd(args, i, "{", "}");
85
- return args.slice(i, end);
86
- }
87
- if (args[i] === '"') {
88
- let escaped = false;
89
- for (let end = i + 1; end < args.length; end++) {
90
- if (escaped) escaped = false;
91
- else if (args[end] === "\\") escaped = true;
92
- else if (args[end] === '"') return args.slice(i, end + 1);
93
- }
94
- }
95
- const comma = args.indexOf(",", i);
96
- return args.slice(i, comma < 0 ? args.length : comma);
97
- }
98
-
99
- function mappingPaths(args) {
100
- const source = String(args || "");
101
- const named = /\b(?:path|value)\s*=/g.exec(source);
102
- if (named) return stringLiterals(initializer(source, source.indexOf("=", named.index)));
103
- const leading = source.trimStart();
104
- if (!leading) return [""];
105
- if (leading[0] === '"' || leading[0] === "{") return stringLiterals(leading);
106
- // `method=`, `produces=` and friends do not specify a path, so they map the class/method root.
107
- if (/^(?:method|produces|consumes|headers|params|name)\s*=/.test(leading)) return [""];
108
- return []; // unresolved positional constant: do not invent a root route
109
- }
110
-
111
- function mappingMethods(name, args) {
112
- if (name !== "RequestMapping") return [name.replace(/Mapping$/, "").toUpperCase()];
113
- const methods = [];
114
- let match;
115
- REQUEST_METHOD.lastIndex = 0;
116
- while ((match = REQUEST_METHOD.exec(args))) if (!methods.includes(match[1])) methods.push(match[1]);
117
- return methods.length ? methods : ["ANY"];
118
- }
119
-
120
- function namedInitializer(args, name) {
121
- const source = String(args || "");
122
- const match = new RegExp(`\\b${name}\\s*=`).exec(source);
123
- return match ? initializer(source, source.indexOf("=", match.index)) : "";
124
- }
125
-
126
- function namedStrings(args, name) {
127
- return stringLiterals(namedInitializer(args, name));
128
- }
129
-
130
- function namedBoolean(args, name, fallback) {
131
- const value = namedInitializer(args, name).trim();
132
- if (/^true\b/i.test(value)) return true;
133
- if (/^false\b/i.test(value)) return false;
134
- return fallback;
135
- }
136
-
137
- function positionalStrings(args) {
138
- const source = String(args || "");
139
- const start = source.trimStart()[0];
140
- return start === '"' || start === "{" ? stringLiterals(initializer(source, -1)) : [];
141
- }
142
-
143
- function expressionDefaultActive(expression) {
144
- const value = String(expression || "").trim();
145
- if (/^(?:true|false)$/i.test(value)) return value.toLowerCase() === "true";
146
- const match = /^\$\{[^{}:]+:(true|false)\}$/i.exec(value);
147
- return match ? match[1].toLowerCase() === "true" : null;
148
- }
149
-
150
- function conditionMetadata(annotation, args) {
151
- if (annotation === "ConditionalOnExpression") {
152
- const expression = namedStrings(args, "value")[0] || stringLiterals(args)[0] || "";
153
- return {
154
- type: annotation,
155
- expression,
156
- defaultActive: expressionDefaultActive(expression),
157
- };
158
- }
159
- const namedProperties = [...namedStrings(args, "name"), ...namedStrings(args, "value")];
160
- const properties = [...new Set(namedProperties.length ? namedProperties : positionalStrings(args))];
161
- const matchIfMissing = namedBoolean(args, "matchIfMissing", false);
162
- return {
163
- type: annotation,
164
- prefix: namedStrings(args, "prefix")[0] || "",
165
- properties,
166
- havingValue: namedStrings(args, "havingValue")[0] || "",
167
- matchIfMissing,
168
- defaultActive: matchIfMissing,
169
- };
170
- }
171
-
172
- function joinPaths(prefix, path) {
173
- const left = String(prefix || "").trim().replace(/^\/+|\/+$/g, "");
174
- const right = String(path || "").trim().replace(/^\/+|\/+$/g, "");
175
- return `/${[left, right].filter(Boolean).join("/")}`.replace(/\/{2,}/g, "/");
176
- }
177
-
178
- function declarationAfter(text, start) {
179
- const declarationStart = skipAnnotations(text, start);
180
- const bounded = text.slice(declarationStart, declarationStart + 2_500);
181
- const terminators = [bounded.indexOf("{"), bounded.indexOf(";")].filter((i) => i >= 0);
182
- const end = terminators.length ? Math.min(...terminators) : bounded.length;
183
- const head = bounded.slice(0, end);
184
- const classMatch = /\b(class|interface|record|enum)\s+([A-Za-z_$][\w$]*)/.exec(head);
185
- if (classMatch) {
186
- const open = bounded.indexOf("{");
187
- const bodyOpen = open < 0 ? -1 : declarationStart + open;
188
- return { kind: "class", name: classMatch[2], start: declarationStart, bodyOpen, bodyClose: bodyOpen < 0 ? text.length : balancedEnd(text, bodyOpen, "{", "}") };
189
- }
190
- const methodMatch = /\b([A-Za-z_$][\w$]*)\s*\(/.exec(head);
191
- return methodMatch ? { kind: "method", name: methodMatch[1], start: declarationStart } : { kind: "unknown", name: "", start: declarationStart };
192
- }
193
-
194
- export function extractSpringEndpoints(text, file) {
195
- if (!/\.java$/i.test(file)) return [];
196
- const code = maskJavaNonCode(text);
197
- if (!/@(?:[\w$]+\.)*(?:Request|Get|Post|Put|Patch|Delete)Mapping\b/.test(code)) return [];
198
- const mappings = [];
199
- let match;
200
- SPRING_MAPPING.lastIndex = 0;
201
- while ((match = SPRING_MAPPING.exec(code))) {
202
- const invocation = annotationInvocation(text, SPRING_MAPPING.lastIndex);
203
- const declaration = declarationAfter(code, invocation.end);
204
- mappings.push({
205
- annotation: match[1],
206
- args: invocation.args,
207
- index: match.index,
208
- line: lineAt(text, match.index),
209
- declaration,
210
- });
211
- SPRING_MAPPING.lastIndex = Math.max(SPRING_MAPPING.lastIndex, invocation.end);
212
- }
213
- const conditions = [];
214
- SPRING_CONDITION.lastIndex = 0;
215
- while ((match = SPRING_CONDITION.exec(code))) {
216
- const invocation = annotationInvocation(text, SPRING_CONDITION.lastIndex);
217
- const declaration = declarationAfter(code, invocation.end);
218
- conditions.push({
219
- index: match.index,
220
- line: lineAt(text, match.index),
221
- declaration,
222
- ...conditionMetadata(match[1], invocation.args),
223
- });
224
- SPRING_CONDITION.lastIndex = Math.max(SPRING_CONDITION.lastIndex, invocation.end);
225
- }
226
-
227
- const classMappings = mappings
228
- .filter((item) => item.declaration.kind === "class")
229
- .map((item) => ({ ...item, paths: mappingPaths(item.args) }));
230
- const out = [];
231
- for (const item of mappings) {
232
- if (item.declaration.kind !== "method") continue;
233
- const owner = classMappings
234
- .filter((candidate) => candidate.declaration.bodyOpen >= 0
235
- && item.index > candidate.declaration.bodyOpen
236
- && item.index < candidate.declaration.bodyClose)
237
- .sort((a, b) => b.declaration.bodyOpen - a.declaration.bodyOpen)[0];
238
- if (owner && !owner.paths.length) continue; // unresolved class prefix makes the full route unknowable
239
- const prefixes = owner?.paths?.length ? owner.paths : [""];
240
- const paths = mappingPaths(item.args);
241
- if (!paths.length) continue; // literal path was requested but could not be resolved
242
- const activationConditions = conditions.filter((condition) => condition.declaration.start === item.declaration.start
243
- || (owner && condition.declaration.start === owner.declaration.start));
244
- const activation = activationConditions.length
245
- ? {
246
- conditional: true,
247
- defaultActive: activationConditions.some((condition) => condition.defaultActive === false)
248
- ? false
249
- : activationConditions.every((condition) => condition.defaultActive === true) ? true : null,
250
- conditions: activationConditions.map(({ declaration: _declaration, index: _index, ...condition }) => condition),
251
- }
252
- : {};
253
- for (const method of mappingMethods(item.annotation, item.args)) {
254
- for (const prefix of prefixes) for (const path of paths) {
255
- out.push({ method, path: joinPaths(prefix, path), handler: item.declaration.name, file, line: item.line, ...activation });
256
- }
257
- }
258
- }
259
- return out;
260
- }
@@ -1,124 +0,0 @@
1
- const RUST_ROUTE_METHOD = "get|post|put|patch|delete|head|options|trace|connect|any";
2
-
3
- // Find a call's closing parenthesis without mistaking parens inside strings for syntax. This is deliberately
4
- // small (not a Rust parser), but keeps route-handler expressions bounded instead of using a cross-statement
5
- // regex that could turn an unrelated function call into a route.
6
- function closingParen(text, openAt) {
7
- let depth = 0, quote = "", escaped = false, blockComment = 0;
8
- for (let i = openAt; i < text.length; i++) {
9
- const ch = text[i];
10
- if (quote) {
11
- if (escaped) escaped = false;
12
- else if (ch === "\\") escaped = true;
13
- else if (ch === quote) quote = "";
14
- continue;
15
- }
16
- if (blockComment) {
17
- if (ch === "/" && text[i + 1] === "*") { blockComment++; i++; }
18
- else if (ch === "*" && text[i + 1] === "/") { blockComment--; i++; }
19
- continue;
20
- }
21
- if (ch === "/" && text[i + 1] === "/") {
22
- const newline = text.indexOf("\n", i + 2);
23
- if (newline < 0) return -1;
24
- i = newline;
25
- continue;
26
- }
27
- if (ch === "/" && text[i + 1] === "*") { blockComment = 1; i++; continue; }
28
- if (ch === '"') { quote = ch; continue; }
29
- if (ch === "'") {
30
- // Skip a Rust character literal, but do not mistake a lifetime (`'a`) for an unterminated string.
31
- const char = /^'(?:\\.|[^\\'\r\n])'/.exec(text.slice(i));
32
- if (char) { i += char[0].length - 1; continue; }
33
- }
34
- if (ch === "(") depth++;
35
- else if (ch === ")" && --depth === 0) return i;
36
- }
37
- return -1;
38
- }
39
-
40
- // Parse axum's MethodRouter expression, including direct method chains:
41
- // get(list).post(create) / axum::routing::get(show).delete(remove)
42
- // Calls inside a handler closure are not scanned and therefore cannot become fake endpoint methods.
43
- function axumRoutes(expr) {
44
- const found = [];
45
- let rest = String(expr || ""), offset = 0;
46
- const firstRe = new RegExp(`^\\s*(?:(?:axum\\s*::\\s*)?routing\\s*::\\s*)?(${RUST_ROUTE_METHOD})\\s*\\(`, "i");
47
- let match = firstRe.exec(rest);
48
- if (!match) return found;
49
-
50
- while (match) {
51
- const openAt = offset + match.index + match[0].lastIndexOf("(");
52
- const closeAt = closingParen(expr, openAt);
53
- if (closeAt < 0) break;
54
- found.push({ method: match[1], handler: expr.slice(openAt + 1, closeAt), index: openAt });
55
- offset = closeAt + 1;
56
- rest = expr.slice(offset);
57
- match = new RegExp(`^\\s*\\.\\s*(${RUST_ROUTE_METHOD})\\s*\\(`, "i").exec(rest);
58
- }
59
- return found;
60
- }
61
-
62
- // Actix's builder form is structurally distinct from axum and from HTTP clients:
63
- // web::get().to(handler) / actix_web::web::post().to(handler)
64
- function actixRoutes(expr) {
65
- const found = [];
66
- const re = new RegExp(`(?:actix_web\\s*::\\s*)?web\\s*::\\s*(${RUST_ROUTE_METHOD})\\s*\\(\\s*\\)\\s*\\.\\s*to\\s*\\(`, "gi");
67
- let match;
68
- while ((match = re.exec(expr))) {
69
- const openAt = re.lastIndex - 1;
70
- const closeAt = closingParen(expr, openAt);
71
- if (closeAt < 0) continue;
72
- found.push({ method: match[1], handler: expr.slice(openAt + 1, closeAt), index: match.index });
73
- re.lastIndex = closeAt + 1;
74
- }
75
- return found;
76
- }
77
-
78
- export function extractRustEndpoints(text, add) {
79
- let match;
80
-
81
- // Axum and actix both expose a path-first `.route(path, ...)` builder. Requiring either an axum
82
- // MethodRouter or actix's web::<method>().to(...) avoids treating arbitrary Rust APIs named `route` as HTTP.
83
- const routeRe = /\.\s*route\s*\(\s*"(\/(?:\\.|[^"\\])*)"\s*,/g;
84
- while ((match = routeRe.exec(text))) {
85
- const openAt = text.indexOf("(", match.index);
86
- const closeAt = closingParen(text, openAt);
87
- if (closeAt < 0) continue;
88
- const exprStart = routeRe.lastIndex;
89
- const expr = text.slice(exprStart, closeAt);
90
- const routes = [...axumRoutes(expr), ...actixRoutes(expr)];
91
- for (const route of routes) add(route.method, match[1], route.handler, exprStart + route.index);
92
- routeRe.lastIndex = closeAt + 1;
93
- }
94
-
95
- // actix `web::resource(path)` associates several method routes with one path.
96
- const resourceRe = /(?:actix_web\s*::\s*)?web\s*::\s*resource\s*\(\s*"(\/(?:\\.|[^"\\])*)"\s*\)/g;
97
- while ((match = resourceRe.exec(text))) {
98
- const tail = text.slice(resourceRe.lastIndex);
99
- const nextResource = tail.search(/(?:actix_web\s*::\s*)?web\s*::\s*resource\s*\(/);
100
- const semicolon = text.indexOf(";", resourceRe.lastIndex);
101
- let end = Math.min(text.length, resourceRe.lastIndex + 4000);
102
- if (nextResource >= 0) end = Math.min(end, resourceRe.lastIndex + nextResource);
103
- if (semicolon >= 0) end = Math.min(end, semicolon);
104
- const chain = text.slice(resourceRe.lastIndex, end);
105
- for (const route of actixRoutes(chain)) add(route.method, match[1], route.handler, resourceRe.lastIndex + route.index);
106
- }
107
-
108
- // Actix attribute macros. The route macro can declare more than one method for the same handler.
109
- const macroRe = new RegExp(`#\\s*\\[\\s*(?:(?:actix_web|actix)\\s*::\\s*)?(${RUST_ROUTE_METHOD})\\s*\\(\\s*"(\\/(?:\\\\.|[^"\\\\])*)"(?:\\s*,[^\\]]*)?\\s*\\)\\s*\\]`, "gi");
110
- while ((match = macroRe.exec(text))) {
111
- const after = text.slice(macroRe.lastIndex, macroRe.lastIndex + 600);
112
- const fn = /\b(?:pub(?:\s*\([^)]*\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/.exec(after);
113
- add(match[1], match[2], fn ? fn[1] : "", match.index);
114
- }
115
-
116
- const multiMacroRe = /#\s*\[\s*(?:(?:actix_web|actix)\s*::\s*)?route\s*\(\s*"(\/(?:\\.|[^"\\])*)"([\s\S]*?)\)\s*\]/gi;
117
- while ((match = multiMacroRe.exec(text))) {
118
- const after = text.slice(multiMacroRe.lastIndex, multiMacroRe.lastIndex + 600);
119
- const fn = /\b(?:pub(?:\s*\([^)]*\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/.exec(after);
120
- const methodRe = /\bmethod\s*=\s*"(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE|CONNECT)"/gi;
121
- let method;
122
- while ((method = methodRe.exec(match[2]))) add(method[1], match[1], fn ? fn[1] : "", match.index);
123
- }
124
- }
@@ -1,3 +0,0 @@
1
- // Stable public facade for server endpoint discovery.
2
- export { nextRoutePath, extractEndpointsFromText } from "./endpoints/extract.js";
3
- export { analyzeEndpointInventory, detectEndpoints } from "./endpoints/inventory.js";
@@ -1,70 +0,0 @@
1
- // Unified Finding factory + rollups for the internal dependency/security engine (DEPS_SECURITY_PLAN.md §2.3).
2
- // Every analyzer (unused / structure / vulnerability / malware) and the external-tool adapter emit THIS shape,
3
- // so the renderer and the AI summarizer consume one contract regardless of engine.
4
- import { createHash } from "node:crypto";
5
-
6
- const SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
7
-
8
- export function dependencyVerification(manifest, imports, decision, mapping) {
9
- return {
10
- evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE",
11
- decision,
12
- manifestDeclaration: { status: "FOUND", file: manifest },
13
- indexedSourceImports: imports.length
14
- ? { status: "FOUND", count: imports.length, files: [...new Set(imports.map((item) => item.file))].slice(0, 10) }
15
- : { status: "ZERO_FOUND", completeness: "COMPLETE_FOR_GRAPH_SCOPE", count: 0, files: [] },
16
- mapping,
17
- };
18
- }
19
-
20
- // Stable id: survives re-runs so the UI can persist expand/dismiss state per finding.
21
- export function makeFinding(f) {
22
- const cycleIdentity = Array.isArray(f.cycleMembers)
23
- ? [...new Set(f.cycleMembers.map(String))].sort().join("\0")
24
- : "";
25
- const id = createHash("sha1")
26
- .update([
27
- f.category, f.rule, f.file || "", f.manifest || "", f.scope || "",
28
- f.package || "", f.symbol || "", cycleIdentity, f.title || "",
29
- ].join("|"))
30
- .digest("hex")
31
- .slice(0, 16);
32
- return {
33
- id,
34
- severity: "info",
35
- confidence: "medium",
36
- title: "",
37
- detail: "",
38
- reason: "",
39
- file: "",
40
- line: 0,
41
- symbol: "",
42
- package: "",
43
- version: "",
44
- graphNodeId: f.graphNodeId || f.file || "",
45
- evidence: [],
46
- source: "internal",
47
- fixHint: "",
48
- ...f,
49
- };
50
- }
51
-
52
- export function summarizeFindings(findings) {
53
- const bySeverity = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
54
- const byCategory = { unused: 0, structure: 0, vulnerability: 0, malware: 0 };
55
- for (const f of findings || []) {
56
- if (f.severity in bySeverity) bySeverity[f.severity]++;
57
- if (f.category in byCategory) byCategory[f.category]++;
58
- }
59
- return { bySeverity, byCategory };
60
- }
61
-
62
- export function sortFindings(findings) {
63
- const sev = new Map(SEVERITY_ORDER.map((s, i) => [s, i]));
64
- return [...(findings || [])].sort(
65
- (a, b) =>
66
- (sev.get(a.severity) ?? 9) - (sev.get(b.severity) ?? 9) ||
67
- String(a.category).localeCompare(String(b.category)) ||
68
- String(a.file || a.package || "").localeCompare(String(b.file || b.package || ""))
69
- );
70
- }
@@ -1,177 +0,0 @@
1
- import {isTestPath} from '../../graph/graph-filter.js'
2
- import {isStructuralRelation} from '../../graph/relations.js'
3
- import {
4
- boundedHistoryInteger,
5
- GIT_HISTORY_DEFAULTS as DEFAULTS,
6
- GIT_HISTORY_HARD_CAPS as HARD_CAPS,
7
- GIT_HISTORY_V,
8
- graphEndpoint,
9
- roundHistoryNumber as round,
10
- safeHistoryPath,
11
- } from './options.js'
12
-
13
- function graphFilesAndAdjacency(graph = {}) {
14
- const byId = new Map(), files = new Set()
15
- for (const node of graph.nodes || []) {
16
- const file = safeHistoryPath(node?.source_file)
17
- if (!file) continue
18
- byId.set(String(node.id), file)
19
- files.add(file)
20
- }
21
- const adjacency = new Map([...files].map((file) => [file, new Set()]))
22
- for (const link of graph.links || []) {
23
- if (isStructuralRelation(link?.relation) || link?.barrelProxy === true) continue
24
- const left = byId.get(String(graphEndpoint(link?.source)))
25
- const right = byId.get(String(graphEndpoint(link?.target)))
26
- if (!left || !right || left === right) continue
27
- adjacency.get(left)?.add(right)
28
- adjacency.get(right)?.add(left)
29
- }
30
- return {files, adjacency}
31
- }
32
-
33
- function graphDistanceAtMostTwo(left, right, adjacency) {
34
- if (left === right) return 0
35
- const neighbors = adjacency.get(left)
36
- if (!neighbors) return null
37
- if (neighbors.has(right)) return 1
38
- for (const middle of neighbors) if (adjacency.get(middle)?.has(right)) return 2
39
- return null
40
- }
41
-
42
- function percentile(value, positiveValues) {
43
- if (!(value > 0) || !positiveValues.length) return 0
44
- let atOrBelow = 0
45
- for (const candidate of positiveValues) if (candidate <= value) atOrBelow++
46
- return round(atOrBelow / positiveValues.length)
47
- }
48
-
49
- const pairSort = (left, right) => right.count - left.count
50
- || right.confidence - left.confidence
51
- || right.lift - left.lift
52
- || left.left.localeCompare(right.left)
53
- || left.right.localeCompare(right.right)
54
-
55
- const publicPair = (pair, graphDistance) => ({
56
- left: pair.left,
57
- right: pair.right,
58
- count: pair.count,
59
- jaccard: pair.jaccard,
60
- lift: pair.lift,
61
- confidence: pair.confidence,
62
- leftConfidence: pair.leftConfidence,
63
- rightConfidence: pair.rightConfidence,
64
- graphDistance,
65
- })
66
-
67
- function fileActivity(commits, graph) {
68
- const activity = new Map(), fileCommits = new Map()
69
- let additions = 0, deletions = 0, binaryChanges = 0
70
- for (const commit of commits) {
71
- const seen = new Set()
72
- for (const stat of commit.files) {
73
- if (seen.has(stat.file)) continue
74
- seen.add(stat.file)
75
- const entry = activity.get(stat.file) || {file: stat.file, commits: 0, additions: 0, deletions: 0, binaryChanges: 0}
76
- entry.commits++
77
- entry.additions += stat.additions
78
- entry.deletions += stat.deletions
79
- entry.binaryChanges += stat.binary ? 1 : 0
80
- activity.set(stat.file, entry)
81
- fileCommits.set(stat.file, (fileCommits.get(stat.file) || 0) + 1)
82
- additions += stat.additions
83
- deletions += stat.deletions
84
- if (stat.binary) binaryChanges++
85
- }
86
- }
87
- const {files: graphFiles, adjacency} = graphFilesAndAdjacency(graph)
88
- const raw = [...activity.values()].map((entry) => ({...entry, churn: entry.additions + entry.deletions}))
89
- const churnValues = raw.map((entry) => entry.churn).filter((value) => value > 0)
90
- const connectivityValues = [...graphFiles].map((file) => adjacency.get(file)?.size || 0).filter((value) => value > 0)
91
- const fileChurn = raw.map((entry) => {
92
- const connectivity = adjacency.get(entry.file)?.size || 0
93
- const churnPercentile = percentile(entry.churn, churnValues)
94
- const connectivityPercentile = percentile(connectivity, connectivityValues)
95
- return {...entry, connectivity, churnPercentile, connectivityPercentile, hotspotScore: round(Math.sqrt(churnPercentile * connectivityPercentile))}
96
- }).sort((a, b) => b.churn - a.churn || b.commits - a.commits || a.file.localeCompare(b.file))
97
- const hotspots = fileChurn.filter((entry) => entry.connectivity > 0)
98
- .sort((a, b) => b.hotspotScore - a.hotspotScore || b.churn - a.churn || b.connectivity - a.connectivity || a.file.localeCompare(b.file))
99
- return {fileCommits, graphFiles, adjacency, fileChurn, hotspots, additions, deletions, binaryChanges}
100
- }
101
-
102
- function coChangePairs(commits, fileCommits, limits) {
103
- const counts = new Map()
104
- let truncated = false
105
- for (const commit of commits) {
106
- const files = [...new Set(commit.files.map((entry) => entry.file))].sort()
107
- for (let left = 0; left < files.length; left++) for (let right = left + 1; right < files.length; right++) {
108
- const key = `${files[left]}\0${files[right]}`
109
- if (!counts.has(key) && counts.size >= limits.maxPairCandidates) { truncated = true; continue }
110
- counts.set(key, (counts.get(key) || 0) + 1)
111
- }
112
- }
113
- const pairs = []
114
- for (const [key, count] of counts) {
115
- if (count < limits.minPairCount || !commits.length) continue
116
- const split = key.indexOf('\0'), left = key.slice(0, split), right = key.slice(split + 1)
117
- const leftCount = fileCommits.get(left) || 0, rightCount = fileCommits.get(right) || 0
118
- const leftConfidence = count / leftCount, rightConfidence = count / rightCount
119
- pairs.push({
120
- left, right, count,
121
- jaccard: round(count / (leftCount + rightCount - count)),
122
- lift: round((count * commits.length) / (leftCount * rightCount)),
123
- confidence: round(Math.max(leftConfidence, rightConfidence)),
124
- leftConfidence: round(leftConfidence),
125
- rightConfidence: round(rightConfidence),
126
- })
127
- }
128
- return {pairs: pairs.sort(pairSort), totalCandidates: counts.size, truncated}
129
- }
130
-
131
- export function buildGitHistoryAnalytics({commits = [], graph = {}, window, limits = {}, status = 'complete'} = {}) {
132
- const maxPairs = boundedHistoryInteger(limits.maxPairs, DEFAULTS.maxPairs, 1, HARD_CAPS.maxPairs)
133
- const minPairCount = boundedHistoryInteger(limits.minPairCount, DEFAULTS.minPairCount, 1, 100)
134
- const maxPairCandidates = boundedHistoryInteger(limits.maxPairCandidates, DEFAULTS.maxPairCandidates, 100, HARD_CAPS.maxPairCandidates)
135
- const eligible = commits.filter((commit) => !commit.oversized && commit.files.length > 0)
136
- const skipped = commits.filter((commit) => commit.oversized)
137
- const activity = fileActivity(eligible, graph)
138
- const cochange = coChangePairs(eligible, activity.fileCommits, {minPairCount, maxPairCandidates})
139
- const observed = cochange.pairs.slice(0, maxPairs).map((pair) => publicPair(pair, graphDistanceAtMostTwo(pair.left, pair.right, activity.adjacency)))
140
- const expectedTestSource = cochange.pairs.filter((pair) => isTestPath(pair.left) !== isTestPath(pair.right)).slice(0, maxPairs).map((pair) => {
141
- const test = isTestPath(pair.left) ? pair.left : pair.right
142
- const source = test === pair.left ? pair.right : pair.left
143
- return {
144
- source, test, count: pair.count, jaccard: pair.jaccard, lift: pair.lift, confidence: pair.confidence,
145
- sourceConfidence: source === pair.left ? pair.leftConfidence : pair.rightConfidence,
146
- testConfidence: test === pair.left ? pair.leftConfidence : pair.rightConfidence,
147
- graphDistance: graphDistanceAtMostTwo(source, test, activity.adjacency),
148
- }
149
- })
150
- const hidden = cochange.pairs.filter((pair) => !isTestPath(pair.left) && !isTestPath(pair.right)
151
- && activity.graphFiles.has(pair.left) && activity.graphFiles.has(pair.right)
152
- && graphDistanceAtMostTwo(pair.left, pair.right, activity.adjacency) === null)
153
- .slice(0, maxPairs).map((pair) => publicPair(pair, null))
154
- const reasons = [
155
- skipped.length ? `${skipped.length} oversized change-set(s) excluded` : null,
156
- cochange.truncated ? 'co-change candidate cap reached' : null,
157
- status === 'partial' ? 'git output or commit window was truncated' : null,
158
- ].filter(Boolean)
159
- return {
160
- gitHistoryV: GIT_HISTORY_V,
161
- status: reasons.length ? 'partial' : status,
162
- window: window || null,
163
- limits: {maxCommits: limits.maxCommits ?? null, maxFilesPerCommit: limits.maxFilesPerCommit ?? null, maxPairs, minPairCount, maxPairCandidates},
164
- completeness: {complete: reasons.length === 0, reasons},
165
- totals: {
166
- commitsRead: commits.length, commitsAnalyzed: eligible.length, oversizedCommitsSkipped: skipped.length,
167
- files: activity.fileChurn.length, additions: activity.additions, deletions: activity.deletions,
168
- churn: activity.additions + activity.deletions, binaryChanges: activity.binaryChanges,
169
- ignoredFiles: commits.reduce((sum, commit) => sum + (commit.ignoredFiles || 0), 0),
170
- invalidPaths: commits.reduce((sum, commit) => sum + (commit.invalidPaths || 0), 0),
171
- graphFiles: activity.graphFiles.size,
172
- },
173
- fileChurn: activity.fileChurn,
174
- hotspots: activity.hotspots,
175
- coupling: {eligibleCommits: eligible.length, totalCandidates: cochange.totalCandidates, candidatesTruncated: cochange.truncated, observed, expectedTestSource, hidden},
176
- }
177
- }