weavatrix 0.2.2 → 0.2.3

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.
package/README.md CHANGED
@@ -173,6 +173,18 @@ disclosed instead of silently guessed; and the server **hot-reloads its watched
173
173
  modules and catalog** when those files change — other MCP helpers and analysis engines require a
174
174
  reconnect.
175
175
 
176
+ ### 0.2.3 real-wrapper patch
177
+
178
+ - Auto-discovery recognizes wrappers that pass a fixed HTTP client method and argument array to a
179
+ shared transport helper, such as `api(axios.get, [url, options])`.
180
+ - Ambiguous handler names can resolve to the unique matching symbol in a module directly imported
181
+ by the route file, allowing proven external frontend calls to suppress only that exact backend
182
+ dead-code candidate.
183
+ - Missing, ambiguous, low-confidence and capped evidence remains review-only; the patch does not
184
+ turn absence of a client match into a dead-code verdict.
185
+
186
+ Full patch notes: [docs/releases/v0.2.3.md](docs/releases/v0.2.3.md).
187
+
176
188
  ### 0.2.2 regression and cross-repository evidence
177
189
 
178
190
  - Permanent TS/JS/Python/Go/Java/Rust regression fixtures now gate graph correctness, output size,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weavatrix",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Weavatrix — code graph & blast-radius MCP server for AI coding agents: change impact, transitive dependents, health audit, duplicate detection, and coverage mapping over any local repository.",
6
6
  "author": "Sergii Ziborov <sergii.ziborov@gmail.com>",
@@ -181,6 +181,15 @@ function delegatedWrapper(mask, definition, clientNames) {
181
181
  const urlArgument = definition.parameters.indexOf(found[2]);
182
182
  if (urlArgument >= 0) matches.push({ method: found[1].toUpperCase(), urlArgument });
183
183
  }
184
+ // Common typed clients keep response/error handling in one helper and pass the concrete
185
+ // axios/http method plus its argument array: api(axios.get, [url, options]). The HTTP method and
186
+ // first transport argument are still statically fixed, so this is as deterministic as a direct
187
+ // axios.get(url) delegation without evaluating the helper.
188
+ const methodReference = new RegExp(`\\b[A-Za-z_$][\\w$]*\\s*\\(\\s*(?:${clients})\\s*(?:\\?\\.|\\.)\\s*(get|post|put|patch|delete|head|options)\\s*,\\s*\\[\\s*([A-Za-z_$][\\w$]*)\\s*(?=[,\\]])`, "gi");
189
+ while ((found = methodReference.exec(body)) && matches.length < 3) {
190
+ const urlArgument = definition.parameters.indexOf(found[2]);
191
+ if (urlArgument >= 0) matches.push({ method: found[1].toUpperCase(), urlArgument });
192
+ }
184
193
  const unique = [...new Map(matches.map((item) => [`${item.method}\0${item.urlArgument}`, item])).values()];
185
194
  return unique.length === 1 ? unique[0] : null;
186
195
  }
@@ -639,10 +639,22 @@ function bareGraphLabel(value) {
639
639
  function handlerNodeEvidence(endpoint, graph) {
640
640
  const handler = /^[A-Za-z_$][\w$]{0,127}$/.test(String(endpoint?.handler || "")) ? String(endpoint.handler) : null;
641
641
  if (!handler) return { handler: null, handlerNodeId: null, handlerResolution: "inline-or-unresolved" };
642
- const matches = (graph?.nodes || []).filter((node) =>
642
+ const nodes = graph?.nodes || [];
643
+ const matches = nodes.filter((node) =>
643
644
  normalizeFile(node?.source_file) && bareGraphLabel(node?.label) === handler && String(node?.id || "") !== normalizeFile(node?.source_file));
644
- const sameFile = matches.filter((node) => normalizeFile(node?.source_file) === normalizeFile(endpoint.file));
645
- const resolved = sameFile.length === 1 ? sameFile[0] : matches.length === 1 ? matches[0] : null;
645
+ const endpointFile = normalizeFile(endpoint.file);
646
+ const sameFile = matches.filter((node) => normalizeFile(node?.source_file) === endpointFile);
647
+ const byId = new Map(nodes.map((node) => [endpointId(node?.id), node]));
648
+ const linkFile = (value) => {
649
+ const id = endpointId(value);
650
+ return normalizeFile(byId.get(id)?.source_file || String(id || "").split("#")[0]);
651
+ };
652
+ const directlyImportedFiles = new Set((graph?.links || [])
653
+ .filter((link) => ["imports", "re_exports"].includes(link?.relation) && linkFile(link?.source) === endpointFile)
654
+ .map((link) => linkFile(link?.target))
655
+ .filter(Boolean));
656
+ const imported = matches.filter((node) => directlyImportedFiles.has(normalizeFile(node?.source_file)));
657
+ const resolved = sameFile.length === 1 ? sameFile[0] : imported.length === 1 ? imported[0] : matches.length === 1 ? matches[0] : null;
646
658
  return {
647
659
  handler,
648
660
  handlerNodeId: resolved ? String(resolved.id) : null,