@neat.is/core 0.9.5 → 0.9.6

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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MultiDirectedGraph } from 'graphology';
2
- import { GraphNode, GraphEdge, SpanAttributes, EdgeTypeValue, StaleEvent, ErrorEvent, BlastRadiusResult, RootCauseResult, RegistryEntry, RegistryStatus, RegistryFile } from '@neat.is/types';
2
+ import { GraphNode, GraphEdge, EdgeEvidence, SpanAttributes, EdgeTypeValue, StaleEvent, ErrorEvent, BlastRadiusResult, RootCauseResult, RegistryEntry, RegistryStatus, RegistryFile } from '@neat.is/types';
3
3
  export { StaleEvent } from '@neat.is/types';
4
4
  import { FastifyInstance } from 'fastify';
5
5
 
@@ -21,11 +21,7 @@ interface DroppedExtractedEdge {
21
21
  type: string;
22
22
  confidence: number;
23
23
  confidenceKind: string;
24
- evidence: {
25
- file: string;
26
- line?: number;
27
- snippet?: string;
28
- };
24
+ evidence: EdgeEvidence;
29
25
  }
30
26
 
31
27
  interface ExtractResult {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MultiDirectedGraph } from 'graphology';
2
- import { GraphNode, GraphEdge, SpanAttributes, EdgeTypeValue, StaleEvent, ErrorEvent, BlastRadiusResult, RootCauseResult, RegistryEntry, RegistryStatus, RegistryFile } from '@neat.is/types';
2
+ import { GraphNode, GraphEdge, EdgeEvidence, SpanAttributes, EdgeTypeValue, StaleEvent, ErrorEvent, BlastRadiusResult, RootCauseResult, RegistryEntry, RegistryStatus, RegistryFile } from '@neat.is/types';
3
3
  export { StaleEvent } from '@neat.is/types';
4
4
  import { FastifyInstance } from 'fastify';
5
5
 
@@ -21,11 +21,7 @@ interface DroppedExtractedEdge {
21
21
  type: string;
22
22
  confidence: number;
23
23
  confidenceKind: string;
24
- evidence: {
25
- file: string;
26
- line?: number;
27
- snippet?: string;
28
- };
24
+ evidence: EdgeEvidence;
29
25
  }
30
26
 
31
27
  interface ExtractResult {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  routeSpanToProject,
3
3
  startDaemon
4
- } from "./chunk-4SLKQNG7.js";
4
+ } from "./chunk-XYAZOGEX.js";
5
5
  import {
6
6
  ProjectNameCollisionError,
7
7
  addProject,
@@ -37,7 +37,7 @@ import {
37
37
  thresholdForEdgeType,
38
38
  touchLastSeen,
39
39
  writeAtomically
40
- } from "./chunk-DGAI4VOE.js";
40
+ } from "./chunk-XOGTJVUM.js";
41
41
  import {
42
42
  startOtelGrpcReceiver
43
43
  } from "./chunk-ERE47MCR.js";
package/dist/neatd.cjs CHANGED
@@ -10722,27 +10722,72 @@ function walk2(node, visit) {
10722
10722
  if (child) walk2(child, visit);
10723
10723
  }
10724
10724
  }
10725
- function reconstructUrl(node) {
10726
- if (node.type === "string") {
10725
+ function resolveStaticString(node, constMap, depth = 0) {
10726
+ if (!node || depth > 4) return null;
10727
+ if (node.type === "string") return stringText(node);
10728
+ if (node.type === "template_string") {
10729
+ let out = "";
10730
+ let sawSub = false;
10727
10731
  for (let i = 0; i < node.namedChildCount; i++) {
10728
10732
  const child = node.namedChild(i);
10729
- if (child?.type === "string_fragment") return child.text;
10733
+ if (!child) continue;
10734
+ if (child.type === "string_fragment") out += child.text;
10735
+ else if (child.type === "template_substitution") sawSub = true;
10730
10736
  }
10731
- return "";
10737
+ return sawSub ? null : out;
10738
+ }
10739
+ if (node.type === "identifier") return constMap.get(node.text) ?? null;
10740
+ if (node.type === "binary_expression") {
10741
+ const left = resolveStaticString(node.childForFieldName("left"), constMap, depth + 1);
10742
+ if (left !== null) return left;
10743
+ return resolveStaticString(node.childForFieldName("right"), constMap, depth + 1);
10744
+ }
10745
+ if (node.type === "parenthesized_expression") {
10746
+ return resolveStaticString(node.namedChild(0), constMap, depth + 1);
10747
+ }
10748
+ return null;
10749
+ }
10750
+ function collectStaticStringBindings(root) {
10751
+ const map = /* @__PURE__ */ new Map();
10752
+ walk2(root, (node) => {
10753
+ if (node.type !== "variable_declarator") return;
10754
+ const name = node.childForFieldName("name");
10755
+ if (!name || name.type !== "identifier") return;
10756
+ const resolved = resolveStaticString(node.childForFieldName("value"), map);
10757
+ if (resolved !== null) map.set(name.text, resolved);
10758
+ });
10759
+ return map;
10760
+ }
10761
+ function reconstructUrl(node, constMap) {
10762
+ if (node.type === "string") {
10763
+ const s = stringText(node);
10764
+ return s === null ? null : { url: s, approximate: false };
10732
10765
  }
10733
10766
  if (node.type === "template_string") {
10734
10767
  let out = "";
10768
+ let approximate = false;
10769
+ let sawPart = false;
10735
10770
  for (let i = 0; i < node.namedChildCount; i++) {
10736
10771
  const child = node.namedChild(i);
10737
10772
  if (!child) continue;
10738
- if (child.type === "string_fragment") out += child.text;
10739
- else if (child.type === "template_substitution") out += ":param";
10773
+ if (child.type === "string_fragment") {
10774
+ out += child.text;
10775
+ sawPart = true;
10776
+ } else if (child.type === "template_substitution") {
10777
+ sawPart = true;
10778
+ const resolved = resolveStaticString(child.namedChild(0), constMap);
10779
+ if (resolved !== null) out += resolved;
10780
+ else {
10781
+ out += ":param";
10782
+ approximate = true;
10783
+ }
10784
+ }
10740
10785
  }
10741
- if (out.length === 0) {
10786
+ if (!sawPart) {
10742
10787
  const raw = node.text;
10743
- return raw.length >= 2 ? raw.slice(1, -1) : "";
10788
+ return { url: raw.length >= 2 ? raw.slice(1, -1) : "", approximate: false };
10744
10789
  }
10745
- return out;
10790
+ return { url: out, approximate };
10746
10791
  }
10747
10792
  return null;
10748
10793
  }
@@ -10796,23 +10841,57 @@ function matchHost(urlStr, knownHosts) {
10796
10841
  }
10797
10842
  return null;
10798
10843
  }
10844
+ function hostSlotUnresolved(url) {
10845
+ const schemeRel = url.indexOf("//");
10846
+ let authority;
10847
+ if (schemeRel >= 0) {
10848
+ const rest = url.slice(schemeRel + 2);
10849
+ const slash = rest.indexOf("/");
10850
+ authority = slash >= 0 ? rest.slice(0, slash) : rest;
10851
+ } else {
10852
+ const slash = url.indexOf("/");
10853
+ authority = slash >= 0 ? url.slice(0, slash) : url;
10854
+ }
10855
+ return authority.length === 0 || authority.includes(":param");
10856
+ }
10857
+ function pathHasLiteralAnchor(pathTemplate) {
10858
+ const segs = normalizePathTemplate(pathTemplate).split("/").filter((s) => s.length > 0);
10859
+ if (segs.length === 0) return false;
10860
+ return segs.some((s) => s !== ":param");
10861
+ }
10799
10862
  function clientCallSitesFromSource(source, parser, knownHosts) {
10800
10863
  const tree = parseSource5(parser, source);
10801
10864
  const out = [];
10865
+ const constMap = collectStaticStringBindings(tree.rootNode);
10802
10866
  const push = (urlNode, method, callNode) => {
10803
- const urlStr = reconstructUrl(urlNode);
10804
- if (!urlStr) return;
10805
- const host = matchHost(urlStr, knownHosts);
10806
- if (!host) return;
10807
- const p = pathOf(urlStr);
10808
- if (p === null) return;
10867
+ const rec = reconstructUrl(urlNode, constMap);
10868
+ if (!rec) return;
10809
10869
  const line = callNode.startPosition.row + 1;
10870
+ const host = matchHost(rec.url, knownHosts);
10871
+ if (host === null) {
10872
+ if (rec.approximate && hostSlotUnresolved(rec.url)) {
10873
+ out.push({
10874
+ host: null,
10875
+ method,
10876
+ pathTemplate: "",
10877
+ line,
10878
+ snippet: snippet(source, line),
10879
+ approximate: true,
10880
+ reason: "base URL interpolation could not be resolved statically"
10881
+ });
10882
+ }
10883
+ return;
10884
+ }
10885
+ const p = pathOf(rec.url);
10886
+ if (p === null) return;
10810
10887
  out.push({
10811
10888
  host,
10812
10889
  method,
10813
10890
  pathTemplate: p,
10814
10891
  line,
10815
- snippet: snippet(source, line)
10892
+ snippet: snippet(source, line),
10893
+ approximate: rec.approximate,
10894
+ reason: rec.approximate ? "path segment interpolation could not be resolved statically" : void 0
10816
10895
  });
10817
10896
  };
10818
10897
  walk2(tree.rootNode, (node) => {
@@ -10908,6 +10987,35 @@ async function addRouteCallEdges(graph, services) {
10908
10987
  if (sites.length === 0) continue;
10909
10988
  const relFile = toPosix(import_node_path38.default.relative(service.dir, file.path));
10910
10989
  for (const site of sites) {
10990
+ if (site.host === null) {
10991
+ const dedupKey2 = `${relFile}|unresolved-host`;
10992
+ if (seen.has(dedupKey2)) continue;
10993
+ seen.add(dedupKey2);
10994
+ const { fileNodeId: fileNodeId2, nodesAdded: n2, edgesAdded: e2 } = ensureFileNode(
10995
+ graph,
10996
+ service.pkg.name,
10997
+ service.node.id,
10998
+ relFile
10999
+ );
11000
+ nodesAdded += n2;
11001
+ edgesAdded += e2;
11002
+ noteExtractedDropped({
11003
+ source: fileNodeId2,
11004
+ target: "route:unresolved",
11005
+ type: import_types27.EdgeType.CALLS,
11006
+ confidence: (0, import_types27.confidenceForExtracted)("reconstructed-approximate"),
11007
+ confidenceKind: "reconstructed-approximate",
11008
+ evidence: {
11009
+ file: relFile,
11010
+ line: site.line,
11011
+ snippet: site.snippet,
11012
+ method: site.method,
11013
+ approximate: true,
11014
+ reason: site.reason
11015
+ }
11016
+ });
11017
+ continue;
11018
+ }
10911
11019
  const serverServiceId = hostToNodeId.get(site.host);
10912
11020
  if (!serverServiceId || serverServiceId === service.node.id) continue;
10913
11021
  const entries = routeIndex.get(serverServiceId);
@@ -10926,13 +11034,16 @@ async function addRouteCallEdges(graph, services) {
10926
11034
  );
10927
11035
  nodesAdded += n;
10928
11036
  edgesAdded += e;
10929
- const confidence = (0, import_types27.confidenceForExtracted)("verified-call-site");
11037
+ const anchored = pathHasLiteralAnchor(site.pathTemplate);
11038
+ const confidenceKind = site.approximate && !anchored ? "reconstructed-approximate" : "verified-call-site";
11039
+ const confidence = (0, import_types27.confidenceForExtracted)(confidenceKind);
10930
11040
  const ev = {
10931
11041
  file: relFile,
10932
11042
  line: site.line,
10933
11043
  snippet: site.snippet,
10934
11044
  method: site.method ?? match.method,
10935
- pathTemplate: site.pathTemplate
11045
+ pathTemplate: site.pathTemplate,
11046
+ ...confidenceKind === "reconstructed-approximate" ? { approximate: true, reason: site.reason } : {}
10936
11047
  };
10937
11048
  if (!(0, import_types27.passesExtractedFloor)(confidence)) {
10938
11049
  noteExtractedDropped({
@@ -10940,7 +11051,7 @@ async function addRouteCallEdges(graph, services) {
10940
11051
  target: match.routeNodeId,
10941
11052
  type: import_types27.EdgeType.CALLS,
10942
11053
  confidence,
10943
- confidenceKind: "verified-call-site",
11054
+ confidenceKind,
10944
11055
  evidence: ev
10945
11056
  });
10946
11057
  continue;