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,199 +0,0 @@
1
- import {summarizeFindings} from '../../analysis/findings.js'
2
- import {runGit} from '../../git-exec.js'
3
- import {createPathClassifier, hasPathClass} from '../../path-classification.js'
4
-
5
- const SEVERITY_RANK = {critical: 0, high: 1, medium: 2, low: 3, info: 4}
6
- const AUDIT_NON_PRODUCT_CLASSES = ['test', 'e2e', 'generated', 'vendored', 'mock', 'story', 'docs', 'benchmark', 'temp']
7
-
8
- export function formatAuditFinding(f) {
9
- const where = f.file ? ` (${f.file}${f.symbol ? ` ${f.symbol}` : ''})` : f.package ? ` (pkg ${f.package}${f.version ? `@${f.version}` : ''}${f.manifest ? `; ${f.manifest}` : ''})` : ''
10
- const verification = f.verification?.evidenceModel
11
- ? `\n verification: ${f.verification.evidenceModel}; manifest ${f.verification.manifestDeclaration?.status || 'N/A'}; indexed imports ${f.verification.indexedSourceImports?.status || 'N/A'}; decision ${f.verification.decision || 'REVIEW_REQUIRED'}`
12
- : ''
13
- return ` [${f.severity}/${f.confidence || '?'}] ${f.rule}: ${f.title}${where}${f.reason ? `\n reason: ${f.reason}` : ''}${verification}${f.cycleRoute ? `\n route: ${f.cycleRoute}` : ''}${f.fixHint ? `\n fix: ${f.fixHint}` : ''}`
14
- }
15
-
16
- const auditFindingPaths = (finding) => {
17
- const paths = []
18
- if (finding?.file) paths.push(String(finding.file))
19
- if (Array.isArray(finding?.files)) paths.push(...finding.files.map(String))
20
- if (finding?.cycleRoute) paths.push(...String(finding.cycleRoute).split(/\s*(?:→|→|->)\s*/))
21
- return [...new Set(paths.map((file) => file.replace(/\\/g, '/').trim()).filter(Boolean))]
22
- }
23
-
24
- export function auditFindingPathScope(findings, {includeClassified = false, repoRoot = null} = {}) {
25
- const all = Array.isArray(findings) ? findings : []
26
- if (includeClassified) return {findings: all, suppressed: 0}
27
- const classifier = createPathClassifier(repoRoot)
28
- const cache = new Map()
29
- const classified = (file) => {
30
- if (!cache.has(file)) cache.set(file, classifier.explain(file, {content: ''}))
31
- const info = cache.get(file)
32
- return info?.excluded || AUDIT_NON_PRODUCT_CLASSES.some((name) => hasPathClass(info, name))
33
- }
34
- const kept = all.filter((finding) => {
35
- const paths = auditFindingPaths(finding)
36
- return !paths.length || paths.some((file) => !classified(file))
37
- })
38
- return {findings: kept, suppressed: all.length - kept.length}
39
- }
40
-
41
- const DEPENDENCY_AUDIT_RULES = new Set(['unused-dep', 'missing-dep', 'duplicate-dep', 'unresolved-import', 'lockfile-drift'])
42
- export const isDependencyAuditFinding = (finding) => DEPENDENCY_AUDIT_RULES.has(String(finding?.rule || ''))
43
-
44
- export const auditFilter = (audit, args, findings = audit.findings, repoRoot = null) => {
45
- const minSev = SEVERITY_RANK[args.min_severity] ?? 4
46
- const category = args.category ? String(args.category) : null
47
- return auditFindingPathScope(findings, {includeClassified: args.include_classified === true, repoRoot}).findings
48
- .filter((finding) => (SEVERITY_RANK[finding.severity] ?? 4) <= minSev)
49
- .filter((finding) => !category || (category === 'dependencies' ? isDependencyAuditFinding(finding) : finding.category === category))
50
- }
51
-
52
- const auditChecksLine = (audit) => {
53
- const check = (name, state) => `${name} ${state?.status || 'ERROR'}${state?.detail ? ` — ${state.detail}` : ''}`
54
- return `Checks: ${check('OSV', audit.checks?.osv)}; ${check('RustSec report', audit.checks?.rustsec)}; ${check('malware', audit.checks?.malware)}. A NOT_CHECKED/PARTIAL/ERROR check is incomplete or unknown, never a clean zero.`
55
- }
56
-
57
- const auditCapabilityLines = (audit) => {
58
- const matrix = audit.healthCapabilities || {}
59
- const rows = [
60
- ['structure', 'structure'],
61
- ['dependencies', 'dependencies'],
62
- ['runtimeCorrectness', 'runtime correctness'],
63
- ['concurrency', 'concurrency'],
64
- ['advisories', 'advisories'],
65
- ['malware', 'malware'],
66
- ['coverage', 'coverage'],
67
- ]
68
- const lines = ['Health capability matrix (status/completeness):']
69
- for (const [key, label] of rows) {
70
- const item = matrix[key]
71
- lines.push(` ${label}: ${item?.status || 'NOT_CHECKED'}/${item?.completeness || 'PARTIAL'} — ${item?.detail || 'Capability evidence was not reported.'}`)
72
- }
73
- return lines
74
- }
75
-
76
- const auditExtensionLines = (audit) => {
77
- const items = audit.extensionCapabilities || []
78
- if (!items.length) return []
79
- return [
80
- 'Extension analyzer matrix (status/completeness):',
81
- ...items.map((item) => ` ${item.extension ? `${item.extension}/` : ''}${item.id}: ${item.status}/${item.completeness} — ${item.detail} (${item.findingCount} finding(s)).`),
82
- ]
83
- }
84
-
85
- const auditDependencyCoverageLines = (deps) => {
86
- const ecosystems = Object.values(deps.ecosystems || {}).filter((item) => item?.present)
87
- const verification = Object.entries(deps.verificationCoverage || {})
88
- return [
89
- ecosystems.length
90
- ? `Dependency ecosystems: ${ecosystems.map((item) => `${item.ecosystem} ${item.status}/${item.completeness} (${item.manifests?.length || 0} manifest(s), ${item.declared || 0} declaration(s))`).join('; ')}.`
91
- : 'Dependency ecosystems: no supported or unsupported dependency manifest was discovered.',
92
- verification.length
93
- ? `Dependency verification coverage: ${verification.map(([ecosystem, state]) => `${ecosystem} ${state}`).join('; ')}. Per-finding evidence: ${deps.perFindingVerification ? 'AVAILABLE' : 'UNAVAILABLE'}.`
94
- : 'Dependency verification coverage: NOT_CHECKED; no manifest evidence was available.',
95
- ]
96
- }
97
-
98
- const scopedDependencyFindingCounts = (allFindings, scopedFindings) => {
99
- const dependencyFindings = (items) => (items || []).filter(isDependencyAuditFinding)
100
- const all = dependencyFindings(allFindings)
101
- const scoped = dependencyFindings(scopedFindings)
102
- const count = (rule) => scoped.filter((finding) => finding.rule === rule).length
103
- return {
104
- unused: count('unused-dep'),
105
- missing: count('missing-dep'),
106
- duplicateDeclarations: count('duplicate-dep'),
107
- suppressed: Math.max(0, all.length - scoped.length),
108
- }
109
- }
110
-
111
- const auditDependencySummaryLine = (audit, deps, scopedCounts = null) => {
112
- const ecosystems = Object.values(deps.ecosystems || {}).filter((item) => item?.present)
113
- const checked = ecosystems.filter((item) => item.status === 'CHECKED')
114
- const unsupported = ecosystems.filter((item) => item.status === 'NOT_SUPPORTED')
115
- const unused = scopedCounts?.unused ?? deps.unused ?? 'unknown'
116
- const missing = scopedCounts?.missing ?? deps.missing ?? 'unknown'
117
- const duplicateDeclarations = scopedCounts?.duplicateDeclarations ?? deps.duplicateDeclarations ?? 'unknown'
118
- const scopedSuffix = scopedCounts?.suppressed
119
- ? ` Production-first path policy suppressed ${scopedCounts.suppressed} classified dependency finding(s).`
120
- : ''
121
- if (!ecosystems.length || deps.status === 'NOT_CHECKED') {
122
- return 'Dependency manifests: NOT_CHECKED — no dependency manifest was discovered; manifest-to-import verification did not run and no dependency verdict was produced.'
123
- }
124
- if (!checked.length && unsupported.length) {
125
- const manifests = unsupported.reduce((total, item) => total + (item.manifests?.length || 0), 0)
126
- const declared = unsupported.reduce((total, item) => total + (item.declared || 0), 0)
127
- const names = unsupported.map((item) => item.ecosystem).join(', ')
128
- return `Dependency manifests: ${deps.status || 'PARTIAL'} — discovered ${declared} declaration(s) in ${manifests} ${names} manifest(s), but package-to-artifact verification is NOT_SUPPORTED; no unused, missing, or duplicate-declaration verdict was produced for those ecosystems.`
129
- }
130
- if (checked.length && unsupported.length) {
131
- const checkedManifests = checked.reduce((total, item) => total + (item.manifests?.length || 0), 0)
132
- const checkedDeclared = checked.reduce((total, item) => total + (item.declared || 0), 0)
133
- const unsupportedManifests = unsupported.reduce((total, item) => total + (item.manifests?.length || 0), 0)
134
- const unsupportedDeclared = unsupported.reduce((total, item) => total + (item.declared || 0), 0)
135
- const checkedNames = checked.map((item) => item.ecosystem).join(', ')
136
- const unsupportedNames = unsupported.map((item) => item.ecosystem).join(', ')
137
- return `Dependency manifests: ${deps.status || 'PARTIAL'} — checked ${checkedDeclared} declaration(s) across ${checkedManifests} supported manifest(s) (${checkedNames}); inventoried ${unsupportedDeclared} declaration(s) across ${unsupportedManifests} unsupported manifest(s) (${unsupportedNames}), where package-to-artifact verification is NOT_SUPPORTED. Supported-ecosystem findings: unused ${unused}, missing ${missing}, duplicate declarations ${duplicateDeclarations}.${scopedSuffix}`
138
- }
139
- return `Dependency manifests: ${deps.status || 'UNKNOWN'} — checked ${deps.declared ?? audit.scanned.manifestDeps ?? 0} declared package(s) against ${deps.importRecords ?? audit.scanned.externalImports ?? 0} external import record(s); unused ${unused}, missing ${missing}, duplicate declarations ${duplicateDeclarations}.${scopedSuffix}`
140
- }
141
-
142
- const auditConventionLines = (audit) => {
143
- const entries = audit.conventionReachability?.entries || []
144
- if (!entries.length) return []
145
- return [
146
- `Convention reachability: ${audit.conventionReachability.count} framework-managed file(s) are external entry points, not orphan/dead findings${audit.conventionReachability.truncated ? ' (evidence capped)' : ''}:`,
147
- ...entries.slice(0, 5).map((entry) => ` [${entry.confidence}] ${entry.file} — ${entry.marker}: ${entry.reason}`),
148
- audit.conventionReachability.count > 5 ? ` … +${audit.conventionReachability.count - 5} more in bounded JSON result data` : null,
149
- ].filter((line) => line != null)
150
- }
151
-
152
- export const formatOrdinaryAudit = (audit, args, findings = audit.findings, heading = null, repoRoot = null) => {
153
- const max = Math.max(1, Math.min(100, Number(args.max_findings) || 30))
154
- const pathScope = auditFindingPathScope(findings, {includeClassified: args.include_classified === true, repoRoot})
155
- const filtered = auditFilter(audit, args, pathScope.findings, repoRoot)
156
- const shown = filtered.slice(0, max)
157
- const summary = summarizeFindings(pathScope.findings)
158
- const sev = summary.bySeverity
159
- const bycat = summary.byCategory
160
- const deps = audit.dependencyReport || {}
161
- const dependencyCounts = scopedDependencyFindingCounts(findings, pathScope.findings)
162
- return [
163
- heading,
164
- `Internal audit of ${audit.repo} (${audit.scanned.files} files, ${audit.scanned.symbols} symbols, ${audit.scanned.externalImports} external imports; malware scan: ${audit.scanned.malwareScanMode}).`,
165
- ...auditConventionLines(audit),
166
- auditDependencySummaryLine(audit, deps, dependencyCounts),
167
- ...auditDependencyCoverageLines(deps),
168
- ...auditCapabilityLines(audit),
169
- ...auditExtensionLines(audit),
170
- `Scoped severity: critical ${sev.critical}, high ${sev.high}, medium ${sev.medium}, low ${sev.low}, info ${sev.info}. Scoped categories: unused ${bycat.unused}, structure ${bycat.structure}, vulnerability ${bycat.vulnerability}, malware ${bycat.malware}.`,
171
- pathScope.suppressed ? `Path policy: production-first; suppressed ${pathScope.suppressed} finding(s) whose evidence is entirely test/e2e/generated/vendored/mock/story/docs/benchmark/temp or explicitly excluded. Pass include_classified:true to include them.` : 'Path policy: production-first; no classified-only findings were suppressed.',
172
- `Repository-level ${auditChecksLine(audit)}`,
173
- '',
174
- `Showing ${shown.length} of ${filtered.length} finding(s)${args.category ? ` in category "${args.category}"` : ''}${args.min_severity ? ` at ≥${args.min_severity}` : ''}:`,
175
- ...shown.map(formatAuditFinding),
176
- filtered.length > shown.length ? ` … +${filtered.length - shown.length} more (raise max_findings or filter by category/min_severity)` : null,
177
- ].filter((line) => line != null).join('\n')
178
- }
179
-
180
- export const gitUntracked = (repoRoot) => {
181
- const result = runGit(repoRoot, ['ls-files', '--others', '--exclude-standard'], {maxBuffer: 2 * 1024 * 1024})
182
- if (result.status !== 0) return {
183
- ok: false,
184
- files: [],
185
- error: String(result.stderr || result.error?.message || 'git ls-files failed').trim(),
186
- }
187
- return {
188
- ok: true,
189
- files: String(result.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean),
190
- error: null,
191
- }
192
- }
193
-
194
- export const pathsFromClassification = (classification) => [...new Set(classification.files
195
- .flatMap((file) => [file.oldPath, file.newPath])
196
- .filter((file) => file && file !== '(diff unavailable)'))]
197
- .sort((left, right) => left.localeCompare(right))
198
-
199
- export const formatDebtFinding = (finding, state) => ` [${state}] ${formatAuditFinding(finding).trimStart()}`
@@ -1,199 +0,0 @@
1
- import {rawGraph, effectiveRawGraph} from '../graph-context.mjs'
2
- import {runInternalAudit} from '../../analysis/internal-audit.js'
3
- import {applyAuditExtensions} from '../../analysis/audit-extensions.js'
4
- import {classifyChangeImpact} from '../../analysis/change-classification.js'
5
- import {
6
- compareAuditDebt,
7
- normalizeAuditScopeFiles,
8
- scopeAuditFindings,
9
- } from '../../analysis/audit-debt.js'
10
- import {buildInternalGraph} from '../../graph/internal-builder.js'
11
- import {resolveGitCommit, withGitRefCheckout} from '../../analysis/git-ref-graph.js'
12
- import {filterGraphForMode} from '../../graph/graph-filter.js'
13
- import {toolResult} from '../tool-result.mjs'
14
- import {summarizeFindings} from '../../analysis/findings.js'
15
- const auditFormatVersion = new URL(import.meta.url).search
16
- const {
17
- auditFilter,
18
- formatDebtFinding,
19
- formatOrdinaryAudit,
20
- gitUntracked,
21
- pathsFromClassification,
22
- } = await import(new URL(`./audit-format.mjs${auditFormatVersion}`, import.meta.url).href)
23
-
24
- const runAudit = async (repoRoot, graph, args, ctx, options = {}) => applyAuditExtensions(
25
- await runInternalAudit(repoRoot, {graph, ...options}),
26
- {providers: ctx.extensions?.auditProviders || [], repoRoot, graph, args},
27
- )
28
-
29
- async function runAuditWithBaseline(args, ctx, currentGraph) {
30
- const resolved = resolveGitCommit(ctx.repoRoot, args.base_ref)
31
- if (!resolved.ok) return toolResult(`Audit baseline unavailable: ${resolved.error}.`, {
32
- status: 'INVALID', comparison: {status: 'UNAVAILABLE', reason: resolved.error}, findings: [],
33
- }, {completeness: {status: 'PARTIAL', reason: resolved.error}})
34
-
35
- let currentAudit
36
- try {
37
- currentAudit = await runAudit(ctx.repoRoot, currentGraph, args, ctx, {skipMalwareScan: !args.include_malware_scan})
38
- } catch (error) {
39
- const reason = error instanceof Error ? error.message : String(error)
40
- return toolResult(`Audit failed while building the current graph: ${reason}`, {
41
- status: 'ERROR', comparison: {status: 'UNAVAILABLE', reason}, findings: [],
42
- }, {completeness: {status: 'PARTIAL', reason}})
43
- }
44
- if (!currentAudit.ok) return toolResult(`Audit failed: ${currentAudit.error}`, {
45
- status: 'ERROR', comparison: {status: 'UNAVAILABLE', reason: currentAudit.error}, findings: [],
46
- }, {completeness: {status: 'PARTIAL', reason: currentAudit.error}})
47
-
48
- const normalized = normalizeAuditScopeFiles(args.changed_files)
49
- if (!normalized.ok) return toolResult(`Audit scope invalid: ${normalized.error}.`, {
50
- status: 'INVALID', comparison: {status: 'UNAVAILABLE', reason: normalized.error}, findings: [],
51
- }, {completeness: {status: 'PARTIAL', reason: normalized.error}})
52
- let changedFiles = normalized.files
53
- let completeChangeSet = false
54
- let scopeSource = 'explicit changed_files'
55
- let changeEvidence = null
56
- if (changedFiles == null) {
57
- completeChangeSet = true
58
- const untracked = gitUntracked(ctx.repoRoot)
59
- if (!untracked.ok) {
60
- return toolResult(`Audit comparison stopped: untracked-file discovery failed (${untracked.error}). Pass changed_files explicitly to establish the scope.`, {
61
- status: 'PARTIAL', comparison: {status: 'UNAVAILABLE', reason: untracked.error}, findings: [],
62
- }, {completeness: {status: 'PARTIAL', reason: untracked.error}})
63
- }
64
- changeEvidence = classifyChangeImpact({
65
- repoRoot: ctx.repoRoot,
66
- graph: currentGraph,
67
- base: resolved.commit,
68
- files: untracked.files,
69
- })
70
- if (!changeEvidence.ok || changeEvidence.bounds?.truncated) {
71
- const reason = changeEvidence.reasons.join(' ')
72
- return toolResult(`Audit comparison stopped: changed-file derivation against ${resolved.ref} was incomplete. ${reason} Pass changed_files explicitly to establish a complete scope.`, {
73
- status: 'PARTIAL', comparison: {status: 'UNAVAILABLE', reason}, findings: [], changeEvidence,
74
- }, {completeness: {status: 'PARTIAL', reason}})
75
- }
76
- changedFiles = pathsFromClassification(changeEvidence)
77
- if (changedFiles.length > 500) {
78
- const reason = `derived changed-file scope contains ${changedFiles.length} files, above the 500-file comparison bound`
79
- return toolResult(`Audit comparison stopped: ${reason}. Pass a narrower explicit changed_files scope.`, {
80
- status: 'PARTIAL', comparison: {status: 'UNAVAILABLE', reason}, findings: [], changeEvidence,
81
- }, {completeness: {status: 'PARTIAL', reason}})
82
- }
83
- scopeSource = `git diff ${resolved.commit.slice(0, 12)}…working-tree`
84
- }
85
-
86
- const baseline = await withGitRefCheckout(ctx.repoRoot, resolved.commit, async (checkout) => {
87
- let graph = await buildInternalGraph(checkout)
88
- const currentMode = ['full', 'no-tests', 'tests-only'].includes(currentGraph?.graphBuildMode)
89
- ? currentGraph.graphBuildMode : 'full'
90
- if (currentMode !== 'full') graph = filterGraphForMode(graph, currentMode, {repoRoot: checkout})
91
- graph.graphBuildMode = currentMode
92
- graph.graphBuildScope = ''
93
- return runAudit(checkout, graph, args, ctx, {skipMalwareScan: true})
94
- })
95
- if (!baseline.ok || !baseline.value?.ok) {
96
- const reason = baseline.error || baseline.value?.error || 'baseline audit failed'
97
- return toolResult(`Audit baseline unavailable: ${reason}.`, {
98
- status: 'ERROR', comparison: {status: 'UNAVAILABLE', reason}, findings: [],
99
- }, {completeness: {status: 'PARTIAL', reason}})
100
- }
101
-
102
- const comparison = compareAuditDebt(currentAudit, baseline.value, changedFiles, {completeChangeSet})
103
- const mode = ['new', 'existing', 'all'].includes(args.debt) ? args.debt : 'new'
104
- const selectedRaw = mode === 'new' ? comparison.new : mode === 'existing' ? comparison.existing : comparison.all
105
- const selected = auditFilter(currentAudit, args, selectedRaw, ctx.repoRoot)
106
- const max = Math.max(1, Math.min(100, Number(args.max_findings) || 30))
107
- const shown = selected.slice(0, max)
108
- const optional = comparison.optional.checks.map((check) => `${check.name.toUpperCase()} UNCOMPARABLE (current ${check.current}; baseline ${check.baseline})`).join('; ')
109
- const stateOf = (finding) => mode === 'all' ? finding.debtState : mode
110
- const fixedShown = auditFilter(baseline.value, args, comparison.fixed, ctx.repoRoot).slice(0, Math.min(10, max))
111
- const text = [
112
- `${mode.toUpperCase()} DEBT — deterministic internal audit vs ${resolved.ref} (${resolved.commit.slice(0, 12)})`,
113
- `Changed scope: ${changedFiles.length} file(s) from ${scopeSource}${changedFiles.length ? ` — ${changedFiles.slice(0, 12).join(', ')}${changedFiles.length > 12 ? ', …' : ''}` : ' — working tree matches the baseline'}.`,
114
- `Scope comparison: ${comparison.totals.scope.new} new, ${comparison.totals.scope.existing} existing, ${comparison.totals.scope.fixed} fixed deterministic finding(s). Repository totals: ${comparison.totals.repository.new} new, ${comparison.totals.repository.existing} existing, ${comparison.totals.repository.fixed} fixed.`,
115
- `Optional checks: ${optional}. Supply-chain findings are not assigned new/existing/fixed state from a source-only checkout.`,
116
- '',
117
- `Showing ${shown.length} of ${selected.length} ${mode} finding(s) after filters:`,
118
- ...shown.map((finding) => formatDebtFinding(finding, stateOf(finding))),
119
- selected.length > shown.length ? ` … +${selected.length - shown.length} more` : null,
120
- '',
121
- `Fixed in this changed scope: ${comparison.fixed.length}${fixedShown.length ? ' (sample below)' : ''}.`,
122
- ...fixedShown.map((finding) => formatDebtFinding(finding, 'fixed')),
123
- ].filter((line) => line != null).join('\n')
124
- return toolResult(text, {
125
- status: 'COMPLETE',
126
- mode: 'baseline-comparison',
127
- debt: mode,
128
- baseline: {ref: resolved.ref, commit: resolved.commit},
129
- scope: {files: changedFiles, source: scopeSource},
130
- comparison: {
131
- status: 'COMPLETE',
132
- scope: comparison.scope,
133
- totals: comparison.totals,
134
- new: comparison.new,
135
- existing: comparison.existing,
136
- fixed: comparison.fixed,
137
- optional: comparison.optional,
138
- },
139
- findings: selected,
140
- changeEvidence,
141
- }, {
142
- page: {shown: shown.length, total: selected.length, capped: shown.length < selected.length},
143
- completeness: {status: 'COMPLETE'},
144
- })
145
- }
146
-
147
- // Full internal health audit: dead code + unused exports, dependency findings (npm/go/py missing &
148
- // unused deps), structure (import cycles / orphans / boundary rules), supply-chain (offline OSV
149
- // advisories, typosquat, lockfile drift), optional malware heuristics.
150
- export async function tRunAudit(g, args, ctx) {
151
- if (!ctx.repoRoot) return toolResult('Audit needs the repo root (not provided to this server).', {
152
- status: 'INVALID', findings: [], reason: 'repo root unavailable',
153
- }, {completeness: {status: 'PARTIAL', reason: 'repo root unavailable'}})
154
- if (args.base_ref) return runAuditWithBaseline(args, ctx, rawGraph(ctx))
155
- const graph = effectiveRawGraph(ctx)
156
- const audit = await runAudit(ctx.repoRoot, graph, args, ctx, {
157
- skipMalwareScan: !args.include_malware_scan, // greps installed packages — slow, so opt-in
158
- })
159
- if (!audit.ok) return toolResult(`Audit failed: ${audit.error}`, {
160
- status: 'ERROR', findings: [], reason: audit.error,
161
- }, {completeness: {status: 'PARTIAL', reason: audit.error}})
162
- if (Array.isArray(args.changed_files)) {
163
- const normalized = normalizeAuditScopeFiles(args.changed_files)
164
- if (!normalized.ok) return toolResult(`Changed-scope audit invalid: ${normalized.error}.`, {
165
- status: 'INVALID', mode: 'changed-scope', findings: [], reason: normalized.error,
166
- }, {completeness: {status: 'PARTIAL', reason: normalized.error}})
167
- const scoped = scopeAuditFindings(audit.findings, normalized.files)
168
- const text = formatOrdinaryAudit(audit, args, scoped,
169
- `CHANGED-SCOPE ONLY — ${normalized.files.length} explicitly supplied file(s); no baseline was provided, so these findings are not classified as new, existing, or fixed.`, ctx.repoRoot)
170
- return toolResult(text, {
171
- status: 'COMPLETE',
172
- mode: 'changed-scope',
173
- scope: {files: normalized.files, source: 'explicit changed_files'},
174
- comparison: {status: 'UNAVAILABLE', reason: 'base_ref was not provided; changed-scope is not a new-debt claim'},
175
- findings: auditFilter(audit, args, scoped, ctx.repoRoot),
176
- }, {completeness: {status: 'COMPLETE'}})
177
- }
178
- const filtered = auditFilter(audit, args, audit.findings, ctx.repoRoot)
179
- const max = Math.max(1, Math.min(100, Number(args.max_findings) || 30))
180
- const shown = filtered.slice(0, max)
181
- return toolResult(formatOrdinaryAudit(audit, args, audit.findings, null, ctx.repoRoot), {
182
- status: 'COMPLETE',
183
- mode: 'ordinary',
184
- repo: audit.repo,
185
- scanned: audit.scanned,
186
- summary: summarizeFindings(filtered),
187
- findings: shown,
188
- healthCapabilities: audit.healthCapabilities,
189
- extensionCapabilities: audit.extensionCapabilities || [],
190
- dependencyReport: audit.dependencyReport,
191
- conventionReachability: audit.conventionReachability,
192
- checks: audit.checks,
193
- }, {
194
- page: {shown: shown.length, total: filtered.length, capped: shown.length < filtered.length},
195
- completeness: {status: 'COMPLETE'},
196
- })
197
- }
198
-
199
- // Named module clusters: graph communities labeled by their dominant folder instead of bare numbers.
@@ -1,110 +0,0 @@
1
- import {effectiveRawGraph} from '../graph-context.mjs'
2
- import {computeDeadCodeReview} from '../../analysis/dead-code-review.js'
3
- import {
4
- collectNonRuntimeRoots,
5
- collectPackageScopes,
6
- collectSourceTexts,
7
- readRepoJson,
8
- } from '../../analysis/internal-audit.collect.js'
9
- import {entryFiles} from '../../analysis/internal-audit.reach.js'
10
- import {createPathClassifier} from '../../path-classification.js'
11
- import {createRepoBoundary} from '../../repo-path.js'
12
- import {readCachedSymbolPrecisionEvidence} from '../../precision/symbol-query.js'
13
- import {toolResult} from '../tool-result.mjs'
14
-
15
- export function tFindDeadCode(g, args, ctx) {
16
- if (!ctx.repoRoot) return 'Dead-code review needs the repo root (not provided to this server).'
17
- const effectiveGraph = effectiveRawGraph(ctx)
18
- const pointEvidence = readCachedSymbolPrecisionEvidence({repoRoot: ctx.repoRoot, graphPath: ctx.graphPath, graph: effectiveGraph})
19
- const graph = {
20
- ...effectiveGraph,
21
- precisionReferenceSymbols: [...new Set([
22
- ...(effectiveGraph.precisionReferenceSymbols || []),
23
- ...pointEvidence.referenceSymbols,
24
- ])],
25
- precisionProductionReferenceSymbols: [...new Set([
26
- ...(effectiveGraph.precisionProductionReferenceSymbols || []),
27
- ...pointEvidence.productionReferenceSymbols,
28
- ])],
29
- precisionTestReferenceSymbols: [...new Set([
30
- ...(effectiveGraph.precisionTestReferenceSymbols || []),
31
- ...pointEvidence.testReferenceSymbols,
32
- ])],
33
- precisionNoReferenceSymbols: [...new Set([
34
- ...(effectiveGraph.precisionNoReferenceSymbols || []),
35
- ...pointEvidence.noReferenceSymbols,
36
- ])],
37
- }
38
- const boundary = createRepoBoundary(ctx.repoRoot)
39
- const pkg = readRepoJson(boundary, 'package.json') || {}
40
- const rules = readRepoJson(boundary, '.weavatrix-deps.json') || {}
41
- const sources = collectSourceTexts(ctx.repoRoot, graph)
42
- const dynamicTargets = new Set((graph.externalImports || [])
43
- .filter((entry) => entry?.dynamic && entry?.target)
44
- .map((entry) => String(entry.target).replace(/\\/g, '/')))
45
- const frameworkEvidence = []
46
- const entries = entryFiles(graph, collectPackageScopes(ctx.repoRoot, pkg), dynamicTargets, {
47
- declaredEntries: rules.entrypoints || rules.entries || [],
48
- sources,
49
- conventionEvidence: frameworkEvidence,
50
- })
51
- for (const root of collectNonRuntimeRoots(ctx.repoRoot, rules)) {
52
- for (const file of sources.keys()) if (file === root || file.startsWith(`${root}/`)) entries.add(file)
53
- }
54
-
55
- const review = computeDeadCodeReview(graph, sources, {
56
- entrySet: entries,
57
- dynamicTargets,
58
- frameworkEvidence,
59
- pathClassifier: createPathClassifier(ctx.repoRoot),
60
- includeTests: args.include_tests === true,
61
- includeClassified: args.include_classified === true,
62
- minConfidence: args.min_confidence,
63
- path: args.path,
64
- kinds: args.kinds,
65
- })
66
- const max = Math.max(1, Math.min(100, Number(args.top_n) || 30))
67
- const shown = review.candidates.slice(0, max)
68
- const counts = review.totals.byConfidence
69
- const suppression = Object.entries(review.suppressed)
70
- .filter(([, count]) => count)
71
- .map(([name, count]) => `${name} ${count}`)
72
- .join(', ')
73
- const lines = shown.map((candidate, index) => {
74
- const subject = candidate.kind === 'file'
75
- ? candidate.file
76
- : `${candidate.owner ? `${candidate.owner}.` : ''}${candidate.symbol || candidate.id}`
77
- const where = `${candidate.file}${candidate.line ? `:${candidate.line}` : ''}`
78
- return [
79
- `${index + 1}. [${candidate.confidence}/${candidate.evidenceTier}/${candidate.classification}] ${subject} (${where})`,
80
- ` evidence: ${candidate.evidence.map((item) => item.fact).join(' ')}`,
81
- candidate.caveats.length ? ` caution: ${candidate.caveats.join(' ')}` : null,
82
- ` remaining: ${candidate.remainingChecks.join(' ')}`,
83
- ].filter(Boolean).join('\n')
84
- })
85
- const text = [
86
- `Dead-code review: ${shown.length} of ${review.candidates.length} candidate(s) shown (high ${counts.high}, medium ${counts.medium}, low ${counts.low}).`,
87
- `Evidence tiers: strong static ${review.totals.byEvidenceTier.strongStatic}, bounded static ${review.totals.byEvidenceTier.boundedStatic}, high uncertainty ${review.totals.byEvidenceTier.highUncertainty}.`,
88
- `Verdict: REVIEW_REQUIRED. This is static evidence, never permission to auto-delete or bulk-delete.`,
89
- suppression ? `Suppressed by current filters: ${suppression}.` : null,
90
- review.suppressed.confidence ? 'Use min_confidence=low only when public/framework/dynamic candidates need explicit review.' : null,
91
- '',
92
- ...(lines.length ? lines : ['No candidates matched the current production/path/kind/confidence filters.']),
93
- '',
94
- 'Before removal: read_source, get_dependents, exact search, framework/config/manifest inspection, and the repository tests.',
95
- ].filter((line) => line != null).join('\n')
96
- return toolResult(text, {
97
- status: 'COMPLETE',
98
- verdict: 'REVIEW_REQUIRED',
99
- candidates: shown,
100
- totals: review.totals,
101
- suppressed: review.suppressed,
102
- repoSignals: review.repoSignals,
103
- policy: review.policy,
104
- }, {
105
- warnings: review.warnings,
106
- page: {shown: shown.length, total: review.candidates.length, capped: shown.length < review.candidates.length},
107
- completeness: {status: 'COMPLETE'},
108
- })
109
- }
110
-
@@ -1,121 +0,0 @@
1
- import {computeDuplicates} from '../../analysis/duplicates.js'
2
- import {analyzeDuplicateGroups} from '../../analysis/duplicate-groups.js'
3
- import {toolResult} from '../tool-result.mjs'
4
-
5
- const NON_PRODUCT_DUPLICATE_CLASSES = new Set(['generated', 'vendored', 'mock', 'story', 'docs', 'benchmark', 'temp'])
6
- const fragmentEligible = (fragment, {tokMin, skipTests, includeClassified}) => {
7
- if (fragment.n < tokMin) return false
8
- const classes = new Set(fragment.classes || [])
9
- if (skipTests && (fragment.test || classes.has('test') || classes.has('e2e'))) return false
10
- if (!includeClassified && (fragment.excluded || [...classes].some((name) => NON_PRODUCT_DUPLICATE_CLASSES.has(name)))) return false
11
- return true
12
- }
13
-
14
- export function tFindDuplicates(g, args, ctx) {
15
- if (!ctx.repoRoot) return toolResult('Duplicate scan needs the repo root (not provided to this server).', {
16
- status: 'INVALID', groups: [], reason: 'repo root unavailable',
17
- }, {completeness: {status: 'PARTIAL', reason: 'repo root unavailable'}})
18
- const simMin = Math.min(100, Math.max(50, Number(args.min_similarity) || 80))
19
- const tokMin = Math.min(400, Math.max(12, Number(args.min_tokens) || 50))
20
- const mode = args.mode === 'strict' ? 'strict' : 'renamed'
21
- const skipTests = args.include_tests ? false : true
22
- const includeClassified = args.include_classified === true || args.include_non_product === true
23
- const includeStrings = !!args.include_strings
24
- // semantic mode: same-name symbols across files, ranked by size — LOW similarity is the signal
25
- // (same name, drifted behavior). Token-clone pairing is skipped entirely.
26
- if (args.mode === 'semantic') {
27
- const data = computeDuplicates(ctx.repoRoot, ctx.graphPath, {nameTwins: true, minTokens: tokMin})
28
- const frags = data.frags
29
- const candidates = []
30
- for (const twin of data.nameTwins || []) {
31
- const allowed = new Set(twin.members.filter((i) => fragmentEligible(frags[i], {tokMin, skipTests, includeClassified})))
32
- const pairs = (twin.pairs || []).filter((p) => allowed.has(p.a) && allowed.has(p.b))
33
- if (!pairs.length) continue
34
- const closest = pairs.slice().sort((a, b) => b.similarity - a.similarity)[0]
35
- const farthest = pairs.slice().sort((a, b) => a.similarity - b.similarity)[0]
36
- if (closest.similarity >= 85) candidates.push({kind: 'clone', label: twin.label, pair: closest})
37
- if (farthest.similarity <= 45) candidates.push({kind: 'collision', label: twin.label, pair: farthest})
38
- }
39
- for (const item of candidates) item.tokens = frags[item.pair.a].n + frags[item.pair.b].n
40
- candidates.sort((a, b) => {
41
- if (a.kind !== b.kind) return a.kind === 'clone' ? -1 : 1
42
- return a.kind === 'clone'
43
- ? b.pair.similarity - a.pair.similarity || b.tokens - a.tokens
44
- : b.tokens - a.tokens || a.pair.similarity - b.pair.similarity
45
- })
46
- if (!candidates.length) return toolResult(
47
- 'No actionable same-name pairs across files (semantic mode; ambiguous middle-similarity pairs are suppressed).',
48
- {status: 'COMPLETE', mode: 'semantic', total: 0, groups: [], thresholds: {minTokens: tokMin}},
49
- {page: {shown: 0, total: 0, capped: false}, completeness: {status: 'COMPLETE'}},
50
- )
51
- const top = candidates.slice(0, Math.min(30, Math.max(1, Number(args.top_n) || 15)))
52
- const lines = top.map((item, k) => {
53
- const a = frags[item.pair.a]
54
- const b = frags[item.pair.b]
55
- const verdict = item.kind === 'clone'
56
- ? 'near-identical duplicate candidate — review, then extract shared logic if the contract is truly shared'
57
- : 'name collision, not a duplicate — inspect only if these definitions should share a contract'
58
- return [
59
- `${k + 1}. "${item.label}" — ${item.pair.similarity}% similar; ${verdict}`,
60
- ` ${a.file}:${a.start}-${a.end} (${a.n} tok)`,
61
- ` ${b.file}:${b.start}-${b.end} (${b.n} tok)`,
62
- ].join('\n')
63
- })
64
- const text = `Found ${candidates.length} actionable same-name pair(s) across files (semantic mode; one closest clone and/or farthest collision per name). Top ${top.length}:\n\n${lines.join('\n\n')}\n\nThese are review candidates, not automatic refactors. Use read_source on both sites before changing code.`
65
- return toolResult(text, {
66
- status: 'COMPLETE', mode: 'semantic', total: candidates.length, thresholds: {minTokens: tokMin},
67
- groups: top.map((item) => ({
68
- kind: item.kind, label: item.label, similarity: item.pair.similarity,
69
- members: [frags[item.pair.a], frags[item.pair.b]].map((fragment) => ({
70
- file: fragment.file, start: fragment.start, end: fragment.end, tokens: fragment.n, label: fragment.label,
71
- })),
72
- })),
73
- }, {page: {shown: top.length, total: candidates.length, capped: top.length < candidates.length}, completeness: {status: 'COMPLETE'}})
74
- }
75
- const analysis = analyzeDuplicateGroups(ctx.repoRoot, ctx.graphPath, args)
76
- const groups = analysis.groups
77
- const smallPolicy = tokMin < 30 ? ' Small fragments below 30 tokens require at least 95% similarity and two shared bounded fingerprints.' : ''
78
- const suppressed = analysis.suppressed
79
- const suppressionNote = suppressed && !includeClassified
80
- ? ` ${suppressed} fragment(s) classified as tests/e2e/generated/vendored/mock/story/docs/benchmark/temp or matched by .weavatrix.json exclude were suppressed; pass include_classified:true (and include_tests:true for tests) to inspect them explicitly.`
81
- : ''
82
- const boilerplateNote = analysis.boilerplateSuppressed
83
- ? ` ${analysis.boilerplateSuppressed} all-router framework boilerplate group(s) were suppressed; pass include_boilerplate:true to inspect them.`
84
- : ''
85
- const declarativeNote = analysis.declarativeSuppressed
86
- ? ` ${analysis.declarativeSuppressed} immutable declarative catalog group(s) were suppressed; pass include_declarative:true to inspect repeated data shapes.`
87
- : ''
88
- if (!groups.length) return toolResult(
89
- `No clones at ≥${simMin}% similarity / ≥${tokMin} tokens (${mode} mode). Try lowering the thresholds.${smallPolicy}${suppressionNote}${boilerplateNote}${declarativeNote}`,
90
- {
91
- status: 'COMPLETE', mode, total: 0, groups: [], thresholds: {minSimilarity: simMin, minTokens: tokMin},
92
- suppressed: {classifiedFragments: suppressed, boilerplateGroups: analysis.boilerplateSuppressed, declarativeGroups: analysis.declarativeSuppressed},
93
- },
94
- {page: {shown: 0, total: 0, capped: false}, completeness: {status: 'COMPLETE'}},
95
- )
96
- const top = groups.slice(0, Math.min(30, Math.max(1, Number(args.top_n) || 15)))
97
- const lines = top.map((grp, k) => {
98
- const isStr = grp.members.some((f) => f.kind === 'string')
99
- const head = `${k + 1}. ${grp.members.length}× "${grp.members[0].label}"${isStr ? ' [string literal]' : ''} — ≤${grp.maxSim}% similar, ${grp.tokens} duplicated tokens`
100
- const sites = grp.members.slice(0, 8).map((f) => ` ${f.file}:${f.start}-${f.end}`)
101
- return [head, ...sites].join('\n')
102
- })
103
- const text = `Found ${groups.length} clone group(s) (${mode} mode, ≥${simMin}%, ≥${tokMin} tok${includeStrings ? ', incl. large string literals' : ''}). Top ${top.length}:\n\n${lines.join('\n\n')}\n\nUse read_source on any two sites to compare, then extract shared logic.${smallPolicy}${suppressionNote}${boilerplateNote}${declarativeNote}`
104
- return toolResult(text, {
105
- status: 'COMPLETE', mode, total: groups.length, thresholds: {minSimilarity: simMin, minTokens: tokMin},
106
- includeStrings,
107
- suppressed: {classifiedFragments: suppressed, boilerplateGroups: analysis.boilerplateSuppressed, declarativeGroups: analysis.declarativeSuppressed},
108
- groups: top.map((group) => ({
109
- similarity: group.maxSim, duplicatedTokens: group.tokens,
110
- members: group.members.slice(0, 8).map((fragment) => ({
111
- file: fragment.file, start: fragment.start, end: fragment.end, tokens: fragment.n,
112
- label: fragment.label, kind: fragment.kind || 'symbol', classes: fragment.classes || [],
113
- })),
114
- memberCount: group.members.length,
115
- })),
116
- }, {page: {shown: top.length, total: groups.length, capped: top.length < groups.length}, completeness: {status: 'COMPLETE'}})
117
- }
118
-
119
- // Focused dead-code review queue. Unlike the broad run_audit surface, this includes functions and
120
- // methods with bounded source-free evidence, and explicitly demotes framework/dynamic/public API
121
- // candidates. It never returns an automatic-delete verdict.