@neat.is/core 0.8.1-dev.20260817 → 0.8.2-dev.20260818

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.
@@ -961,7 +961,7 @@ import { promises as fs3 } from "fs";
961
961
  import path3 from "path";
962
962
  import { parse as parseYaml } from "yaml";
963
963
  import { extractedEdgeId } from "@neat.is/types";
964
- var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
964
+ var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java"]);
965
965
  var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
966
966
  var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
967
967
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -1301,6 +1301,10 @@ function languageForPath(relPath) {
1301
1301
  return "ruby";
1302
1302
  case ".php":
1303
1303
  return "php";
1304
+ case ".cs":
1305
+ return "csharp";
1306
+ case ".java":
1307
+ return "java";
1304
1308
  case ".ts":
1305
1309
  case ".tsx":
1306
1310
  return "typescript";
@@ -1842,8 +1846,8 @@ function chiRoutesFromSource(source, parser) {
1842
1846
  chiWalk(tree.rootNode, "", out);
1843
1847
  return out;
1844
1848
  }
1845
- function stripChiRegex(path64) {
1846
- return path64.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1849
+ function stripChiRegex(path67) {
1850
+ return path67.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
1847
1851
  }
1848
1852
  function chiWalk(node, prefix, out) {
1849
1853
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -2515,9 +2519,9 @@ function rubyRocketRoute(args) {
2515
2519
  if (!pair || pair.type !== "pair") continue;
2516
2520
  const k = pair.childForFieldName("key");
2517
2521
  if (k?.type !== "string") continue;
2518
- const path64 = rubyLiteral(k);
2519
- if (path64 === null) continue;
2520
- return { path: path64, target: rubyLiteral(pair.childForFieldName("value")) };
2522
+ const path67 = rubyLiteral(k);
2523
+ if (path67 === null) continue;
2524
+ return { path: path67, target: rubyLiteral(pair.childForFieldName("value")) };
2521
2525
  }
2522
2526
  return null;
2523
2527
  }
@@ -3794,7 +3798,7 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
3794
3798
  }
3795
3799
  return sid;
3796
3800
  }
3797
- function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3801
+ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite, mint = true) {
3798
3802
  const line = callSite.line;
3799
3803
  if (line === void 0) return fileNodeId;
3800
3804
  let sawSymbol = false;
@@ -3809,7 +3813,7 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
3809
3813
  }
3810
3814
  });
3811
3815
  if (candidates.length > 0) return pickContainingSymbol(candidates, callSite.fn);
3812
- if (sawSymbol && callSite.fn) {
3816
+ if (mint && sawSymbol && callSite.fn) {
3813
3817
  return ensureObservedSymbolNode(graph, fileNodeId, service, relPath, callSite.fn, line);
3814
3818
  }
3815
3819
  return fileNodeId;
@@ -4215,6 +4219,20 @@ function incidentAffectedNode(span, graph, scanPath) {
4215
4219
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
4216
4220
  if (callSite) {
4217
4221
  const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
4222
+ const canonicalService = serviceNode && typeof serviceNode.name === "string" ? serviceNode.name : span.service;
4223
+ if (graph) {
4224
+ const fusedFileId = fileId3(canonicalService, relPath);
4225
+ if (graph.hasNode(fusedFileId)) {
4226
+ return landObservedSymbol(
4227
+ graph,
4228
+ fusedFileId,
4229
+ canonicalService,
4230
+ relPath,
4231
+ { ...callSite, relPath },
4232
+ false
4233
+ );
4234
+ }
4235
+ }
4218
4236
  return fileId3(span.service, relPath);
4219
4237
  }
4220
4238
  return sid;
@@ -4578,7 +4596,11 @@ async function handleSpan(ctx, span) {
4578
4596
  ...span.exception?.type ? { exceptionType: span.exception.type } : {},
4579
4597
  ...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
4580
4598
  ...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
4581
- affectedNode
4599
+ // Attribute to where the failure originated — the symbol / file / service
4600
+ // the throwing span named (incidentAffectedNode, ADR-191) — the same
4601
+ // source-based attribution the durable receiver write uses, not the
4602
+ // outbound edge target this span happened to mint.
4603
+ affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath)
4582
4604
  };
4583
4605
  await appendErrorEvent(ctx, ev);
4584
4606
  }
@@ -4616,29 +4638,29 @@ function promoteFrontierNodes(graph, opts = {}) {
4616
4638
  toPromote.push({ frontierId: id, serviceId: target });
4617
4639
  });
4618
4640
  let promoted = 0;
4619
- for (const { frontierId: frontierId2, serviceId: serviceId9 } of toPromote) {
4641
+ for (const { frontierId: frontierId2, serviceId: serviceId12 } of toPromote) {
4620
4642
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
4621
4643
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
4622
4644
  if (!gate.allowed) {
4623
4645
  continue;
4624
4646
  }
4625
4647
  }
4626
- rewireFrontierEdges(graph, frontierId2, serviceId9);
4648
+ rewireFrontierEdges(graph, frontierId2, serviceId12);
4627
4649
  graph.dropNode(frontierId2);
4628
4650
  promoted++;
4629
4651
  }
4630
4652
  return promoted;
4631
4653
  }
4632
- function rewireFrontierEdges(graph, frontierId2, serviceId9) {
4654
+ function rewireFrontierEdges(graph, frontierId2, serviceId12) {
4633
4655
  const inbound = [...graph.inboundEdges(frontierId2)];
4634
4656
  const outbound = [...graph.outboundEdges(frontierId2)];
4635
4657
  for (const edgeId of inbound) {
4636
4658
  const edge = graph.getEdgeAttributes(edgeId);
4637
- rebuildEdge(graph, edge, edge.source, serviceId9, edgeId);
4659
+ rebuildEdge(graph, edge, edge.source, serviceId12, edgeId);
4638
4660
  }
4639
4661
  for (const edgeId of outbound) {
4640
4662
  const edge = graph.getEdgeAttributes(edgeId);
4641
- rebuildEdge(graph, edge, serviceId9, edge.target, edgeId);
4663
+ rebuildEdge(graph, edge, serviceId12, edge.target, edgeId);
4642
4664
  }
4643
4665
  }
4644
4666
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -4857,7 +4879,9 @@ import {
4857
4879
  Provenance as Provenance6,
4858
4880
  RelateResultSchema,
4859
4881
  RootCauseResultSchema,
4860
- TransitiveDependenciesResultSchema
4882
+ TransitiveDependenciesResultSchema,
4883
+ fileId as fileId4,
4884
+ parseSymbolId
4861
4885
  } from "@neat.is/types";
4862
4886
  var ROOT_CAUSE_MAX_DEPTH = 5;
4863
4887
  var BLAST_RADIUS_DEFAULT_DEPTH = 10;
@@ -4968,19 +4992,19 @@ function confidenceFromMix(edges, now = Date.now()) {
4968
4992
  function longestIncomingWalk(graph, start, maxDepth) {
4969
4993
  let best = { path: [start], edges: [] };
4970
4994
  const visited = /* @__PURE__ */ new Set([start]);
4971
- function step(node, path64, edges) {
4972
- if (path64.length > best.path.length) {
4973
- best = { path: [...path64], edges: [...edges] };
4995
+ function step(node, path67, edges) {
4996
+ if (path67.length > best.path.length) {
4997
+ best = { path: [...path67], edges: [...edges] };
4974
4998
  }
4975
- if (path64.length - 1 >= maxDepth) return;
4999
+ if (path67.length - 1 >= maxDepth) return;
4976
5000
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
4977
5001
  for (const [srcId, edge] of incoming) {
4978
5002
  if (visited.has(srcId)) continue;
4979
5003
  visited.add(srcId);
4980
- path64.push(srcId);
5004
+ path67.push(srcId);
4981
5005
  edges.push(edge);
4982
- step(srcId, path64, edges);
4983
- path64.pop();
5006
+ step(srcId, path67, edges);
5007
+ path67.pop();
4984
5008
  edges.pop();
4985
5009
  visited.delete(srcId);
4986
5010
  }
@@ -4995,7 +5019,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
4995
5019
  for (const id of walk9.path) {
4996
5020
  const owner = resolveOwningService(graph, id);
4997
5021
  if (!owner) continue;
4998
- const { id: serviceId9, svc } = owner;
5022
+ const { id: serviceId12, svc } = owner;
4999
5023
  const deps = svc.dependencies ?? {};
5000
5024
  for (const pair of candidatePairs) {
5001
5025
  const declared = deps[pair.driver];
@@ -5008,7 +5032,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
5008
5032
  );
5009
5033
  if (!result.compatible) {
5010
5034
  return {
5011
- rootCauseNode: serviceId9,
5035
+ rootCauseNode: serviceId12,
5012
5036
  rootCauseReason: result.reason ?? "incompatible driver",
5013
5037
  ...result.minDriverVersion ? {
5014
5038
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -5023,7 +5047,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5023
5047
  for (const id of walk9.path) {
5024
5048
  const owner = resolveOwningService(graph, id);
5025
5049
  if (!owner) continue;
5026
- const { id: serviceId9, svc } = owner;
5050
+ const { id: serviceId12, svc } = owner;
5027
5051
  const deps = svc.dependencies ?? {};
5028
5052
  const serviceNodeEngine = svc.nodeEngine;
5029
5053
  for (const constraint of nodeEngineConstraints()) {
@@ -5032,7 +5056,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5032
5056
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
5033
5057
  if (!result.compatible && result.reason) {
5034
5058
  return {
5035
- rootCauseNode: serviceId9,
5059
+ rootCauseNode: serviceId12,
5036
5060
  rootCauseReason: result.reason,
5037
5061
  ...result.requiredNodeVersion ? {
5038
5062
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -5047,7 +5071,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
5047
5071
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
5048
5072
  if (!result.compatible && result.reason) {
5049
5073
  return {
5050
- rootCauseNode: serviceId9,
5074
+ rootCauseNode: serviceId12,
5051
5075
  rootCauseReason: result.reason,
5052
5076
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
5053
5077
  };
@@ -5099,7 +5123,11 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
5099
5123
  }
5100
5124
  var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
5101
5125
  function incidentMatchesNode(ev, nodeId) {
5102
- return ev.affectedNode === nodeId || ev.service === nodeId.replace(/^service:/, "");
5126
+ if (ev.affectedNode === nodeId) return true;
5127
+ if (ev.service === nodeId.replace(/^service:/, "")) return true;
5128
+ const sym = parseSymbolId(ev.affectedNode);
5129
+ if (sym && fileId4(sym.service, sym.relPath) === nodeId) return true;
5130
+ return false;
5103
5131
  }
5104
5132
  function localizeFromIncidents(nodeId, incidents, errorEvent) {
5105
5133
  const pool = incidents && incidents.length > 0 ? incidents : errorEvent ? [errorEvent] : [];
@@ -5117,8 +5145,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
5117
5145
  const reasonParts = [`${latest.service}: ${latest.errorMessage}`];
5118
5146
  if (location) reasonParts.push(`surfaced at ${location}`);
5119
5147
  const rootCauseReason = `${reasonParts.join(" \u2014 ")}${tail}`;
5120
- const localizesToFile = latest.affectedNode !== nodeId && latest.affectedNode.startsWith("file:");
5121
- const fileNode = localizesToFile ? latest.affectedNode : void 0;
5148
+ const localizesFiner = latest.affectedNode !== nodeId && (latest.affectedNode.startsWith("file:") || latest.affectedNode.startsWith("symbol:"));
5149
+ const fileNode = localizesFiner ? latest.affectedNode : void 0;
5122
5150
  const fixRecommendation = location ? `Inspect ${location}${route ? ` handling ${route}` : ""}` : route ? `Inspect ${latest.service}'s handler for ${route}` : void 0;
5123
5151
  return {
5124
5152
  rootCauseNode: fileNode ?? nodeId,
@@ -5144,9 +5172,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
5144
5172
  function isFailingCallEdge(e) {
5145
5173
  return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
5146
5174
  }
5147
- function callSourcesForService(graph, serviceId9) {
5148
- const ids = [serviceId9];
5149
- for (const edgeId of graph.outboundEdges(serviceId9)) {
5175
+ function callSourcesForService(graph, serviceId12) {
5176
+ const ids = [serviceId12];
5177
+ for (const edgeId of graph.outboundEdges(serviceId12)) {
5150
5178
  const e = graph.getEdgeAttributes(edgeId);
5151
5179
  if (e.type !== EdgeType6.CONTAINS) continue;
5152
5180
  const tgt = graph.getNodeAttributes(e.target);
@@ -5170,9 +5198,9 @@ function failingCallDominates(e, id, curEdge, curId) {
5170
5198
  }
5171
5199
  return id < curId;
5172
5200
  }
5173
- function dominantFailingCall(graph, serviceId9, visited) {
5201
+ function dominantFailingCall(graph, serviceId12, visited) {
5174
5202
  let best = null;
5175
- for (const src of callSourcesForService(graph, serviceId9)) {
5203
+ for (const src of callSourcesForService(graph, serviceId12)) {
5176
5204
  for (const edgeId of graph.outboundEdges(src)) {
5177
5205
  const e = graph.getEdgeAttributes(edgeId);
5178
5206
  if (!isFailingCallEdge(e)) continue;
@@ -5187,26 +5215,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
5187
5215
  return best;
5188
5216
  }
5189
5217
  function followFailingCallChain(graph, originServiceId, maxDepth) {
5190
- const path64 = [originServiceId];
5218
+ const path67 = [originServiceId];
5191
5219
  const edges = [];
5192
5220
  const visited = /* @__PURE__ */ new Set([originServiceId]);
5193
5221
  let current = originServiceId;
5194
5222
  for (let depth = 0; depth < maxDepth; depth++) {
5195
5223
  const hop = dominantFailingCall(graph, current, visited);
5196
5224
  if (!hop) break;
5197
- path64.push(hop.nextService);
5225
+ path67.push(hop.nextService);
5198
5226
  edges.push(hop.edge);
5199
5227
  visited.add(hop.nextService);
5200
5228
  current = hop.nextService;
5201
5229
  }
5202
5230
  if (edges.length === 0) return null;
5203
- return { path: path64, edges, culprit: current };
5231
+ return { path: path67, edges, culprit: current };
5204
5232
  }
5205
5233
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5206
5234
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
5207
5235
  if (!chain) return null;
5208
5236
  const culprit = chain.culprit;
5209
- const path64 = [...chain.path];
5237
+ const path67 = [...chain.path];
5210
5238
  const edgeProvenances = chain.edges.map((e) => e.provenance);
5211
5239
  const baseConfidence = confidenceFromMix(chain.edges);
5212
5240
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -5214,14 +5242,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5214
5242
  if (loc) {
5215
5243
  let rootCauseNode = culprit;
5216
5244
  if (loc.fileNode) {
5217
- path64.push(loc.fileNode);
5245
+ path67.push(loc.fileNode);
5218
5246
  edgeProvenances.push(Provenance6.OBSERVED);
5219
5247
  rootCauseNode = loc.fileNode;
5220
5248
  }
5221
5249
  return RootCauseResultSchema.parse({
5222
5250
  rootCauseNode,
5223
5251
  rootCauseReason: loc.rootCauseReason,
5224
- traversalPath: path64,
5252
+ traversalPath: path67,
5225
5253
  edgeProvenances,
5226
5254
  confidence,
5227
5255
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -5233,7 +5261,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
5233
5261
  return RootCauseResultSchema.parse({
5234
5262
  rootCauseNode: culprit,
5235
5263
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
5236
- traversalPath: path64,
5264
+ traversalPath: path67,
5237
5265
  edgeProvenances,
5238
5266
  confidence,
5239
5267
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -5603,6 +5631,22 @@ function findLoadOrigin(graph, alertNodeId, incidents, now) {
5603
5631
  }
5604
5632
  return best ? { node: best.node, ctx: best.ctx } : null;
5605
5633
  }
5634
+ var LOAD_ORIGIN_CONFIDENCE_CEILING = 0.9;
5635
+ function loadOriginConfidence(originCtx, seedCtx) {
5636
+ const cleanSplit = seedCtx.errorsFromCallers > 0 && seedCtx.errorsEmittedHere === 0;
5637
+ const partialSplit = seedCtx.errorsFromCallers > seedCtx.errorsEmittedHere;
5638
+ let evidence = 0;
5639
+ evidence += cleanSplit ? 0.45 : partialSplit ? 0.2 : 0;
5640
+ evidence += seedCtx.stale ? 0.2 : 0;
5641
+ evidence += isSaturated(seedCtx) ? 0.15 : 0;
5642
+ evidence += (volumeWeight(originCtx.outboundVolume) - 0.5) * 0.4;
5643
+ const anchor = 0.45;
5644
+ const confidence = anchor + Math.min(1, evidence) * (LOAD_ORIGIN_CONFIDENCE_CEILING - anchor);
5645
+ return Math.max(0, Math.min(LOAD_ORIGIN_CONFIDENCE_CEILING, confidence));
5646
+ }
5647
+ function displayNameOf(nodeId) {
5648
+ return nodeId.replace(/^[a-z]+:/, "");
5649
+ }
5606
5650
  function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
5607
5651
  const legacy = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
5608
5652
  if (!legacy) return null;
@@ -5617,20 +5661,20 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5617
5661
  const candidates = [];
5618
5662
  if (seedCtx && isVictimSeed(seedCtx)) {
5619
5663
  const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
5664
+ const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
5665
+ const satNote = isSaturated(seedCtx) ? `; its inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
5620
5666
  if (origin) {
5621
- const path64 = findPath(graph, errorNodeId, origin.node, "up", ROOT_CAUSE_MAX_DEPTH);
5622
- const originConfidence = confidenceFromMix(path64?.edges ?? [], now);
5667
+ const originName = displayNameOf(origin.node);
5668
+ const seedName = displayNameOf(seedNode);
5623
5669
  candidates.push({
5624
5670
  node: origin.node,
5625
5671
  classification: "primary-failure",
5626
- reason: `Highest-volume upstream source (${origin.ctx.outboundVolume} observed outbound calls) driving a saturated/stale subgraph; the alerting path decays downstream into a starved victim rather than a fault at the callee.`,
5672
+ reason: `${originName} is the root cause: it overloads the failing subgraph, and the alerting node ${seedName} is a downstream symptom, not the fault \u2014 errors reach ${seedName} from its callers (${seedCtx.errorsFromCallers}), none originate there${staleNote}${satNote}. ${originName} is the upstream source driving that load (${origin.ctx.outboundVolume} observed outbound calls).`,
5627
5673
  context: origin.ctx,
5628
- confidence: Math.max(0.3, Math.min(0.8, originConfidence || 0.5)),
5674
+ confidence: loadOriginConfidence(origin.ctx, seedCtx),
5629
5675
  provenance: Provenance6.OBSERVED
5630
5676
  });
5631
5677
  }
5632
- const staleNote = seedCtx.stale ? "; the node has gone STALE" : "";
5633
- const satNote = isSaturated(seedCtx) ? `; inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
5634
5678
  candidates.push({
5635
5679
  node: seedNode,
5636
5680
  classification: "symptom-only",
@@ -5653,32 +5697,43 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
5653
5697
  let traversalPath = legacy.traversalPath;
5654
5698
  let edgeProvenances = legacy.edgeProvenances;
5655
5699
  if (top.node !== seedNode) {
5656
- const path64 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5657
- if (path64) {
5658
- traversalPath = path64.nodes;
5659
- edgeProvenances = path64.edges.map((e) => e.provenance);
5700
+ const path67 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
5701
+ if (path67) {
5702
+ traversalPath = path67.nodes;
5703
+ edgeProvenances = path67.edges.map((e) => e.provenance);
5660
5704
  } else {
5661
5705
  traversalPath = [errorNodeId, top.node];
5662
5706
  edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
5663
5707
  }
5664
5708
  }
5709
+ const fixRecommendation = fixRecommendationForTop(top, seedNode, legacy);
5665
5710
  return RootCauseResultSchema.parse({
5666
5711
  rootCauseNode: top.node,
5667
5712
  rootCauseReason: top.reason,
5668
5713
  traversalPath,
5669
5714
  edgeProvenances,
5670
5715
  confidence: top.confidence,
5671
- ...legacy.fixRecommendation ? { fixRecommendation: legacy.fixRecommendation } : {},
5716
+ ...fixRecommendation ? { fixRecommendation } : {},
5672
5717
  candidates
5673
5718
  });
5674
5719
  }
5720
+ function fixRecommendationForTop(top, seedNode, legacy) {
5721
+ if (top.classification === "primary-failure" && top.node === seedNode) {
5722
+ return legacy.fixRecommendation;
5723
+ }
5724
+ const name = top.node.replace(/^service:/, "");
5725
+ if (top.classification === "primary-failure") {
5726
+ return `Reduce or throttle the load from ${name} (or scale the saturated downstream capacity it drives) \u2014 the failure originates at this overloading source, not the starved callee.`;
5727
+ }
5728
+ return `${name} is a saturated/starved downstream victim; investigate its inbound load and upstream callers, not its own handler.`;
5729
+ }
5675
5730
 
5676
5731
  // src/extract/services.ts
5677
- import { promises as fs12 } from "fs";
5678
- import path14 from "path";
5732
+ import { promises as fs15 } from "fs";
5733
+ import path17 from "path";
5679
5734
  import ignore from "ignore";
5680
5735
  import { minimatch as minimatch2 } from "minimatch";
5681
- import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
5736
+ import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
5682
5737
 
5683
5738
  // src/extract/python.ts
5684
5739
  import { promises as fs8 } from "fs";
@@ -5867,18 +5922,230 @@ async function discoverPhpService(scanPath, dir) {
5867
5922
  return { pkg, dir, node };
5868
5923
  }
5869
5924
 
5870
- // src/extract/owners.ts
5925
+ // src/extract/csharp.ts
5871
5926
  import { promises as fs11 } from "fs";
5872
5927
  import path13 from "path";
5928
+ import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
5929
+ var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
5930
+ async function projectFiles(dir) {
5931
+ const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
5932
+ const out = [];
5933
+ for (const entry of entries) {
5934
+ if (!entry.isFile()) continue;
5935
+ if (CSHARP_PROJECT_EXTS.has(path13.extname(entry.name).toLowerCase())) out.push(entry.name);
5936
+ }
5937
+ return out.sort();
5938
+ }
5939
+ var NESTED_PROJECT_DIR = "src";
5940
+ async function resolveProjectDir(dir) {
5941
+ if ((await projectFiles(dir)).length > 0) return dir;
5942
+ const nested = path13.join(dir, NESTED_PROJECT_DIR);
5943
+ if ((await projectFiles(nested)).length > 0) return nested;
5944
+ return null;
5945
+ }
5946
+ async function hasCsharpProject(dir) {
5947
+ return await resolveProjectDir(dir) !== null;
5948
+ }
5949
+ function parseCsproj(source) {
5950
+ const dependencies = {};
5951
+ const re = /<PackageReference\b([^>]*?)\/?>/g;
5952
+ let m;
5953
+ while ((m = re.exec(source)) !== null) {
5954
+ const attrs = m[1] ?? "";
5955
+ const include = attrs.match(/\bInclude\s*=\s*"([^"]+)"/)?.[1];
5956
+ if (!include) continue;
5957
+ const version = attrs.match(/\bVersion\s*=\s*"([^"]+)"/)?.[1];
5958
+ dependencies[include] = version ?? "";
5959
+ }
5960
+ return { dependencies };
5961
+ }
5962
+ async function discoverCsharpService(scanPath, dir) {
5963
+ const projectDir = await resolveProjectDir(dir);
5964
+ if (projectDir === null) return null;
5965
+ const projects = await projectFiles(projectDir);
5966
+ const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
5967
+ const name = csproj ? path13.basename(csproj, path13.extname(csproj)) : path13.basename(dir);
5968
+ let dependencies = {};
5969
+ if (csproj) {
5970
+ const raw = await fs11.readFile(path13.join(projectDir, csproj), "utf8").catch(() => "");
5971
+ dependencies = parseCsproj(raw).dependencies;
5972
+ }
5973
+ const pkg = { name, dependencies };
5974
+ const node = {
5975
+ id: serviceId5(name),
5976
+ type: NodeType9.ServiceNode,
5977
+ name,
5978
+ language: "csharp",
5979
+ dependencies,
5980
+ // Anchor on the directory that holds the project and its `.cs` files, so file
5981
+ // attribution reaches the source whether it sits at the root or under `src/`.
5982
+ repoPath: path13.relative(scanPath, projectDir)
5983
+ };
5984
+ return { pkg, dir: projectDir, node };
5985
+ }
5986
+
5987
+ // src/extract/java.ts
5988
+ import { promises as fs12 } from "fs";
5989
+ import path14 from "path";
5990
+ import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
5991
+ var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
5992
+ async function hasJavaManifest(dir) {
5993
+ for (const manifest of JAVA_MANIFESTS) {
5994
+ if (await exists(path14.join(dir, manifest))) return true;
5995
+ }
5996
+ return false;
5997
+ }
5998
+ function parsePomXml(source) {
5999
+ const dependencies = {};
6000
+ const depBlock = /<dependency\b[^>]*>([\s\S]*?)<\/dependency>/g;
6001
+ let d;
6002
+ while ((d = depBlock.exec(source)) !== null) {
6003
+ const body = d[1] ?? "";
6004
+ const groupId = body.match(/<groupId>\s*([^<]+?)\s*<\/groupId>/)?.[1];
6005
+ const artifactId = body.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
6006
+ if (!groupId || !artifactId) continue;
6007
+ const version = body.match(/<version>\s*([^<]+?)\s*<\/version>/)?.[1];
6008
+ dependencies[`${groupId}:${artifactId}`] = version ?? "";
6009
+ }
6010
+ const withoutBlocks = source.replace(/<parent\b[^>]*>[\s\S]*?<\/parent>/g, "").replace(/<dependencyManagement\b[^>]*>[\s\S]*?<\/dependencyManagement>/g, "").replace(/<dependencies\b[^>]*>[\s\S]*?<\/dependencies>/g, "").replace(/<build\b[^>]*>[\s\S]*?<\/build>/g, "");
6011
+ const name = withoutBlocks.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
6012
+ return { name, dependencies };
6013
+ }
6014
+ var GRADLE_CONFIGS = "implementation|api|compileOnly|runtimeOnly|compileOnlyApi|testImplementation|testRuntimeOnly|testCompileOnly|annotationProcessor|developmentOnly|runtimeClasspath";
6015
+ function parseGradle(source) {
6016
+ const dependencies = {};
6017
+ const re = new RegExp(`\\b(?:${GRADLE_CONFIGS})\\s*\\(?\\s*(['"])([^'"]+)\\1`, "g");
6018
+ let m;
6019
+ while ((m = re.exec(source)) !== null) {
6020
+ const coord = m[2] ?? "";
6021
+ const parts = coord.split(":");
6022
+ if (parts.length < 2 || !parts[0] || !parts[1]) continue;
6023
+ const key = `${parts[0]}:${parts[1]}`;
6024
+ dependencies[key] = parts.slice(2).join(":");
6025
+ }
6026
+ return { dependencies };
6027
+ }
6028
+ async function discoverJavaService(scanPath, dir) {
6029
+ const pomPath = path14.join(dir, "pom.xml");
6030
+ const gradlePath = path14.join(dir, "build.gradle");
6031
+ const gradleKtsPath = path14.join(dir, "build.gradle.kts");
6032
+ let manifest = null;
6033
+ if (await exists(pomPath)) {
6034
+ manifest = parsePomXml(await fs12.readFile(pomPath, "utf8").catch(() => ""));
6035
+ } else if (await exists(gradlePath)) {
6036
+ manifest = parseGradle(await fs12.readFile(gradlePath, "utf8").catch(() => ""));
6037
+ } else if (await exists(gradleKtsPath)) {
6038
+ manifest = parseGradle(await fs12.readFile(gradleKtsPath, "utf8").catch(() => ""));
6039
+ }
6040
+ if (!manifest) return null;
6041
+ const name = manifest.name?.trim() || path14.basename(dir);
6042
+ const dependencies = manifest.dependencies;
6043
+ const pkg = { name, dependencies };
6044
+ const node = {
6045
+ id: serviceId6(name),
6046
+ type: NodeType10.ServiceNode,
6047
+ name,
6048
+ language: "java",
6049
+ dependencies,
6050
+ repoPath: path14.relative(scanPath, dir)
6051
+ };
6052
+ return { pkg, dir, node };
6053
+ }
6054
+
6055
+ // src/extract/dockerfile-service.ts
6056
+ import { promises as fs13 } from "fs";
6057
+ import path15 from "path";
6058
+ import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
6059
+ function languageForSourceExt(ext) {
6060
+ switch (ext) {
6061
+ case ".py":
6062
+ return "python";
6063
+ case ".go":
6064
+ return "go";
6065
+ case ".rb":
6066
+ return "ruby";
6067
+ case ".php":
6068
+ return "php";
6069
+ case ".cs":
6070
+ return "csharp";
6071
+ case ".java":
6072
+ return "java";
6073
+ case ".ts":
6074
+ case ".tsx":
6075
+ return "typescript";
6076
+ case ".js":
6077
+ case ".mjs":
6078
+ case ".cjs":
6079
+ return "javascript";
6080
+ default:
6081
+ return null;
6082
+ }
6083
+ }
6084
+ var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java"];
6085
+ async function inferServiceLanguage(dir) {
6086
+ const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
6087
+ const counts = /* @__PURE__ */ new Map();
6088
+ for (const entry of entries) {
6089
+ if (!entry.isFile()) continue;
6090
+ if (isNeatAuthoredSourceFile(entry.name)) continue;
6091
+ const ext = path15.extname(entry.name).toLowerCase();
6092
+ if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
6093
+ const lang = languageForSourceExt(ext);
6094
+ if (!lang) continue;
6095
+ counts.set(lang, (counts.get(lang) ?? 0) + 1);
6096
+ }
6097
+ if (counts.size === 0) return null;
6098
+ let best = null;
6099
+ let bestCount = -1;
6100
+ for (const lang of LANGUAGE_PRECEDENCE) {
6101
+ const c = counts.get(lang) ?? 0;
6102
+ if (c > bestCount) {
6103
+ best = lang;
6104
+ bestCount = c;
6105
+ }
6106
+ }
6107
+ return best;
6108
+ }
6109
+ async function hasLanguageManifest(dir) {
6110
+ return await exists(path15.join(dir, "package.json")) || await exists(path15.join(dir, "pyproject.toml")) || await exists(path15.join(dir, "requirements.txt")) || await exists(path15.join(dir, "setup.py")) || await exists(path15.join(dir, "go.mod")) || await exists(path15.join(dir, "Gemfile")) || await exists(path15.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
6111
+ // rather than probing a fixed filename (ADR-196).
6112
+ await hasCsharpProject(dir) || // Java keys on a Maven / Gradle build manifest (ADR-197).
6113
+ await hasJavaManifest(dir);
6114
+ }
6115
+ async function hasDockerfileDeclaredService(dir) {
6116
+ if (!await exists(path15.join(dir, "Dockerfile"))) return false;
6117
+ return await inferServiceLanguage(dir) !== null;
6118
+ }
6119
+ async function discoverDockerfileService(scanPath, dir) {
6120
+ if (!await exists(path15.join(dir, "Dockerfile"))) return null;
6121
+ if (await hasLanguageManifest(dir)) return null;
6122
+ const language = await inferServiceLanguage(dir);
6123
+ if (!language) return null;
6124
+ const name = path15.basename(dir);
6125
+ const pkg = { name, dependencies: {} };
6126
+ const node = {
6127
+ id: serviceId7(name),
6128
+ type: NodeType11.ServiceNode,
6129
+ name,
6130
+ language,
6131
+ dependencies: {},
6132
+ repoPath: path15.relative(scanPath, dir)
6133
+ };
6134
+ return { pkg, dir, node };
6135
+ }
6136
+
6137
+ // src/extract/owners.ts
6138
+ import { promises as fs14 } from "fs";
6139
+ import path16 from "path";
5873
6140
  import { minimatch } from "minimatch";
5874
6141
  async function loadCodeowners(scanPath) {
5875
6142
  const candidates = [
5876
- path13.join(scanPath, "CODEOWNERS"),
5877
- path13.join(scanPath, ".github", "CODEOWNERS")
6143
+ path16.join(scanPath, "CODEOWNERS"),
6144
+ path16.join(scanPath, ".github", "CODEOWNERS")
5878
6145
  ];
5879
6146
  for (const file of candidates) {
5880
6147
  if (await exists(file)) {
5881
- const raw = await fs11.readFile(file, "utf8");
6148
+ const raw = await fs14.readFile(file, "utf8");
5882
6149
  return parseCodeowners(raw);
5883
6150
  }
5884
6151
  }
@@ -5896,7 +6163,7 @@ function parseCodeowners(raw) {
5896
6163
  return { rules };
5897
6164
  }
5898
6165
  function matchOwner(file, repoPath) {
5899
- const normalized = repoPath.split(path13.sep).join("/");
6166
+ const normalized = repoPath.split(path16.sep).join("/");
5900
6167
  for (const rule of file.rules) {
5901
6168
  if (matchesPattern(rule.pattern, normalized)) return rule.owners;
5902
6169
  }
@@ -5912,7 +6179,7 @@ function matchesPattern(rawPattern, repoPath) {
5912
6179
  return false;
5913
6180
  }
5914
6181
  async function readPackageJsonAuthor(serviceDir) {
5915
- const pkgPath = path13.join(serviceDir, "package.json");
6182
+ const pkgPath = path16.join(serviceDir, "package.json");
5916
6183
  if (!await exists(pkgPath)) return null;
5917
6184
  try {
5918
6185
  const pkg = await readJson(pkgPath);
@@ -5949,33 +6216,36 @@ function workspaceGlobs(pkg) {
5949
6216
  return null;
5950
6217
  }
5951
6218
  async function hasPythonManifest(dir) {
5952
- return await exists(path14.join(dir, "pyproject.toml")) || await exists(path14.join(dir, "requirements.txt")) || await exists(path14.join(dir, "setup.py"));
6219
+ return await exists(path17.join(dir, "pyproject.toml")) || await exists(path17.join(dir, "requirements.txt")) || await exists(path17.join(dir, "setup.py"));
5953
6220
  }
5954
6221
  async function hasGoManifest(dir) {
5955
- return exists(path14.join(dir, "go.mod"));
6222
+ return exists(path17.join(dir, "go.mod"));
5956
6223
  }
5957
6224
  async function hasRubyManifest(dir) {
5958
- return exists(path14.join(dir, "Gemfile"));
6225
+ return exists(path17.join(dir, "Gemfile"));
5959
6226
  }
5960
6227
  async function hasPhpManifest(dir) {
5961
- return exists(path14.join(dir, "composer.json"));
6228
+ return exists(path17.join(dir, "composer.json"));
6229
+ }
6230
+ async function hasCsharpManifest(dir) {
6231
+ return hasCsharpProject(dir);
5962
6232
  }
5963
6233
  async function loadGitignore(scanPath) {
5964
- const gitignorePath = path14.join(scanPath, ".gitignore");
6234
+ const gitignorePath = path17.join(scanPath, ".gitignore");
5965
6235
  if (!await exists(gitignorePath)) return null;
5966
- const raw = await fs12.readFile(gitignorePath, "utf8");
6236
+ const raw = await fs15.readFile(gitignorePath, "utf8");
5967
6237
  return ignore().add(raw);
5968
6238
  }
5969
6239
  async function walkDirs(start, scanPath, options, visit) {
5970
6240
  async function recurse(current, depth) {
5971
6241
  if (depth > options.maxDepth) return;
5972
- const entries = await fs12.readdir(current, { withFileTypes: true }).catch(() => []);
6242
+ const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
5973
6243
  for (const entry of entries) {
5974
6244
  if (!entry.isDirectory()) continue;
5975
6245
  if (IGNORED_DIRS.has(entry.name)) continue;
5976
- const child = path14.join(current, entry.name);
6246
+ const child = path17.join(current, entry.name);
5977
6247
  if (options.ig) {
5978
- const rel = path14.relative(scanPath, child).split(path14.sep).join("/");
6248
+ const rel = path17.relative(scanPath, child).split(path17.sep).join("/");
5979
6249
  if (rel && options.ig.ignores(rel + "/")) continue;
5980
6250
  }
5981
6251
  if (await isPythonVenvDir(child)) continue;
@@ -5991,8 +6261,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
5991
6261
  for (const raw of globs) {
5992
6262
  const pattern = raw.replace(/^\.\//, "");
5993
6263
  if (!pattern.includes("*")) {
5994
- const candidate = path14.join(scanPath, pattern);
5995
- if (await exists(path14.join(candidate, "package.json"))) found.add(candidate);
6264
+ const candidate = path17.join(scanPath, pattern);
6265
+ if (await exists(path17.join(candidate, "package.json"))) found.add(candidate);
5996
6266
  continue;
5997
6267
  }
5998
6268
  const segments = pattern.split("/");
@@ -6001,13 +6271,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
6001
6271
  if (seg.includes("*")) break;
6002
6272
  staticSegments.push(seg);
6003
6273
  }
6004
- const start = path14.join(scanPath, ...staticSegments);
6274
+ const start = path17.join(scanPath, ...staticSegments);
6005
6275
  if (!await exists(start)) continue;
6006
6276
  const hasDoubleStar = pattern.includes("**");
6007
6277
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
6008
6278
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
6009
- const rel = path14.relative(scanPath, dir).split(path14.sep).join("/");
6010
- if (minimatch2(rel, pattern) && await exists(path14.join(dir, "package.json"))) {
6279
+ const rel = path17.relative(scanPath, dir).split(path17.sep).join("/");
6280
+ if (minimatch2(rel, pattern) && await exists(path17.join(dir, "package.json"))) {
6011
6281
  found.add(dir);
6012
6282
  }
6013
6283
  });
@@ -6030,31 +6300,31 @@ function detectJsFramework(pkg) {
6030
6300
  async function detectJsServiceLanguage(dir, pkg) {
6031
6301
  const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
6032
6302
  if (deps["typescript"] !== void 0) return "typescript";
6033
- const entries = await fs12.readdir(dir).catch(() => []);
6303
+ const entries = await fs15.readdir(dir).catch(() => []);
6034
6304
  if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
6035
6305
  return "javascript";
6036
6306
  }
6037
6307
  async function discoverNodeService(scanPath, dir) {
6038
- const pkgPath = path14.join(dir, "package.json");
6308
+ const pkgPath = path17.join(dir, "package.json");
6039
6309
  if (!await exists(pkgPath)) return null;
6040
6310
  let pkg;
6041
6311
  try {
6042
6312
  pkg = await readJson(pkgPath);
6043
6313
  } catch (err) {
6044
- recordExtractionError("services", path14.relative(scanPath, pkgPath), err);
6314
+ recordExtractionError("services", path17.relative(scanPath, pkgPath), err);
6045
6315
  return null;
6046
6316
  }
6047
6317
  if (!pkg.name) return null;
6048
6318
  const framework = detectJsFramework(pkg);
6049
6319
  const language = await detectJsServiceLanguage(dir, pkg);
6050
6320
  const node = {
6051
- id: serviceId5(pkg.name),
6052
- type: NodeType9.ServiceNode,
6321
+ id: serviceId8(pkg.name),
6322
+ type: NodeType12.ServiceNode,
6053
6323
  name: pkg.name,
6054
6324
  language,
6055
6325
  version: pkg.version,
6056
6326
  dependencies: pkg.dependencies ?? {},
6057
- repoPath: path14.relative(scanPath, dir),
6327
+ repoPath: path17.relative(scanPath, dir),
6058
6328
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
6059
6329
  ...framework ? { framework } : {}
6060
6330
  };
@@ -6065,18 +6335,18 @@ async function discoverPyService(scanPath, dir) {
6065
6335
  if (!py) return null;
6066
6336
  const pkg = pythonToPackage(py);
6067
6337
  const node = {
6068
- id: serviceId5(py.name),
6069
- type: NodeType9.ServiceNode,
6338
+ id: serviceId8(py.name),
6339
+ type: NodeType12.ServiceNode,
6070
6340
  name: py.name,
6071
6341
  language: "python",
6072
6342
  version: py.version,
6073
6343
  dependencies: py.dependencies,
6074
- repoPath: path14.relative(scanPath, dir)
6344
+ repoPath: path17.relative(scanPath, dir)
6075
6345
  };
6076
6346
  return { pkg, dir, node };
6077
6347
  }
6078
6348
  async function discoverServices(scanPath) {
6079
- const rootPkgPath = path14.join(scanPath, "package.json");
6349
+ const rootPkgPath = path17.join(scanPath, "package.json");
6080
6350
  let rootPkg = null;
6081
6351
  if (await exists(rootPkgPath)) {
6082
6352
  try {
@@ -6084,7 +6354,7 @@ async function discoverServices(scanPath) {
6084
6354
  } catch (err) {
6085
6355
  recordExtractionError(
6086
6356
  "services workspaces",
6087
- path14.relative(scanPath, rootPkgPath),
6357
+ path17.relative(scanPath, rootPkgPath),
6088
6358
  err
6089
6359
  );
6090
6360
  }
@@ -6096,7 +6366,7 @@ async function discoverServices(scanPath) {
6096
6366
  } else {
6097
6367
  if (rootPkg && rootPkg.name) {
6098
6368
  candidateDirs.push(scanPath);
6099
- } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath)) {
6369
+ } else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath)) {
6100
6370
  candidateDirs.push(scanPath);
6101
6371
  }
6102
6372
  const ig = await loadGitignore(scanPath);
@@ -6105,9 +6375,11 @@ async function discoverServices(scanPath) {
6105
6375
  scanPath,
6106
6376
  { maxDepth: parseScanDepth(), ig },
6107
6377
  async (dir) => {
6108
- if (await exists(path14.join(dir, "package.json"))) {
6378
+ if (await exists(path17.join(dir, "package.json"))) {
6379
+ candidateDirs.push(dir);
6380
+ } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir)) {
6109
6381
  candidateDirs.push(dir);
6110
- } else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir)) {
6382
+ } else if (await hasDockerfileDeclaredService(dir)) {
6111
6383
  candidateDirs.push(dir);
6112
6384
  }
6113
6385
  }
@@ -6115,14 +6387,17 @@ async function discoverServices(scanPath) {
6115
6387
  }
6116
6388
  candidateDirs.sort();
6117
6389
  const seen = /* @__PURE__ */ new Map();
6390
+ const seenDirs = /* @__PURE__ */ new Set();
6118
6391
  const out = [];
6119
6392
  for (const dir of candidateDirs) {
6120
- const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir);
6393
+ const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir) ?? await discoverCsharpService(scanPath, dir) ?? await discoverJavaService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
6121
6394
  if (!service) continue;
6395
+ if (seenDirs.has(service.dir)) continue;
6396
+ seenDirs.add(service.dir);
6122
6397
  const existingDir = seen.get(service.node.name);
6123
6398
  if (existingDir !== void 0) {
6124
- const a = path14.relative(scanPath, existingDir) || ".";
6125
- const b = path14.relative(scanPath, dir) || ".";
6399
+ const a = path17.relative(scanPath, existingDir) || ".";
6400
+ const b = path17.relative(scanPath, dir) || ".";
6126
6401
  console.warn(
6127
6402
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
6128
6403
  );
@@ -6158,20 +6433,20 @@ function addServiceNodes(graph, services) {
6158
6433
  }
6159
6434
 
6160
6435
  // src/extract/aliases.ts
6161
- import path15 from "path";
6162
- import { promises as fs13 } from "fs";
6436
+ import path18 from "path";
6437
+ import { promises as fs16 } from "fs";
6163
6438
  import { parseAllDocuments } from "yaml";
6164
- import { NodeType as NodeType10 } from "@neat.is/types";
6439
+ import { NodeType as NodeType13 } from "@neat.is/types";
6165
6440
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
6166
6441
  "Service",
6167
6442
  "Deployment",
6168
6443
  "StatefulSet",
6169
6444
  "DaemonSet"
6170
6445
  ]);
6171
- function addAliases(graph, serviceId9, candidates) {
6172
- if (!graph.hasNode(serviceId9)) return;
6173
- const node = graph.getNodeAttributes(serviceId9);
6174
- if (node.type !== NodeType10.ServiceNode) return;
6446
+ function addAliases(graph, serviceId12, candidates) {
6447
+ if (!graph.hasNode(serviceId12)) return;
6448
+ const node = graph.getNodeAttributes(serviceId12);
6449
+ if (node.type !== NodeType13.ServiceNode) return;
6175
6450
  const set = new Set(node.aliases ?? []);
6176
6451
  for (const c of candidates) {
6177
6452
  if (!c) continue;
@@ -6180,20 +6455,20 @@ function addAliases(graph, serviceId9, candidates) {
6180
6455
  }
6181
6456
  if (set.size === 0) return;
6182
6457
  const updated = { ...node, aliases: [...set].sort() };
6183
- graph.replaceNodeAttributes(serviceId9, updated);
6458
+ graph.replaceNodeAttributes(serviceId12, updated);
6184
6459
  }
6185
6460
  function indexServicesByName(services) {
6186
6461
  const map = /* @__PURE__ */ new Map();
6187
6462
  for (const s of services) {
6188
6463
  map.set(s.node.name, s.node.id);
6189
- map.set(path15.basename(s.dir), s.node.id);
6464
+ map.set(path18.basename(s.dir), s.node.id);
6190
6465
  }
6191
6466
  return map;
6192
6467
  }
6193
6468
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
6194
6469
  let composePath = null;
6195
6470
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6196
- const abs = path15.join(scanPath, name);
6471
+ const abs = path18.join(scanPath, name);
6197
6472
  if (await exists(abs)) {
6198
6473
  composePath = abs;
6199
6474
  break;
@@ -6206,19 +6481,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
6206
6481
  } catch (err) {
6207
6482
  recordExtractionError(
6208
6483
  "aliases compose",
6209
- path15.relative(scanPath, composePath),
6484
+ path18.relative(scanPath, composePath),
6210
6485
  err
6211
6486
  );
6212
6487
  return;
6213
6488
  }
6214
6489
  if (!compose?.services) return;
6215
6490
  for (const [composeName, svc] of Object.entries(compose.services)) {
6216
- const serviceId9 = serviceIndex.get(composeName);
6217
- if (!serviceId9) continue;
6491
+ const serviceId12 = serviceIndex.get(composeName);
6492
+ if (!serviceId12) continue;
6218
6493
  const aliases = /* @__PURE__ */ new Set([composeName]);
6219
6494
  if (svc.container_name) aliases.add(svc.container_name);
6220
6495
  if (svc.hostname) aliases.add(svc.hostname);
6221
- addAliases(graph, serviceId9, aliases);
6496
+ addAliases(graph, serviceId12, aliases);
6222
6497
  }
6223
6498
  }
6224
6499
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -6249,11 +6524,11 @@ function parseDockerfileLabels(content) {
6249
6524
  }
6250
6525
  async function collectDockerfileAliases(graph, services) {
6251
6526
  for (const service of services) {
6252
- const dockerfilePath = path15.join(service.dir, "Dockerfile");
6527
+ const dockerfilePath = path18.join(service.dir, "Dockerfile");
6253
6528
  if (!await exists(dockerfilePath)) continue;
6254
6529
  let content;
6255
6530
  try {
6256
- content = await fs13.readFile(dockerfilePath, "utf8");
6531
+ content = await fs16.readFile(dockerfilePath, "utf8");
6257
6532
  } catch (err) {
6258
6533
  recordExtractionError("aliases dockerfile", dockerfilePath, err);
6259
6534
  continue;
@@ -6265,15 +6540,15 @@ async function collectDockerfileAliases(graph, services) {
6265
6540
  async function walkYamlFiles(start, depth = 0, max = 5) {
6266
6541
  if (depth > max) return [];
6267
6542
  const out = [];
6268
- const entries = await fs13.readdir(start, { withFileTypes: true }).catch(() => []);
6543
+ const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
6269
6544
  for (const entry of entries) {
6270
6545
  if (entry.isDirectory()) {
6271
6546
  if (IGNORED_DIRS.has(entry.name)) continue;
6272
- const child = path15.join(start, entry.name);
6547
+ const child = path18.join(start, entry.name);
6273
6548
  if (await isPythonVenvDir(child)) continue;
6274
6549
  out.push(...await walkYamlFiles(child, depth + 1, max));
6275
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path15.extname(entry.name))) {
6276
- out.push(path15.join(start, entry.name));
6550
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path18.extname(entry.name))) {
6551
+ out.push(path18.join(start, entry.name));
6277
6552
  }
6278
6553
  }
6279
6554
  return out;
@@ -6300,7 +6575,7 @@ function k8sServiceTarget(doc, byName) {
6300
6575
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
6301
6576
  const files = await walkYamlFiles(scanPath);
6302
6577
  for (const file of files) {
6303
- const content = await fs13.readFile(file, "utf8");
6578
+ const content = await fs16.readFile(file, "utf8");
6304
6579
  let docs;
6305
6580
  try {
6306
6581
  docs = parseAllDocuments(content).map((d) => d.toJSON());
@@ -6324,14 +6599,14 @@ async function addServiceAliases(graph, scanPath, services) {
6324
6599
  }
6325
6600
 
6326
6601
  // src/extract/files.ts
6327
- import path16 from "path";
6602
+ import path19 from "path";
6328
6603
  async function addFiles(graph, services) {
6329
6604
  let nodesAdded = 0;
6330
6605
  let edgesAdded = 0;
6331
6606
  for (const service of services) {
6332
6607
  const filePaths = await walkSourceFiles(service.dir);
6333
6608
  for (const filePath of filePaths) {
6334
- const relPath = toPosix(path16.relative(service.dir, filePath));
6609
+ const relPath = toPosix(path19.relative(service.dir, filePath));
6335
6610
  const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
6336
6611
  graph,
6337
6612
  service.pkg.name,
@@ -6346,13 +6621,19 @@ async function addFiles(graph, services) {
6346
6621
  }
6347
6622
 
6348
6623
  // src/extract/symbols.ts
6349
- import path17 from "path";
6624
+ import path20 from "path";
6350
6625
  import Parser3 from "tree-sitter";
6351
6626
  import JavaScript3 from "tree-sitter-javascript";
6352
6627
  import TypeScript from "tree-sitter-typescript";
6628
+ import Python3 from "tree-sitter-python";
6629
+ import Go3 from "tree-sitter-go";
6630
+ import Ruby2 from "tree-sitter-ruby";
6631
+ import Php2 from "tree-sitter-php";
6632
+ import CSharp from "tree-sitter-c-sharp";
6633
+ import Java from "tree-sitter-java";
6353
6634
  import {
6354
6635
  EdgeType as EdgeType7,
6355
- NodeType as NodeType11,
6636
+ NodeType as NodeType14,
6356
6637
  Provenance as Provenance7,
6357
6638
  confidenceForExtracted as confidenceForExtracted4,
6358
6639
  extractedEdgeId as extractedEdgeId6,
@@ -6367,6 +6648,15 @@ var GRAMMAR_BY_EXT = {
6367
6648
  ".mjs": JavaScript3,
6368
6649
  ".cjs": JavaScript3
6369
6650
  };
6651
+ var SYMBOL_GRAMMAR_BY_EXT = {
6652
+ ...GRAMMAR_BY_EXT,
6653
+ ".py": Python3,
6654
+ ".go": Go3,
6655
+ ".rb": Ruby2,
6656
+ ".php": Php2.php_only,
6657
+ ".cs": CSharp,
6658
+ ".java": Java
6659
+ };
6370
6660
  function parseSource3(parser, source) {
6371
6661
  return parser.parse(
6372
6662
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
@@ -6376,7 +6666,352 @@ function methodName(node) {
6376
6666
  const name = node.childForFieldName("name");
6377
6667
  return name ? name.text : null;
6378
6668
  }
6379
- function collectSymbolDefs(root) {
6669
+ function collectSymbolDefs(root) {
6670
+ const out = [];
6671
+ const push = (kind, qualname, node) => {
6672
+ out.push({
6673
+ kind,
6674
+ qualname,
6675
+ startLine: node.startPosition.row + 1,
6676
+ endLine: node.endPosition.row + 1
6677
+ });
6678
+ };
6679
+ const visit = (node, classCtx) => {
6680
+ switch (node.type) {
6681
+ case "function_declaration":
6682
+ case "generator_function_declaration": {
6683
+ const name = node.childForFieldName("name")?.text;
6684
+ if (name) push("function", name, node);
6685
+ break;
6686
+ }
6687
+ case "class_declaration":
6688
+ case "abstract_class_declaration":
6689
+ case "class": {
6690
+ const name = node.childForFieldName("name")?.text;
6691
+ if (name) push("class", name, node);
6692
+ const body = node.childForFieldName("body");
6693
+ if (body) {
6694
+ for (let i = 0; i < body.namedChildCount; i++) {
6695
+ const child = body.namedChild(i);
6696
+ if (child) visit(child, name ?? classCtx);
6697
+ }
6698
+ }
6699
+ return;
6700
+ }
6701
+ case "method_definition": {
6702
+ const name = methodName(node);
6703
+ if (name) {
6704
+ const kind = name === "constructor" ? "constructor" : "method";
6705
+ push(kind, classCtx ? `${classCtx}.${name}` : name, node);
6706
+ }
6707
+ break;
6708
+ }
6709
+ case "variable_declarator": {
6710
+ const value = node.childForFieldName("value");
6711
+ if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
6712
+ const nameNode = node.childForFieldName("name");
6713
+ if (nameNode && nameNode.type === "identifier") {
6714
+ push("function", nameNode.text, node);
6715
+ }
6716
+ }
6717
+ break;
6718
+ }
6719
+ }
6720
+ for (let i = 0; i < node.namedChildCount; i++) {
6721
+ const child = node.namedChild(i);
6722
+ if (child) visit(child, classCtx);
6723
+ }
6724
+ };
6725
+ visit(root, void 0);
6726
+ return out;
6727
+ }
6728
+ function collectPythonSymbolDefs(root) {
6729
+ const out = [];
6730
+ const push = (kind, qualname, node) => {
6731
+ out.push({
6732
+ kind,
6733
+ qualname,
6734
+ startLine: node.startPosition.row + 1,
6735
+ endLine: node.endPosition.row + 1
6736
+ });
6737
+ };
6738
+ const visit = (node, classCtx) => {
6739
+ switch (node.type) {
6740
+ case "function_definition": {
6741
+ const name = node.childForFieldName("name")?.text;
6742
+ if (name) {
6743
+ if (classCtx) {
6744
+ const kind = name === "__init__" ? "constructor" : "method";
6745
+ push(kind, `${classCtx}.${name}`, node);
6746
+ } else {
6747
+ push("function", name, node);
6748
+ }
6749
+ }
6750
+ const body = node.childForFieldName("body");
6751
+ if (body) {
6752
+ for (let i = 0; i < body.namedChildCount; i++) {
6753
+ const child = body.namedChild(i);
6754
+ if (child) visit(child, void 0);
6755
+ }
6756
+ }
6757
+ return;
6758
+ }
6759
+ case "class_definition": {
6760
+ const name = node.childForFieldName("name")?.text;
6761
+ if (name) push("class", name, node);
6762
+ const body = node.childForFieldName("body");
6763
+ if (body) {
6764
+ for (let i = 0; i < body.namedChildCount; i++) {
6765
+ const child = body.namedChild(i);
6766
+ if (child) visit(child, name ?? classCtx);
6767
+ }
6768
+ }
6769
+ return;
6770
+ }
6771
+ case "decorated_definition": {
6772
+ const def = node.childForFieldName("definition");
6773
+ if (def) visit(def, classCtx);
6774
+ return;
6775
+ }
6776
+ }
6777
+ for (let i = 0; i < node.namedChildCount; i++) {
6778
+ const child = node.namedChild(i);
6779
+ if (child) visit(child, classCtx);
6780
+ }
6781
+ };
6782
+ visit(root, void 0);
6783
+ return out;
6784
+ }
6785
+ function goReceiverType(node) {
6786
+ const receiver = node.childForFieldName("receiver");
6787
+ if (!receiver) return void 0;
6788
+ for (let i = 0; i < receiver.namedChildCount; i++) {
6789
+ const param = receiver.namedChild(i);
6790
+ if (param?.type !== "parameter_declaration") continue;
6791
+ let typeNode = param.childForFieldName("type");
6792
+ if (typeNode?.type === "pointer_type") typeNode = typeNode.namedChild(0) ?? typeNode;
6793
+ if (typeNode?.type === "generic_type") {
6794
+ typeNode = typeNode.childForFieldName("type") ?? typeNode.namedChild(0) ?? typeNode;
6795
+ }
6796
+ if (typeNode?.type === "type_identifier") return typeNode.text;
6797
+ return typeNode?.text;
6798
+ }
6799
+ return void 0;
6800
+ }
6801
+ function collectGoSymbolDefs(root) {
6802
+ const out = [];
6803
+ const push = (kind, qualname, node) => {
6804
+ out.push({
6805
+ kind,
6806
+ qualname,
6807
+ startLine: node.startPosition.row + 1,
6808
+ endLine: node.endPosition.row + 1
6809
+ });
6810
+ };
6811
+ const visit = (node) => {
6812
+ switch (node.type) {
6813
+ case "function_declaration": {
6814
+ const name = node.childForFieldName("name")?.text;
6815
+ if (name) push("function", name, node);
6816
+ break;
6817
+ }
6818
+ case "method_declaration": {
6819
+ const name = node.childForFieldName("name")?.text;
6820
+ if (name) {
6821
+ const recv = goReceiverType(node);
6822
+ push("method", recv ? `${recv}.${name}` : name, node);
6823
+ }
6824
+ break;
6825
+ }
6826
+ }
6827
+ for (let i = 0; i < node.namedChildCount; i++) {
6828
+ const child = node.namedChild(i);
6829
+ if (child) visit(child);
6830
+ }
6831
+ };
6832
+ visit(root);
6833
+ return out;
6834
+ }
6835
+ function collectRubySymbolDefs(root) {
6836
+ const out = [];
6837
+ const push = (kind, qualname, node) => {
6838
+ out.push({
6839
+ kind,
6840
+ qualname,
6841
+ startLine: node.startPosition.row + 1,
6842
+ endLine: node.endPosition.row + 1
6843
+ });
6844
+ };
6845
+ const nameText = (node) => node.childForFieldName("name")?.text.replace(/::/g, ".");
6846
+ const walkBody = (node, ctx) => {
6847
+ const body = node.childForFieldName("body");
6848
+ if (!body) return;
6849
+ for (let i = 0; i < body.namedChildCount; i++) {
6850
+ const child = body.namedChild(i);
6851
+ if (child) visit(child, ctx);
6852
+ }
6853
+ };
6854
+ const visit = (node, classCtx) => {
6855
+ switch (node.type) {
6856
+ case "method":
6857
+ case "singleton_method": {
6858
+ const name = node.childForFieldName("name")?.text;
6859
+ if (name) {
6860
+ if (classCtx) {
6861
+ const kind = node.type === "method" && name === "initialize" ? "constructor" : "method";
6862
+ push(kind, `${classCtx}.${name}`, node);
6863
+ } else {
6864
+ push("function", name, node);
6865
+ }
6866
+ }
6867
+ walkBody(node, void 0);
6868
+ return;
6869
+ }
6870
+ case "class":
6871
+ case "module": {
6872
+ const name = nameText(node);
6873
+ if (name) push("class", classCtx ? `${classCtx}.${name}` : name, node);
6874
+ walkBody(node, name ? classCtx ? `${classCtx}.${name}` : name : classCtx);
6875
+ return;
6876
+ }
6877
+ }
6878
+ for (let i = 0; i < node.namedChildCount; i++) {
6879
+ const child = node.namedChild(i);
6880
+ if (child) visit(child, classCtx);
6881
+ }
6882
+ };
6883
+ visit(root, void 0);
6884
+ return out;
6885
+ }
6886
+ function collectPhpSymbolDefs(root) {
6887
+ const out = [];
6888
+ const push = (kind, qualname, node) => {
6889
+ out.push({
6890
+ kind,
6891
+ qualname,
6892
+ startLine: node.startPosition.row + 1,
6893
+ endLine: node.endPosition.row + 1
6894
+ });
6895
+ };
6896
+ const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
6897
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
6898
+ const walkChildren = (container, nsCtx, classCtx) => {
6899
+ let ns = nsCtx;
6900
+ for (let i = 0; i < container.namedChildCount; i++) {
6901
+ const child = container.namedChild(i);
6902
+ if (!child) continue;
6903
+ if (child.type === "namespace_definition" && !child.childForFieldName("body")) {
6904
+ const nameNode = child.childForFieldName("name");
6905
+ if (nameNode) ns = dot(nameNode.text);
6906
+ continue;
6907
+ }
6908
+ visit(child, ns, classCtx);
6909
+ }
6910
+ };
6911
+ const walkBody = (node, nsCtx, classCtx) => {
6912
+ const body = node.childForFieldName("body");
6913
+ if (body) walkChildren(body, nsCtx, classCtx);
6914
+ };
6915
+ const visit = (node, nsCtx, classCtx) => {
6916
+ switch (node.type) {
6917
+ case "function_definition": {
6918
+ const name = node.childForFieldName("name")?.text;
6919
+ if (name) push("function", join(nsCtx, name), node);
6920
+ walkBody(node, nsCtx, void 0);
6921
+ return;
6922
+ }
6923
+ case "class_declaration":
6924
+ case "trait_declaration":
6925
+ case "interface_declaration": {
6926
+ const name = node.childForFieldName("name")?.text;
6927
+ const full = name ? join(nsCtx, name) : classCtx;
6928
+ if (name) push("class", full, node);
6929
+ walkBody(node, nsCtx, full);
6930
+ return;
6931
+ }
6932
+ case "method_declaration": {
6933
+ const name = node.childForFieldName("name")?.text;
6934
+ if (name) {
6935
+ const kind = name === "__construct" ? "constructor" : "method";
6936
+ push(kind, join(classCtx ?? nsCtx, name), node);
6937
+ }
6938
+ return;
6939
+ }
6940
+ case "namespace_definition": {
6941
+ const nameNode = node.childForFieldName("name");
6942
+ const ns = nameNode ? dot(nameNode.text) : nsCtx;
6943
+ walkBody(node, ns, classCtx);
6944
+ return;
6945
+ }
6946
+ }
6947
+ walkChildren(node, nsCtx, classCtx);
6948
+ };
6949
+ walkChildren(root, void 0, void 0);
6950
+ return out;
6951
+ }
6952
+ function collectCsharpSymbolDefs(root) {
6953
+ const out = [];
6954
+ const push = (kind, qualname, node) => {
6955
+ out.push({
6956
+ kind,
6957
+ qualname,
6958
+ startLine: node.startPosition.row + 1,
6959
+ endLine: node.endPosition.row + 1
6960
+ });
6961
+ };
6962
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
6963
+ const walkChildren = (container, nsCtx, classCtx) => {
6964
+ let ns = nsCtx;
6965
+ for (let i = 0; i < container.namedChildCount; i++) {
6966
+ const child = container.namedChild(i);
6967
+ if (!child) continue;
6968
+ if (child.type === "file_scoped_namespace_declaration") {
6969
+ const nameNode = child.childForFieldName("name");
6970
+ if (nameNode) ns = join(nsCtx, nameNode.text);
6971
+ continue;
6972
+ }
6973
+ visit(child, ns, classCtx);
6974
+ }
6975
+ };
6976
+ const walkBody = (node, nsCtx, classCtx) => {
6977
+ const body = node.childForFieldName("body");
6978
+ if (body) walkChildren(body, nsCtx, classCtx);
6979
+ };
6980
+ const visit = (node, nsCtx, classCtx) => {
6981
+ switch (node.type) {
6982
+ case "namespace_declaration": {
6983
+ const nameNode = node.childForFieldName("name");
6984
+ const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
6985
+ walkBody(node, ns, classCtx);
6986
+ return;
6987
+ }
6988
+ case "class_declaration":
6989
+ case "interface_declaration":
6990
+ case "struct_declaration":
6991
+ case "record_declaration": {
6992
+ const name = node.childForFieldName("name")?.text;
6993
+ const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
6994
+ if (name) push("class", full, node);
6995
+ walkBody(node, nsCtx, full);
6996
+ return;
6997
+ }
6998
+ case "method_declaration": {
6999
+ const name = node.childForFieldName("name")?.text;
7000
+ if (name) push("method", join(classCtx ?? nsCtx, name), node);
7001
+ return;
7002
+ }
7003
+ case "constructor_declaration": {
7004
+ const name = node.childForFieldName("name")?.text;
7005
+ if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
7006
+ return;
7007
+ }
7008
+ }
7009
+ walkChildren(node, nsCtx, classCtx);
7010
+ };
7011
+ walkChildren(root, void 0, void 0);
7012
+ return out;
7013
+ }
7014
+ function collectJavaSymbolDefs(root) {
6380
7015
  const out = [];
6381
7016
  const push = (kind, qualname, node) => {
6382
7017
  out.push({
@@ -6386,55 +7021,71 @@ function collectSymbolDefs(root) {
6386
7021
  endLine: node.endPosition.row + 1
6387
7022
  });
6388
7023
  };
7024
+ const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
7025
+ let pkg;
7026
+ for (let i = 0; i < root.namedChildCount; i++) {
7027
+ const child = root.namedChild(i);
7028
+ if (child?.type === "package_declaration") {
7029
+ pkg = child.namedChild(0)?.text;
7030
+ break;
7031
+ }
7032
+ }
7033
+ const walkChildren = (container, classCtx) => {
7034
+ for (let i = 0; i < container.namedChildCount; i++) {
7035
+ const child = container.namedChild(i);
7036
+ if (child) visit(child, classCtx);
7037
+ }
7038
+ };
7039
+ const walkBody = (node, classCtx) => {
7040
+ const body = node.childForFieldName("body");
7041
+ if (body) walkChildren(body, classCtx);
7042
+ };
6389
7043
  const visit = (node, classCtx) => {
6390
7044
  switch (node.type) {
6391
- case "function_declaration":
6392
- case "generator_function_declaration": {
6393
- const name = node.childForFieldName("name")?.text;
6394
- if (name) push("function", name, node);
6395
- break;
6396
- }
6397
7045
  case "class_declaration":
6398
- case "abstract_class_declaration":
6399
- case "class": {
7046
+ case "interface_declaration":
7047
+ case "enum_declaration":
7048
+ case "record_declaration": {
6400
7049
  const name = node.childForFieldName("name")?.text;
6401
- if (name) push("class", name, node);
6402
- const body = node.childForFieldName("body");
6403
- if (body) {
6404
- for (let i = 0; i < body.namedChildCount; i++) {
6405
- const child = body.namedChild(i);
6406
- if (child) visit(child, name ?? classCtx);
6407
- }
6408
- }
7050
+ const full = name ? join(classCtx ?? pkg, name) : classCtx;
7051
+ if (name) push("class", full, node);
7052
+ walkBody(node, full);
6409
7053
  return;
6410
7054
  }
6411
- case "method_definition": {
6412
- const name = methodName(node);
6413
- if (name) {
6414
- const kind = name === "constructor" ? "constructor" : "method";
6415
- push(kind, classCtx ? `${classCtx}.${name}` : name, node);
6416
- }
6417
- break;
7055
+ case "method_declaration": {
7056
+ const name = node.childForFieldName("name")?.text;
7057
+ if (name) push("method", join(classCtx ?? pkg, name), node);
7058
+ return;
6418
7059
  }
6419
- case "variable_declarator": {
6420
- const value = node.childForFieldName("value");
6421
- if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
6422
- const nameNode = node.childForFieldName("name");
6423
- if (nameNode && nameNode.type === "identifier") {
6424
- push("function", nameNode.text, node);
6425
- }
6426
- }
6427
- break;
7060
+ case "constructor_declaration": {
7061
+ const name = node.childForFieldName("name")?.text;
7062
+ if (name) push("constructor", join(classCtx ?? pkg, name), node);
7063
+ return;
6428
7064
  }
6429
7065
  }
6430
- for (let i = 0; i < node.namedChildCount; i++) {
6431
- const child = node.namedChild(i);
6432
- if (child) visit(child, classCtx);
6433
- }
7066
+ walkChildren(node, classCtx);
6434
7067
  };
6435
- visit(root, void 0);
7068
+ walkChildren(root, void 0);
6436
7069
  return out;
6437
7070
  }
7071
+ function collectSymbolDefsForExt(ext, root) {
7072
+ switch (ext) {
7073
+ case ".py":
7074
+ return collectPythonSymbolDefs(root);
7075
+ case ".go":
7076
+ return collectGoSymbolDefs(root);
7077
+ case ".rb":
7078
+ return collectRubySymbolDefs(root);
7079
+ case ".php":
7080
+ return collectPhpSymbolDefs(root);
7081
+ case ".cs":
7082
+ return collectCsharpSymbolDefs(root);
7083
+ case ".java":
7084
+ return collectJavaSymbolDefs(root);
7085
+ default:
7086
+ return collectSymbolDefs(root);
7087
+ }
7088
+ }
6438
7089
  function disambiguate(defs) {
6439
7090
  const counts = /* @__PURE__ */ new Map();
6440
7091
  for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
@@ -6449,7 +7100,7 @@ function disambiguate(defs) {
6449
7100
  async function addSymbols(graph, services) {
6450
7101
  const parsers = /* @__PURE__ */ new Map();
6451
7102
  const parserForExt5 = (ext) => {
6452
- const grammar = GRAMMAR_BY_EXT[ext];
7103
+ const grammar = SYMBOL_GRAMMAR_BY_EXT[ext];
6453
7104
  if (!grammar) return null;
6454
7105
  let parser = parsers.get(ext);
6455
7106
  if (!parser) {
@@ -6464,13 +7115,14 @@ async function addSymbols(graph, services) {
6464
7115
  for (const service of services) {
6465
7116
  const files = await loadSourceFiles(service.dir);
6466
7117
  for (const file of files) {
6467
- const parser = parserForExt5(path17.extname(file.path));
7118
+ const ext = path20.extname(file.path);
7119
+ const parser = parserForExt5(ext);
6468
7120
  if (!parser) continue;
6469
- const relPath = toPosix(path17.relative(service.dir, file.path));
7121
+ const relPath = toPosix(path20.relative(service.dir, file.path));
6470
7122
  let defs;
6471
7123
  try {
6472
7124
  const tree = parseSource3(parser, file.content);
6473
- defs = collectSymbolDefs(tree.rootNode);
7125
+ defs = collectSymbolDefsForExt(ext, tree.rootNode);
6474
7126
  } catch (err) {
6475
7127
  recordExtractionError("symbol extraction", file.path, err);
6476
7128
  continue;
@@ -6489,7 +7141,7 @@ async function addSymbols(graph, services) {
6489
7141
  if (!graph.hasNode(sid)) {
6490
7142
  const node = {
6491
7143
  id: sid,
6492
- type: NodeType11.SymbolNode,
7144
+ type: NodeType14.SymbolNode,
6493
7145
  kind: def.kind,
6494
7146
  qualname: def.qualname,
6495
7147
  span: { startLine: def.startLine, endLine: def.endLine },
@@ -6525,11 +7177,11 @@ async function addSymbols(graph, services) {
6525
7177
  }
6526
7178
 
6527
7179
  // src/extract/symbol-edges.ts
6528
- import path18 from "path";
7180
+ import path21 from "path";
6529
7181
  import Parser4 from "tree-sitter";
6530
7182
  import {
6531
7183
  EdgeType as EdgeType8,
6532
- NodeType as NodeType12,
7184
+ NodeType as NodeType15,
6533
7185
  Provenance as Provenance8,
6534
7186
  confidenceForExtracted as confidenceForExtracted5,
6535
7187
  extractedEdgeId as extractedEdgeId7,
@@ -6639,10 +7291,10 @@ async function addSymbolEdges(graph, services) {
6639
7291
  const tsPaths = await loadTsPathConfig(service.dir);
6640
7292
  const files = await loadSourceFiles(service.dir);
6641
7293
  for (const file of files) {
6642
- const parser = parserForExt5(path18.extname(file.path));
7294
+ const parser = parserForExt5(path21.extname(file.path));
6643
7295
  if (!parser) continue;
6644
- const relPath = toPosix(path18.relative(service.dir, file.path));
6645
- const fileDir = path18.dirname(file.path);
7296
+ const relPath = toPosix(path21.relative(service.dir, file.path));
7297
+ const fileDir = path21.dirname(file.path);
6646
7298
  let root;
6647
7299
  try {
6648
7300
  root = parseSource3(parser, file.content).rootNode;
@@ -6684,7 +7336,7 @@ async function addSymbolEdges(graph, services) {
6684
7336
  const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
6685
7337
  if (graph.hasNode(candidate)) {
6686
7338
  const node = graph.getNodeAttributes(candidate);
6687
- if (node.type === NodeType12.SymbolNode && node.kind === wantKind) return candidate;
7339
+ if (node.type === NodeType15.SymbolNode && node.kind === wantKind) return candidate;
6688
7340
  }
6689
7341
  }
6690
7342
  return null;
@@ -6776,11 +7428,11 @@ async function addSymbolEdges(graph, services) {
6776
7428
  }
6777
7429
 
6778
7430
  // src/extract/actions.ts
6779
- import path19 from "path";
7431
+ import path22 from "path";
6780
7432
  import Parser5 from "tree-sitter";
6781
7433
  import {
6782
7434
  EdgeType as EdgeType9,
6783
- NodeType as NodeType13,
7435
+ NodeType as NodeType16,
6784
7436
  Provenance as Provenance9,
6785
7437
  confidenceForExtracted as confidenceForExtracted6,
6786
7438
  extractedEdgeId as extractedEdgeId8,
@@ -6930,9 +7582,9 @@ async function addServerActions(graph, services) {
6930
7582
  const files = await loadSourceFiles(service.dir);
6931
7583
  for (const file of files) {
6932
7584
  if (isTestPath(file.path)) continue;
6933
- const parser = parserForExt5(path19.extname(file.path));
7585
+ const parser = parserForExt5(path22.extname(file.path));
6934
7586
  if (!parser) continue;
6935
- const relPath = toPosix(path19.relative(service.dir, file.path));
7587
+ const relPath = toPosix(path22.relative(service.dir, file.path));
6936
7588
  let root;
6937
7589
  try {
6938
7590
  root = parseSource3(parser, file.content).rootNode;
@@ -6959,7 +7611,7 @@ async function addServerActions(graph, services) {
6959
7611
  if (!graph.hasNode(aid)) {
6960
7612
  const node = {
6961
7613
  id: aid,
6962
- type: NodeType13.ServerActionNode,
7614
+ type: NodeType16.ServerActionNode,
6963
7615
  name: action.exportName,
6964
7616
  service: service.pkg.name,
6965
7617
  module: relPath,
@@ -6993,10 +7645,10 @@ async function addServerActions(graph, services) {
6993
7645
  }
6994
7646
  for (const file of files) {
6995
7647
  if (isTestPath(file.path)) continue;
6996
- const parser = parserForExt5(path19.extname(file.path));
7648
+ const parser = parserForExt5(path22.extname(file.path));
6997
7649
  if (!parser) continue;
6998
- const relPath = toPosix(path19.relative(service.dir, file.path));
6999
- const fileDir = path19.dirname(file.path);
7650
+ const relPath = toPosix(path22.relative(service.dir, file.path));
7651
+ const fileDir = path22.dirname(file.path);
7000
7652
  let root;
7001
7653
  try {
7002
7654
  root = parseSource3(parser, file.content).rootNode;
@@ -7048,10 +7700,10 @@ async function addServerActions(graph, services) {
7048
7700
  }
7049
7701
 
7050
7702
  // src/extract/databases/index.ts
7051
- import path27 from "path";
7703
+ import path30 from "path";
7052
7704
  import {
7053
7705
  EdgeType as EdgeType10,
7054
- NodeType as NodeType14,
7706
+ NodeType as NodeType17,
7055
7707
  Provenance as Provenance10,
7056
7708
  configId,
7057
7709
  databaseId as databaseId2,
@@ -7059,9 +7711,9 @@ import {
7059
7711
  } from "@neat.is/types";
7060
7712
 
7061
7713
  // src/extract/databases/db-config-yaml.ts
7062
- import path20 from "path";
7714
+ import path23 from "path";
7063
7715
  async function parse(serviceDir) {
7064
- const yamlPath = path20.join(serviceDir, "db-config.yaml");
7716
+ const yamlPath = path23.join(serviceDir, "db-config.yaml");
7065
7717
  if (!await exists(yamlPath)) return [];
7066
7718
  const raw = await readYaml(yamlPath);
7067
7719
  return [
@@ -7078,12 +7730,12 @@ async function parse(serviceDir) {
7078
7730
  var dbConfigYamlParser = { name: "db-config.yaml", parse };
7079
7731
 
7080
7732
  // src/extract/databases/dotenv.ts
7081
- import { promises as fs15 } from "fs";
7082
- import path22 from "path";
7733
+ import { promises as fs18 } from "fs";
7734
+ import path25 from "path";
7083
7735
 
7084
7736
  // src/extract/databases/shared.ts
7085
- import { promises as fs14 } from "fs";
7086
- import path21 from "path";
7737
+ import { promises as fs17 } from "fs";
7738
+ import path24 from "path";
7087
7739
  function schemeToEngine(scheme) {
7088
7740
  const s = scheme.toLowerCase().split("+")[0];
7089
7741
  switch (s) {
@@ -7122,18 +7774,18 @@ function parseConnectionString(url) {
7122
7774
  }
7123
7775
  async function readIfExists(filePath) {
7124
7776
  try {
7125
- return await fs14.readFile(filePath, "utf8");
7777
+ return await fs17.readFile(filePath, "utf8");
7126
7778
  } catch {
7127
7779
  return null;
7128
7780
  }
7129
7781
  }
7130
7782
  async function resolveEnvVar(serviceDir, name) {
7131
- const entries = await fs14.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7783
+ const entries = await fs17.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7132
7784
  const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
7133
7785
  const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
7134
7786
  envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
7135
7787
  for (const fileName of envNames) {
7136
- const content = await readIfExists(path21.join(serviceDir, fileName));
7788
+ const content = await readIfExists(path24.join(serviceDir, fileName));
7137
7789
  if (!content) continue;
7138
7790
  for (const line of content.split("\n")) {
7139
7791
  const trimmed = line.trim();
@@ -7152,7 +7804,7 @@ async function resolveEnvVar(serviceDir, name) {
7152
7804
  }
7153
7805
  async function findFirst(serviceDir, candidates) {
7154
7806
  for (const rel of candidates) {
7155
- const abs = path21.join(serviceDir, rel);
7807
+ const abs = path24.join(serviceDir, rel);
7156
7808
  const content = await readIfExists(abs);
7157
7809
  if (content !== null) return abs;
7158
7810
  }
@@ -7210,15 +7862,15 @@ function parseDotenvLine(line) {
7210
7862
  return { key, value };
7211
7863
  }
7212
7864
  async function parse2(serviceDir) {
7213
- const entries = await fs15.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7865
+ const entries = await fs18.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
7214
7866
  const configs = [];
7215
7867
  const seen = /* @__PURE__ */ new Set();
7216
7868
  for (const entry of entries) {
7217
7869
  if (!entry.isFile()) continue;
7218
7870
  const match = isConfigFile(entry.name);
7219
7871
  if (!match.match || match.fileType !== "env") continue;
7220
- const filePath = path22.join(serviceDir, entry.name);
7221
- const content = await fs15.readFile(filePath, "utf8");
7872
+ const filePath = path25.join(serviceDir, entry.name);
7873
+ const content = await fs18.readFile(filePath, "utf8");
7222
7874
  for (const line of content.split("\n")) {
7223
7875
  const parsed = parseDotenvLine(line);
7224
7876
  if (!parsed) continue;
@@ -7236,9 +7888,9 @@ async function parse2(serviceDir) {
7236
7888
  var dotenvParser = { name: ".env", parse: parse2 };
7237
7889
 
7238
7890
  // src/extract/databases/prisma.ts
7239
- import path23 from "path";
7891
+ import path26 from "path";
7240
7892
  async function parse3(serviceDir) {
7241
- const schemaPath = path23.join(serviceDir, "prisma", "schema.prisma");
7893
+ const schemaPath = path26.join(serviceDir, "prisma", "schema.prisma");
7242
7894
  const content = await readIfExists(schemaPath);
7243
7895
  if (!content) return [];
7244
7896
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -7393,10 +8045,10 @@ async function parse5(serviceDir) {
7393
8045
  var knexParser = { name: "knex", parse: parse5 };
7394
8046
 
7395
8047
  // src/extract/databases/ormconfig.ts
7396
- import path24 from "path";
8048
+ import path27 from "path";
7397
8049
  async function parse6(serviceDir) {
7398
8050
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
7399
- const abs = path24.join(serviceDir, candidate);
8051
+ const abs = path27.join(serviceDir, candidate);
7400
8052
  if (!await exists(abs)) continue;
7401
8053
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
7402
8054
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -7454,9 +8106,9 @@ async function parse7(serviceDir) {
7454
8106
  var typeormParser = { name: "typeorm", parse: parse7 };
7455
8107
 
7456
8108
  // src/extract/databases/sequelize.ts
7457
- import path25 from "path";
8109
+ import path28 from "path";
7458
8110
  async function parse8(serviceDir) {
7459
- const configPath = path25.join(serviceDir, "config", "config.json");
8111
+ const configPath = path28.join(serviceDir, "config", "config.json");
7460
8112
  if (!await exists(configPath)) return [];
7461
8113
  const raw = await readJson(configPath);
7462
8114
  const out = [];
@@ -7482,7 +8134,7 @@ async function parse8(serviceDir) {
7482
8134
  var sequelizeParser = { name: "sequelize", parse: parse8 };
7483
8135
 
7484
8136
  // src/extract/databases/docker-compose.ts
7485
- import path26 from "path";
8137
+ import path29 from "path";
7486
8138
  function portFromService(svc) {
7487
8139
  for (const raw of svc.ports ?? []) {
7488
8140
  const str = String(raw);
@@ -7509,7 +8161,7 @@ function databaseFromEnv(svc) {
7509
8161
  }
7510
8162
  async function parse9(serviceDir) {
7511
8163
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7512
- const abs = path26.join(serviceDir, name);
8164
+ const abs = path29.join(serviceDir, name);
7513
8165
  if (!await exists(abs)) continue;
7514
8166
  const raw = await readYaml(abs);
7515
8167
  if (!raw?.services) return [];
@@ -7551,7 +8203,7 @@ function compatibleDriversFor(engine) {
7551
8203
  function toDatabaseNode(config) {
7552
8204
  return {
7553
8205
  id: databaseId2(config.host),
7554
- type: NodeType14.DatabaseNode,
8206
+ type: NodeType17.DatabaseNode,
7555
8207
  name: config.database || config.host,
7556
8208
  engine: config.engine,
7557
8209
  engineVersion: config.engineVersion,
@@ -7681,7 +8333,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7681
8333
  discoveredVia: mergedDiscoveredVia
7682
8334
  });
7683
8335
  }
7684
- const relConfigFile = toPosix(path27.relative(service.dir, config.sourceFile));
8336
+ const relConfigFile = toPosix(path30.relative(service.dir, config.sourceFile));
7685
8337
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7686
8338
  graph,
7687
8339
  service.pkg.name,
@@ -7690,7 +8342,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7690
8342
  );
7691
8343
  nodesAdded += fn;
7692
8344
  edgesAdded += fe;
7693
- const evidenceFile = toPosix(path27.relative(scanPath, config.sourceFile));
8345
+ const evidenceFile = toPosix(path30.relative(scanPath, config.sourceFile));
7694
8346
  const edge = {
7695
8347
  id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
7696
8348
  source: fileNodeId,
@@ -7708,15 +8360,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7708
8360
  if (allConfigs.length === 1) {
7709
8361
  const primary = allConfigs[0];
7710
8362
  service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
7711
- const relPath = path27.relative(scanPath, primary.sourceFile);
8363
+ const relPath = path30.relative(scanPath, primary.sourceFile);
7712
8364
  const cfgId = configId(relPath);
7713
8365
  if (!graph.hasNode(cfgId)) {
7714
8366
  const cfgNode = {
7715
8367
  id: cfgId,
7716
- type: NodeType14.ConfigNode,
7717
- name: path27.basename(primary.sourceFile),
8368
+ type: NodeType17.ConfigNode,
8369
+ name: path30.basename(primary.sourceFile),
7718
8370
  path: relPath,
7719
- fileType: isConfigFile(path27.basename(primary.sourceFile)).fileType || "config"
8371
+ fileType: isConfigFile(path30.basename(primary.sourceFile)).fileType || "config"
7720
8372
  };
7721
8373
  graph.addNode(cfgId, cfgNode);
7722
8374
  nodesAdded++;
@@ -7756,11 +8408,11 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
7756
8408
  }
7757
8409
 
7758
8410
  // src/extract/configs.ts
7759
- import { promises as fs16 } from "fs";
7760
- import path28 from "path";
8411
+ import { promises as fs19 } from "fs";
8412
+ import path31 from "path";
7761
8413
  import {
7762
8414
  EdgeType as EdgeType11,
7763
- NodeType as NodeType15,
8415
+ NodeType as NodeType18,
7764
8416
  Provenance as Provenance11,
7765
8417
  configId as configId2,
7766
8418
  confidenceForExtracted as confidenceForExtracted8
@@ -7768,9 +8420,9 @@ import {
7768
8420
  async function walkConfigFiles(dir) {
7769
8421
  const out = [];
7770
8422
  async function walk9(current) {
7771
- const entries = await fs16.readdir(current, { withFileTypes: true });
8423
+ const entries = await fs19.readdir(current, { withFileTypes: true });
7772
8424
  for (const entry of entries) {
7773
- const full = path28.join(current, entry.name);
8425
+ const full = path31.join(current, entry.name);
7774
8426
  if (entry.isDirectory()) {
7775
8427
  if (IGNORED_DIRS.has(entry.name)) continue;
7776
8428
  if (await isPythonVenvDir(full)) continue;
@@ -7789,19 +8441,19 @@ async function addConfigNodes(graph, services, scanPath) {
7789
8441
  for (const service of services) {
7790
8442
  const configFiles = await walkConfigFiles(service.dir);
7791
8443
  for (const file of configFiles) {
7792
- const relPath = path28.relative(scanPath, file);
8444
+ const relPath = path31.relative(scanPath, file);
7793
8445
  const node = {
7794
8446
  id: configId2(relPath),
7795
- type: NodeType15.ConfigNode,
7796
- name: path28.basename(file),
8447
+ type: NodeType18.ConfigNode,
8448
+ name: path31.basename(file),
7797
8449
  path: relPath,
7798
- fileType: isConfigFile(path28.basename(file)).fileType
8450
+ fileType: isConfigFile(path31.basename(file)).fileType
7799
8451
  };
7800
8452
  if (!graph.hasNode(node.id)) {
7801
8453
  graph.addNode(node.id, node);
7802
8454
  nodesAdded++;
7803
8455
  }
7804
- const relToService = toPosix(path28.relative(service.dir, file));
8456
+ const relToService = toPosix(path31.relative(service.dir, file));
7805
8457
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7806
8458
  graph,
7807
8459
  service.pkg.name,
@@ -7817,7 +8469,7 @@ async function addConfigNodes(graph, services, scanPath) {
7817
8469
  type: EdgeType11.CONFIGURED_BY,
7818
8470
  provenance: Provenance11.EXTRACTED,
7819
8471
  confidence: confidenceForExtracted8("structural"),
7820
- evidence: { file: relPath.split(path28.sep).join("/") }
8472
+ evidence: { file: relPath.split(path31.sep).join("/") }
7821
8473
  };
7822
8474
  if (!graph.hasEdge(edge.id)) {
7823
8475
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -7829,11 +8481,11 @@ async function addConfigNodes(graph, services, scanPath) {
7829
8481
  }
7830
8482
 
7831
8483
  // src/extract/proto.ts
7832
- import { promises as fs17 } from "fs";
7833
- import path29 from "path";
8484
+ import { promises as fs20 } from "fs";
8485
+ import path32 from "path";
7834
8486
  import {
7835
8487
  EdgeType as EdgeType12,
7836
- NodeType as NodeType16,
8488
+ NodeType as NodeType19,
7837
8489
  Provenance as Provenance12,
7838
8490
  confidenceForExtracted as confidenceForExtracted9,
7839
8491
  extractedEdgeId as extractedEdgeId9,
@@ -7877,14 +8529,14 @@ function grpcMethodsFromProto(content, fqPackage) {
7877
8529
  async function walkProtoFiles(dir) {
7878
8530
  const out = [];
7879
8531
  async function walk9(current) {
7880
- const entries = await fs17.readdir(current, { withFileTypes: true }).catch(() => []);
8532
+ const entries = await fs20.readdir(current, { withFileTypes: true }).catch(() => []);
7881
8533
  for (const entry of entries) {
7882
- const full = path29.join(current, entry.name);
8534
+ const full = path32.join(current, entry.name);
7883
8535
  if (entry.isDirectory()) {
7884
8536
  if (IGNORED_DIRS.has(entry.name)) continue;
7885
8537
  if (await isPythonVenvDir(full)) continue;
7886
8538
  await walk9(full);
7887
- } else if (entry.isFile() && path29.extname(entry.name) === PROTO_EXTENSION) {
8539
+ } else if (entry.isFile() && path32.extname(entry.name) === PROTO_EXTENSION) {
7888
8540
  out.push(full);
7889
8541
  }
7890
8542
  }
@@ -7899,10 +8551,10 @@ async function addGrpcMethods(graph, services) {
7899
8551
  const protoPaths = await walkProtoFiles(service.dir);
7900
8552
  for (const protoPath of protoPaths) {
7901
8553
  if (isTestPath(protoPath)) continue;
7902
- const relFile = toPosix(path29.relative(service.dir, protoPath));
8554
+ const relFile = toPosix(path32.relative(service.dir, protoPath));
7903
8555
  let content;
7904
8556
  try {
7905
- content = await fs17.readFile(protoPath, "utf8");
8557
+ content = await fs20.readFile(protoPath, "utf8");
7906
8558
  } catch (err) {
7907
8559
  recordExtractionError("proto extraction", protoPath, err);
7908
8560
  continue;
@@ -7920,7 +8572,7 @@ async function addGrpcMethods(graph, services) {
7920
8572
  if (!graph.hasNode(mid)) {
7921
8573
  const node = {
7922
8574
  id: mid,
7923
- type: NodeType16.GrpcMethodNode,
8575
+ type: NodeType19.GrpcMethodNode,
7924
8576
  name: `${method.rpcService}/${method.rpcMethod}`,
7925
8577
  rpcService: method.rpcService,
7926
8578
  rpcMethod: method.rpcMethod,
@@ -7958,18 +8610,18 @@ async function addGrpcMethods(graph, services) {
7958
8610
  // src/extract/calls/index.ts
7959
8611
  import {
7960
8612
  EdgeType as EdgeType15,
7961
- NodeType as NodeType18,
8613
+ NodeType as NodeType21,
7962
8614
  Provenance as Provenance15,
7963
8615
  confidenceForExtracted as confidenceForExtracted12,
7964
8616
  passesExtractedFloor as passesExtractedFloor3
7965
8617
  } from "@neat.is/types";
7966
8618
 
7967
8619
  // src/extract/calls/http.ts
7968
- import path30 from "path";
8620
+ import path33 from "path";
7969
8621
  import Parser6 from "tree-sitter";
7970
8622
  import JavaScript4 from "tree-sitter-javascript";
7971
8623
  import TypeScript2 from "tree-sitter-typescript";
7972
- import Python3 from "tree-sitter-python";
8624
+ import Python4 from "tree-sitter-python";
7973
8625
  import {
7974
8626
  EdgeType as EdgeType13,
7975
8627
  Provenance as Provenance13,
@@ -8031,7 +8683,7 @@ var GRAMMAR_BY_EXT2 = {
8031
8683
  ".jsx": JavaScript4,
8032
8684
  ".mjs": JavaScript4,
8033
8685
  ".cjs": JavaScript4,
8034
- ".py": Python3
8686
+ ".py": Python4
8035
8687
  };
8036
8688
  function parserForExt(ext, cache) {
8037
8689
  const grammar = GRAMMAR_BY_EXT2[ext] ?? JavaScript4;
@@ -8053,7 +8705,7 @@ async function addHttpCallEdges(graph, services) {
8053
8705
  const seen = /* @__PURE__ */ new Set();
8054
8706
  for (const file of files) {
8055
8707
  if (isTestPath(file.path)) continue;
8056
- const parser = parserForExt(path30.extname(file.path), parserCache);
8708
+ const parser = parserForExt(path33.extname(file.path), parserCache);
8057
8709
  let sites;
8058
8710
  try {
8059
8711
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8062,7 +8714,7 @@ async function addHttpCallEdges(graph, services) {
8062
8714
  continue;
8063
8715
  }
8064
8716
  if (sites.length === 0) continue;
8065
- const relFile = toPosix(path30.relative(service.dir, file.path));
8717
+ const relFile = toPosix(path33.relative(service.dir, file.path));
8066
8718
  for (const site of sites) {
8067
8719
  const targetId = hostToNodeId.get(site.host);
8068
8720
  if (!targetId || targetId === service.node.id) continue;
@@ -8115,16 +8767,16 @@ async function addHttpCallEdges(graph, services) {
8115
8767
  }
8116
8768
 
8117
8769
  // src/extract/calls/route-match.ts
8118
- import path31 from "path";
8770
+ import path34 from "path";
8119
8771
  import Parser7 from "tree-sitter";
8120
8772
  import JavaScript5 from "tree-sitter-javascript";
8121
8773
  import {
8122
8774
  EdgeType as EdgeType14,
8123
- NodeType as NodeType17,
8775
+ NodeType as NodeType20,
8124
8776
  Provenance as Provenance14,
8125
8777
  confidenceForExtracted as confidenceForExtracted11,
8126
8778
  passesExtractedFloor as passesExtractedFloor2,
8127
- serviceId as serviceId6
8779
+ serviceId as serviceId9
8128
8780
  } from "@neat.is/types";
8129
8781
  var PARSE_CHUNK5 = 16384;
8130
8782
  function parseSource5(parser, source) {
@@ -8290,9 +8942,9 @@ function buildRouteIndex(graph) {
8290
8942
  const index = /* @__PURE__ */ new Map();
8291
8943
  graph.forEachNode((_id, attrs) => {
8292
8944
  const node = attrs;
8293
- if (node.type !== NodeType17.RouteNode) return;
8945
+ if (node.type !== NodeType20.RouteNode) return;
8294
8946
  const route = attrs;
8295
- const owner = serviceId6(route.service);
8947
+ const owner = serviceId9(route.service);
8296
8948
  const entry = {
8297
8949
  method: route.method.toUpperCase(),
8298
8950
  normalizedPath: normalizePathTemplate(route.pathTemplate),
@@ -8321,7 +8973,7 @@ async function addRouteCallEdges(graph, services) {
8321
8973
  const seen = /* @__PURE__ */ new Set();
8322
8974
  for (const file of files) {
8323
8975
  if (isTestPath(file.path)) continue;
8324
- if (!JS_CLIENT_EXTENSIONS.has(path31.extname(file.path))) continue;
8976
+ if (!JS_CLIENT_EXTENSIONS.has(path34.extname(file.path))) continue;
8325
8977
  let sites;
8326
8978
  try {
8327
8979
  sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
@@ -8330,7 +8982,7 @@ async function addRouteCallEdges(graph, services) {
8330
8982
  continue;
8331
8983
  }
8332
8984
  if (sites.length === 0) continue;
8333
- const relFile = toPosix(path31.relative(service.dir, file.path));
8985
+ const relFile = toPosix(path34.relative(service.dir, file.path));
8334
8986
  for (const site of sites) {
8335
8987
  const serverServiceId = hostToNodeId.get(site.host);
8336
8988
  if (!serverServiceId || serverServiceId === service.node.id) continue;
@@ -8390,7 +9042,7 @@ async function addRouteCallEdges(graph, services) {
8390
9042
  }
8391
9043
 
8392
9044
  // src/extract/calls/kafka.ts
8393
- import path32 from "path";
9045
+ import path35 from "path";
8394
9046
  import { infraId as infraId2 } from "@neat.is/types";
8395
9047
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
8396
9048
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -8421,7 +9073,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
8421
9073
  // tier (ADR-066).
8422
9074
  confidenceKind: "verified-call-site",
8423
9075
  evidence: {
8424
- file: path32.relative(serviceDir, file.path),
9076
+ file: path35.relative(serviceDir, file.path),
8425
9077
  line,
8426
9078
  snippet: snippet(file.content, line)
8427
9079
  }
@@ -8433,7 +9085,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
8433
9085
  }
8434
9086
 
8435
9087
  // src/extract/calls/redis.ts
8436
- import path33 from "path";
9088
+ import path36 from "path";
8437
9089
  import { infraId as infraId3 } from "@neat.is/types";
8438
9090
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
8439
9091
  function redisEndpointsFromFile(file, serviceDir) {
@@ -8456,7 +9108,7 @@ function redisEndpointsFromFile(file, serviceDir) {
8456
9108
  // support tier (ADR-066).
8457
9109
  confidenceKind: "url-with-structural-support",
8458
9110
  evidence: {
8459
- file: path33.relative(serviceDir, file.path),
9111
+ file: path36.relative(serviceDir, file.path),
8460
9112
  line,
8461
9113
  snippet: snippet(file.content, line)
8462
9114
  }
@@ -8466,7 +9118,7 @@ function redisEndpointsFromFile(file, serviceDir) {
8466
9118
  }
8467
9119
 
8468
9120
  // src/extract/calls/aws.ts
8469
- import path34 from "path";
9121
+ import path37 from "path";
8470
9122
  import { infraId as infraId4 } from "@neat.is/types";
8471
9123
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
8472
9124
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -8500,7 +9152,7 @@ function awsEndpointsFromFile(file, serviceDir) {
8500
9152
  // (ADR-066).
8501
9153
  confidenceKind: "verified-call-site",
8502
9154
  evidence: {
8503
- file: path34.relative(serviceDir, file.path),
9155
+ file: path37.relative(serviceDir, file.path),
8504
9156
  line,
8505
9157
  snippet: snippet(file.content, line)
8506
9158
  }
@@ -8524,7 +9176,7 @@ function awsEndpointsFromFile(file, serviceDir) {
8524
9176
  }
8525
9177
 
8526
9178
  // src/extract/calls/grpc.ts
8527
- import path35 from "path";
9179
+ import path38 from "path";
8528
9180
  import { infraId as infraId5 } from "@neat.is/types";
8529
9181
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
8530
9182
  var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
@@ -8583,7 +9235,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
8583
9235
  // tier (ADR-066).
8584
9236
  confidenceKind: "verified-call-site",
8585
9237
  evidence: {
8586
- file: path35.relative(serviceDir, file.path),
9238
+ file: path38.relative(serviceDir, file.path),
8587
9239
  line,
8588
9240
  snippet: snippet(file.content, line)
8589
9241
  }
@@ -8593,7 +9245,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
8593
9245
  }
8594
9246
 
8595
9247
  // src/extract/calls/supabase.ts
8596
- import path36 from "path";
9248
+ import path39 from "path";
8597
9249
  import { infraId as infraId6 } from "@neat.is/types";
8598
9250
  var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
8599
9251
  var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
@@ -8652,7 +9304,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8652
9304
  // tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
8653
9305
  confidenceKind: "verified-call-site",
8654
9306
  evidence: {
8655
- file: path36.relative(serviceDir, file.path),
9307
+ file: path39.relative(serviceDir, file.path),
8656
9308
  line,
8657
9309
  snippet: snippet(file.content, line)
8658
9310
  }
@@ -8679,7 +9331,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8679
9331
  edgeType: "CALLS",
8680
9332
  confidenceKind: "verified-call-site",
8681
9333
  evidence: {
8682
- file: path36.relative(serviceDir, file.path),
9334
+ file: path39.relative(serviceDir, file.path),
8683
9335
  line,
8684
9336
  snippet: snippet(file.content, line)
8685
9337
  }
@@ -8690,7 +9342,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
8690
9342
  }
8691
9343
 
8692
9344
  // src/extract/calls/firestore.ts
8693
- import path37 from "path";
9345
+ import path40 from "path";
8694
9346
  import Parser8 from "tree-sitter";
8695
9347
  import JavaScript6 from "tree-sitter-javascript";
8696
9348
  import { infraId as infraId7 } from "@neat.is/types";
@@ -8861,7 +9513,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8861
9513
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
8862
9514
  if (!hasClient && !hasAdmin) return [];
8863
9515
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
8864
- const tree = parseSource3(parserForExt2(path37.extname(file.path)), file.content);
9516
+ const tree = parseSource3(parserForExt2(path40.extname(file.path)), file.content);
8865
9517
  const clientVars = firestoreClientVars(tree.rootNode);
8866
9518
  const collLine = /* @__PURE__ */ new Map();
8867
9519
  const writes = /* @__PURE__ */ new Map();
@@ -8965,7 +9617,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8965
9617
  ...columnSet.size > 0 ? { columns: [...columnSet] } : {},
8966
9618
  ...sdkWrites ? { sdkWrites } : {},
8967
9619
  evidence: {
8968
- file: path37.relative(serviceDir, file.path),
9620
+ file: path40.relative(serviceDir, file.path),
8969
9621
  line,
8970
9622
  snippet: snippet(file.content, line)
8971
9623
  }
@@ -8975,7 +9627,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
8975
9627
  }
8976
9628
 
8977
9629
  // src/extract/calls/mongoose.ts
8978
- import path38 from "path";
9630
+ import path41 from "path";
8979
9631
  import { infraId as infraId8 } from "@neat.is/types";
8980
9632
  var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
8981
9633
  var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
@@ -9126,7 +9778,7 @@ function endpoint(r, file, serviceDir, matchText) {
9126
9778
  kind: r.kind,
9127
9779
  edgeType: "CALLS",
9128
9780
  confidenceKind: "verified-call-site",
9129
- evidence: { file: path38.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9781
+ evidence: { file: path41.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9130
9782
  };
9131
9783
  }
9132
9784
  function mongooseEndpointsFromFile(file, serviceDir) {
@@ -9216,7 +9868,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9216
9868
  const registry = /* @__PURE__ */ new Map();
9217
9869
  for (const f of mongooseFiles) {
9218
9870
  const fx = fileExportsOf(f.content, pluralizeOn);
9219
- if (fx) registry.set(toPosix(path38.relative(serviceDir, f.path)), fx);
9871
+ if (fx) registry.set(toPosix(path41.relative(serviceDir, f.path)), fx);
9220
9872
  }
9221
9873
  if (registry.size === 0) return [];
9222
9874
  const out = [];
@@ -9227,7 +9879,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9227
9879
  const directColl = /* @__PURE__ */ new Map();
9228
9880
  const nsExports = /* @__PURE__ */ new Map();
9229
9881
  for (const b of bindings) {
9230
- const resolvedRel = await resolveJsImport(b.specifier, path38.dirname(f.path), serviceDir, null);
9882
+ const resolvedRel = await resolveJsImport(b.specifier, path41.dirname(f.path), serviceDir, null);
9231
9883
  if (!resolvedRel) continue;
9232
9884
  const fx = registry.get(resolvedRel);
9233
9885
  if (!fx) continue;
@@ -9270,15 +9922,15 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
9270
9922
  }
9271
9923
 
9272
9924
  // src/extract/calls/sqlalchemy.ts
9273
- import path39 from "path";
9925
+ import path42 from "path";
9274
9926
  import Parser9 from "tree-sitter";
9275
- import Python4 from "tree-sitter-python";
9927
+ import Python5 from "tree-sitter-python";
9276
9928
  import { infraId as infraId9 } from "@neat.is/types";
9277
9929
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9278
9930
  var PARSE_CHUNK6 = 16384;
9279
9931
  function makePyParser3() {
9280
9932
  const p = new Parser9();
9281
- p.setLanguage(Python4);
9933
+ p.setLanguage(Python5);
9282
9934
  return p;
9283
9935
  }
9284
9936
  function parseSource6(parser, source) {
@@ -9409,7 +10061,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9409
10061
  childTable,
9410
10062
  parentTable,
9411
10063
  evidence: {
9412
- file: path39.relative(serviceDir, file.path),
10064
+ file: path42.relative(serviceDir, file.path),
9413
10065
  line,
9414
10066
  snippet: snippet(file.content, line)
9415
10067
  }
@@ -9434,7 +10086,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9434
10086
  confidenceKind: "verified-call-site",
9435
10087
  ...columns && columns.length > 0 ? { columns } : {},
9436
10088
  evidence: {
9437
- file: path39.relative(serviceDir, file.path),
10089
+ file: path42.relative(serviceDir, file.path),
9438
10090
  line,
9439
10091
  snippet: snippet(file.content, line)
9440
10092
  }
@@ -9541,7 +10193,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
9541
10193
  edgeType: "CALLS",
9542
10194
  confidenceKind: "verified-call-site",
9543
10195
  evidence: {
9544
- file: path39.relative(serviceDir, file.path),
10196
+ file: path42.relative(serviceDir, file.path),
9545
10197
  line,
9546
10198
  snippet: snippet(file.content, line)
9547
10199
  }
@@ -9552,15 +10204,15 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
9552
10204
  }
9553
10205
 
9554
10206
  // src/extract/calls/django-orm.ts
9555
- import path40 from "path";
10207
+ import path43 from "path";
9556
10208
  import Parser10 from "tree-sitter";
9557
- import Python5 from "tree-sitter-python";
10209
+ import Python6 from "tree-sitter-python";
9558
10210
  import { infraId as infraId10 } from "@neat.is/types";
9559
10211
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9560
10212
  var PARSE_CHUNK7 = 16384;
9561
10213
  function makePyParser4() {
9562
10214
  const p = new Parser10();
9563
- p.setLanguage(Python5);
10215
+ p.setLanguage(Python6);
9564
10216
  return p;
9565
10217
  }
9566
10218
  function parseSource7(parser, source) {
@@ -9622,7 +10274,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
9622
10274
  const tree = parseSource7(makePyParser4(), file.content);
9623
10275
  const out = [];
9624
10276
  const seen = /* @__PURE__ */ new Set();
9625
- const defaultAppLabel = path40.basename(path40.dirname(file.path));
10277
+ const defaultAppLabel = path43.basename(path43.dirname(file.path));
9626
10278
  walk4(tree.rootNode, (node) => {
9627
10279
  if (node.type !== "class_definition") return;
9628
10280
  if (!extendsDjangoModel(node)) return;
@@ -9640,14 +10292,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
9640
10292
  kind: "sql-table",
9641
10293
  edgeType: "CALLS",
9642
10294
  confidenceKind: "verified-call-site",
9643
- evidence: { file: path40.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
10295
+ evidence: { file: path43.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
9644
10296
  });
9645
10297
  });
9646
10298
  return out;
9647
10299
  }
9648
10300
 
9649
10301
  // src/extract/calls/drizzle.ts
9650
- import path41 from "path";
10302
+ import path44 from "path";
9651
10303
  import Parser11 from "tree-sitter";
9652
10304
  import JavaScript7 from "tree-sitter-javascript";
9653
10305
  import { infraId as infraId11 } from "@neat.is/types";
@@ -9725,7 +10377,7 @@ function columnsFromObject(obj) {
9725
10377
  }
9726
10378
  function drizzleEndpointsFromFile(file, serviceDir) {
9727
10379
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9728
- const tree = parseSource3(parserForExt3(path41.extname(file.path)), file.content);
10380
+ const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
9729
10381
  const out = [];
9730
10382
  const seen = /* @__PURE__ */ new Set();
9731
10383
  const walk9 = (node) => {
@@ -9748,7 +10400,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
9748
10400
  confidenceKind: "structural",
9749
10401
  columns,
9750
10402
  evidence: {
9751
- file: path41.relative(serviceDir, file.path),
10403
+ file: path44.relative(serviceDir, file.path),
9752
10404
  line,
9753
10405
  snippet: snippet(file.content, line)
9754
10406
  }
@@ -9814,7 +10466,7 @@ function referencesTargetVar(call) {
9814
10466
  }
9815
10467
  function drizzleForeignKeys(file, serviceDir) {
9816
10468
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9817
- const tree = parseSource3(parserForExt3(path41.extname(file.path)), file.content);
10469
+ const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
9818
10470
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
9819
10471
  const out = [];
9820
10472
  const seen = /* @__PURE__ */ new Set();
@@ -9833,7 +10485,7 @@ function drizzleForeignKeys(file, serviceDir) {
9833
10485
  childTable: table.tableName,
9834
10486
  parentTable,
9835
10487
  evidence: {
9836
- file: path41.relative(serviceDir, file.path),
10488
+ file: path44.relative(serviceDir, file.path),
9837
10489
  line,
9838
10490
  snippet: snippet(file.content, line)
9839
10491
  }
@@ -9849,7 +10501,7 @@ function drizzleForeignKeys(file, serviceDir) {
9849
10501
  }
9850
10502
 
9851
10503
  // src/extract/calls/prisma.ts
9852
- import path42 from "path";
10504
+ import path45 from "path";
9853
10505
  import { infraId as infraId12 } from "@neat.is/types";
9854
10506
  var SCALAR_TYPES = /* @__PURE__ */ new Set([
9855
10507
  "Int",
@@ -9905,7 +10557,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
9905
10557
  confidenceKind: "structural",
9906
10558
  columns: b.columns,
9907
10559
  evidence: {
9908
- file: path42.relative(serviceDir, file.path),
10560
+ file: path45.relative(serviceDir, file.path),
9909
10561
  line: b.startLine,
9910
10562
  snippet: snippet(content, b.startLine)
9911
10563
  }
@@ -9966,7 +10618,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
9966
10618
  }
9967
10619
  async function prismaColumnEndpoints(serviceDir) {
9968
10620
  const schemaPath = await findFirst(serviceDir, [
9969
- path42.join("prisma", "schema.prisma"),
10621
+ path45.join("prisma", "schema.prisma"),
9970
10622
  "schema.prisma"
9971
10623
  ]);
9972
10624
  if (!schemaPath) return [];
@@ -10041,7 +10693,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10041
10693
  childTable: current.table,
10042
10694
  parentTable,
10043
10695
  evidence: {
10044
- file: path42.relative(serviceDir, file.path),
10696
+ file: path45.relative(serviceDir, file.path),
10045
10697
  line: lineNo,
10046
10698
  snippet: snippet(content, lineNo)
10047
10699
  }
@@ -10056,7 +10708,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
10056
10708
  }
10057
10709
  async function prismaForeignKeys(serviceDir) {
10058
10710
  const schemaPath = await findFirst(serviceDir, [
10059
- path42.join("prisma", "schema.prisma"),
10711
+ path45.join("prisma", "schema.prisma"),
10060
10712
  "schema.prisma"
10061
10713
  ]);
10062
10714
  if (!schemaPath) return [];
@@ -10066,15 +10718,15 @@ async function prismaForeignKeys(serviceDir) {
10066
10718
  }
10067
10719
 
10068
10720
  // src/extract/calls/activerecord.ts
10069
- import path43 from "path";
10721
+ import path46 from "path";
10070
10722
  import Parser12 from "tree-sitter";
10071
- import Ruby2 from "tree-sitter-ruby";
10723
+ import Ruby3 from "tree-sitter-ruby";
10072
10724
  import { infraId as infraId13 } from "@neat.is/types";
10073
10725
  var AR_SCHEMA_RE = /ActiveRecord::Schema/;
10074
10726
  var PARSE_CHUNK8 = 16384;
10075
10727
  function makeRubyParser2() {
10076
10728
  const p = new Parser12();
10077
- p.setLanguage(Ruby2);
10729
+ p.setLanguage(Ruby3);
10078
10730
  return p;
10079
10731
  }
10080
10732
  function parseSource8(parser, source) {
@@ -10274,7 +10926,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
10274
10926
  confidenceKind: "structural",
10275
10927
  ...table.columns.length > 0 ? { columns: table.columns } : {},
10276
10928
  evidence: {
10277
- file: path43.relative(serviceDir, file.path),
10929
+ file: path46.relative(serviceDir, file.path),
10278
10930
  line: table.line,
10279
10931
  snippet: snippet(file.content, table.line)
10280
10932
  }
@@ -10296,7 +10948,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
10296
10948
  childTable,
10297
10949
  parentTable,
10298
10950
  evidence: {
10299
- file: path43.relative(serviceDir, file.path),
10951
+ file: path46.relative(serviceDir, file.path),
10300
10952
  line,
10301
10953
  snippet: snippet(file.content, line)
10302
10954
  }
@@ -10379,7 +11031,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
10379
11031
  edgeType: "CALLS",
10380
11032
  confidenceKind: "verified-call-site",
10381
11033
  evidence: {
10382
- file: path43.relative(serviceDir, file.path),
11034
+ file: path46.relative(serviceDir, file.path),
10383
11035
  line,
10384
11036
  snippet: snippet(file.content, line)
10385
11037
  }
@@ -10416,7 +11068,7 @@ function railsModelForeignKeys(file, serviceDir) {
10416
11068
  childTable,
10417
11069
  parentTable,
10418
11070
  evidence: {
10419
- file: path43.relative(serviceDir, file.path),
11071
+ file: path46.relative(serviceDir, file.path),
10420
11072
  line,
10421
11073
  snippet: snippet(file.content, line)
10422
11074
  }
@@ -10427,15 +11079,15 @@ function railsModelForeignKeys(file, serviceDir) {
10427
11079
  }
10428
11080
 
10429
11081
  // src/extract/calls/eloquent.ts
10430
- import path44 from "path";
11082
+ import path47 from "path";
10431
11083
  import Parser13 from "tree-sitter";
10432
- import Php2 from "tree-sitter-php";
11084
+ import Php3 from "tree-sitter-php";
10433
11085
  import { infraId as infraId14 } from "@neat.is/types";
10434
11086
  var LARAVEL_SCHEMA_RE = /\bSchema::(create|createIfNotExists|table)\b/;
10435
11087
  var PARSE_CHUNK9 = 16384;
10436
11088
  function makePhpParser2() {
10437
11089
  const p = new Parser13();
10438
- p.setLanguage(Php2.php_only);
11090
+ p.setLanguage(Php3.php_only);
10439
11091
  return p;
10440
11092
  }
10441
11093
  function parseSource9(parser, source) {
@@ -10684,7 +11336,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
10684
11336
  confidenceKind: "structural",
10685
11337
  ...columns.length > 0 ? { columns } : {},
10686
11338
  evidence: {
10687
- file: path44.relative(serviceDir, file.path),
11339
+ file: path47.relative(serviceDir, file.path),
10688
11340
  line: bp.line,
10689
11341
  snippet: snippet(file.content, bp.line)
10690
11342
  }
@@ -10706,7 +11358,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
10706
11358
  childTable,
10707
11359
  parentTable,
10708
11360
  evidence: {
10709
- file: path44.relative(serviceDir, file.path),
11361
+ file: path47.relative(serviceDir, file.path),
10710
11362
  line,
10711
11363
  snippet: snippet(file.content, line)
10712
11364
  }
@@ -10826,7 +11478,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
10826
11478
  edgeType: "CALLS",
10827
11479
  confidenceKind: "verified-call-site",
10828
11480
  evidence: {
10829
- file: path44.relative(serviceDir, file.path),
11481
+ file: path47.relative(serviceDir, file.path),
10830
11482
  line,
10831
11483
  snippet: snippet(file.content, line)
10832
11484
  }
@@ -10862,7 +11514,7 @@ function laravelModelForeignKeys(file, serviceDir) {
10862
11514
  childTable,
10863
11515
  parentTable,
10864
11516
  evidence: {
10865
- file: path44.relative(serviceDir, file.path),
11517
+ file: path47.relative(serviceDir, file.path),
10866
11518
  line,
10867
11519
  snippet: snippet(file.content, line)
10868
11520
  }
@@ -10873,9 +11525,9 @@ function laravelModelForeignKeys(file, serviceDir) {
10873
11525
  }
10874
11526
 
10875
11527
  // src/extract/calls/go.ts
10876
- import path45 from "path";
11528
+ import path48 from "path";
10877
11529
  import Parser14 from "tree-sitter";
10878
- import Go3 from "tree-sitter-go";
11530
+ import Go4 from "tree-sitter-go";
10879
11531
  import { infraId as infraId15 } from "@neat.is/types";
10880
11532
  var PARSE_CHUNK10 = 16384;
10881
11533
  var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
@@ -10904,7 +11556,7 @@ var DATABASE_SQL_IMPORT = "database/sql";
10904
11556
  var SQLX_IMPORT = "github.com/jmoiron/sqlx";
10905
11557
  function makeGoParser3() {
10906
11558
  const p = new Parser14();
10907
- p.setLanguage(Go3);
11559
+ p.setLanguage(Go4);
10908
11560
  return p;
10909
11561
  }
10910
11562
  function parseSource10(parser, source) {
@@ -10947,7 +11599,7 @@ function firstStringLiteralArg(argsNode) {
10947
11599
  return null;
10948
11600
  }
10949
11601
  function goSqlEndpointsFromFile(file, serviceDir) {
10950
- if (path45.extname(file.path) !== ".go") return [];
11602
+ if (path48.extname(file.path) !== ".go") return [];
10951
11603
  if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
10952
11604
  const tree = parseSource10(makeGoParser3(), file.content);
10953
11605
  const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
@@ -10976,7 +11628,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
10976
11628
  confidenceKind: "verified-call-site",
10977
11629
  ...columns.length > 0 ? { columns } : {},
10978
11630
  evidence: {
10979
- file: toPosix(path45.relative(serviceDir, file.path)),
11631
+ file: toPosix(path48.relative(serviceDir, file.path)),
10980
11632
  line,
10981
11633
  snippet: snippet(file.content, line)
10982
11634
  }
@@ -10986,15 +11638,15 @@ function goSqlEndpointsFromFile(file, serviceDir) {
10986
11638
  }
10987
11639
 
10988
11640
  // src/extract/calls/gorm.ts
10989
- import path46 from "path";
11641
+ import path49 from "path";
10990
11642
  import Parser15 from "tree-sitter";
10991
- import Go4 from "tree-sitter-go";
11643
+ import Go5 from "tree-sitter-go";
10992
11644
  import { infraId as infraId16 } from "@neat.is/types";
10993
11645
  var GORM_IMPORT_RE = /gorm\.io\/gorm/;
10994
11646
  var PARSE_CHUNK11 = 16384;
10995
11647
  function makeGoParser4() {
10996
11648
  const p = new Parser15();
10997
- p.setLanguage(Go4);
11649
+ p.setLanguage(Go5);
10998
11650
  return p;
10999
11651
  }
11000
11652
  function parseSource11(parser, source) {
@@ -11419,7 +12071,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
11419
12071
  seen.delete(struct.name);
11420
12072
  }
11421
12073
  function gormEndpointsFromFile(file, serviceDir) {
11422
- if (path46.extname(file.path) !== ".go") return [];
12074
+ if (path49.extname(file.path) !== ".go") return [];
11423
12075
  if (!GORM_IMPORT_RE.test(file.content)) return [];
11424
12076
  const tree = parseSource11(makeGoParser4(), file.content);
11425
12077
  const { structs, models, tableFor } = analyze(tree);
@@ -11441,7 +12093,7 @@ function gormEndpointsFromFile(file, serviceDir) {
11441
12093
  confidenceKind: "structural",
11442
12094
  ...columns.length > 0 ? { columns } : {},
11443
12095
  evidence: {
11444
- file: toPosix(path46.relative(serviceDir, file.path)),
12096
+ file: toPosix(path49.relative(serviceDir, file.path)),
11445
12097
  line: struct.line,
11446
12098
  snippet: snippet(file.content, struct.line)
11447
12099
  }
@@ -11450,7 +12102,7 @@ function gormEndpointsFromFile(file, serviceDir) {
11450
12102
  return out;
11451
12103
  }
11452
12104
  function gormForeignKeys(file, serviceDir) {
11453
- if (path46.extname(file.path) !== ".go") return [];
12105
+ if (path49.extname(file.path) !== ".go") return [];
11454
12106
  if (!GORM_IMPORT_RE.test(file.content)) return [];
11455
12107
  const tree = parseSource11(makeGoParser4(), file.content);
11456
12108
  const { structs, models, tableFor } = analyze(tree);
@@ -11465,7 +12117,7 @@ function gormForeignKeys(file, serviceDir) {
11465
12117
  childTable,
11466
12118
  parentTable,
11467
12119
  evidence: {
11468
- file: toPosix(path46.relative(serviceDir, file.path)),
12120
+ file: toPosix(path49.relative(serviceDir, file.path)),
11469
12121
  line,
11470
12122
  snippet: snippet(file.content, line)
11471
12123
  }
@@ -11568,7 +12220,7 @@ async function addExternalEndpointEdges(graph, services) {
11568
12220
  if (!graph.hasNode(ep.infraId)) {
11569
12221
  const node = {
11570
12222
  id: ep.infraId,
11571
- type: NodeType18.InfraNode,
12223
+ type: NodeType21.InfraNode,
11572
12224
  name: ep.name,
11573
12225
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
11574
12226
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -11581,7 +12233,7 @@ async function addExternalEndpointEdges(graph, services) {
11581
12233
  }
11582
12234
  if (ep.columns && ep.columns.length > 0) {
11583
12235
  const node = graph.getNodeAttributes(ep.infraId);
11584
- if (node.type === NodeType18.InfraNode) {
12236
+ if (node.type === NodeType21.InfraNode) {
11585
12237
  graph.replaceNodeAttributes(ep.infraId, {
11586
12238
  ...node,
11587
12239
  columns: foldColumns(
@@ -11595,7 +12247,7 @@ async function addExternalEndpointEdges(graph, services) {
11595
12247
  }
11596
12248
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
11597
12249
  const node = graph.getNodeAttributes(ep.infraId);
11598
- if (node.type === NodeType18.InfraNode) {
12250
+ if (node.type === NodeType21.InfraNode) {
11599
12251
  graph.replaceNodeAttributes(ep.infraId, {
11600
12252
  ...node,
11601
12253
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -11657,7 +12309,7 @@ async function addCallEdges(graph, services) {
11657
12309
  // src/extract/table-edges.ts
11658
12310
  import {
11659
12311
  EdgeType as EdgeType16,
11660
- NodeType as NodeType19,
12312
+ NodeType as NodeType22,
11661
12313
  Provenance as Provenance16,
11662
12314
  confidenceForExtracted as confidenceForExtracted13,
11663
12315
  extractedEdgeId as extractedEdgeId10,
@@ -11716,7 +12368,7 @@ function ensureTableNode(graph, id, name) {
11716
12368
  if (graph.hasNode(id)) return 0;
11717
12369
  const node = {
11718
12370
  id,
11719
- type: NodeType19.InfraNode,
12371
+ type: NodeType22.InfraNode,
11720
12372
  name,
11721
12373
  provider: "self",
11722
12374
  kind: "sql-table"
@@ -11726,15 +12378,15 @@ function ensureTableNode(graph, id, name) {
11726
12378
  }
11727
12379
 
11728
12380
  // src/extract/infra/docker-compose.ts
11729
- import path47 from "path";
12381
+ import path50 from "path";
11730
12382
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
11731
12383
 
11732
12384
  // src/extract/infra/shared.ts
11733
- import { NodeType as NodeType20, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
12385
+ import { NodeType as NodeType23, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
11734
12386
  function makeInfraNode(kind, name, provider = "self", extras) {
11735
12387
  return {
11736
12388
  id: infraId18(kind, name),
11737
- type: NodeType20.InfraNode,
12389
+ type: NodeType23.InfraNode,
11738
12390
  name,
11739
12391
  provider,
11740
12392
  kind,
@@ -11796,7 +12448,7 @@ function dependsOnList(value) {
11796
12448
  }
11797
12449
  function serviceNameToServiceNode(name, services) {
11798
12450
  for (const s of services) {
11799
- if (s.node.name === name || path47.basename(s.dir) === name) return s.node.id;
12451
+ if (s.node.name === name || path50.basename(s.dir) === name) return s.node.id;
11800
12452
  }
11801
12453
  return null;
11802
12454
  }
@@ -11805,7 +12457,7 @@ async function addComposeInfra(graph, scanPath, services) {
11805
12457
  let edgesAdded = 0;
11806
12458
  let composePath = null;
11807
12459
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
11808
- const abs = path47.join(scanPath, name);
12460
+ const abs = path50.join(scanPath, name);
11809
12461
  if (await exists(abs)) {
11810
12462
  composePath = abs;
11811
12463
  break;
@@ -11818,13 +12470,13 @@ async function addComposeInfra(graph, scanPath, services) {
11818
12470
  } catch (err) {
11819
12471
  recordExtractionError(
11820
12472
  "infra docker-compose",
11821
- path47.relative(scanPath, composePath),
12473
+ path50.relative(scanPath, composePath),
11822
12474
  err
11823
12475
  );
11824
12476
  return { nodesAdded, edgesAdded };
11825
12477
  }
11826
12478
  if (!compose?.services) return { nodesAdded, edgesAdded };
11827
- const evidenceFile = path47.relative(scanPath, composePath).split(path47.sep).join("/");
12479
+ const evidenceFile = path50.relative(scanPath, composePath).split(path50.sep).join("/");
11828
12480
  const composeNameToNodeId = /* @__PURE__ */ new Map();
11829
12481
  for (const [composeName, svc] of Object.entries(compose.services)) {
11830
12482
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -11865,8 +12517,8 @@ async function addComposeInfra(graph, scanPath, services) {
11865
12517
  }
11866
12518
 
11867
12519
  // src/extract/infra/dockerfile.ts
11868
- import path48 from "path";
11869
- import { promises as fs18 } from "fs";
12520
+ import path51 from "path";
12521
+ import { promises as fs21 } from "fs";
11870
12522
  import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
11871
12523
  function readDockerfile(content) {
11872
12524
  let image = null;
@@ -11896,15 +12548,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11896
12548
  let nodesAdded = 0;
11897
12549
  let edgesAdded = 0;
11898
12550
  for (const service of services) {
11899
- const dockerfilePath = path48.join(service.dir, "Dockerfile");
12551
+ const dockerfilePath = path51.join(service.dir, "Dockerfile");
11900
12552
  if (!await exists(dockerfilePath)) continue;
11901
12553
  let content;
11902
12554
  try {
11903
- content = await fs18.readFile(dockerfilePath, "utf8");
12555
+ content = await fs21.readFile(dockerfilePath, "utf8");
11904
12556
  } catch (err) {
11905
12557
  recordExtractionError(
11906
12558
  "infra dockerfile",
11907
- path48.relative(scanPath, dockerfilePath),
12559
+ path51.relative(scanPath, dockerfilePath),
11908
12560
  err
11909
12561
  );
11910
12562
  continue;
@@ -11916,8 +12568,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11916
12568
  graph.addNode(node.id, node);
11917
12569
  nodesAdded++;
11918
12570
  }
11919
- const relDockerfile = toPosix(path48.relative(service.dir, dockerfilePath));
11920
- const evidenceFile = toPosix(path48.relative(scanPath, dockerfilePath));
12571
+ const relDockerfile = toPosix(path51.relative(service.dir, dockerfilePath));
12572
+ const evidenceFile = toPosix(path51.relative(scanPath, dockerfilePath));
11921
12573
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
11922
12574
  graph,
11923
12575
  service.pkg.name,
@@ -11968,23 +12620,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
11968
12620
  }
11969
12621
 
11970
12622
  // src/extract/infra/terraform.ts
11971
- import { promises as fs19 } from "fs";
11972
- import path49 from "path";
12623
+ import { promises as fs22 } from "fs";
12624
+ import path52 from "path";
11973
12625
  import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
11974
12626
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
11975
12627
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
11976
12628
  async function walkTfFiles(start, depth = 0, max = 5) {
11977
12629
  if (depth > max) return [];
11978
12630
  const out = [];
11979
- const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
12631
+ const entries = await fs22.readdir(start, { withFileTypes: true }).catch(() => []);
11980
12632
  for (const entry of entries) {
11981
12633
  if (entry.isDirectory()) {
11982
12634
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
11983
- const child = path49.join(start, entry.name);
12635
+ const child = path52.join(start, entry.name);
11984
12636
  if (await isPythonVenvDir(child)) continue;
11985
12637
  out.push(...await walkTfFiles(child, depth + 1, max));
11986
12638
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
11987
- out.push(path49.join(start, entry.name));
12639
+ out.push(path52.join(start, entry.name));
11988
12640
  }
11989
12641
  }
11990
12642
  return out;
@@ -12015,8 +12667,8 @@ async function addTerraformResources(graph, scanPath) {
12015
12667
  let edgesAdded = 0;
12016
12668
  const files = await walkTfFiles(scanPath);
12017
12669
  for (const file of files) {
12018
- const content = await fs19.readFile(file, "utf8");
12019
- const evidenceFile = toPosix(path49.relative(scanPath, file));
12670
+ const content = await fs22.readFile(file, "utf8");
12671
+ const evidenceFile = toPosix(path52.relative(scanPath, file));
12020
12672
  const resources = [];
12021
12673
  const byKey = /* @__PURE__ */ new Map();
12022
12674
  RESOURCE_RE.lastIndex = 0;
@@ -12072,8 +12724,8 @@ async function addTerraformResources(graph, scanPath) {
12072
12724
  }
12073
12725
 
12074
12726
  // src/extract/infra/k8s.ts
12075
- import { promises as fs20 } from "fs";
12076
- import path50 from "path";
12727
+ import { promises as fs23 } from "fs";
12728
+ import path53 from "path";
12077
12729
  import { parseAllDocuments as parseAllDocuments2 } from "yaml";
12078
12730
  var K8S_KIND_TO_INFRA_KIND = {
12079
12731
  Service: "k8s-service",
@@ -12087,15 +12739,15 @@ var K8S_KIND_TO_INFRA_KIND = {
12087
12739
  async function walkYamlFiles2(start, depth = 0, max = 5) {
12088
12740
  if (depth > max) return [];
12089
12741
  const out = [];
12090
- const entries = await fs20.readdir(start, { withFileTypes: true }).catch(() => []);
12742
+ const entries = await fs23.readdir(start, { withFileTypes: true }).catch(() => []);
12091
12743
  for (const entry of entries) {
12092
12744
  if (entry.isDirectory()) {
12093
12745
  if (IGNORED_DIRS.has(entry.name)) continue;
12094
- const child = path50.join(start, entry.name);
12746
+ const child = path53.join(start, entry.name);
12095
12747
  if (await isPythonVenvDir(child)) continue;
12096
12748
  out.push(...await walkYamlFiles2(child, depth + 1, max));
12097
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path50.extname(entry.name))) {
12098
- out.push(path50.join(start, entry.name));
12749
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path53.extname(entry.name))) {
12750
+ out.push(path53.join(start, entry.name));
12099
12751
  }
12100
12752
  }
12101
12753
  return out;
@@ -12104,7 +12756,7 @@ async function addK8sResources(graph, scanPath) {
12104
12756
  let nodesAdded = 0;
12105
12757
  const files = await walkYamlFiles2(scanPath);
12106
12758
  for (const file of files) {
12107
- const content = await fs20.readFile(file, "utf8");
12759
+ const content = await fs23.readFile(file, "utf8");
12108
12760
  let docs;
12109
12761
  try {
12110
12762
  docs = parseAllDocuments2(content).map((d) => d.toJSON());
@@ -12127,16 +12779,16 @@ async function addK8sResources(graph, scanPath) {
12127
12779
  }
12128
12780
 
12129
12781
  // src/extract/infra/cloudflare.ts
12130
- import { promises as fs21 } from "fs";
12131
- import path51 from "path";
12782
+ import { promises as fs24 } from "fs";
12783
+ import path54 from "path";
12132
12784
  import { parse as parseToml2 } from "smol-toml";
12133
12785
  import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
12134
12786
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
12135
12787
  async function readWranglerConfig(dir) {
12136
12788
  for (const filename of WRANGLER_FILENAMES) {
12137
- const abs = path51.join(dir, filename);
12789
+ const abs = path54.join(dir, filename);
12138
12790
  if (!await exists(abs)) continue;
12139
- const raw = await fs21.readFile(abs, "utf8");
12791
+ const raw = await fs24.readFile(abs, "utf8");
12140
12792
  const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
12141
12793
  return { config, relFile: filename, raw };
12142
12794
  }
@@ -12197,11 +12849,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12197
12849
  try {
12198
12850
  read = await readWranglerConfig(service.dir);
12199
12851
  } catch (err) {
12200
- recordExtractionError("infra cloudflare", path51.relative(scanPath, service.dir), err);
12852
+ recordExtractionError("infra cloudflare", path54.relative(scanPath, service.dir), err);
12201
12853
  continue;
12202
12854
  }
12203
12855
  if (!read || !read.config.name) continue;
12204
- const evidenceFile = toPosix(path51.relative(scanPath, path51.join(service.dir, read.relFile)));
12856
+ const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, read.relFile)));
12205
12857
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
12206
12858
  }
12207
12859
  for (const worker of discovered) {
@@ -12213,7 +12865,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12213
12865
  }
12214
12866
  let anchorId = service.node.id;
12215
12867
  if (config.main) {
12216
- const entryRelPath = toPosix(path51.normalize(config.main));
12868
+ const entryRelPath = toPosix(path54.normalize(config.main));
12217
12869
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
12218
12870
  graph,
12219
12871
  service.pkg.name,
@@ -12359,24 +13011,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
12359
13011
  }
12360
13012
 
12361
13013
  // src/extract/infra/vercel.ts
12362
- import { promises as fs22 } from "fs";
12363
- import path52 from "path";
13014
+ import { promises as fs25 } from "fs";
13015
+ import path55 from "path";
12364
13016
  import { EdgeType as EdgeType21 } from "@neat.is/types";
12365
13017
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
12366
13018
  async function readVercelConfig(dir) {
12367
13019
  for (const filename of VERCEL_CONFIG_FILENAMES) {
12368
- const abs = path52.join(dir, filename);
13020
+ const abs = path55.join(dir, filename);
12369
13021
  if (!await exists(abs)) continue;
12370
- const raw = await fs22.readFile(abs, "utf8");
13022
+ const raw = await fs25.readFile(abs, "utf8");
12371
13023
  const config = JSON.parse(maskCommentsInSource(raw));
12372
13024
  return { config, relFile: filename, raw };
12373
13025
  }
12374
13026
  return null;
12375
13027
  }
12376
13028
  async function readLinkedProjectName(dir) {
12377
- const abs = path52.join(dir, ".vercel", "project.json");
13029
+ const abs = path55.join(dir, ".vercel", "project.json");
12378
13030
  if (!await exists(abs)) return void 0;
12379
- const parsed = JSON.parse(await fs22.readFile(abs, "utf8"));
13031
+ const parsed = JSON.parse(await fs25.readFile(abs, "utf8"));
12380
13032
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
12381
13033
  }
12382
13034
  function routeSource(route) {
@@ -12392,7 +13044,7 @@ async function addVercelServices(graph, services, scanPath) {
12392
13044
  read = await readVercelConfig(service.dir);
12393
13045
  projectName = await readLinkedProjectName(service.dir);
12394
13046
  } catch (err) {
12395
- recordExtractionError("infra vercel", path52.relative(scanPath, service.dir), err);
13047
+ recordExtractionError("infra vercel", path55.relative(scanPath, service.dir), err);
12396
13048
  continue;
12397
13049
  }
12398
13050
  if (!read && !projectName) continue;
@@ -12408,7 +13060,7 @@ async function addVercelServices(graph, services, scanPath) {
12408
13060
  const anchorId = service.node.id;
12409
13061
  if (!read) continue;
12410
13062
  const { config, relFile, raw } = read;
12411
- const evidenceFile = toPosix(path52.relative(scanPath, path52.join(service.dir, relFile)));
13063
+ const evidenceFile = toPosix(path55.relative(scanPath, path55.join(service.dir, relFile)));
12412
13064
  const add = (edgeType, kind, name) => {
12413
13065
  if (!name) return;
12414
13066
  const result = emitPlatformResourceEdge(
@@ -12436,16 +13088,16 @@ async function addVercelServices(graph, services, scanPath) {
12436
13088
  }
12437
13089
 
12438
13090
  // src/extract/infra/railway.ts
12439
- import { promises as fs23 } from "fs";
12440
- import path53 from "path";
13091
+ import { promises as fs26 } from "fs";
13092
+ import path56 from "path";
12441
13093
  import { parse as parseToml3 } from "smol-toml";
12442
13094
  import { EdgeType as EdgeType22 } from "@neat.is/types";
12443
13095
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
12444
13096
  async function readRailwayConfig(dir) {
12445
13097
  for (const filename of RAILWAY_FILENAMES) {
12446
- const abs = path53.join(dir, filename);
13098
+ const abs = path56.join(dir, filename);
12447
13099
  if (!await exists(abs)) continue;
12448
- const raw = await fs23.readFile(abs, "utf8");
13100
+ const raw = await fs26.readFile(abs, "utf8");
12449
13101
  const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
12450
13102
  return { config, relFile: filename, raw };
12451
13103
  }
@@ -12459,7 +13111,7 @@ async function addRailwayServices(graph, services, scanPath) {
12459
13111
  try {
12460
13112
  read = await readRailwayConfig(service.dir);
12461
13113
  } catch (err) {
12462
- recordExtractionError("infra railway", path53.relative(scanPath, service.dir), err);
13114
+ recordExtractionError("infra railway", path56.relative(scanPath, service.dir), err);
12463
13115
  continue;
12464
13116
  }
12465
13117
  if (!read) continue;
@@ -12469,7 +13121,7 @@ async function addRailwayServices(graph, services, scanPath) {
12469
13121
  }
12470
13122
  const anchorId = service.node.id;
12471
13123
  const { config, relFile, raw } = read;
12472
- const evidenceFile = toPosix(path53.relative(scanPath, path53.join(service.dir, relFile)));
13124
+ const evidenceFile = toPosix(path56.relative(scanPath, path56.join(service.dir, relFile)));
12473
13125
  const add = (edgeType, kind, name) => {
12474
13126
  if (!name) return;
12475
13127
  const result = emitPlatformResourceEdge(
@@ -12493,15 +13145,15 @@ async function addRailwayServices(graph, services, scanPath) {
12493
13145
  }
12494
13146
 
12495
13147
  // src/extract/infra/supabase.ts
12496
- import { promises as fs24 } from "fs";
12497
- import path54 from "path";
13148
+ import { promises as fs27 } from "fs";
13149
+ import path57 from "path";
12498
13150
  import { parse as parseToml4 } from "smol-toml";
12499
13151
  import { EdgeType as EdgeType23 } from "@neat.is/types";
12500
13152
  async function readSupabaseConfig(dir) {
12501
- const relFile = path54.join("supabase", "config.toml");
12502
- const abs = path54.join(dir, relFile);
13153
+ const relFile = path57.join("supabase", "config.toml");
13154
+ const abs = path57.join(dir, relFile);
12503
13155
  if (!await exists(abs)) return null;
12504
- const raw = await fs24.readFile(abs, "utf8");
13156
+ const raw = await fs27.readFile(abs, "utf8");
12505
13157
  const config = parseToml4(raw);
12506
13158
  return { config, relFile, raw };
12507
13159
  }
@@ -12513,7 +13165,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12513
13165
  try {
12514
13166
  read = await readSupabaseConfig(service.dir);
12515
13167
  } catch (err) {
12516
- recordExtractionError("infra supabase", path54.relative(scanPath, service.dir), err);
13168
+ recordExtractionError("infra supabase", path57.relative(scanPath, service.dir), err);
12517
13169
  continue;
12518
13170
  }
12519
13171
  if (!read) continue;
@@ -12528,7 +13180,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
12528
13180
  });
12529
13181
  }
12530
13182
  const anchorId = service.node.id;
12531
- const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, relFile)));
13183
+ const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, relFile)));
12532
13184
  const add = (edgeType, kind, name) => {
12533
13185
  if (!name) return;
12534
13186
  const result = emitPlatformResourceEdge(
@@ -12569,12 +13221,12 @@ async function addInfra(graph, scanPath, services) {
12569
13221
  }
12570
13222
 
12571
13223
  // src/extract/zod-shapes.ts
12572
- import path55 from "path";
13224
+ import path58 from "path";
12573
13225
  import Parser16 from "tree-sitter";
12574
13226
  import JavaScript8 from "tree-sitter-javascript";
12575
13227
  import {
12576
13228
  EdgeType as EdgeType24,
12577
- NodeType as NodeType21,
13229
+ NodeType as NodeType24,
12578
13230
  Provenance as Provenance22,
12579
13231
  confidenceForExtracted as confidenceForExtracted19,
12580
13232
  extractedEdgeId as extractedEdgeId11,
@@ -12671,7 +13323,7 @@ function topLevelSchemas(root) {
12671
13323
  }
12672
13324
  function zodShapesFromFile(file, serviceDir) {
12673
13325
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12674
- const tree = parseSource3(parserForExt4(path55.extname(file.path)), file.content);
13326
+ const tree = parseSource3(parserForExt4(path58.extname(file.path)), file.content);
12675
13327
  const out = [];
12676
13328
  const seen = /* @__PURE__ */ new Set();
12677
13329
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -12689,7 +13341,7 @@ function zodShapesFromFile(file, serviceDir) {
12689
13341
  name,
12690
13342
  fields,
12691
13343
  evidence: {
12692
- file: path55.relative(serviceDir, file.path),
13344
+ file: path58.relative(serviceDir, file.path),
12693
13345
  line,
12694
13346
  snippet: snippet(file.content, line)
12695
13347
  }
@@ -12720,7 +13372,7 @@ async function addZodShapes(graph, services) {
12720
13372
  if (!graph.hasNode(shape.infraId)) {
12721
13373
  const node = {
12722
13374
  id: shape.infraId,
12723
- type: NodeType21.InfraNode,
13375
+ type: NodeType24.InfraNode,
12724
13376
  name: shape.name,
12725
13377
  provider: "self",
12726
13378
  kind: "zod-schema"
@@ -12730,7 +13382,7 @@ async function addZodShapes(graph, services) {
12730
13382
  }
12731
13383
  if (shape.fields.length > 0) {
12732
13384
  const node = graph.getNodeAttributes(shape.infraId);
12733
- if (node.type === NodeType21.InfraNode) {
13385
+ if (node.type === NodeType24.InfraNode) {
12734
13386
  graph.replaceNodeAttributes(shape.infraId, {
12735
13387
  ...node,
12736
13388
  columns: foldColumns(
@@ -12772,7 +13424,7 @@ async function addZodShapes(graph, services) {
12772
13424
  }
12773
13425
 
12774
13426
  // src/extract/firestore-rules.ts
12775
- import { NodeType as NodeType22 } from "@neat.is/types";
13427
+ import { NodeType as NodeType25 } from "@neat.is/types";
12776
13428
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
12777
13429
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
12778
13430
  function stripComments(src) {
@@ -12912,7 +13564,7 @@ async function addFirestoreRules(graph, services) {
12912
13564
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
12913
13565
  graph.forEachNode((id, attrs) => {
12914
13566
  const node = attrs;
12915
- if (node.type !== NodeType22.InfraNode) return;
13567
+ if (node.type !== NodeType25.InfraNode) return;
12916
13568
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
12917
13569
  const fields = guards.get(collectionKeyFromName(node.name));
12918
13570
  if (!fields || fields.size === 0) return;
@@ -12925,16 +13577,16 @@ async function addFirestoreRules(graph, services) {
12925
13577
  }
12926
13578
 
12927
13579
  // src/extract/index.ts
12928
- import path57 from "path";
13580
+ import path60 from "path";
12929
13581
 
12930
13582
  // src/extract/retire.ts
12931
13583
  import { existsSync as existsSync2 } from "fs";
12932
- import path56 from "path";
12933
- import { NodeType as NodeType23, Provenance as Provenance23 } from "@neat.is/types";
13584
+ import path59 from "path";
13585
+ import { NodeType as NodeType26, Provenance as Provenance23 } from "@neat.is/types";
12934
13586
  function dropOrphanedFileNodes(graph) {
12935
13587
  const orphans = [];
12936
13588
  graph.forEachNode((id, attrs) => {
12937
- if (attrs.type !== NodeType23.FileNode) return;
13589
+ if (attrs.type !== NodeType26.FileNode) return;
12938
13590
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
12939
13591
  orphans.push(id);
12940
13592
  }
@@ -12963,11 +13615,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
12963
13615
  if (edge.provenance !== Provenance23.EXTRACTED) return;
12964
13616
  const evidenceFile = edge.evidence?.file;
12965
13617
  if (!evidenceFile) return;
12966
- if (path56.isAbsolute(evidenceFile)) {
13618
+ if (path59.isAbsolute(evidenceFile)) {
12967
13619
  if (!existsSync2(evidenceFile)) toDrop.push(id);
12968
13620
  return;
12969
13621
  }
12970
- const found = bases.some((base) => existsSync2(path56.join(base, evidenceFile)));
13622
+ const found = bases.some((base) => existsSync2(path59.join(base, evidenceFile)));
12971
13623
  if (!found) toDrop.push(id);
12972
13624
  });
12973
13625
  for (const id of toDrop) graph.dropEdge(id);
@@ -13024,7 +13676,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
13024
13676
  }
13025
13677
  const droppedEntries = drainDroppedExtracted();
13026
13678
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
13027
- const rejectedPath = path57.join(path57.dirname(opts.errorsPath), "rejected.ndjson");
13679
+ const rejectedPath = path60.join(path60.dirname(opts.errorsPath), "rejected.ndjson");
13028
13680
  try {
13029
13681
  await writeRejectedExtracted(droppedEntries, rejectedPath);
13030
13682
  } catch (err) {
@@ -13061,11 +13713,11 @@ import {
13061
13713
  databaseId as databaseId3,
13062
13714
  DivergenceResultSchema,
13063
13715
  EdgeType as EdgeType25,
13064
- NodeType as NodeType24,
13716
+ NodeType as NodeType27,
13065
13717
  parseEdgeId,
13066
13718
  parseFileId,
13067
13719
  Provenance as Provenance24,
13068
- serviceId as serviceId7
13720
+ serviceId as serviceId10
13069
13721
  } from "@neat.is/types";
13070
13722
  function bucketKey(source, target, type) {
13071
13723
  return `${type}|${source}|${target}`;
@@ -13075,8 +13727,8 @@ function bucketSourceFor(graph, edge) {
13075
13727
  const parsed = parseFileId(edge.source);
13076
13728
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
13077
13729
  const target = graph.getNodeAttributes(edge.target);
13078
- if (target.type !== NodeType24.DatabaseNode) return edge.source;
13079
- return serviceId7(parsed.service);
13730
+ if (target.type !== NodeType27.DatabaseNode) return edge.source;
13731
+ return serviceId10(parsed.service);
13080
13732
  }
13081
13733
  function bucketEdges(graph) {
13082
13734
  const buckets2 = /* @__PURE__ */ new Map();
@@ -13107,22 +13759,22 @@ function bucketEdges(graph) {
13107
13759
  function nodeIsFrontier(graph, nodeId) {
13108
13760
  if (!graph.hasNode(nodeId)) return false;
13109
13761
  const attrs = graph.getNodeAttributes(nodeId);
13110
- return attrs.type === NodeType24.FrontierNode;
13762
+ return attrs.type === NodeType27.FrontierNode;
13111
13763
  }
13112
13764
  function nodeIsWebsocketChannel(graph, nodeId) {
13113
13765
  if (!graph.hasNode(nodeId)) return false;
13114
13766
  const attrs = graph.getNodeAttributes(nodeId);
13115
- return attrs.type === NodeType24.WebSocketChannelNode;
13767
+ return attrs.type === NodeType27.WebSocketChannelNode;
13116
13768
  }
13117
13769
  function nodeIsServerAction(graph, nodeId) {
13118
13770
  if (!graph.hasNode(nodeId)) return false;
13119
13771
  const attrs = graph.getNodeAttributes(nodeId);
13120
- return attrs.type === NodeType24.ServerActionNode;
13772
+ return attrs.type === NodeType27.ServerActionNode;
13121
13773
  }
13122
13774
  function nodeIsSymbol(graph, nodeId) {
13123
13775
  if (!graph.hasNode(nodeId)) return false;
13124
13776
  const attrs = graph.getNodeAttributes(nodeId);
13125
- return attrs.type === NodeType24.SymbolNode;
13777
+ return attrs.type === NodeType27.SymbolNode;
13126
13778
  }
13127
13779
  function clampConfidence(n) {
13128
13780
  if (!Number.isFinite(n)) return 0;
@@ -13207,7 +13859,7 @@ function detectHostMismatch(graph, svcId, svc) {
13207
13859
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13208
13860
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13209
13861
  const target = graph.getNodeAttributes(edge.target);
13210
- if (target.type !== NodeType24.DatabaseNode) continue;
13862
+ if (target.type !== NodeType27.DatabaseNode) continue;
13211
13863
  const observedHost = target.host?.trim();
13212
13864
  if (!observedHost) continue;
13213
13865
  if (observedHost === declaredHost) continue;
@@ -13232,7 +13884,7 @@ function detectCompatDivergences(graph, svcId, svc) {
13232
13884
  if (edge.type !== EdgeType25.CONNECTS_TO) continue;
13233
13885
  if (edge.provenance !== Provenance24.OBSERVED) continue;
13234
13886
  const target = graph.getNodeAttributes(edge.target);
13235
- if (target.type !== NodeType24.DatabaseNode) continue;
13887
+ if (target.type !== NodeType27.DatabaseNode) continue;
13236
13888
  for (const pair of compatPairs()) {
13237
13889
  if (pair.engine !== target.engine) continue;
13238
13890
  const declared = deps[pair.driver];
@@ -13348,13 +14000,13 @@ function computeDivergences(graph, opts = {}) {
13348
14000
  }
13349
14001
  graph.forEachNode((nodeId, attrs) => {
13350
14002
  const n = attrs;
13351
- if (n.type === NodeType24.ServiceNode) {
14003
+ if (n.type === NodeType27.ServiceNode) {
13352
14004
  const svc = n;
13353
14005
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
13354
14006
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
13355
14007
  return;
13356
14008
  }
13357
- if (n.type === NodeType24.InfraNode && n.kind === "sql-table") {
14009
+ if (n.type === NodeType27.InfraNode && n.kind === "sql-table") {
13358
14010
  for (const d of detectColumnDrift(n)) all.push(d);
13359
14011
  }
13360
14012
  });
@@ -13398,9 +14050,9 @@ function computeDivergences(graph, opts = {}) {
13398
14050
  }
13399
14051
 
13400
14052
  // src/persist.ts
13401
- import { promises as fs25 } from "fs";
13402
- import path58 from "path";
13403
- import { NodeType as NodeType25, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
14053
+ import { promises as fs28 } from "fs";
14054
+ import path61 from "path";
14055
+ import { NodeType as NodeType28, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
13404
14056
  var SCHEMA_VERSION = 7;
13405
14057
  function migrateV1ToV2(payload) {
13406
14058
  const nodes = payload.graph.nodes;
@@ -13424,7 +14076,7 @@ function migrateV5ToV6(payload) {
13424
14076
  if (Array.isArray(nodes)) {
13425
14077
  for (const node of nodes) {
13426
14078
  const attrs = node.attributes;
13427
- if (!attrs || attrs.type !== NodeType25.InfraNode) continue;
14079
+ if (!attrs || attrs.type !== NodeType28.InfraNode) continue;
13428
14080
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
13429
14081
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
13430
14082
  }
@@ -13454,7 +14106,7 @@ function migrateV2ToV3(payload) {
13454
14106
  return { ...payload, schemaVersion: 3 };
13455
14107
  }
13456
14108
  async function ensureDir(filePath) {
13457
- await fs25.mkdir(path58.dirname(filePath), { recursive: true });
14109
+ await fs28.mkdir(path61.dirname(filePath), { recursive: true });
13458
14110
  }
13459
14111
  async function saveGraphToDisk(graph, outPath) {
13460
14112
  await ensureDir(outPath);
@@ -13464,13 +14116,13 @@ async function saveGraphToDisk(graph, outPath) {
13464
14116
  graph: graph.export()
13465
14117
  };
13466
14118
  const tmp = `${outPath}.tmp`;
13467
- await fs25.writeFile(tmp, JSON.stringify(payload), "utf8");
13468
- await fs25.rename(tmp, outPath);
14119
+ await fs28.writeFile(tmp, JSON.stringify(payload), "utf8");
14120
+ await fs28.rename(tmp, outPath);
13469
14121
  }
13470
14122
  async function loadGraphFromDisk(graph, outPath) {
13471
14123
  let raw;
13472
14124
  try {
13473
- raw = await fs25.readFile(outPath, "utf8");
14125
+ raw = await fs28.readFile(outPath, "utf8");
13474
14126
  } catch (err) {
13475
14127
  if (err.code === "ENOENT") return;
13476
14128
  throw err;
@@ -13543,7 +14195,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
13543
14195
  }
13544
14196
 
13545
14197
  // src/diff.ts
13546
- import { promises as fs26 } from "fs";
14198
+ import { promises as fs29 } from "fs";
13547
14199
  async function loadSnapshotForDiff(target) {
13548
14200
  if (/^https?:\/\//i.test(target)) {
13549
14201
  const res = await fetch(target);
@@ -13552,7 +14204,7 @@ async function loadSnapshotForDiff(target) {
13552
14204
  }
13553
14205
  return await res.json();
13554
14206
  }
13555
- const raw = await fs26.readFile(target, "utf8");
14207
+ const raw = await fs29.readFile(target, "utf8");
13556
14208
  return JSON.parse(raw);
13557
14209
  }
13558
14210
  function indexEntries(entries) {
@@ -13619,23 +14271,23 @@ function canonicalJson(value) {
13619
14271
  }
13620
14272
 
13621
14273
  // src/projects.ts
13622
- import path59 from "path";
14274
+ import path62 from "path";
13623
14275
  function pathsForProject(project, baseDir) {
13624
14276
  if (project === DEFAULT_PROJECT) {
13625
14277
  return {
13626
- snapshotPath: path59.join(baseDir, "graph.json"),
13627
- errorsPath: path59.join(baseDir, "errors.ndjson"),
13628
- staleEventsPath: path59.join(baseDir, "stale-events.ndjson"),
13629
- embeddingsCachePath: path59.join(baseDir, "embeddings.json"),
13630
- policyViolationsPath: path59.join(baseDir, "policy-violations.ndjson")
14278
+ snapshotPath: path62.join(baseDir, "graph.json"),
14279
+ errorsPath: path62.join(baseDir, "errors.ndjson"),
14280
+ staleEventsPath: path62.join(baseDir, "stale-events.ndjson"),
14281
+ embeddingsCachePath: path62.join(baseDir, "embeddings.json"),
14282
+ policyViolationsPath: path62.join(baseDir, "policy-violations.ndjson")
13631
14283
  };
13632
14284
  }
13633
14285
  return {
13634
- snapshotPath: path59.join(baseDir, `${project}.json`),
13635
- errorsPath: path59.join(baseDir, `errors.${project}.ndjson`),
13636
- staleEventsPath: path59.join(baseDir, `stale-events.${project}.ndjson`),
13637
- embeddingsCachePath: path59.join(baseDir, `embeddings.${project}.json`),
13638
- policyViolationsPath: path59.join(baseDir, `policy-violations.${project}.ndjson`)
14286
+ snapshotPath: path62.join(baseDir, `${project}.json`),
14287
+ errorsPath: path62.join(baseDir, `errors.${project}.ndjson`),
14288
+ staleEventsPath: path62.join(baseDir, `stale-events.${project}.ndjson`),
14289
+ embeddingsCachePath: path62.join(baseDir, `embeddings.${project}.json`),
14290
+ policyViolationsPath: path62.join(baseDir, `policy-violations.${project}.ndjson`)
13639
14291
  };
13640
14292
  }
13641
14293
  var Projects = class {
@@ -13674,9 +14326,9 @@ function parseExtraProjects(raw) {
13674
14326
  }
13675
14327
 
13676
14328
  // src/registry.ts
13677
- import { promises as fs27 } from "fs";
14329
+ import { promises as fs30 } from "fs";
13678
14330
  import os2 from "os";
13679
- import path60 from "path";
14331
+ import path63 from "path";
13680
14332
  import {
13681
14333
  RegistryFileSchema
13682
14334
  } from "@neat.is/types";
@@ -13684,20 +14336,20 @@ var LOCK_TIMEOUT_MS = 5e3;
13684
14336
  var LOCK_RETRY_MS = 50;
13685
14337
  function neatHome() {
13686
14338
  const override = process.env.NEAT_HOME;
13687
- if (override && override.length > 0) return path60.resolve(override);
13688
- return path60.join(os2.homedir(), ".neat");
14339
+ if (override && override.length > 0) return path63.resolve(override);
14340
+ return path63.join(os2.homedir(), ".neat");
13689
14341
  }
13690
14342
  function registryPath() {
13691
- return path60.join(neatHome(), "projects.json");
14343
+ return path63.join(neatHome(), "projects.json");
13692
14344
  }
13693
14345
  function registryLockPath() {
13694
- return path60.join(neatHome(), "projects.json.lock");
14346
+ return path63.join(neatHome(), "projects.json.lock");
13695
14347
  }
13696
14348
  function daemonPidPath() {
13697
- return path60.join(neatHome(), "neatd.pid");
14349
+ return path63.join(neatHome(), "neatd.pid");
13698
14350
  }
13699
14351
  function daemonsDir() {
13700
- return path60.join(neatHome(), "daemons");
14352
+ return path63.join(neatHome(), "daemons");
13701
14353
  }
13702
14354
  function isFiniteInt(v) {
13703
14355
  return typeof v === "number" && Number.isFinite(v);
@@ -13730,7 +14382,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13730
14382
  const dir = daemonsDir();
13731
14383
  let names;
13732
14384
  try {
13733
- names = await fs27.readdir(dir);
14385
+ names = await fs30.readdir(dir);
13734
14386
  } catch (err) {
13735
14387
  if (err.code === "ENOENT") return [];
13736
14388
  throw err;
@@ -13738,10 +14390,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13738
14390
  const out = [];
13739
14391
  for (const name of names) {
13740
14392
  if (!name.endsWith(".json")) continue;
13741
- const file = path60.join(dir, name);
14393
+ const file = path63.join(dir, name);
13742
14394
  let raw;
13743
14395
  try {
13744
- raw = await fs27.readFile(file, "utf8");
14396
+ raw = await fs30.readFile(file, "utf8");
13745
14397
  } catch {
13746
14398
  continue;
13747
14399
  }
@@ -13754,7 +14406,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
13754
14406
  return out;
13755
14407
  }
13756
14408
  async function removeDaemonRecord(source) {
13757
- await fs27.unlink(source).catch(() => {
14409
+ await fs30.unlink(source).catch(() => {
13758
14410
  });
13759
14411
  }
13760
14412
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -13811,7 +14463,7 @@ function isPidAliveDefault(pid) {
13811
14463
  }
13812
14464
  async function readPidFile(file) {
13813
14465
  try {
13814
- const raw = await fs27.readFile(file, "utf8");
14466
+ const raw = await fs30.readFile(file, "utf8");
13815
14467
  const pid = Number.parseInt(raw.trim(), 10);
13816
14468
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
13817
14469
  } catch {
@@ -13859,32 +14511,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
13859
14511
  }
13860
14512
  }
13861
14513
  async function normalizeProjectPath(input) {
13862
- const resolved = path60.resolve(input);
14514
+ const resolved = path63.resolve(input);
13863
14515
  try {
13864
- return await fs27.realpath(resolved);
14516
+ return await fs30.realpath(resolved);
13865
14517
  } catch {
13866
14518
  return resolved;
13867
14519
  }
13868
14520
  }
13869
14521
  async function writeAtomically(target, contents) {
13870
- await fs27.mkdir(path60.dirname(target), { recursive: true });
14522
+ await fs30.mkdir(path63.dirname(target), { recursive: true });
13871
14523
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
13872
- const fd = await fs27.open(tmp, "w");
14524
+ const fd = await fs30.open(tmp, "w");
13873
14525
  try {
13874
14526
  await fd.writeFile(contents, "utf8");
13875
14527
  await fd.sync();
13876
14528
  } finally {
13877
14529
  await fd.close();
13878
14530
  }
13879
- await fs27.rename(tmp, target);
14531
+ await fs30.rename(tmp, target);
13880
14532
  }
13881
14533
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
13882
14534
  const deadline = Date.now() + timeoutMs;
13883
- await fs27.mkdir(path60.dirname(lockPath), { recursive: true });
14535
+ await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
13884
14536
  let probedHolder = false;
13885
14537
  while (true) {
13886
14538
  try {
13887
- const fd = await fs27.open(lockPath, "wx");
14539
+ const fd = await fs30.open(lockPath, "wx");
13888
14540
  try {
13889
14541
  await fd.writeFile(`${process.pid}
13890
14542
  `, "utf8");
@@ -13909,7 +14561,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
13909
14561
  }
13910
14562
  }
13911
14563
  async function releaseLock(lockPath) {
13912
- await fs27.unlink(lockPath).catch(() => {
14564
+ await fs30.unlink(lockPath).catch(() => {
13913
14565
  });
13914
14566
  }
13915
14567
  async function withLock(fn) {
@@ -13925,7 +14577,7 @@ async function readRegistry() {
13925
14577
  const file = registryPath();
13926
14578
  let raw;
13927
14579
  try {
13928
- raw = await fs27.readFile(file, "utf8");
14580
+ raw = await fs30.readFile(file, "utf8");
13929
14581
  } catch (err) {
13930
14582
  if (err.code === "ENOENT") {
13931
14583
  return { version: 1, projects: [] };
@@ -14030,7 +14682,7 @@ function pruneTtlMs() {
14030
14682
  }
14031
14683
  async function statPathStatus(p) {
14032
14684
  try {
14033
- const stat = await fs27.stat(p);
14685
+ const stat = await fs30.stat(p);
14034
14686
  return stat.isDirectory() ? "present" : "unknown";
14035
14687
  } catch (err) {
14036
14688
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -14075,14 +14727,14 @@ import cors from "@fastify/cors";
14075
14727
  import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
14076
14728
 
14077
14729
  // src/extend/index.ts
14078
- import { promises as fs29 } from "fs";
14079
- import path62 from "path";
14730
+ import { promises as fs32 } from "fs";
14731
+ import path65 from "path";
14080
14732
  import os3 from "os";
14081
14733
  import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
14082
14734
 
14083
14735
  // src/installers/package-manager.ts
14084
- import { promises as fs28 } from "fs";
14085
- import path61 from "path";
14736
+ import { promises as fs31 } from "fs";
14737
+ import path64 from "path";
14086
14738
  import { spawn } from "child_process";
14087
14739
  var LOCKFILE_PRIORITY = [
14088
14740
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -14097,29 +14749,29 @@ var LOCKFILE_PRIORITY = [
14097
14749
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
14098
14750
  async function exists2(p) {
14099
14751
  try {
14100
- await fs28.access(p);
14752
+ await fs31.access(p);
14101
14753
  return true;
14102
14754
  } catch {
14103
14755
  return false;
14104
14756
  }
14105
14757
  }
14106
14758
  async function detectPackageManager(serviceDir) {
14107
- let dir = path61.resolve(serviceDir);
14759
+ let dir = path64.resolve(serviceDir);
14108
14760
  const stops = /* @__PURE__ */ new Set();
14109
14761
  for (let i = 0; i < 64; i++) {
14110
14762
  if (stops.has(dir)) break;
14111
14763
  stops.add(dir);
14112
14764
  for (const candidate of LOCKFILE_PRIORITY) {
14113
- const lockPath = path61.join(dir, candidate.lockfile);
14765
+ const lockPath = path64.join(dir, candidate.lockfile);
14114
14766
  if (await exists2(lockPath)) {
14115
14767
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
14116
14768
  }
14117
14769
  }
14118
- const parent = path61.dirname(dir);
14770
+ const parent = path64.dirname(dir);
14119
14771
  if (parent === dir) break;
14120
14772
  dir = parent;
14121
14773
  }
14122
- return { pm: "npm", cwd: path61.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
14774
+ return { pm: "npm", cwd: path64.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
14123
14775
  }
14124
14776
  async function runPackageManagerInstall(cmd) {
14125
14777
  return new Promise((resolve) => {
@@ -14161,15 +14813,15 @@ ${err.message}`
14161
14813
  // src/extend/index.ts
14162
14814
  async function fileExists2(p) {
14163
14815
  try {
14164
- await fs29.access(p);
14816
+ await fs32.access(p);
14165
14817
  return true;
14166
14818
  } catch {
14167
14819
  return false;
14168
14820
  }
14169
14821
  }
14170
14822
  async function readPackageJson(scanPath) {
14171
- const pkgPath = path62.join(scanPath, "package.json");
14172
- const raw = await fs29.readFile(pkgPath, "utf8");
14823
+ const pkgPath = path65.join(scanPath, "package.json");
14824
+ const raw = await fs32.readFile(pkgPath, "utf8");
14173
14825
  return JSON.parse(raw);
14174
14826
  }
14175
14827
  var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
@@ -14183,15 +14835,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
14183
14835
  async function findHookFiles(scanPath) {
14184
14836
  const found = [];
14185
14837
  const walk9 = async (dir) => {
14186
- const entries = await fs29.readdir(dir, { withFileTypes: true }).catch(() => []);
14838
+ const entries = await fs32.readdir(dir, { withFileTypes: true }).catch(() => []);
14187
14839
  for (const entry of entries) {
14188
14840
  if (entry.isDirectory()) {
14189
14841
  if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
14190
- await walk9(path62.join(dir, entry.name));
14842
+ await walk9(path65.join(dir, entry.name));
14191
14843
  } else if (entry.isFile()) {
14192
14844
  if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
14193
- const rel = path62.relative(scanPath, path62.join(dir, entry.name));
14194
- found.push(rel.split(path62.sep).join("/"));
14845
+ const rel = path65.relative(scanPath, path65.join(dir, entry.name));
14846
+ found.push(rel.split(path65.sep).join("/"));
14195
14847
  }
14196
14848
  }
14197
14849
  }
@@ -14202,7 +14854,7 @@ async function findHookFiles(scanPath) {
14202
14854
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14203
14855
  let fallback = null;
14204
14856
  for (const file of hookFiles) {
14205
- const content = await fs29.readFile(path62.join(scanPath, file), "utf8");
14857
+ const content = await fs32.readFile(path65.join(scanPath, file), "utf8");
14206
14858
  const patched = splicedContent(content, snippet2);
14207
14859
  if (patched !== null) return { file, content, patched };
14208
14860
  if (fallback === null) fallback = { file, content };
@@ -14210,12 +14862,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
14210
14862
  return { file: fallback.file, content: fallback.content, patched: null };
14211
14863
  }
14212
14864
  function extendLogPath() {
14213
- return process.env.NEAT_EXTEND_LOG ?? path62.join(os3.homedir(), ".neat", "extend-log.ndjson");
14865
+ return process.env.NEAT_EXTEND_LOG ?? path65.join(os3.homedir(), ".neat", "extend-log.ndjson");
14214
14866
  }
14215
14867
  async function appendExtendLog(entry) {
14216
14868
  const logPath = extendLogPath();
14217
- await fs29.mkdir(path62.dirname(logPath), { recursive: true });
14218
- await fs29.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
14869
+ await fs32.mkdir(path65.dirname(logPath), { recursive: true });
14870
+ await fs32.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
14219
14871
  }
14220
14872
  function splicedContent(fileContent, snippet2) {
14221
14873
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -14273,7 +14925,7 @@ function lookupInstrumentation(library, installedVersion) {
14273
14925
  }
14274
14926
  async function describeProjectInstrumentation(ctx) {
14275
14927
  const hookFiles = await findHookFiles(ctx.scanPath);
14276
- const envNeat = await fileExists2(path62.join(ctx.scanPath, ".env.neat"));
14928
+ const envNeat = await fileExists2(path65.join(ctx.scanPath, ".env.neat"));
14277
14929
  const registryInstrPackages = new Set(
14278
14930
  registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
14279
14931
  );
@@ -14295,7 +14947,7 @@ async function applyExtension(ctx, args, options) {
14295
14947
  );
14296
14948
  }
14297
14949
  for (const file of hookFiles) {
14298
- const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
14950
+ const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
14299
14951
  if (content.includes(args.registration_snippet)) {
14300
14952
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
14301
14953
  }
@@ -14307,18 +14959,18 @@ async function applyExtension(ctx, args, options) {
14307
14959
  );
14308
14960
  }
14309
14961
  const primaryFile = primary.file;
14310
- const primaryPath = path62.join(ctx.scanPath, primaryFile);
14962
+ const primaryPath = path65.join(ctx.scanPath, primaryFile);
14311
14963
  const filesTouched = [];
14312
14964
  const depsAdded = [];
14313
- const pkgPath = path62.join(ctx.scanPath, "package.json");
14965
+ const pkgPath = path65.join(ctx.scanPath, "package.json");
14314
14966
  const pkg = await readPackageJson(ctx.scanPath);
14315
14967
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
14316
14968
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
14317
- await fs29.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14969
+ await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14318
14970
  filesTouched.push("package.json");
14319
14971
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
14320
14972
  }
14321
- await fs29.writeFile(primaryPath, primary.patched, "utf8");
14973
+ await fs32.writeFile(primaryPath, primary.patched, "utf8");
14322
14974
  filesTouched.push(primaryFile);
14323
14975
  const cmd = await detectPackageManager(ctx.scanPath);
14324
14976
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -14349,7 +15001,7 @@ async function dryRunExtension(ctx, args) {
14349
15001
  };
14350
15002
  }
14351
15003
  for (const file of hookFiles) {
14352
- const content = await fs29.readFile(path62.join(ctx.scanPath, file), "utf8");
15004
+ const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
14353
15005
  if (content.includes(args.registration_snippet)) {
14354
15006
  return {
14355
15007
  library: args.library,
@@ -14384,28 +15036,28 @@ async function rollbackExtension(ctx, args) {
14384
15036
  if (!await fileExists2(logPath)) {
14385
15037
  return { undone: false, message: "no apply found for library" };
14386
15038
  }
14387
- const raw = await fs29.readFile(logPath, "utf8");
15039
+ const raw = await fs32.readFile(logPath, "utf8");
14388
15040
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
14389
15041
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
14390
15042
  if (!match) {
14391
15043
  return { undone: false, message: "no apply found for library" };
14392
15044
  }
14393
- const pkgPath = path62.join(ctx.scanPath, "package.json");
15045
+ const pkgPath = path65.join(ctx.scanPath, "package.json");
14394
15046
  if (await fileExists2(pkgPath)) {
14395
15047
  const pkg = await readPackageJson(ctx.scanPath);
14396
15048
  if (pkg.dependencies?.[match.instrumentation_package]) {
14397
15049
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
14398
15050
  pkg.dependencies = rest;
14399
- await fs29.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
15051
+ await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
14400
15052
  }
14401
15053
  }
14402
15054
  const hookFiles = await findHookFiles(ctx.scanPath);
14403
15055
  for (const file of hookFiles) {
14404
- const filePath = path62.join(ctx.scanPath, file);
14405
- const content = await fs29.readFile(filePath, "utf8");
15056
+ const filePath = path65.join(ctx.scanPath, file);
15057
+ const content = await fs32.readFile(filePath, "utf8");
14406
15058
  if (content.includes(match.registration_snippet)) {
14407
15059
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
14408
- await fs29.writeFile(filePath, filtered, "utf8");
15060
+ await fs32.writeFile(filePath, filtered, "utf8");
14409
15061
  break;
14410
15062
  }
14411
15063
  }
@@ -14516,8 +15168,8 @@ data: ${JSON.stringify(envelope.payload)}
14516
15168
 
14517
15169
  // src/connectors-config.ts
14518
15170
  import os4 from "os";
14519
- import path63 from "path";
14520
- import { promises as fs30 } from "fs";
15171
+ import path66 from "path";
15172
+ import { promises as fs33 } from "fs";
14521
15173
  var CONNECTORS_CONFIG_VERSION = 1;
14522
15174
  var EnvRefUnsetError = class extends Error {
14523
15175
  ref;
@@ -14531,17 +15183,17 @@ var EnvRefUnsetError = class extends Error {
14531
15183
  };
14532
15184
  function neatHome2() {
14533
15185
  const override = process.env.NEAT_HOME;
14534
- if (override && override.length > 0) return path63.resolve(override);
14535
- return path63.join(os4.homedir(), ".neat");
15186
+ if (override && override.length > 0) return path66.resolve(override);
15187
+ return path66.join(os4.homedir(), ".neat");
14536
15188
  }
14537
15189
  function connectorsConfigPath(home = neatHome2()) {
14538
- return path63.join(home, "connectors.json");
15190
+ return path66.join(home, "connectors.json");
14539
15191
  }
14540
15192
  var MODE_MASK_LOOSER_THAN_0600 = 63;
14541
15193
  async function warnIfModeLooserThan0600(file) {
14542
15194
  if (process.platform === "win32") return;
14543
15195
  try {
14544
- const stat = await fs30.stat(file);
15196
+ const stat = await fs33.stat(file);
14545
15197
  if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
14546
15198
  const mode = (stat.mode & 511).toString(8).padStart(3, "0");
14547
15199
  console.warn(
@@ -14555,7 +15207,7 @@ async function readConnectorsConfig(home = neatHome2()) {
14555
15207
  const file = connectorsConfigPath(home);
14556
15208
  let raw;
14557
15209
  try {
14558
- raw = await fs30.readFile(file, "utf8");
15210
+ raw = await fs33.readFile(file, "utf8");
14559
15211
  } catch (err) {
14560
15212
  if (err.code === "ENOENT") {
14561
15213
  return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
@@ -14666,7 +15318,7 @@ function connectorMatchesProject(entry, project) {
14666
15318
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
14667
15319
  var CONNECTORS_LOCK_RETRY_MS = 50;
14668
15320
  function connectorsConfigLockPath(home = neatHome2()) {
14669
- return path63.join(home, "connectors.json.lock");
15321
+ return path66.join(home, "connectors.json.lock");
14670
15322
  }
14671
15323
  function isEnvRef(value) {
14672
15324
  return value.length > 1 && value.startsWith("$");
@@ -14679,9 +15331,9 @@ function redactCredentialRef(ref) {
14679
15331
  return out;
14680
15332
  }
14681
15333
  async function writeConfigAtomically0600(file, contents) {
14682
- await fs30.mkdir(path63.dirname(file), { recursive: true });
15334
+ await fs33.mkdir(path66.dirname(file), { recursive: true });
14683
15335
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
14684
- const fd = await fs30.open(tmp, "w", 384);
15336
+ const fd = await fs33.open(tmp, "w", 384);
14685
15337
  try {
14686
15338
  await fd.writeFile(contents, "utf8");
14687
15339
  await fd.chmod(384);
@@ -14689,14 +15341,14 @@ async function writeConfigAtomically0600(file, contents) {
14689
15341
  } finally {
14690
15342
  await fd.close();
14691
15343
  }
14692
- await fs30.rename(tmp, file);
15344
+ await fs33.rename(tmp, file);
14693
15345
  }
14694
15346
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
14695
15347
  const deadline = Date.now() + timeoutMs;
14696
- await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
15348
+ await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
14697
15349
  for (; ; ) {
14698
15350
  try {
14699
- const fd = await fs30.open(lockPath, "wx");
15351
+ const fd = await fs33.open(lockPath, "wx");
14700
15352
  try {
14701
15353
  await fd.writeFile(`${process.pid}
14702
15354
  `, "utf8");
@@ -14716,7 +15368,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
14716
15368
  }
14717
15369
  }
14718
15370
  async function releaseConnectorsLock(lockPath) {
14719
- await fs30.unlink(lockPath).catch(() => {
15371
+ await fs33.unlink(lockPath).catch(() => {
14720
15372
  });
14721
15373
  }
14722
15374
  async function withConnectorsLock(home, fn) {
@@ -14841,7 +15493,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
14841
15493
  }
14842
15494
 
14843
15495
  // src/connectors/index.ts
14844
- import { NodeType as NodeType26, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
15496
+ import { NodeType as NodeType29, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
14845
15497
  var NO_ENV = "unknown";
14846
15498
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
14847
15499
  if (!graph.hasNode(targetNodeId)) return void 0;
@@ -14860,7 +15512,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
14860
15512
  function routeCallSiteFor(graph, targetNodeId) {
14861
15513
  if (!graph.hasNode(targetNodeId)) return void 0;
14862
15514
  const attrs = graph.getNodeAttributes(targetNodeId);
14863
- if (attrs.type !== NodeType26.RouteNode || !attrs.path) return void 0;
15515
+ if (attrs.type !== NodeType29.RouteNode || !attrs.path) return void 0;
14864
15516
  const site = { relPath: attrs.path };
14865
15517
  if (attrs.line !== void 0) site.line = attrs.line;
14866
15518
  return site;
@@ -15345,10 +15997,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
15345
15997
  // src/connectors/supabase/map.ts
15346
15998
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
15347
15999
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
15348
- function targetFromRestPath(path64) {
15349
- const rpcMatch = REST_RPC_PATH_RE.exec(path64);
16000
+ function targetFromRestPath(path67) {
16001
+ const rpcMatch = REST_RPC_PATH_RE.exec(path67);
15350
16002
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
15351
- const tableMatch = REST_TABLE_PATH_RE.exec(path64);
16003
+ const tableMatch = REST_TABLE_PATH_RE.exec(path67);
15352
16004
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
15353
16005
  return null;
15354
16006
  }
@@ -15565,7 +16217,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
15565
16217
  }
15566
16218
 
15567
16219
  // src/connectors/railway/index.ts
15568
- import { EdgeType as EdgeType27, NodeType as NodeType27, serviceId as serviceId8 } from "@neat.is/types";
16220
+ import { EdgeType as EdgeType27, NodeType as NodeType30, serviceId as serviceId11 } from "@neat.is/types";
15569
16221
 
15570
16222
  // src/connectors/railway/client.ts
15571
16223
  var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
@@ -15715,7 +16367,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
15715
16367
  const out = [];
15716
16368
  graph.forEachNode((_id, attrs) => {
15717
16369
  const node = attrs;
15718
- if (node.type !== NodeType27.RouteNode) return;
16370
+ if (node.type !== NodeType30.RouteNode) return;
15719
16371
  const route = attrs;
15720
16372
  if (route.service !== serviceName) return;
15721
16373
  out.push({
@@ -15824,7 +16476,7 @@ function createRailwayResolveTarget(config) {
15824
16476
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
15825
16477
  const peerName = config.serviceNameById[signal.targetName];
15826
16478
  if (!peerName) return null;
15827
- return { targetNodeId: serviceId8(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
16479
+ return { targetNodeId: serviceId11(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
15828
16480
  }
15829
16481
  return null;
15830
16482
  };
@@ -15943,9 +16595,9 @@ function parseFirebaseTargetName(targetName) {
15943
16595
  const secondSep = rest.indexOf(FIELD_SEP);
15944
16596
  if (secondSep === -1) return null;
15945
16597
  const method = rest.slice(0, secondSep);
15946
- const path64 = rest.slice(secondSep + 1);
15947
- if (!resourceName || !method || !path64) return null;
15948
- return { resourceName, method, path: path64 };
16598
+ const path67 = rest.slice(secondSep + 1);
16599
+ if (!resourceName || !method || !path67) return null;
16600
+ return { resourceName, method, path: path67 };
15949
16601
  }
15950
16602
  function resourceNameFor(type, labels) {
15951
16603
  if (!labels) return null;
@@ -15983,14 +16635,14 @@ function mapLogEntryToSignal(entry) {
15983
16635
  if (!req) return null;
15984
16636
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
15985
16637
  const method = req.requestMethod.toUpperCase();
15986
- const path64 = pathFromRequestUrl(req.requestUrl);
15987
- if (path64 === null) return null;
16638
+ const path67 = pathFromRequestUrl(req.requestUrl);
16639
+ if (path67 === null) return null;
15988
16640
  const timestamp = entry.timestamp;
15989
16641
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
15990
16642
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
15991
16643
  return {
15992
16644
  targetKind: resourceType,
15993
- targetName: packFirebaseTargetName({ resourceName, method, path: path64 }),
16645
+ targetName: packFirebaseTargetName({ resourceName, method, path: path67 }),
15994
16646
  callCount: 1,
15995
16647
  errorCount: isError ? 1 : 0,
15996
16648
  lastObservedIso: timestamp
@@ -16006,7 +16658,7 @@ function mapLogEntriesToSignals(entries) {
16006
16658
  }
16007
16659
 
16008
16660
  // src/connectors/firebase/resolve.ts
16009
- import { NodeType as NodeType28, EdgeType as EdgeType28 } from "@neat.is/types";
16661
+ import { NodeType as NodeType31, EdgeType as EdgeType28 } from "@neat.is/types";
16010
16662
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
16011
16663
  switch (resourceType) {
16012
16664
  case "cloud_function":
@@ -16021,7 +16673,7 @@ function routeEntriesFor(graph, serviceName) {
16021
16673
  const entries = [];
16022
16674
  graph.forEachNode((_id, attrs) => {
16023
16675
  const node = attrs;
16024
- if (node.type !== NodeType28.RouteNode) return;
16676
+ if (node.type !== NodeType31.RouteNode) return;
16025
16677
  const route = attrs;
16026
16678
  if (route.service !== serviceName) return;
16027
16679
  entries.push({
@@ -16076,7 +16728,7 @@ function createFirebaseConnector(graph, serviceMap) {
16076
16728
  }
16077
16729
 
16078
16730
  // src/connectors/cloudflare/connector.ts
16079
- import { EdgeType as EdgeType29, NodeType as NodeType29, fileId as fileId4, infraId as infraId21 } from "@neat.is/types";
16731
+ import { EdgeType as EdgeType29, NodeType as NodeType32, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
16080
16732
 
16081
16733
  // src/connectors/cloudflare/client.ts
16082
16734
  import { randomUUID } from "crypto";
@@ -16187,7 +16839,7 @@ function mapEventToSignal(event) {
16187
16839
  if (Number.isNaN(observedAt.getTime())) return null;
16188
16840
  const statusCode = metadata?.statusCode;
16189
16841
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
16190
- const path64 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16842
+ const path67 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
16191
16843
  return {
16192
16844
  targetKind: CLOUDFLARE_TARGET_KIND,
16193
16845
  targetName: scriptName,
@@ -16195,7 +16847,7 @@ function mapEventToSignal(event) {
16195
16847
  errorCount: isError ? 1 : 0,
16196
16848
  lastObservedIso: observedAt.toISOString(),
16197
16849
  method,
16198
- ...path64 ? { path: path64 } : {},
16850
+ ...path67 ? { path: path67 } : {},
16199
16851
  ...typeof statusCode === "number" ? { statusCode } : {},
16200
16852
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
16201
16853
  };
@@ -16235,19 +16887,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
16235
16887
  graph.forEachNode((id, attrs) => {
16236
16888
  if (found) return;
16237
16889
  const a = attrs;
16238
- if (a.type === NodeType29.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16890
+ if (a.type === NodeType32.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
16239
16891
  found = id;
16240
16892
  }
16241
16893
  });
16242
16894
  return found;
16243
16895
  }
16244
- function findMatchingRouteNode(graph, serviceName, method, path64) {
16245
- const normalizedPath = normalizePathTemplate(path64);
16896
+ function findMatchingRouteNode(graph, serviceName, method, path67) {
16897
+ const normalizedPath = normalizePathTemplate(path67);
16246
16898
  let found = null;
16247
16899
  graph.forEachNode((id, attrs) => {
16248
16900
  if (found) return;
16249
16901
  const a = attrs;
16250
- if (a.type !== NodeType29.RouteNode || a.service !== serviceName) return;
16902
+ if (a.type !== NodeType32.RouteNode || a.service !== serviceName) return;
16251
16903
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
16252
16904
  const routeMethod = (a.method ?? "").toUpperCase();
16253
16905
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -16259,14 +16911,14 @@ function createCloudflareResolveTarget(config, graph) {
16259
16911
  return (signal) => {
16260
16912
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
16261
16913
  const scriptName = signal.targetName;
16262
- const { method, path: path64 } = signal;
16914
+ const { method, path: path67 } = signal;
16263
16915
  const resolveRouteGrain = (serviceName, wholeFileId) => {
16264
- if (!method || !path64) return wholeFileId;
16265
- return findMatchingRouteNode(graph, serviceName, method, path64) ?? wholeFileId;
16916
+ if (!method || !path67) return wholeFileId;
16917
+ return findMatchingRouteNode(graph, serviceName, method, path67) ?? wholeFileId;
16266
16918
  };
16267
16919
  const mapping = config.workers?.[scriptName];
16268
16920
  if (mapping) {
16269
- const wholeFileId = fileId4(mapping.service, mapping.entryFile);
16921
+ const wholeFileId = fileId5(mapping.service, mapping.entryFile);
16270
16922
  return {
16271
16923
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
16272
16924
  serviceName: mapping.service,
@@ -16592,9 +17244,9 @@ function parseCloudRunTargetName(targetName) {
16592
17244
  const secondSep = rest.indexOf(FIELD_SEP2);
16593
17245
  if (secondSep === -1) return null;
16594
17246
  const method = rest.slice(0, secondSep);
16595
- const path64 = rest.slice(secondSep + 1);
16596
- if (!serviceName || !method || !path64) return null;
16597
- return { serviceName, method, path: path64 };
17247
+ const path67 = rest.slice(secondSep + 1);
17248
+ if (!serviceName || !method || !path67) return null;
17249
+ return { serviceName, method, path: path67 };
16598
17250
  }
16599
17251
 
16600
17252
  // src/connectors/cloud-run/map.ts
@@ -16623,14 +17275,14 @@ function mapLogEntryToSignal2(entry) {
16623
17275
  if (!req) return null;
16624
17276
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
16625
17277
  const method = req.requestMethod.toUpperCase();
16626
- const path64 = pathFromRequestUrl2(req.requestUrl);
16627
- if (path64 === null) return null;
17278
+ const path67 = pathFromRequestUrl2(req.requestUrl);
17279
+ if (path67 === null) return null;
16628
17280
  const timestamp = entry.timestamp;
16629
17281
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
16630
17282
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
16631
17283
  return {
16632
17284
  targetKind: CLOUD_RUN_TARGET_KIND,
16633
- targetName: packCloudRunTargetName({ serviceName, method, path: path64 }),
17285
+ targetName: packCloudRunTargetName({ serviceName, method, path: path67 }),
16634
17286
  callCount: 1,
16635
17287
  errorCount: isError ? 1 : 0,
16636
17288
  lastObservedIso: timestamp
@@ -16646,14 +17298,14 @@ function mapLogEntriesToSignals2(entries) {
16646
17298
  }
16647
17299
 
16648
17300
  // src/connectors/cloud-run/resolve.ts
16649
- import { EdgeType as EdgeType31, NodeType as NodeType30, infraId as infraId23 } from "@neat.is/types";
17301
+ import { EdgeType as EdgeType31, NodeType as NodeType33, infraId as infraId23 } from "@neat.is/types";
16650
17302
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
16651
17303
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
16652
17304
  let found = null;
16653
17305
  graph.forEachNode((_id, attrs) => {
16654
17306
  if (found) return;
16655
17307
  const node = attrs;
16656
- if (node.type !== NodeType30.RouteNode) return;
17308
+ if (node.type !== NodeType33.RouteNode) return;
16657
17309
  const route = attrs;
16658
17310
  if (route.service !== serviceName || !route.pathTemplate) return;
16659
17311
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -16668,14 +17320,14 @@ function createCloudRunResolveTarget(graph, config) {
16668
17320
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
16669
17321
  const identity = parseCloudRunTargetName(signal.targetName);
16670
17322
  if (!identity) return null;
16671
- const { serviceName: gcpServiceName, method, path: path64 } = identity;
17323
+ const { serviceName: gcpServiceName, method, path: path67 } = identity;
16672
17324
  const mappedService = config.serviceMap?.[gcpServiceName];
16673
17325
  if (mappedService) {
16674
17326
  const routeNodeId = findMatchingRouteNode2(
16675
17327
  graph,
16676
17328
  mappedService,
16677
17329
  method,
16678
- normalizePathTemplate(path64)
17330
+ normalizePathTemplate(path67)
16679
17331
  );
16680
17332
  if (routeNodeId) {
16681
17333
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
@@ -16724,7 +17376,7 @@ function createCloudRunConnector(graph, config = {}) {
16724
17376
  }
16725
17377
 
16726
17378
  // src/connectors/render/index.ts
16727
- import { EdgeType as EdgeType32, NodeType as NodeType31 } from "@neat.is/types";
17379
+ import { EdgeType as EdgeType32, NodeType as NodeType34 } from "@neat.is/types";
16728
17380
 
16729
17381
  // src/connectors/render/types.ts
16730
17382
  function readRenderToken(credentials) {
@@ -16800,7 +17452,7 @@ function buildRenderRouteIndex(graph, serviceName) {
16800
17452
  const out = [];
16801
17453
  graph.forEachNode((_id, attrs) => {
16802
17454
  const node = attrs;
16803
- if (node.type !== NodeType31.RouteNode) return;
17455
+ if (node.type !== NodeType34.RouteNode) return;
16804
17456
  const route = attrs;
16805
17457
  if (route.service !== serviceName) return;
16806
17458
  out.push({
@@ -17284,7 +17936,7 @@ function mapBuildsToSignals(builds, serviceName) {
17284
17936
  }
17285
17937
 
17286
17938
  // src/connectors/eas/resolve.ts
17287
- import { EdgeType as EdgeType34, NodeType as NodeType32, parseFileId as parseFileId3 } from "@neat.is/types";
17939
+ import { EdgeType as EdgeType34, NodeType as NodeType35, parseFileId as parseFileId3 } from "@neat.is/types";
17288
17940
  var NO_ENV2 = "unknown";
17289
17941
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
17290
17942
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -17312,7 +17964,7 @@ function findConfigNode(graph, basenames, serviceName) {
17312
17964
  graph.forEachNode((id, attrs) => {
17313
17965
  if (scoped) return;
17314
17966
  const node = attrs;
17315
- if (node.type !== NodeType32.ConfigNode) return;
17967
+ if (node.type !== NodeType35.ConfigNode) return;
17316
17968
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
17317
17969
  if (anyMatch === null) anyMatch = id;
17318
17970
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -18924,4 +19576,4 @@ export {
18924
19576
  deprovisionConnector,
18925
19577
  buildApi
18926
19578
  };
18927
- //# sourceMappingURL=chunk-3PXRQH53.js.map
19579
+ //# sourceMappingURL=chunk-T4UGEMWN.js.map