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,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', '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/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,174 +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
- const auditFormatVersion = new URL(import.meta.url).search
15
- const {
16
- auditFilter,
17
- formatDebtFinding,
18
- formatOrdinaryAudit,
19
- gitUntracked,
20
- pathsFromClassification,
21
- } = await import(new URL(`./audit-format.mjs${auditFormatVersion}`, import.meta.url).href)
22
-
23
- const runAudit = async (repoRoot, graph, args, ctx, options = {}) => applyAuditExtensions(
24
- await runInternalAudit(repoRoot, {graph, ...options}),
25
- {providers: ctx.extensions?.auditProviders || [], repoRoot, graph, args},
26
- )
27
-
28
- async function runAuditWithBaseline(args, ctx, currentGraph) {
29
- const resolved = resolveGitCommit(ctx.repoRoot, args.base_ref)
30
- if (!resolved.ok) return toolResult(`Audit baseline unavailable: ${resolved.error}.`, {
31
- status: 'INVALID', comparison: {status: 'UNAVAILABLE', reason: resolved.error}, findings: [],
32
- }, {completeness: {status: 'PARTIAL', reason: resolved.error}})
33
-
34
- let currentAudit
35
- try {
36
- currentAudit = await runAudit(ctx.repoRoot, currentGraph, args, ctx, {skipMalwareScan: !args.include_malware_scan})
37
- } catch (error) {
38
- const reason = error instanceof Error ? error.message : String(error)
39
- return toolResult(`Audit failed while building the current graph: ${reason}`, {
40
- status: 'ERROR', comparison: {status: 'UNAVAILABLE', reason}, findings: [],
41
- }, {completeness: {status: 'PARTIAL', reason}})
42
- }
43
- if (!currentAudit.ok) return toolResult(`Audit failed: ${currentAudit.error}`, {
44
- status: 'ERROR', comparison: {status: 'UNAVAILABLE', reason: currentAudit.error}, findings: [],
45
- }, {completeness: {status: 'PARTIAL', reason: currentAudit.error}})
46
-
47
- const normalized = normalizeAuditScopeFiles(args.changed_files)
48
- if (!normalized.ok) return toolResult(`Audit scope invalid: ${normalized.error}.`, {
49
- status: 'INVALID', comparison: {status: 'UNAVAILABLE', reason: normalized.error}, findings: [],
50
- }, {completeness: {status: 'PARTIAL', reason: normalized.error}})
51
- let changedFiles = normalized.files
52
- let completeChangeSet = false
53
- let scopeSource = 'explicit changed_files'
54
- let changeEvidence = null
55
- if (changedFiles == null) {
56
- completeChangeSet = true
57
- const untracked = gitUntracked(ctx.repoRoot)
58
- if (!untracked.ok) {
59
- return toolResult(`Audit comparison stopped: untracked-file discovery failed (${untracked.error}). Pass changed_files explicitly to establish the scope.`, {
60
- status: 'PARTIAL', comparison: {status: 'UNAVAILABLE', reason: untracked.error}, findings: [],
61
- }, {completeness: {status: 'PARTIAL', reason: untracked.error}})
62
- }
63
- changeEvidence = classifyChangeImpact({
64
- repoRoot: ctx.repoRoot,
65
- graph: currentGraph,
66
- base: resolved.commit,
67
- files: untracked.files,
68
- })
69
- if (!changeEvidence.ok || changeEvidence.bounds?.truncated) {
70
- const reason = changeEvidence.reasons.join(' ')
71
- return toolResult(`Audit comparison stopped: changed-file derivation against ${resolved.ref} was incomplete. ${reason} Pass changed_files explicitly to establish a complete scope.`, {
72
- status: 'PARTIAL', comparison: {status: 'UNAVAILABLE', reason}, findings: [], changeEvidence,
73
- }, {completeness: {status: 'PARTIAL', reason}})
74
- }
75
- changedFiles = pathsFromClassification(changeEvidence)
76
- if (changedFiles.length > 500) {
77
- const reason = `derived changed-file scope contains ${changedFiles.length} files, above the 500-file comparison bound`
78
- return toolResult(`Audit comparison stopped: ${reason}. Pass a narrower explicit changed_files scope.`, {
79
- status: 'PARTIAL', comparison: {status: 'UNAVAILABLE', reason}, findings: [], changeEvidence,
80
- }, {completeness: {status: 'PARTIAL', reason}})
81
- }
82
- scopeSource = `git diff ${resolved.commit.slice(0, 12)}…working-tree`
83
- }
84
-
85
- const baseline = await withGitRefCheckout(ctx.repoRoot, resolved.commit, async (checkout) => {
86
- let graph = await buildInternalGraph(checkout)
87
- const currentMode = ['full', 'no-tests', 'tests-only'].includes(currentGraph?.graphBuildMode)
88
- ? currentGraph.graphBuildMode : 'full'
89
- if (currentMode !== 'full') graph = filterGraphForMode(graph, currentMode, {repoRoot: checkout})
90
- graph.graphBuildMode = currentMode
91
- graph.graphBuildScope = ''
92
- return runAudit(checkout, graph, args, ctx, {skipMalwareScan: true})
93
- })
94
- if (!baseline.ok || !baseline.value?.ok) {
95
- const reason = baseline.error || baseline.value?.error || 'baseline audit failed'
96
- return toolResult(`Audit baseline unavailable: ${reason}.`, {
97
- status: 'ERROR', comparison: {status: 'UNAVAILABLE', reason}, findings: [],
98
- }, {completeness: {status: 'PARTIAL', reason}})
99
- }
100
-
101
- const comparison = compareAuditDebt(currentAudit, baseline.value, changedFiles, {completeChangeSet})
102
- const mode = ['new', 'existing', 'all'].includes(args.debt) ? args.debt : 'new'
103
- const selectedRaw = mode === 'new' ? comparison.new : mode === 'existing' ? comparison.existing : comparison.all
104
- const selected = auditFilter(currentAudit, args, selectedRaw, ctx.repoRoot)
105
- const max = Math.max(1, Math.min(100, Number(args.max_findings) || 30))
106
- const shown = selected.slice(0, max)
107
- const optional = comparison.optional.checks.map((check) => `${check.name.toUpperCase()} UNCOMPARABLE (current ${check.current}; baseline ${check.baseline})`).join('; ')
108
- const stateOf = (finding) => mode === 'all' ? finding.debtState : mode
109
- const fixedShown = auditFilter(baseline.value, args, comparison.fixed, ctx.repoRoot).slice(0, Math.min(10, max))
110
- const text = [
111
- `${mode.toUpperCase()} DEBT — deterministic internal audit vs ${resolved.ref} (${resolved.commit.slice(0, 12)})`,
112
- `Changed scope: ${changedFiles.length} file(s) from ${scopeSource}${changedFiles.length ? ` — ${changedFiles.slice(0, 12).join(', ')}${changedFiles.length > 12 ? ', …' : ''}` : ' — working tree matches the baseline'}.`,
113
- `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.`,
114
- `Optional checks: ${optional}. Supply-chain findings are not assigned new/existing/fixed state from a source-only checkout.`,
115
- '',
116
- `Showing ${shown.length} of ${selected.length} ${mode} finding(s) after filters:`,
117
- ...shown.map((finding) => formatDebtFinding(finding, stateOf(finding))),
118
- selected.length > shown.length ? ` … +${selected.length - shown.length} more` : null,
119
- '',
120
- `Fixed in this changed scope: ${comparison.fixed.length}${fixedShown.length ? ' (sample below)' : ''}.`,
121
- ...fixedShown.map((finding) => formatDebtFinding(finding, 'fixed')),
122
- ].filter((line) => line != null).join('\n')
123
- return toolResult(text, {
124
- status: 'COMPLETE',
125
- mode: 'baseline-comparison',
126
- debt: mode,
127
- baseline: {ref: resolved.ref, commit: resolved.commit},
128
- scope: {files: changedFiles, source: scopeSource},
129
- comparison: {
130
- status: 'COMPLETE',
131
- scope: comparison.scope,
132
- totals: comparison.totals,
133
- new: comparison.new,
134
- existing: comparison.existing,
135
- fixed: comparison.fixed,
136
- optional: comparison.optional,
137
- },
138
- findings: selected,
139
- changeEvidence,
140
- }, {
141
- page: {shown: shown.length, total: selected.length, capped: shown.length < selected.length},
142
- completeness: {status: 'COMPLETE'},
143
- })
144
- }
145
-
146
- // Full internal health audit: dead code + unused exports, dependency findings (npm/go/py missing &
147
- // unused deps), structure (import cycles / orphans / boundary rules), supply-chain (offline OSV
148
- // advisories, typosquat, lockfile drift), optional malware heuristics.
149
- export async function tRunAudit(g, args, ctx) {
150
- if (!ctx.repoRoot) return 'Audit needs the repo root (not provided to this server).'
151
- if (args.base_ref) return runAuditWithBaseline(args, ctx, rawGraph(ctx))
152
- const graph = effectiveRawGraph(ctx)
153
- const audit = await runAudit(ctx.repoRoot, graph, args, ctx, {
154
- skipMalwareScan: !args.include_malware_scan, // greps installed packages — slow, so opt-in
155
- })
156
- if (!audit.ok) return `Audit failed: ${audit.error}`
157
- if (Array.isArray(args.changed_files)) {
158
- const normalized = normalizeAuditScopeFiles(args.changed_files)
159
- if (!normalized.ok) return `Changed-scope audit invalid: ${normalized.error}.`
160
- const scoped = scopeAuditFindings(audit.findings, normalized.files)
161
- const text = formatOrdinaryAudit(audit, args, scoped,
162
- `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)
163
- return toolResult(text, {
164
- status: 'COMPLETE',
165
- mode: 'changed-scope',
166
- scope: {files: normalized.files, source: 'explicit changed_files'},
167
- comparison: {status: 'UNAVAILABLE', reason: 'base_ref was not provided; changed-scope is not a new-debt claim'},
168
- findings: auditFilter(audit, args, scoped, ctx.repoRoot),
169
- }, {completeness: {status: 'COMPLETE'}})
170
- }
171
- return formatOrdinaryAudit(audit, args, audit.findings, null, ctx.repoRoot)
172
- }
173
-
174
- // 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,85 +0,0 @@
1
- import {computeDuplicates} from '../../analysis/duplicates.js'
2
- import {analyzeDuplicateGroups} from '../../analysis/duplicate-groups.js'
3
-
4
- const NON_PRODUCT_DUPLICATE_CLASSES = new Set(['generated', 'mock', 'story', 'docs', 'benchmark', 'temp'])
5
- const fragmentEligible = (fragment, {tokMin, skipTests, includeClassified}) => {
6
- if (fragment.n < tokMin) return false
7
- const classes = new Set(fragment.classes || [])
8
- if (skipTests && (fragment.test || classes.has('test') || classes.has('e2e'))) return false
9
- if (!includeClassified && (fragment.excluded || [...classes].some((name) => NON_PRODUCT_DUPLICATE_CLASSES.has(name)))) return false
10
- return true
11
- }
12
-
13
- export function tFindDuplicates(g, args, ctx) {
14
- if (!ctx.repoRoot) return 'Duplicate scan needs the repo root (not provided to this server).'
15
- const simMin = Math.min(100, Math.max(50, Number(args.min_similarity) || 80))
16
- const tokMin = Math.min(400, Math.max(12, Number(args.min_tokens) || 50))
17
- const mode = args.mode === 'strict' ? 'strict' : 'renamed'
18
- const skipTests = args.include_tests ? false : true
19
- const includeClassified = args.include_classified === true || args.include_non_product === true
20
- const includeStrings = !!args.include_strings
21
- // semantic mode: same-name symbols across files, ranked by size — LOW similarity is the signal
22
- // (same name, drifted behavior). Token-clone pairing is skipped entirely.
23
- if (args.mode === 'semantic') {
24
- const data = computeDuplicates(ctx.repoRoot, ctx.graphPath, {nameTwins: true, minTokens: tokMin})
25
- const frags = data.frags
26
- const candidates = []
27
- for (const twin of data.nameTwins || []) {
28
- const allowed = new Set(twin.members.filter((i) => fragmentEligible(frags[i], {tokMin, skipTests, includeClassified})))
29
- const pairs = (twin.pairs || []).filter((p) => allowed.has(p.a) && allowed.has(p.b))
30
- if (!pairs.length) continue
31
- const closest = pairs.slice().sort((a, b) => b.similarity - a.similarity)[0]
32
- const farthest = pairs.slice().sort((a, b) => a.similarity - b.similarity)[0]
33
- if (closest.similarity >= 85) candidates.push({kind: 'clone', label: twin.label, pair: closest})
34
- if (farthest.similarity <= 45) candidates.push({kind: 'collision', label: twin.label, pair: farthest})
35
- }
36
- for (const item of candidates) item.tokens = frags[item.pair.a].n + frags[item.pair.b].n
37
- candidates.sort((a, b) => {
38
- if (a.kind !== b.kind) return a.kind === 'clone' ? -1 : 1
39
- return a.kind === 'clone'
40
- ? b.pair.similarity - a.pair.similarity || b.tokens - a.tokens
41
- : b.tokens - a.tokens || a.pair.similarity - b.pair.similarity
42
- })
43
- if (!candidates.length) return 'No actionable same-name pairs across files (semantic mode; ambiguous middle-similarity pairs are suppressed).'
44
- const top = candidates.slice(0, Math.min(30, Math.max(1, Number(args.top_n) || 15)))
45
- const lines = top.map((item, k) => {
46
- const a = frags[item.pair.a]
47
- const b = frags[item.pair.b]
48
- const verdict = item.kind === 'clone'
49
- ? 'near-identical duplicate candidate — review, then extract shared logic if the contract is truly shared'
50
- : 'name collision, not a duplicate — inspect only if these definitions should share a contract'
51
- return [
52
- `${k + 1}. "${item.label}" — ${item.pair.similarity}% similar; ${verdict}`,
53
- ` ${a.file}:${a.start}-${a.end} (${a.n} tok)`,
54
- ` ${b.file}:${b.start}-${b.end} (${b.n} tok)`,
55
- ].join('\n')
56
- })
57
- return `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.`
58
- }
59
- const analysis = analyzeDuplicateGroups(ctx.repoRoot, ctx.graphPath, args)
60
- const groups = analysis.groups
61
- const smallPolicy = tokMin < 30 ? ' Small fragments below 30 tokens require at least 95% similarity and two shared bounded fingerprints.' : ''
62
- const suppressed = analysis.suppressed
63
- const suppressionNote = suppressed && !includeClassified
64
- ? ` ${suppressed} fragment(s) classified as tests/e2e/generated/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.`
65
- : ''
66
- const boilerplateNote = analysis.boilerplateSuppressed
67
- ? ` ${analysis.boilerplateSuppressed} all-router framework boilerplate group(s) were suppressed; pass include_boilerplate:true to inspect them.`
68
- : ''
69
- const declarativeNote = analysis.declarativeSuppressed
70
- ? ` ${analysis.declarativeSuppressed} immutable declarative catalog group(s) were suppressed; pass include_declarative:true to inspect repeated data shapes.`
71
- : ''
72
- if (!groups.length) return `No clones at ≥${simMin}% similarity / ≥${tokMin} tokens (${mode} mode). Try lowering the thresholds.${smallPolicy}${suppressionNote}${boilerplateNote}${declarativeNote}`
73
- const top = groups.slice(0, Math.min(30, Math.max(1, Number(args.top_n) || 15)))
74
- const lines = top.map((grp, k) => {
75
- const isStr = grp.members.some((f) => f.kind === 'string')
76
- const head = `${k + 1}. ${grp.members.length}× "${grp.members[0].label}"${isStr ? ' [string literal]' : ''} — ≤${grp.maxSim}% similar, ${grp.tokens} duplicated tokens`
77
- const sites = grp.members.slice(0, 8).map((f) => ` ${f.file}:${f.start}-${f.end}`)
78
- return [head, ...sites].join('\n')
79
- })
80
- return `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}`
81
- }
82
-
83
- // Focused dead-code review queue. Unlike the broad run_audit surface, this includes functions and
84
- // methods with bounded source-free evidence, and explicitly demotes framework/dynamic/public API
85
- // candidates. It never returns an automatic-delete verdict.
@@ -1,71 +0,0 @@
1
- import {rawGraph} from '../graph-context.mjs'
2
- import {analyzeEndpointInventory} from '../../analysis/endpoints.js'
3
- import {PATH_CLASS_NAMES, createPathClassifier, hasPathClass} from '../../path-classification.js'
4
- import {toolResult} from '../tool-result.mjs'
5
-
6
- export function tListEndpoints(g, args, ctx) {
7
- if (!ctx.repoRoot) return 'Endpoint detection needs the repo root (not provided to this server).'
8
- const graph = rawGraph(ctx)
9
- const codeFiles = [...new Set(
10
- (graph.nodes || [])
11
- .filter((n) => !String(n.id).includes('#') && n.source_file && n.file_type === 'code')
12
- .map((n) => n.source_file)
13
- )]
14
- const inventory = analyzeEndpointInventory(ctx.repoRoot, codeFiles)
15
- let eps = inventory.endpoints
16
- const method = args.method ? String(args.method).toUpperCase() : null
17
- const path = args.path ? String(args.path) : null
18
- if (method) eps = eps.filter((endpoint) => endpoint.method === method)
19
- if (path) eps = eps.filter((endpoint) => endpoint.path === path || endpoint.path.endsWith(path))
20
- // Production-first: classified endpoints are suppressed by default; a tests-only graph would
21
- // suppress everything, so that build mode auto-includes them (same precedent as module_map).
22
- const includeClassified = args.include_classified === true || graph.graphBuildMode === 'tests-only'
23
- const classifier = createPathClassifier(ctx.repoRoot)
24
- const byFile = new Map()
25
- const classesOf = (file) => {
26
- const key = String(file || '')
27
- if (!byFile.has(key)) {
28
- const info = classifier.explain(key, {content: ''})
29
- byFile.set(key, {classified: info.excluded || hasPathClass(info, ...PATH_CLASS_NAMES), pathClasses: info.classes})
30
- }
31
- return byFile.get(key)
32
- }
33
- const production = eps.filter((endpoint) => !classesOf(endpoint.file).classified)
34
- const classified = eps.filter((endpoint) => classesOf(endpoint.file).classified)
35
- const suppressed = includeClassified ? 0 : classified.length
36
- eps = includeClassified ? [...production, ...classified] : production
37
- const suppressionNote = suppressed
38
- ? `${suppressed} endpoint(s) in classified test/e2e/generated/mock/story/docs/benchmark/temp or explicitly excluded paths were suppressed; pass include_classified:true to inspect them.`
39
- : ''
40
- if (!eps.length) return suppressed
41
- ? `No production HTTP endpoints detected; ${suppressionNote}`
42
- : 'No HTTP endpoints detected in the indexed code files.'
43
- const max = Math.max(1, Math.min(300, Number(args.max_results) || 100))
44
- const shown = eps.slice(0, max)
45
- const stats = inventory.stats
46
- const text = [
47
- `${eps.length} endpoint(s) matched${eps.length > shown.length ? `, showing ${shown.length}` : ''}. Inventory: ${stats.declaredRoutes} declaration(s); ${stats.reachableStaticRoutes} statically reachable composed route(s); ${stats.localDeclarations} local/root declaration candidate(s); ${stats.staticMounts} static router mount(s)${stats.truncated ? `; TRUNCATED at ${stats.maxEndpoints}` : ''}.${suppressed ? ` ${suppressionNote}` : ''}`,
48
- ...shown.map((e) => {
49
- const via = e.mountChain?.length
50
- ? `\n declared ${e.declaredPath} in ${e.file}${e.line ? `:${e.line}` : ''}; mount chain ${e.mountChain.map((mount) => `${mount.file}:${mount.line} ${mount.path}`).join(' → ')}`
51
- : ''
52
- const activation = e.conditional
53
- ? `; conditional default ${e.defaultActive === false ? 'inactive' : e.defaultActive === true ? 'active' : 'unknown'}`
54
- : ''
55
- const info = classesOf(e.file)
56
- const tag = info.classified ? ` [classified${info.pathClasses.length ? `:${info.pathClasses.join('+')}` : ''}]` : ''
57
- return ` ${e.method.toUpperCase().padEnd(6)} ${e.path}${e.handler ? ` → ${e.handler}` : ''} (${e.file}${e.line ? `:${e.line}` : ''}; ${e.mountState}/${e.confidence}${activation})${tag}${via}`
58
- }),
59
- ].join('\n')
60
- return toolResult(text, {
61
- filters: {method, path},
62
- stats,
63
- pathPolicy: includeClassified ? 'all' : 'production-first',
64
- suppressed,
65
- endpoints: shown.map((e) => {
66
- const info = classesOf(e.file)
67
- return info.classified ? {...e, classified: true, pathClasses: info.pathClasses} : e
68
- }),
69
- page: {shown: shown.length, total: eps.length, truncated: eps.length > shown.length || stats.truncated},
70
- }, {completeness: {status: stats.truncated ? 'PARTIAL' : 'COMPLETE', reason: stats.truncated ? `endpoint cap ${stats.maxEndpoints} reached` : 'all indexed code files scanned'}})
71
- }