@neat.is/core 0.10.6-dev.20261010 → 0.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -949,7 +949,7 @@ import {
949
949
  EdgeType as EdgeType5,
950
950
  GraphEdgeSchema,
951
951
  GraphNodeSchema,
952
- NodeType as NodeType4,
952
+ NodeType as NodeType5,
953
953
  Provenance as Provenance5,
954
954
  confidenceForObservedSignal,
955
955
  databaseId,
@@ -3758,6 +3758,97 @@ async function addRoutes(graph, services) {
3758
3758
  return { nodesAdded, edgesAdded };
3759
3759
  }
3760
3760
 
3761
+ // src/extract/source-baseline.ts
3762
+ import { NodeType as NodeType4 } from "@neat.is/types";
3763
+ var states = /* @__PURE__ */ new WeakMap();
3764
+ var REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
3765
+ function validCommit(commit, repository) {
3766
+ return !!commit && commit.repository === repository && /^[0-9a-f]{40}$/.test(commit.sha);
3767
+ }
3768
+ function stateOf(graph) {
3769
+ let state = states.get(graph);
3770
+ if (!state) {
3771
+ state = { repos: /* @__PURE__ */ new Map(), active: /* @__PURE__ */ new Set(), conflicted: /* @__PURE__ */ new Set() };
3772
+ states.set(graph, state);
3773
+ }
3774
+ return state;
3775
+ }
3776
+ function setEntry(state, repository, baseline, generation = /* @__PURE__ */ Symbol()) {
3777
+ state.repos.set(repository, {
3778
+ baseline,
3779
+ generation,
3780
+ services: state.repos.get(repository)?.services ?? /* @__PURE__ */ new Set()
3781
+ });
3782
+ }
3783
+ function readSourceBaselines(graph) {
3784
+ const state = states.get(graph);
3785
+ if (!state) return [];
3786
+ return [...state.repos.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, entry]) => ({ ...entry.baseline }));
3787
+ }
3788
+ function invalidateSourceBaseline(graph, repository, status = "unavailable") {
3789
+ if (repository !== void 0) {
3790
+ if (REPOSITORY.test(repository)) setEntry(stateOf(graph), repository, { status, repository });
3791
+ return;
3792
+ }
3793
+ const state = states.get(graph);
3794
+ if (!state) return;
3795
+ for (const generation of state.active) state.conflicted.add(generation);
3796
+ for (const name of state.repos.keys()) {
3797
+ setEntry(state, name, { status: "unavailable", repository: name });
3798
+ }
3799
+ }
3800
+ function retainSourceBaselines(graph, repositories) {
3801
+ const state = states.get(graph);
3802
+ if (!state) return;
3803
+ const keep = new Set(repositories);
3804
+ for (const name of [...state.repos.keys()]) if (!keep.has(name)) state.repos.delete(name);
3805
+ }
3806
+ function beginSourceExtraction(graph, source, commit) {
3807
+ const generation = /* @__PURE__ */ Symbol();
3808
+ const state = stateOf(graph);
3809
+ if (source === void 0) {
3810
+ invalidateSourceBaseline(graph);
3811
+ state.active.add(generation);
3812
+ return generation;
3813
+ }
3814
+ state.active.add(generation);
3815
+ if (!REPOSITORY.test(source)) return generation;
3816
+ setEntry(
3817
+ state,
3818
+ source,
3819
+ validCommit(commit, source) ? { status: "syncing", repository: source } : { status: "unavailable", repository: source },
3820
+ generation
3821
+ );
3822
+ return generation;
3823
+ }
3824
+ function servicesOf(graph, repository) {
3825
+ const services = /* @__PURE__ */ new Set();
3826
+ graph.forEachNode((_id, attrs) => {
3827
+ const node = attrs;
3828
+ if (node.type === NodeType4.FileNode && node.source === repository && node.service) {
3829
+ services.add(node.service);
3830
+ }
3831
+ });
3832
+ return services;
3833
+ }
3834
+ function finishSourceExtraction(graph, generation, source, commit, result) {
3835
+ const state = states.get(graph);
3836
+ if (!state) return;
3837
+ state.active.delete(generation);
3838
+ const conflicted = state.conflicted.delete(generation);
3839
+ if (source === void 0 || !REPOSITORY.test(source)) return;
3840
+ const entry = state.repos.get(source);
3841
+ if (!entry || entry.generation !== generation) return;
3842
+ entry.services = servicesOf(graph, source);
3843
+ let shared = false;
3844
+ for (const [name, other] of state.repos) {
3845
+ if (name === source || ![...entry.services].some((s) => other.services.has(s))) continue;
3846
+ shared = true;
3847
+ setEntry(state, name, { status: "unavailable", repository: name }, other.generation);
3848
+ }
3849
+ entry.baseline = !conflicted && !shared && validCommit(commit, source) && result.extractionErrors === 0 && result.skippedFiles === 0 ? { status: "ready", repository: source, sha: commit.sha } : { status: "unavailable", repository: source };
3850
+ }
3851
+
3761
3852
  // src/columns.ts
3762
3853
  import { Provenance as Provenance4 } from "@neat.is/types";
3763
3854
  var OBSERVED_COLUMN_CONFIDENCE = 0.9;
@@ -4271,7 +4362,7 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
4271
4362
  let best = null;
4272
4363
  graph.forEachNode((_id, attrs) => {
4273
4364
  const a = attrs;
4274
- if (a.type !== NodeType4.FileNode || a.service !== serviceName) return;
4365
+ if (a.type !== NodeType5.FileNode || a.service !== serviceName) return;
4275
4366
  if (a.discoveredVia === "otel") return;
4276
4367
  const p = a.path;
4277
4368
  if (!p) return;
@@ -4283,14 +4374,14 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
4283
4374
  }
4284
4375
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
4285
4376
  const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
4286
- const canonicalService = svcAttrs && svcAttrs.type === NodeType4.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
4377
+ const canonicalService = svcAttrs && svcAttrs.type === NodeType5.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
4287
4378
  const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
4288
4379
  const fileNodeId = fileId3(canonicalService, relPath);
4289
4380
  if (!graph.hasNode(fileNodeId)) {
4290
4381
  const language = languageForExt(relPath);
4291
4382
  const node = {
4292
4383
  id: fileNodeId,
4293
- type: NodeType4.FileNode,
4384
+ type: NodeType5.FileNode,
4294
4385
  service: canonicalService,
4295
4386
  path: relPath,
4296
4387
  ...language ? { language } : {},
@@ -4334,7 +4425,7 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
4334
4425
  if (!graph.hasNode(sid)) {
4335
4426
  const node = {
4336
4427
  id: sid,
4337
- type: NodeType4.SymbolNode,
4428
+ type: NodeType5.SymbolNode,
4338
4429
  kind: "function",
4339
4430
  qualname: fn,
4340
4431
  span: { startLine: line, endLine: line },
@@ -4365,7 +4456,7 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite, mint
4365
4456
  graph.forEachOutboundEdge(fileNodeId, (_edge, edgeAttrs, _source, target) => {
4366
4457
  if (edgeAttrs.type !== EdgeType5.CONTAINS) return;
4367
4458
  const t = graph.getNodeAttributes(target);
4368
- if (t.type !== NodeType4.SymbolNode) return;
4459
+ if (t.type !== NodeType5.SymbolNode) return;
4369
4460
  sawSymbol = true;
4370
4461
  if (line >= t.span.startLine && line <= t.span.endLine) {
4371
4462
  candidates.push({ id: target, symbol: t });
@@ -4408,7 +4499,7 @@ function ensureGraphqlOperationNode(graph, serviceName, operationType, operation
4408
4499
  if (graph.hasNode(id)) return id;
4409
4500
  const node = {
4410
4501
  id,
4411
- type: NodeType4.GraphQLOperationNode,
4502
+ type: NodeType5.GraphQLOperationNode,
4412
4503
  name: operationName,
4413
4504
  service: serviceName,
4414
4505
  operationType: operationType.toLowerCase(),
@@ -4426,7 +4517,7 @@ function ensureGrpcMethodNode(graph, rpcService, rpcMethod) {
4426
4517
  if (graph.hasNode(id)) return id;
4427
4518
  const node = {
4428
4519
  id,
4429
- type: NodeType4.GrpcMethodNode,
4520
+ type: NodeType5.GrpcMethodNode,
4430
4521
  name: `${rpcService}/${rpcMethod}`,
4431
4522
  rpcService,
4432
4523
  rpcMethod,
@@ -4443,7 +4534,7 @@ function ensureWebsocketChannelNode(graph, serviceName, channel) {
4443
4534
  if (graph.hasNode(id)) return id;
4444
4535
  const node = {
4445
4536
  id,
4446
- type: NodeType4.WebSocketChannelNode,
4537
+ type: NodeType5.WebSocketChannelNode,
4447
4538
  name: channel,
4448
4539
  service: serviceName,
4449
4540
  channel,
@@ -4460,7 +4551,7 @@ function ensureMessagingDestinationNode(graph, system, destination) {
4460
4551
  if (graph.hasNode(id)) return id;
4461
4552
  const node = {
4462
4553
  id,
4463
- type: NodeType4.InfraNode,
4554
+ type: NodeType5.InfraNode,
4464
4555
  name: destination,
4465
4556
  provider: "self",
4466
4557
  kind: messagingDestinationKind(system)
@@ -4514,7 +4605,7 @@ function resolveServiceId(graph, host, env) {
4514
4605
  graph.forEachNode((id, attrs) => {
4515
4606
  if (sameEnv) return;
4516
4607
  const a = attrs;
4517
- if (a.type !== NodeType4.ServiceNode) return;
4608
+ if (a.type !== NodeType5.ServiceNode) return;
4518
4609
  const matchesByName = a.name === host;
4519
4610
  const matchesByAlias = a.aliases ? a.aliases.includes(host) : false;
4520
4611
  if (!matchesByName && !matchesByAlias) return;
@@ -4536,7 +4627,7 @@ function resolveFusedServiceId(graph, serviceName, env) {
4536
4627
  if (graph.hasNode(id)) return id;
4537
4628
  const wanted = serviceName.toLowerCase();
4538
4629
  const extractedId = graph.findNode((_nid, attrs) => {
4539
- if (attrs.type !== NodeType4.ServiceNode) return false;
4630
+ if (attrs.type !== NodeType5.ServiceNode) return false;
4540
4631
  const svc = attrs;
4541
4632
  if (svc.discoveredVia === "otel") return false;
4542
4633
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
@@ -4548,7 +4639,7 @@ function ensureServiceNode(graph, serviceName, env) {
4548
4639
  if (graph.hasNode(resolved)) return resolved;
4549
4640
  const node = {
4550
4641
  id: resolved,
4551
- type: NodeType4.ServiceNode,
4642
+ type: NodeType5.ServiceNode,
4552
4643
  name: serviceName,
4553
4644
  language: "unknown",
4554
4645
  discoveredVia: "otel",
@@ -4562,7 +4653,7 @@ function ensureInfraNode(graph, kind, name, provider) {
4562
4653
  if (graph.hasNode(id)) return id;
4563
4654
  const node = {
4564
4655
  id,
4565
- type: NodeType4.InfraNode,
4656
+ type: NodeType5.InfraNode,
4566
4657
  name,
4567
4658
  provider,
4568
4659
  kind
@@ -4574,7 +4665,7 @@ var COLUMN_BEARING_INFRA_KINDS = /* @__PURE__ */ new Set(["sql-table", "supabase
4574
4665
  function mergeColumnsAt(graph, tableNodeId, columns, provenance, confidence) {
4575
4666
  if (!columns || columns.length === 0 || !graph.hasNode(tableNodeId)) return;
4576
4667
  const node = graph.getNodeAttributes(tableNodeId);
4577
- if (node.type !== NodeType4.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
4668
+ if (node.type !== NodeType5.InfraNode || !node.kind || !COLUMN_BEARING_INFRA_KINDS.has(node.kind)) {
4578
4669
  return;
4579
4670
  }
4580
4671
  graph.replaceNodeAttributes(tableNodeId, {
@@ -4598,7 +4689,7 @@ function ensureDatabaseNode(graph, host, engine) {
4598
4689
  if (graph.hasNode(id)) return id;
4599
4690
  const node = {
4600
4691
  id,
4601
- type: NodeType4.DatabaseNode,
4692
+ type: NodeType5.DatabaseNode,
4602
4693
  name: host,
4603
4694
  engine,
4604
4695
  engineVersion: "unknown",
@@ -4614,7 +4705,7 @@ function ensureLocalDatabaseNode(graph, serviceName, name, engine) {
4614
4705
  if (graph.hasNode(id)) return id;
4615
4706
  const node = {
4616
4707
  id,
4617
- type: NodeType4.DatabaseNode,
4708
+ type: NodeType5.DatabaseNode,
4618
4709
  name,
4619
4710
  engine,
4620
4711
  engineVersion: "unknown",
@@ -4639,7 +4730,7 @@ function findDeclaredDatabaseForService(graph, serviceNodeId, engine) {
4639
4730
  if (edge.type !== EdgeType5.CONNECTS_TO || edge.provenance !== Provenance5.EXTRACTED) continue;
4640
4731
  if (!graph.hasNode(edge.target)) continue;
4641
4732
  const target = graph.getNodeAttributes(edge.target);
4642
- if (target.type !== NodeType4.DatabaseNode || target.engine !== engine) continue;
4733
+ if (target.type !== NodeType5.DatabaseNode || target.engine !== engine) continue;
4643
4734
  matches.add(edge.target);
4644
4735
  }
4645
4736
  }
@@ -4654,7 +4745,7 @@ function ensureFrontierNode(graph, host, ts) {
4654
4745
  }
4655
4746
  const node = {
4656
4747
  id,
4657
- type: NodeType4.FrontierNode,
4748
+ type: NodeType5.FrontierNode,
4658
4749
  name: host,
4659
4750
  host,
4660
4751
  firstObserved: ts,
@@ -5052,7 +5143,7 @@ function findRouteNodeByHttpRoute(graph, serviceName, method, httpRoute) {
5052
5143
  graph.forEachNode((id, attrs) => {
5053
5144
  if (found) return;
5054
5145
  const a = attrs;
5055
- if (a.type !== NodeType4.RouteNode || a.service !== serviceName) return;
5146
+ if (a.type !== NodeType5.RouteNode || a.service !== serviceName) return;
5056
5147
  if (m && a.method !== "ALL" && a.method !== m) return;
5057
5148
  if (normalizePathTemplate(a.pathTemplate) === target) found = id;
5058
5149
  });
@@ -5315,7 +5406,7 @@ function promoteFrontierNodes(graph, opts = {}) {
5315
5406
  const aliasIndex = /* @__PURE__ */ new Map();
5316
5407
  graph.forEachNode((id, attrs) => {
5317
5408
  const a = attrs;
5318
- if (a.type !== NodeType4.ServiceNode) return;
5409
+ if (a.type !== NodeType5.ServiceNode) return;
5319
5410
  aliasIndex.set(a.name, id);
5320
5411
  if (a.aliases) {
5321
5412
  for (const alias of a.aliases) aliasIndex.set(alias, id);
@@ -5324,7 +5415,7 @@ function promoteFrontierNodes(graph, opts = {}) {
5324
5415
  const toPromote = [];
5325
5416
  graph.forEachNode((id, attrs) => {
5326
5417
  const a = attrs;
5327
- if (a.type !== NodeType4.FrontierNode) return;
5418
+ if (a.type !== NodeType5.FrontierNode) return;
5328
5419
  const target = aliasIndex.get(a.host);
5329
5420
  if (!target) return;
5330
5421
  if (target === id) return;
@@ -5589,6 +5680,7 @@ function describeZodIssues(error) {
5589
5680
  return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
5590
5681
  }
5591
5682
  function mergeSnapshot(graph, snapshot) {
5683
+ invalidateSourceBaseline(graph);
5592
5684
  const exported = snapshot.graph;
5593
5685
  const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
5594
5686
  const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
@@ -5639,7 +5731,7 @@ import {
5639
5731
  BlastRadiusResultSchema,
5640
5732
  EdgeType as EdgeType6,
5641
5733
  ExpandResultSchema,
5642
- NodeType as NodeType5,
5734
+ NodeType as NodeType6,
5643
5735
  ObservedDependenciesResultSchema,
5644
5736
  PROV_RANK,
5645
5737
  Provenance as Provenance6,
@@ -5655,23 +5747,23 @@ var BLAST_RADIUS_DEFAULT_DEPTH = 10;
5655
5747
  function isFrontierNode(graph, nodeId) {
5656
5748
  if (!graph.hasNode(nodeId)) return false;
5657
5749
  const attrs = graph.getNodeAttributes(nodeId);
5658
- return attrs.type === NodeType5.FrontierNode;
5750
+ return attrs.type === NodeType6.FrontierNode;
5659
5751
  }
5660
5752
  function resolveOwningService(graph, nodeId) {
5661
5753
  if (!graph.hasNode(nodeId)) return null;
5662
5754
  const attrs = graph.getNodeAttributes(nodeId);
5663
- if (attrs.type === NodeType5.ServiceNode) {
5755
+ if (attrs.type === NodeType6.ServiceNode) {
5664
5756
  return { id: nodeId, svc: attrs };
5665
5757
  }
5666
- if (attrs.type === NodeType5.FileNode || attrs.type === NodeType5.SymbolNode) {
5758
+ if (attrs.type === NodeType6.FileNode || attrs.type === NodeType6.SymbolNode) {
5667
5759
  for (const edgeId of graph.inboundEdges(nodeId)) {
5668
5760
  const e = graph.getEdgeAttributes(edgeId);
5669
5761
  if (e.type !== EdgeType6.CONTAINS) continue;
5670
5762
  const owner = graph.getNodeAttributes(e.source);
5671
- if (owner.type === NodeType5.ServiceNode) {
5763
+ if (owner.type === NodeType6.ServiceNode) {
5672
5764
  return { id: e.source, svc: owner };
5673
5765
  }
5674
- if (owner.type === NodeType5.FileNode) {
5766
+ if (owner.type === NodeType6.FileNode) {
5675
5767
  return resolveOwningService(graph, e.source);
5676
5768
  }
5677
5769
  }
@@ -5867,10 +5959,10 @@ function symbolRootCauseShape(graph, origin, walk11) {
5867
5959
  return serviceRootCauseShape(graph, owner.svc, walk11);
5868
5960
  }
5869
5961
  var rootCauseShapes = {
5870
- [NodeType5.DatabaseNode]: databaseRootCauseShape,
5871
- [NodeType5.ServiceNode]: serviceRootCauseShape,
5872
- [NodeType5.FileNode]: fileRootCauseShape,
5873
- [NodeType5.SymbolNode]: symbolRootCauseShape
5962
+ [NodeType6.DatabaseNode]: databaseRootCauseShape,
5963
+ [NodeType6.ServiceNode]: serviceRootCauseShape,
5964
+ [NodeType6.FileNode]: fileRootCauseShape,
5965
+ [NodeType6.SymbolNode]: symbolRootCauseShape
5874
5966
  };
5875
5967
  function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
5876
5968
  if (!graph.hasNode(errorNodeId)) return null;
@@ -5894,7 +5986,7 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
5894
5986
  };
5895
5987
  }
5896
5988
  }
5897
- if (origin.type === NodeType5.ServiceNode) {
5989
+ if (origin.type === NodeType6.ServiceNode) {
5898
5990
  const crossService = crossServiceRootCause(graph, errorNodeId, incidents, errorEvent);
5899
5991
  if (crossService) return { result: crossService, source: "cross-service" };
5900
5992
  }
@@ -5958,13 +6050,13 @@ function callSourcesForService(graph, serviceId17) {
5958
6050
  const e = graph.getEdgeAttributes(edgeId);
5959
6051
  if (e.type !== EdgeType6.CONTAINS) continue;
5960
6052
  const tgt = graph.getNodeAttributes(e.target);
5961
- if (tgt.type !== NodeType5.FileNode) continue;
6053
+ if (tgt.type !== NodeType6.FileNode) continue;
5962
6054
  ids.push(e.target);
5963
6055
  for (const symEdgeId of graph.outboundEdges(e.target)) {
5964
6056
  const se = graph.getEdgeAttributes(symEdgeId);
5965
6057
  if (se.type !== EdgeType6.CONTAINS) continue;
5966
6058
  const sym = graph.getNodeAttributes(se.target);
5967
- if (sym.type === NodeType5.SymbolNode) ids.push(se.target);
6059
+ if (sym.type === NodeType6.SymbolNode) ids.push(se.target);
5968
6060
  }
5969
6061
  }
5970
6062
  return ids;
@@ -6193,12 +6285,12 @@ function getObservedDependencies(graph, nodeId) {
6193
6285
  }
6194
6286
  const attrs = graph.getNodeAttributes(nodeId);
6195
6287
  const scope = [nodeId];
6196
- if (attrs.type === NodeType5.ServiceNode) {
6288
+ if (attrs.type === NodeType6.ServiceNode) {
6197
6289
  for (const edgeId of graph.outboundEdges(nodeId)) {
6198
6290
  const e = graph.getEdgeAttributes(edgeId);
6199
6291
  if (e.type !== EdgeType6.CONTAINS) continue;
6200
6292
  const owned = graph.getNodeAttributes(e.target);
6201
- if (owned.type === NodeType5.FileNode) scope.push(e.target);
6293
+ if (owned.type === NodeType6.FileNode) scope.push(e.target);
6202
6294
  }
6203
6295
  }
6204
6296
  const dependencies = [];
@@ -6254,12 +6346,12 @@ function nodeScope(graph, nodeId) {
6254
6346
  const scope = [nodeId];
6255
6347
  if (!graph.hasNode(nodeId)) return scope;
6256
6348
  const attrs = graph.getNodeAttributes(nodeId);
6257
- if (attrs.type === NodeType5.ServiceNode) {
6349
+ if (attrs.type === NodeType6.ServiceNode) {
6258
6350
  for (const edgeId of graph.outboundEdges(nodeId)) {
6259
6351
  const e = graph.getEdgeAttributes(edgeId);
6260
6352
  if (e.type !== EdgeType6.CONTAINS) continue;
6261
6353
  const owned = graph.getNodeAttributes(e.target);
6262
- if (owned.type === NodeType5.FileNode) scope.push(e.target);
6354
+ if (owned.type === NodeType6.FileNode) scope.push(e.target);
6263
6355
  }
6264
6356
  }
6265
6357
  return scope;
@@ -6414,9 +6506,9 @@ function hasFailingOutbound(graph, nodeId, seedSource, incidents) {
6414
6506
  function grainOf(graph, nodeId) {
6415
6507
  if (!graph.hasNode(nodeId)) return "unknown";
6416
6508
  const t = graph.getNodeAttributes(nodeId).type;
6417
- if (t === NodeType5.ServiceNode) return "service";
6418
- if (t === NodeType5.FileNode) return "file";
6419
- if (t === NodeType5.SymbolNode) return "symbol";
6509
+ if (t === NodeType6.ServiceNode) return "service";
6510
+ if (t === NodeType6.FileNode) return "file";
6511
+ if (t === NodeType6.SymbolNode) return "symbol";
6420
6512
  return t;
6421
6513
  }
6422
6514
  function findPath(graph, from, to, direction, maxDepth) {
@@ -6785,7 +6877,7 @@ import { promises as fs20 } from "fs";
6785
6877
  import path22 from "path";
6786
6878
  import ignore2 from "ignore";
6787
6879
  import { minimatch as minimatch2 } from "minimatch";
6788
- import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
6880
+ import { NodeType as NodeType16, serviceId as serviceId11 } from "@neat.is/types";
6789
6881
 
6790
6882
  // src/extract/python.ts
6791
6883
  import { promises as fs10 } from "fs";
@@ -6856,7 +6948,7 @@ function pythonToPackage(service) {
6856
6948
  // src/extract/go.ts
6857
6949
  import { promises as fs11 } from "fs";
6858
6950
  import path12 from "path";
6859
- import { NodeType as NodeType6, serviceId as serviceId2 } from "@neat.is/types";
6951
+ import { NodeType as NodeType7, serviceId as serviceId2 } from "@neat.is/types";
6860
6952
  function parseGoMod(source) {
6861
6953
  const module = source.match(/^\s*module\s+(\S+)\s*$/m)?.[1];
6862
6954
  if (!module) return null;
@@ -6893,7 +6985,7 @@ async function discoverGoService(scanPath, dir) {
6893
6985
  const framework = goFramework(mod.dependencies);
6894
6986
  const node = {
6895
6987
  id: serviceId2(name),
6896
- type: NodeType6.ServiceNode,
6988
+ type: NodeType7.ServiceNode,
6897
6989
  name,
6898
6990
  language: "go",
6899
6991
  dependencies: mod.dependencies,
@@ -6906,7 +6998,7 @@ async function discoverGoService(scanPath, dir) {
6906
6998
  // src/extract/ruby.ts
6907
6999
  import { promises as fs12 } from "fs";
6908
7000
  import path13 from "path";
6909
- import { NodeType as NodeType7, serviceId as serviceId3 } from "@neat.is/types";
7001
+ import { NodeType as NodeType8, serviceId as serviceId3 } from "@neat.is/types";
6910
7002
  function parseGemfile(source) {
6911
7003
  const dependencies = {};
6912
7004
  for (const raw of source.split(/\r?\n/)) {
@@ -6931,7 +7023,7 @@ async function discoverRubyService(scanPath, dir) {
6931
7023
  const pkg = { name, dependencies: gemfile.dependencies };
6932
7024
  const node = {
6933
7025
  id: serviceId3(name),
6934
- type: NodeType7.ServiceNode,
7026
+ type: NodeType8.ServiceNode,
6935
7027
  name,
6936
7028
  language: "ruby",
6937
7029
  dependencies: gemfile.dependencies,
@@ -6943,7 +7035,7 @@ async function discoverRubyService(scanPath, dir) {
6943
7035
 
6944
7036
  // src/extract/php.ts
6945
7037
  import path14 from "path";
6946
- import { NodeType as NodeType8, serviceId as serviceId4 } from "@neat.is/types";
7038
+ import { NodeType as NodeType9, serviceId as serviceId4 } from "@neat.is/types";
6947
7039
  function parseComposerJson(composer) {
6948
7040
  return {
6949
7041
  dependencies: {
@@ -6964,7 +7056,7 @@ async function discoverPhpService(scanPath, dir) {
6964
7056
  const pkg = { name, dependencies };
6965
7057
  const node = {
6966
7058
  id: serviceId4(name),
6967
- type: NodeType8.ServiceNode,
7059
+ type: NodeType9.ServiceNode,
6968
7060
  name,
6969
7061
  language: "php",
6970
7062
  dependencies,
@@ -6977,7 +7069,7 @@ async function discoverPhpService(scanPath, dir) {
6977
7069
  // src/extract/csharp.ts
6978
7070
  import { promises as fs13 } from "fs";
6979
7071
  import path15 from "path";
6980
- import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
7072
+ import { NodeType as NodeType10, serviceId as serviceId5 } from "@neat.is/types";
6981
7073
  var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
6982
7074
  async function projectFiles(dir) {
6983
7075
  const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
@@ -7025,7 +7117,7 @@ async function discoverCsharpService(scanPath, dir) {
7025
7117
  const pkg = { name, dependencies };
7026
7118
  const node = {
7027
7119
  id: serviceId5(name),
7028
- type: NodeType9.ServiceNode,
7120
+ type: NodeType10.ServiceNode,
7029
7121
  name,
7030
7122
  language: "csharp",
7031
7123
  dependencies,
@@ -7039,7 +7131,7 @@ async function discoverCsharpService(scanPath, dir) {
7039
7131
  // src/extract/java.ts
7040
7132
  import { promises as fs14 } from "fs";
7041
7133
  import path16 from "path";
7042
- import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
7134
+ import { NodeType as NodeType11, serviceId as serviceId6 } from "@neat.is/types";
7043
7135
  var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
7044
7136
  async function hasJavaManifest(dir) {
7045
7137
  for (const manifest of JAVA_MANIFESTS) {
@@ -7095,7 +7187,7 @@ async function discoverJavaService(scanPath, dir) {
7095
7187
  const pkg = { name, dependencies };
7096
7188
  const node = {
7097
7189
  id: serviceId6(name),
7098
- type: NodeType10.ServiceNode,
7190
+ type: NodeType11.ServiceNode,
7099
7191
  name,
7100
7192
  language: "java",
7101
7193
  dependencies,
@@ -7107,7 +7199,7 @@ async function discoverJavaService(scanPath, dir) {
7107
7199
  // src/extract/kotlin.ts
7108
7200
  import { promises as fs15 } from "fs";
7109
7201
  import path17 from "path";
7110
- import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
7202
+ import { NodeType as NodeType12, serviceId as serviceId7 } from "@neat.is/types";
7111
7203
  var KOTLIN_SOURCE_EXT = ".kt";
7112
7204
  var JAVA_SOURCE_EXT = ".java";
7113
7205
  var SOURCE_PROBE_MAX_DEPTH = 8;
@@ -7159,7 +7251,7 @@ async function discoverKotlinService(scanPath, dir) {
7159
7251
  const pkg = { name, dependencies };
7160
7252
  const node = {
7161
7253
  id: serviceId7(name),
7162
- type: NodeType11.ServiceNode,
7254
+ type: NodeType12.ServiceNode,
7163
7255
  name,
7164
7256
  language: "kotlin",
7165
7257
  dependencies,
@@ -7172,7 +7264,7 @@ async function discoverKotlinService(scanPath, dir) {
7172
7264
  import { promises as fs16 } from "fs";
7173
7265
  import path18 from "path";
7174
7266
  import { parse as parseToml2 } from "smol-toml";
7175
- import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
7267
+ import { NodeType as NodeType13, serviceId as serviceId8 } from "@neat.is/types";
7176
7268
  var CARGO_MANIFEST = "Cargo.toml";
7177
7269
  async function hasRustManifest(dir) {
7178
7270
  return exists(path18.join(dir, CARGO_MANIFEST));
@@ -7208,7 +7300,7 @@ async function discoverRustService(scanPath, dir) {
7208
7300
  const pkg = { name, dependencies };
7209
7301
  const node = {
7210
7302
  id: serviceId8(name),
7211
- type: NodeType12.ServiceNode,
7303
+ type: NodeType13.ServiceNode,
7212
7304
  name,
7213
7305
  language: "rust",
7214
7306
  dependencies,
@@ -7220,7 +7312,7 @@ async function discoverRustService(scanPath, dir) {
7220
7312
  // src/extract/cpp.ts
7221
7313
  import { promises as fs17 } from "fs";
7222
7314
  import path19 from "path";
7223
- import { NodeType as NodeType13, serviceId as serviceId9 } from "@neat.is/types";
7315
+ import { NodeType as NodeType14, serviceId as serviceId9 } from "@neat.is/types";
7224
7316
  var CMAKE_MANIFEST = "CMakeLists.txt";
7225
7317
  async function hasCppManifest(dir) {
7226
7318
  return exists(path19.join(dir, CMAKE_MANIFEST));
@@ -7239,7 +7331,7 @@ async function discoverCppService(scanPath, dir) {
7239
7331
  const pkg = { name, dependencies };
7240
7332
  const node = {
7241
7333
  id: serviceId9(name),
7242
- type: NodeType13.ServiceNode,
7334
+ type: NodeType14.ServiceNode,
7243
7335
  name,
7244
7336
  language: "cpp",
7245
7337
  dependencies,
@@ -7251,7 +7343,7 @@ async function discoverCppService(scanPath, dir) {
7251
7343
  // src/extract/dockerfile-service.ts
7252
7344
  import { promises as fs18 } from "fs";
7253
7345
  import path20 from "path";
7254
- import { NodeType as NodeType14, serviceId as serviceId10 } from "@neat.is/types";
7346
+ import { NodeType as NodeType15, serviceId as serviceId10 } from "@neat.is/types";
7255
7347
  function languageForSourceExt(ext) {
7256
7348
  switch (ext) {
7257
7349
  case ".py":
@@ -7338,7 +7430,7 @@ async function discoverDockerfileService(scanPath, dir) {
7338
7430
  const pkg = { name, dependencies: {} };
7339
7431
  const node = {
7340
7432
  id: serviceId10(name),
7341
- type: NodeType14.ServiceNode,
7433
+ type: NodeType15.ServiceNode,
7342
7434
  name,
7343
7435
  language,
7344
7436
  dependencies: {},
@@ -7532,7 +7624,7 @@ async function discoverNodeService(scanPath, dir) {
7532
7624
  const language = await detectJsServiceLanguage(dir, pkg);
7533
7625
  const node = {
7534
7626
  id: serviceId11(pkg.name),
7535
- type: NodeType15.ServiceNode,
7627
+ type: NodeType16.ServiceNode,
7536
7628
  name: pkg.name,
7537
7629
  language,
7538
7630
  version: pkg.version,
@@ -7549,7 +7641,7 @@ async function discoverPyService(scanPath, dir) {
7549
7641
  const pkg = pythonToPackage(py);
7550
7642
  const node = {
7551
7643
  id: serviceId11(py.name),
7552
- type: NodeType15.ServiceNode,
7644
+ type: NodeType16.ServiceNode,
7553
7645
  name: py.name,
7554
7646
  language: "python",
7555
7647
  version: py.version,
@@ -7667,7 +7759,7 @@ function addServiceNodes(graph, services) {
7667
7759
  import path23 from "path";
7668
7760
  import { promises as fs21 } from "fs";
7669
7761
  import { parseAllDocuments } from "yaml";
7670
- import { NodeType as NodeType16 } from "@neat.is/types";
7762
+ import { NodeType as NodeType17 } from "@neat.is/types";
7671
7763
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
7672
7764
  "Service",
7673
7765
  "Deployment",
@@ -7677,7 +7769,7 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
7677
7769
  function addAliases(graph, serviceId17, candidates) {
7678
7770
  if (!graph.hasNode(serviceId17)) return;
7679
7771
  const node = graph.getNodeAttributes(serviceId17);
7680
- if (node.type !== NodeType16.ServiceNode) return;
7772
+ if (node.type !== NodeType17.ServiceNode) return;
7681
7773
  const set = new Set(node.aliases ?? []);
7682
7774
  for (const c of candidates) {
7683
7775
  if (!c) continue;
@@ -7867,7 +7959,7 @@ import Rust2 from "tree-sitter-rust";
7867
7959
  import Cpp from "tree-sitter-cpp";
7868
7960
  import {
7869
7961
  EdgeType as EdgeType7,
7870
- NodeType as NodeType17,
7962
+ NodeType as NodeType18,
7871
7963
  Provenance as Provenance7,
7872
7964
  confidenceForExtracted as confidenceForExtracted4,
7873
7965
  extractedEdgeId as extractedEdgeId6,
@@ -7925,7 +8017,9 @@ function collectSymbolDefs(root) {
7925
8017
  kind,
7926
8018
  qualname,
7927
8019
  startLine: node.startPosition.row + 1,
7928
- endLine: node.endPosition.row + 1
8020
+ endLine: node.endPosition.row + 1,
8021
+ startIndex: node.startIndex,
8022
+ endIndex: node.endIndex
7929
8023
  });
7930
8024
  };
7931
8025
  const visit = (node, classCtx) => {
@@ -7984,7 +8078,9 @@ function collectPythonSymbolDefs(root) {
7984
8078
  kind,
7985
8079
  qualname,
7986
8080
  startLine: node.startPosition.row + 1,
7987
- endLine: node.endPosition.row + 1
8081
+ endLine: node.endPosition.row + 1,
8082
+ startIndex: node.startIndex,
8083
+ endIndex: node.endIndex
7988
8084
  });
7989
8085
  };
7990
8086
  const visit = (node, classCtx) => {
@@ -8057,7 +8153,9 @@ function collectGoSymbolDefs(root) {
8057
8153
  kind,
8058
8154
  qualname,
8059
8155
  startLine: node.startPosition.row + 1,
8060
- endLine: node.endPosition.row + 1
8156
+ endLine: node.endPosition.row + 1,
8157
+ startIndex: node.startIndex,
8158
+ endIndex: node.endIndex
8061
8159
  });
8062
8160
  };
8063
8161
  const visit = (node) => {
@@ -8091,7 +8189,9 @@ function collectRubySymbolDefs(root) {
8091
8189
  kind,
8092
8190
  qualname,
8093
8191
  startLine: node.startPosition.row + 1,
8094
- endLine: node.endPosition.row + 1
8192
+ endLine: node.endPosition.row + 1,
8193
+ startIndex: node.startIndex,
8194
+ endIndex: node.endIndex
8095
8195
  });
8096
8196
  };
8097
8197
  const nameText = (node) => node.childForFieldName("name")?.text.replace(/::/g, ".");
@@ -8142,7 +8242,9 @@ function collectPhpSymbolDefs(root) {
8142
8242
  kind,
8143
8243
  qualname,
8144
8244
  startLine: node.startPosition.row + 1,
8145
- endLine: node.endPosition.row + 1
8245
+ endLine: node.endPosition.row + 1,
8246
+ startIndex: node.startIndex,
8247
+ endIndex: node.endIndex
8146
8248
  });
8147
8249
  };
8148
8250
  const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
@@ -8208,7 +8310,9 @@ function collectCsharpSymbolDefs(root) {
8208
8310
  kind,
8209
8311
  qualname,
8210
8312
  startLine: node.startPosition.row + 1,
8211
- endLine: node.endPosition.row + 1
8313
+ endLine: node.endPosition.row + 1,
8314
+ startIndex: node.startIndex,
8315
+ endIndex: node.endIndex
8212
8316
  });
8213
8317
  };
8214
8318
  const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
@@ -8270,7 +8374,9 @@ function collectJavaSymbolDefs(root) {
8270
8374
  kind,
8271
8375
  qualname,
8272
8376
  startLine: node.startPosition.row + 1,
8273
- endLine: node.endPosition.row + 1
8377
+ endLine: node.endPosition.row + 1,
8378
+ startIndex: node.startIndex,
8379
+ endIndex: node.endIndex
8274
8380
  });
8275
8381
  };
8276
8382
  const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
@@ -8327,7 +8433,9 @@ function collectKotlinSymbolDefs(root) {
8327
8433
  kind,
8328
8434
  qualname,
8329
8435
  startLine: node.startPosition.row + 1,
8330
- endLine: node.endPosition.row + 1
8436
+ endLine: node.endPosition.row + 1,
8437
+ startIndex: node.startIndex,
8438
+ endIndex: node.endIndex
8331
8439
  });
8332
8440
  };
8333
8441
  const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
@@ -8397,7 +8505,9 @@ function collectRustSymbolDefs(root) {
8397
8505
  kind,
8398
8506
  qualname,
8399
8507
  startLine: node.startPosition.row + 1,
8400
- endLine: node.endPosition.row + 1
8508
+ endLine: node.endPosition.row + 1,
8509
+ startIndex: node.startIndex,
8510
+ endIndex: node.endIndex
8401
8511
  });
8402
8512
  };
8403
8513
  const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
@@ -8465,7 +8575,9 @@ function collectCppSymbolDefs(root) {
8465
8575
  kind,
8466
8576
  qualname,
8467
8577
  startLine: node.startPosition.row + 1,
8468
- endLine: node.endPosition.row + 1
8578
+ endLine: node.endPosition.row + 1,
8579
+ startIndex: node.startIndex,
8580
+ endIndex: node.endIndex
8469
8581
  });
8470
8582
  };
8471
8583
  const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
@@ -8593,6 +8705,22 @@ function disambiguate(defs) {
8593
8705
  return { def, disambiguator: ordinal };
8594
8706
  });
8595
8707
  }
8708
+ function utf8Offsets(content, defs) {
8709
+ if (content.includes("\uFFFD")) return /* @__PURE__ */ new Map();
8710
+ const indices = [...new Set(defs.flatMap(({ startIndex, endIndex }) => [startIndex, endIndex]))].sort(
8711
+ (a, b) => a - b
8712
+ );
8713
+ const offsets = /* @__PURE__ */ new Map();
8714
+ let cursor = 0;
8715
+ let bytes = 0;
8716
+ for (const index of indices) {
8717
+ if (index < cursor || index > content.length) return /* @__PURE__ */ new Map();
8718
+ bytes += Buffer.byteLength(content.slice(cursor, index), "utf8");
8719
+ offsets.set(index, bytes);
8720
+ cursor = index;
8721
+ }
8722
+ return offsets;
8723
+ }
8596
8724
  async function addSymbols(graph, services) {
8597
8725
  const parsers = /* @__PURE__ */ new Map();
8598
8726
  const parserForExt5 = (ext) => {
@@ -8616,37 +8744,49 @@ async function addSymbols(graph, services) {
8616
8744
  if (!parser) continue;
8617
8745
  const relPath = toPosix(path25.relative(service.dir, file.path));
8618
8746
  let defs;
8747
+ let completeParse;
8619
8748
  try {
8620
8749
  const tree = parseSource3(parser, file.content);
8621
8750
  defs = collectSymbolDefsForExt(ext, tree.rootNode);
8751
+ completeParse = !tree.rootNode.hasError;
8622
8752
  } catch (err) {
8623
8753
  recordExtractionError("symbol extraction", file.path, err);
8624
8754
  continue;
8625
8755
  }
8626
8756
  if (defs.length === 0) continue;
8627
- const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
8628
- graph,
8629
- service.pkg.name,
8630
- service.node.id,
8631
- relPath
8632
- );
8757
+ const offsets = completeParse ? utf8Offsets(file.content, defs) : /* @__PURE__ */ new Map();
8758
+ const {
8759
+ fileNodeId,
8760
+ nodesAdded: fn,
8761
+ edgesAdded: fe
8762
+ } = ensureFileNode(graph, service.pkg.name, service.node.id, relPath);
8633
8763
  nodesAdded += fn;
8634
8764
  edgesAdded += fe;
8635
8765
  for (const { def, disambiguator } of disambiguate(defs)) {
8636
8766
  const sid = symbolId2(service.pkg.name, relPath, def.qualname, disambiguator);
8767
+ const startByte = offsets.get(def.startIndex);
8768
+ const endByte = offsets.get(def.endIndex);
8769
+ const precise = startByte !== void 0 && endByte !== void 0 && endByte > startByte ? { startByte, endByte } : {};
8770
+ const node = {
8771
+ id: sid,
8772
+ type: NodeType18.SymbolNode,
8773
+ kind: def.kind,
8774
+ qualname: def.qualname,
8775
+ span: { startLine: def.startLine, endLine: def.endLine, ...precise },
8776
+ service: service.pkg.name,
8777
+ relPath,
8778
+ discoveredVia: "static"
8779
+ };
8637
8780
  if (!graph.hasNode(sid)) {
8638
- const node = {
8639
- id: sid,
8640
- type: NodeType17.SymbolNode,
8641
- kind: def.kind,
8642
- qualname: def.qualname,
8643
- span: { startLine: def.startLine, endLine: def.endLine },
8644
- service: service.pkg.name,
8645
- relPath,
8646
- discoveredVia: "static"
8647
- };
8648
8781
  graph.addNode(sid, node);
8649
8782
  nodesAdded++;
8783
+ } else {
8784
+ const previous = graph.getNodeAttributes(sid);
8785
+ const discoveredVia = previous.discoveredVia === "otel" || previous.discoveredVia === "merged" ? "merged" : "static";
8786
+ const moved = previous.span.startLine !== node.span.startLine || previous.span.endLine !== node.span.endLine || previous.span.startByte !== node.span.startByte || previous.span.endByte !== node.span.endByte;
8787
+ if (moved || previous.kind !== node.kind || previous.qualname !== node.qualname || previous.discoveredVia !== discoveredVia || previous.service !== node.service || previous.relPath !== node.relPath) {
8788
+ graph.replaceNodeAttributes(sid, { ...previous, ...node, discoveredVia });
8789
+ }
8650
8790
  }
8651
8791
  const containsId = extractedEdgeId6(fileNodeId, sid, EdgeType7.CONTAINS);
8652
8792
  if (!graph.hasEdge(containsId)) {
@@ -8677,7 +8817,7 @@ import path26 from "path";
8677
8817
  import Parser4 from "tree-sitter";
8678
8818
  import {
8679
8819
  EdgeType as EdgeType8,
8680
- NodeType as NodeType18,
8820
+ NodeType as NodeType19,
8681
8821
  Provenance as Provenance8,
8682
8822
  confidenceForExtracted as confidenceForExtracted5,
8683
8823
  extractedEdgeId as extractedEdgeId7,
@@ -8832,7 +8972,7 @@ async function addSymbolEdges(graph, services) {
8832
8972
  const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
8833
8973
  if (graph.hasNode(candidate)) {
8834
8974
  const node = graph.getNodeAttributes(candidate);
8835
- if (node.type === NodeType18.SymbolNode && node.kind === wantKind) return candidate;
8975
+ if (node.type === NodeType19.SymbolNode && node.kind === wantKind) return candidate;
8836
8976
  }
8837
8977
  }
8838
8978
  return null;
@@ -8928,7 +9068,7 @@ import path27 from "path";
8928
9068
  import Parser5 from "tree-sitter";
8929
9069
  import {
8930
9070
  EdgeType as EdgeType9,
8931
- NodeType as NodeType19,
9071
+ NodeType as NodeType20,
8932
9072
  Provenance as Provenance9,
8933
9073
  confidenceForExtracted as confidenceForExtracted6,
8934
9074
  extractedEdgeId as extractedEdgeId8,
@@ -9107,7 +9247,7 @@ async function addServerActions(graph, services) {
9107
9247
  if (!graph.hasNode(aid)) {
9108
9248
  const node = {
9109
9249
  id: aid,
9110
- type: NodeType19.ServerActionNode,
9250
+ type: NodeType20.ServerActionNode,
9111
9251
  name: action.exportName,
9112
9252
  service: service.pkg.name,
9113
9253
  module: relPath,
@@ -9199,7 +9339,7 @@ async function addServerActions(graph, services) {
9199
9339
  import path36 from "path";
9200
9340
  import {
9201
9341
  EdgeType as EdgeType10,
9202
- NodeType as NodeType20,
9342
+ NodeType as NodeType21,
9203
9343
  Provenance as Provenance10,
9204
9344
  configId,
9205
9345
  databaseId as databaseId2,
@@ -9853,7 +9993,7 @@ function compatibleDriversFor(engine) {
9853
9993
  function toDatabaseNode(config) {
9854
9994
  return {
9855
9995
  id: databaseId2(config.host),
9856
- type: NodeType20.DatabaseNode,
9996
+ type: NodeType21.DatabaseNode,
9857
9997
  name: config.database || config.host,
9858
9998
  engine: config.engine,
9859
9999
  engineVersion: config.engineVersion,
@@ -10018,7 +10158,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
10018
10158
  if (!graph.hasNode(cfgId)) {
10019
10159
  const cfgNode = {
10020
10160
  id: cfgId,
10021
- type: NodeType20.ConfigNode,
10161
+ type: NodeType21.ConfigNode,
10022
10162
  name: path36.basename(primary.sourceFile),
10023
10163
  path: relPath,
10024
10164
  fileType: isConfigFile(path36.basename(primary.sourceFile)).fileType || "config"
@@ -10065,7 +10205,7 @@ import { promises as fs25 } from "fs";
10065
10205
  import path37 from "path";
10066
10206
  import {
10067
10207
  EdgeType as EdgeType11,
10068
- NodeType as NodeType21,
10208
+ NodeType as NodeType22,
10069
10209
  Provenance as Provenance11,
10070
10210
  configId as configId2,
10071
10211
  confidenceForExtracted as confidenceForExtracted8
@@ -10099,7 +10239,7 @@ async function addConfigNodes(graph, services, scanPath) {
10099
10239
  const relPath = path37.relative(scanPath, file);
10100
10240
  const node = {
10101
10241
  id: configId2(relPath),
10102
- type: NodeType21.ConfigNode,
10242
+ type: NodeType22.ConfigNode,
10103
10243
  name: path37.basename(file),
10104
10244
  path: relPath,
10105
10245
  fileType: isConfigFile(path37.basename(file)).fileType
@@ -10140,7 +10280,7 @@ import { promises as fs26 } from "fs";
10140
10280
  import path38 from "path";
10141
10281
  import {
10142
10282
  EdgeType as EdgeType12,
10143
- NodeType as NodeType22,
10283
+ NodeType as NodeType23,
10144
10284
  Provenance as Provenance12,
10145
10285
  confidenceForExtracted as confidenceForExtracted9,
10146
10286
  extractedEdgeId as extractedEdgeId9,
@@ -10229,7 +10369,7 @@ async function addGrpcMethods(graph, services) {
10229
10369
  if (!graph.hasNode(mid)) {
10230
10370
  const node = {
10231
10371
  id: mid,
10232
- type: NodeType22.GrpcMethodNode,
10372
+ type: NodeType23.GrpcMethodNode,
10233
10373
  name: `${method.rpcService}/${method.rpcMethod}`,
10234
10374
  rpcService: method.rpcService,
10235
10375
  rpcMethod: method.rpcMethod,
@@ -10267,7 +10407,7 @@ async function addGrpcMethods(graph, services) {
10267
10407
  // src/extract/calls/index.ts
10268
10408
  import {
10269
10409
  EdgeType as EdgeType15,
10270
- NodeType as NodeType24,
10410
+ NodeType as NodeType25,
10271
10411
  Provenance as Provenance15,
10272
10412
  confidenceForExtracted as confidenceForExtracted12,
10273
10413
  passesExtractedFloor as passesExtractedFloor3
@@ -10429,7 +10569,7 @@ import Parser7 from "tree-sitter";
10429
10569
  import JavaScript5 from "tree-sitter-javascript";
10430
10570
  import {
10431
10571
  EdgeType as EdgeType14,
10432
- NodeType as NodeType23,
10572
+ NodeType as NodeType24,
10433
10573
  Provenance as Provenance14,
10434
10574
  confidenceForExtracted as confidenceForExtracted11,
10435
10575
  passesExtractedFloor as passesExtractedFloor2,
@@ -10678,7 +10818,7 @@ function buildRouteIndex(graph) {
10678
10818
  const index = /* @__PURE__ */ new Map();
10679
10819
  graph.forEachNode((_id, attrs) => {
10680
10820
  const node = attrs;
10681
- if (node.type !== NodeType23.RouteNode) return;
10821
+ if (node.type !== NodeType24.RouteNode) return;
10682
10822
  const route = attrs;
10683
10823
  const owner = serviceId12(route.service);
10684
10824
  const entry = {
@@ -14357,7 +14497,7 @@ async function addExternalEndpointEdges(graph, services) {
14357
14497
  if (!graph.hasNode(ep.infraId)) {
14358
14498
  const node = {
14359
14499
  id: ep.infraId,
14360
- type: NodeType24.InfraNode,
14500
+ type: NodeType25.InfraNode,
14361
14501
  name: ep.name,
14362
14502
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
14363
14503
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -14370,7 +14510,7 @@ async function addExternalEndpointEdges(graph, services) {
14370
14510
  }
14371
14511
  if (ep.columns && ep.columns.length > 0) {
14372
14512
  const node = graph.getNodeAttributes(ep.infraId);
14373
- if (node.type === NodeType24.InfraNode) {
14513
+ if (node.type === NodeType25.InfraNode) {
14374
14514
  graph.replaceNodeAttributes(ep.infraId, {
14375
14515
  ...node,
14376
14516
  columns: foldColumns(
@@ -14384,7 +14524,7 @@ async function addExternalEndpointEdges(graph, services) {
14384
14524
  }
14385
14525
  if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
14386
14526
  const node = graph.getNodeAttributes(ep.infraId);
14387
- if (node.type === NodeType24.InfraNode) {
14527
+ if (node.type === NodeType25.InfraNode) {
14388
14528
  graph.replaceNodeAttributes(ep.infraId, {
14389
14529
  ...node,
14390
14530
  columns: foldSdkWrites(node.columns, ep.sdkWrites)
@@ -14446,7 +14586,7 @@ async function addCallEdges(graph, services) {
14446
14586
  // src/extract/table-edges.ts
14447
14587
  import {
14448
14588
  EdgeType as EdgeType16,
14449
- NodeType as NodeType25,
14589
+ NodeType as NodeType26,
14450
14590
  Provenance as Provenance16,
14451
14591
  confidenceForExtracted as confidenceForExtracted13,
14452
14592
  extractedEdgeId as extractedEdgeId10,
@@ -14505,7 +14645,7 @@ function ensureTableNode(graph, id, name) {
14505
14645
  if (graph.hasNode(id)) return 0;
14506
14646
  const node = {
14507
14647
  id,
14508
- type: NodeType25.InfraNode,
14648
+ type: NodeType26.InfraNode,
14509
14649
  name,
14510
14650
  provider: "self",
14511
14651
  kind: "sql-table"
@@ -14519,11 +14659,11 @@ import path58 from "path";
14519
14659
  import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
14520
14660
 
14521
14661
  // src/extract/infra/shared.ts
14522
- import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId20 } from "@neat.is/types";
14662
+ import { NodeType as NodeType27, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId20 } from "@neat.is/types";
14523
14663
  function makeInfraNode(kind, name, provider = "self", extras) {
14524
14664
  return {
14525
14665
  id: infraId20(kind, name),
14526
- type: NodeType26.InfraNode,
14666
+ type: NodeType27.InfraNode,
14527
14667
  name,
14528
14668
  provider,
14529
14669
  kind,
@@ -15398,7 +15538,7 @@ import Parser18 from "tree-sitter";
15398
15538
  import JavaScript8 from "tree-sitter-javascript";
15399
15539
  import {
15400
15540
  EdgeType as EdgeType25,
15401
- NodeType as NodeType27,
15541
+ NodeType as NodeType28,
15402
15542
  Provenance as Provenance22,
15403
15543
  confidenceForExtracted as confidenceForExtracted19,
15404
15544
  extractedEdgeId as extractedEdgeId11,
@@ -15544,7 +15684,7 @@ async function addZodShapes(graph, services) {
15544
15684
  if (!graph.hasNode(shape.infraId)) {
15545
15685
  const node = {
15546
15686
  id: shape.infraId,
15547
- type: NodeType27.InfraNode,
15687
+ type: NodeType28.InfraNode,
15548
15688
  name: shape.name,
15549
15689
  provider: "self",
15550
15690
  kind: "zod-schema"
@@ -15554,7 +15694,7 @@ async function addZodShapes(graph, services) {
15554
15694
  }
15555
15695
  if (shape.fields.length > 0) {
15556
15696
  const node = graph.getNodeAttributes(shape.infraId);
15557
- if (node.type === NodeType27.InfraNode) {
15697
+ if (node.type === NodeType28.InfraNode) {
15558
15698
  graph.replaceNodeAttributes(shape.infraId, {
15559
15699
  ...node,
15560
15700
  columns: foldColumns(
@@ -15596,7 +15736,7 @@ async function addZodShapes(graph, services) {
15596
15736
  }
15597
15737
 
15598
15738
  // src/extract/firestore-rules.ts
15599
- import { NodeType as NodeType28 } from "@neat.is/types";
15739
+ import { NodeType as NodeType29 } from "@neat.is/types";
15600
15740
  var FIRESTORE_COLLECTION_KIND = "firestore-collection";
15601
15741
  var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
15602
15742
  function stripComments(src) {
@@ -15767,7 +15907,7 @@ async function addFirestoreRules(graph, services) {
15767
15907
  if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
15768
15908
  graph.forEachNode((id, attrs) => {
15769
15909
  const node = attrs;
15770
- if (node.type !== NodeType28.InfraNode) return;
15910
+ if (node.type !== NodeType29.InfraNode) return;
15771
15911
  if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
15772
15912
  const fields = guards.get(collectionKeyFromName(node.name));
15773
15913
  if (!fields || fields.size === 0) return;
@@ -15785,12 +15925,12 @@ import path68 from "path";
15785
15925
  // src/extract/retire.ts
15786
15926
  import { existsSync as existsSync2 } from "fs";
15787
15927
  import path67 from "path";
15788
- import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
15928
+ import { NodeType as NodeType30, Provenance as Provenance23 } from "@neat.is/types";
15789
15929
  function dropOrphanedFileNodes(graph, source) {
15790
15930
  const orphans = [];
15791
15931
  graph.forEachNode((id, attrs) => {
15792
15932
  const node = attrs;
15793
- if (node.type !== NodeType29.FileNode) return;
15933
+ if (node.type !== NodeType30.FileNode) return;
15794
15934
  if (source && node.source !== source) return;
15795
15935
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
15796
15936
  orphans.push(id);
@@ -15817,7 +15957,7 @@ function outOfScope(graph, edge, source) {
15817
15957
  for (const endpoint2 of [edge.source, edge.target]) {
15818
15958
  if (!graph.hasNode(endpoint2)) continue;
15819
15959
  const node = graph.getNodeAttributes(endpoint2);
15820
- if (node.type !== NodeType29.FileNode) continue;
15960
+ if (node.type !== NodeType30.FileNode) continue;
15821
15961
  return node.source !== source;
15822
15962
  }
15823
15963
  return false;
@@ -15846,7 +15986,23 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = [], so
15846
15986
  // src/extract/index.ts
15847
15987
  var passQueue = Promise.resolve();
15848
15988
  function extractFromDirectory(graph, scanPath, opts = {}) {
15849
- const pass = passQueue.then(() => runExtractionPass(graph, scanPath, opts));
15989
+ invalidateSourceBaseline(graph, opts.source, "syncing");
15990
+ const pass = passQueue.then(async () => {
15991
+ const sourceGeneration = beginSourceExtraction(graph, opts.source, opts.sourceCommit);
15992
+ let result;
15993
+ try {
15994
+ result = await runExtractionPass(graph, scanPath, opts);
15995
+ return result;
15996
+ } finally {
15997
+ finishSourceExtraction(
15998
+ graph,
15999
+ sourceGeneration,
16000
+ opts.source,
16001
+ opts.sourceCommit,
16002
+ result ?? { extractionErrors: 1, skippedFiles: 0 }
16003
+ );
16004
+ }
16005
+ });
15850
16006
  passQueue = pass.catch(() => {
15851
16007
  });
15852
16008
  return pass;
@@ -15943,7 +16099,7 @@ import {
15943
16099
  databaseId as databaseId3,
15944
16100
  DivergenceResultSchema,
15945
16101
  EdgeType as EdgeType26,
15946
- NodeType as NodeType30,
16102
+ NodeType as NodeType31,
15947
16103
  parseEdgeId,
15948
16104
  parseFileId,
15949
16105
  parseSymbolId as parseSymbolId2,
@@ -15958,7 +16114,7 @@ function bucketSourceFor(graph, edge) {
15958
16114
  const parsed = parseFileId(edge.source);
15959
16115
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
15960
16116
  const target = graph.getNodeAttributes(edge.target);
15961
- if (target.type !== NodeType30.DatabaseNode) return edge.source;
16117
+ if (target.type !== NodeType31.DatabaseNode) return edge.source;
15962
16118
  return serviceId14(parsed.service);
15963
16119
  }
15964
16120
  function bucketEdges(graph) {
@@ -15990,22 +16146,22 @@ function bucketEdges(graph) {
15990
16146
  function nodeIsFrontier(graph, nodeId) {
15991
16147
  if (!graph.hasNode(nodeId)) return false;
15992
16148
  const attrs = graph.getNodeAttributes(nodeId);
15993
- return attrs.type === NodeType30.FrontierNode;
16149
+ return attrs.type === NodeType31.FrontierNode;
15994
16150
  }
15995
16151
  function nodeIsWebsocketChannel(graph, nodeId) {
15996
16152
  if (!graph.hasNode(nodeId)) return false;
15997
16153
  const attrs = graph.getNodeAttributes(nodeId);
15998
- return attrs.type === NodeType30.WebSocketChannelNode;
16154
+ return attrs.type === NodeType31.WebSocketChannelNode;
15999
16155
  }
16000
16156
  function nodeIsServerAction(graph, nodeId) {
16001
16157
  if (!graph.hasNode(nodeId)) return false;
16002
16158
  const attrs = graph.getNodeAttributes(nodeId);
16003
- return attrs.type === NodeType30.ServerActionNode;
16159
+ return attrs.type === NodeType31.ServerActionNode;
16004
16160
  }
16005
16161
  function nodeIsSymbol(graph, nodeId) {
16006
16162
  if (!graph.hasNode(nodeId)) return false;
16007
16163
  const attrs = graph.getNodeAttributes(nodeId);
16008
- return attrs.type === NodeType30.SymbolNode;
16164
+ return attrs.type === NodeType31.SymbolNode;
16009
16165
  }
16010
16166
  function clampConfidence(n) {
16011
16167
  if (!Number.isFinite(n)) return 0;
@@ -16090,7 +16246,7 @@ function detectHostMismatch(graph, svcId, svc) {
16090
16246
  if (edge.type !== EdgeType26.CONNECTS_TO) continue;
16091
16247
  if (edge.provenance !== Provenance24.OBSERVED) continue;
16092
16248
  const target = graph.getNodeAttributes(edge.target);
16093
- if (target.type !== NodeType30.DatabaseNode) continue;
16249
+ if (target.type !== NodeType31.DatabaseNode) continue;
16094
16250
  const observedHost = target.host?.trim();
16095
16251
  if (!observedHost) continue;
16096
16252
  if (observedHost === declaredHost) continue;
@@ -16115,7 +16271,7 @@ function detectCompatDivergences(graph, svcId, svc) {
16115
16271
  if (edge.type !== EdgeType26.CONNECTS_TO) continue;
16116
16272
  if (edge.provenance !== Provenance24.OBSERVED) continue;
16117
16273
  const target = graph.getNodeAttributes(edge.target);
16118
- if (target.type !== NodeType30.DatabaseNode) continue;
16274
+ if (target.type !== NodeType31.DatabaseNode) continue;
16119
16275
  for (const pair of compatPairs()) {
16120
16276
  if (pair.engine !== target.engine) continue;
16121
16277
  const declared = deps[pair.driver];
@@ -16480,7 +16636,7 @@ function involvesNode(d, nodeId) {
16480
16636
  function datastoreEverObserved(graph, nodeId) {
16481
16637
  if (!graph.hasNode(nodeId)) return false;
16482
16638
  const n = graph.getNodeAttributes(nodeId);
16483
- if (n.type === NodeType30.DatabaseNode) {
16639
+ if (n.type === NodeType31.DatabaseNode) {
16484
16640
  const via = n.discoveredVia;
16485
16641
  if (via === "otel" || via === "merged") return true;
16486
16642
  }
@@ -16497,7 +16653,7 @@ function serviceHasObservedSameEngineStore(graph, buckets2, serviceId17, engine,
16497
16653
  if (bucket.target === excludeTarget) continue;
16498
16654
  if (!graph.hasNode(bucket.target)) continue;
16499
16655
  const target = graph.getNodeAttributes(bucket.target);
16500
- if (target.type !== NodeType30.DatabaseNode) continue;
16656
+ if (target.type !== NodeType31.DatabaseNode) continue;
16501
16657
  if (target.engine !== engine) continue;
16502
16658
  if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
16503
16659
  }
@@ -16510,7 +16666,7 @@ function dampenDeadCodeProbes(graph, buckets2, all) {
16510
16666
  if (!d.extracted || d.edgeType !== EdgeType26.CONNECTS_TO) return d;
16511
16667
  if (!graph.hasNode(d.target)) return d;
16512
16668
  const target = graph.getNodeAttributes(d.target);
16513
- if (target.type !== NodeType30.DatabaseNode) return d;
16669
+ if (target.type !== NodeType31.DatabaseNode) return d;
16514
16670
  if (d.extracted.evidence?.hostSource !== "literal") return d;
16515
16671
  if (datastoreEverObserved(graph, d.target)) return d;
16516
16672
  const engine = target.engine;
@@ -16550,14 +16706,14 @@ function computeDivergences(graph, opts = {}) {
16550
16706
  }
16551
16707
  graph.forEachNode((nodeId, attrs) => {
16552
16708
  const n = attrs;
16553
- if (n.type === NodeType30.ServiceNode) {
16709
+ if (n.type === NodeType31.ServiceNode) {
16554
16710
  const svc = n;
16555
16711
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
16556
16712
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
16557
16713
  for (const d of detectDeployDivergence(svc)) all.push(d);
16558
16714
  return;
16559
16715
  }
16560
- if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
16716
+ if (n.type === NodeType31.InfraNode && n.kind === "sql-table") {
16561
16717
  for (const d of detectColumnDrift(n)) all.push(d);
16562
16718
  }
16563
16719
  });
@@ -16632,7 +16788,7 @@ function computeDivergences(graph, opts = {}) {
16632
16788
  // src/persist.ts
16633
16789
  import { promises as fs34 } from "fs";
16634
16790
  import path69 from "path";
16635
- import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
16791
+ import { NodeType as NodeType32, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
16636
16792
  var SCHEMA_VERSION = 7;
16637
16793
  function migrateV1ToV2(payload) {
16638
16794
  const nodes = payload.graph.nodes;
@@ -16656,7 +16812,7 @@ function migrateV5ToV6(payload) {
16656
16812
  if (Array.isArray(nodes)) {
16657
16813
  for (const node of nodes) {
16658
16814
  const attrs = node.attributes;
16659
- if (!attrs || attrs.type !== NodeType31.InfraNode) continue;
16815
+ if (!attrs || attrs.type !== NodeType32.InfraNode) continue;
16660
16816
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
16661
16817
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
16662
16818
  }
@@ -17609,7 +17765,7 @@ function gradeIncidentCard(card) {
17609
17765
  }
17610
17766
 
17611
17767
  // src/ask.ts
17612
- import { AskResultSchema, NodeType as NodeType32, Provenance as Provenance27, serviceId as serviceId15 } from "@neat.is/types";
17768
+ import { AskResultSchema, NodeType as NodeType33, Provenance as Provenance27, serviceId as serviceId15 } from "@neat.is/types";
17613
17769
  var DEFAULT_MAX_NODES = 3;
17614
17770
  var MAX_FACTS_PER_SECTION = 6;
17615
17771
  var INTENT_RULES = [
@@ -17845,11 +18001,11 @@ function idBody(id) {
17845
18001
  return colon >= 0 ? id.slice(colon + 1) : id;
17846
18002
  }
17847
18003
  var TYPE_NOUNS = [
17848
- { nouns: ["database", "db", "table"], type: NodeType32.DatabaseNode },
17849
- { nouns: ["service", "api", "backend"], type: NodeType32.ServiceNode },
17850
- { nouns: ["route", "endpoint"], type: NodeType32.RouteNode },
17851
- { nouns: ["file"], type: NodeType32.FileNode },
17852
- { nouns: ["config"], type: NodeType32.ConfigNode }
18004
+ { nouns: ["database", "db", "table"], type: NodeType33.DatabaseNode },
18005
+ { nouns: ["service", "api", "backend"], type: NodeType33.ServiceNode },
18006
+ { nouns: ["route", "endpoint"], type: NodeType33.RouteNode },
18007
+ { nouns: ["file"], type: NodeType33.FileNode },
18008
+ { nouns: ["config"], type: NodeType33.ConfigNode }
17853
18009
  ];
17854
18010
  var EMBED_MIN_SCORE = 0.35;
17855
18011
  async function resolveEntities(graph, question, searchIndex, maxNodes) {
@@ -17863,7 +18019,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17863
18019
  };
17864
18020
  graph.forEachNode((id, attrs) => {
17865
18021
  const node = attrs;
17866
- if (node.type === NodeType32.FrontierNode) return;
18022
+ if (node.type === NodeType33.FrontierNode) return;
17867
18023
  const name = nodeName(node);
17868
18024
  const body = idBody(id);
17869
18025
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
@@ -17882,7 +18038,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17882
18038
  const res = await searchIndex.search(question, 10);
17883
18039
  if (res.provider !== "substring") {
17884
18040
  for (const m of res.matches) {
17885
- if (m.node.type === NodeType32.FrontierNode) continue;
18041
+ if (m.node.type === NodeType33.FrontierNode) continue;
17886
18042
  if (!graph.hasNode(m.node.id)) continue;
17887
18043
  const already = best.get(m.node.id);
17888
18044
  if (!already && m.score < EMBED_MIN_SCORE) continue;
@@ -18015,10 +18171,10 @@ function buildBlastSection(graph, node) {
18015
18171
  }
18016
18172
  function buildDatabaseCallersSection(graph, node) {
18017
18173
  const callers = getBlastRadius(graph, node).affectedNodes.filter((item) => {
18018
- if (graph.getNodeAttributes(item.nodeId).type !== NodeType32.ServiceNode) return false;
18174
+ if (graph.getNodeAttributes(item.nodeId).type !== NodeType33.ServiceNode) return false;
18019
18175
  return item.path.slice(1, -1).every((id) => {
18020
18176
  const type = graph.getNodeAttributes(id).type;
18021
- return type === NodeType32.FileNode || type === NodeType32.SymbolNode;
18177
+ return type === NodeType33.FileNode || type === NodeType33.SymbolNode;
18022
18178
  });
18023
18179
  });
18024
18180
  return {
@@ -18157,7 +18313,7 @@ function buildOverviewSections(graph, incidents) {
18157
18313
  graph.forEachNode((_id, attrs) => {
18158
18314
  const node = attrs;
18159
18315
  nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
18160
- if (node.type === NodeType32.ServiceNode) services.push(node.id);
18316
+ if (node.type === NodeType33.ServiceNode) services.push(node.id);
18161
18317
  });
18162
18318
  const edgeByProv = /* @__PURE__ */ new Map();
18163
18319
  graph.forEachEdge((_id, attrs) => {
@@ -18169,7 +18325,7 @@ function buildOverviewSections(graph, incidents) {
18169
18325
  const shapeFacts = [
18170
18326
  { text: `${graph.order} nodes, ${graph.size} edges` },
18171
18327
  {
18172
- text: `${count(NodeType32.ServiceNode)} services, ${count(NodeType32.FileNode)} files, ${count(NodeType32.SymbolNode)} symbols, ${count(NodeType32.DatabaseNode)} databases`
18328
+ text: `${count(NodeType33.ServiceNode)} services, ${count(NodeType33.FileNode)} files, ${count(NodeType33.SymbolNode)} symbols, ${count(NodeType33.DatabaseNode)} databases`
18173
18329
  }
18174
18330
  ];
18175
18331
  for (const p of [Provenance27.EXTRACTED, Provenance27.OBSERVED, Provenance27.INFERRED, Provenance27.STALE]) {
@@ -18328,7 +18484,7 @@ async function askGraph(graph, question, opts = {}) {
18328
18484
  let scope;
18329
18485
  if (primary) {
18330
18486
  scope = "node";
18331
- const callersQuestion = intent === "dependencies" && graph.getNodeAttributes(primary).type === NodeType32.DatabaseNode && /\b(?:which|what)\s+services?\s+(?:talks?\s+to|connects?\s+to|uses?|hits?|calls?|reads?\s+from|writes?\s+to)\b/i.test(question);
18487
+ const callersQuestion = intent === "dependencies" && graph.getNodeAttributes(primary).type === NodeType33.DatabaseNode && /\b(?:which|what)\s+services?\s+(?:talks?\s+to|connects?\s+to|uses?|hits?|calls?|reads?\s+from|writes?\s+to)\b/i.test(question);
18332
18488
  if (callersQuestion) sections.push(buildDatabaseCallersSection(graph, primary));
18333
18489
  for (const kind of SECTION_ORDER[intent]) {
18334
18490
  if (callersQuestion && kind === "blast") continue;
@@ -18829,6 +18985,7 @@ var defaultCloneRepo = async (cloneUrl, ref, destDir) => {
18829
18985
  });
18830
18986
  try {
18831
18987
  await Promise.race([clone, timeout]);
18988
+ return await git.resolveRef({ fs: fs40, dir: destDir, ref: "HEAD" });
18832
18989
  } finally {
18833
18990
  if (timer) clearTimeout(timer);
18834
18991
  }
@@ -18880,6 +19037,19 @@ function scrubCloneToken(s) {
18880
19037
  function needsSync(r) {
18881
19038
  return r.syncStatus === void 0 || r.syncStatus === "syncing";
18882
19039
  }
19040
+ function validRepoRow(row) {
19041
+ if (!row || typeof row !== "object") return false;
19042
+ const r = row;
19043
+ if (typeof r.owner !== "string" || typeof r.name !== "string" || typeof r.cloneUrl !== "string") return false;
19044
+ if (!/^[A-Za-z0-9_.-]+$/.test(r.owner) || !/^[A-Za-z0-9_.-]+$/.test(r.name)) return false;
19045
+ if (r.syncStatus !== void 0 && !["syncing", "synced", "failed"].includes(r.syncStatus)) return false;
19046
+ try {
19047
+ const url = new URL(r.cloneUrl);
19048
+ return url.protocol === "https:" && url.hostname === "github.com" && !url.port && !url.search && !url.hash && url.pathname === `/${r.owner}/${r.name}.git`;
19049
+ } catch {
19050
+ return false;
19051
+ }
19052
+ }
18883
19053
  async function syncOneRepo(r, input) {
18884
19054
  const { deps, graph } = input;
18885
19055
  const cloneRepo = input.cloneRepo ?? defaultCloneRepo;
@@ -18888,20 +19058,30 @@ async function syncOneRepo(r, input) {
18888
19058
  const tmpRoot = input.tmpRoot ?? os3.tmpdir();
18889
19059
  let dir;
18890
19060
  try {
19061
+ invalidateSourceBaseline(graph, label, "syncing");
18891
19062
  dir = await mkdtemp(path73.join(tmpRoot, "neat-repo-"));
18892
19063
  await cpPostStatus(deps, r.owner, r.name, { syncStatus: "syncing", detail: "cloning" }).catch(() => {
18893
19064
  });
18894
- await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
18895
- const extracted = await extract(graph, dir, { source: label });
19065
+ const sha = await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
19066
+ const extracted = await extract(graph, dir, {
19067
+ source: label,
19068
+ ...typeof sha === "string" ? { sourceCommit: { repository: label, sha } } : {}
19069
+ });
19070
+ input.onExtracted?.(label);
19071
+ const errors = extracted?.extractionErrors ?? 0;
19072
+ const skipped = extracted?.skippedFiles ?? 0;
19073
+ const incomplete = errors > 0 || skipped > 0;
19074
+ if (incomplete) invalidateSourceBaseline(graph, label);
18896
19075
  const nodes = extracted?.nodesAdded ?? 0;
18897
19076
  const edges = extracted?.edgesAdded ?? 0;
18898
19077
  await cpPostStatus(deps, r.owner, r.name, {
18899
19078
  syncStatus: "synced",
18900
- detail: `extracted ${nodes} node${nodes === 1 ? "" : "s"}, ${edges} edge${edges === 1 ? "" : "s"}`,
19079
+ detail: `extracted ${nodes} node${nodes === 1 ? "" : "s"}, ${edges} edge${edges === 1 ? "" : "s"}` + (incomplete ? ` (incomplete: ${errors} errors, ${skipped} skipped files)` : ""),
18901
19080
  lastSyncAt: new Date(input.now?.() ?? Date.now()).toISOString()
18902
19081
  });
18903
19082
  return true;
18904
19083
  } catch (err) {
19084
+ invalidateSourceBaseline(graph, label);
18905
19085
  input.onError?.(label, err);
18906
19086
  await cpPostStatus(deps, r.owner, r.name, {
18907
19087
  syncStatus: "failed",
@@ -18920,13 +19100,30 @@ async function runRepoSyncPassCounted(input) {
18920
19100
  try {
18921
19101
  repos = await cpGet(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
18922
19102
  } catch (err) {
19103
+ invalidateSourceBaseline(input.graph);
18923
19104
  input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
18924
19105
  return counts;
18925
19106
  }
18926
- if (!Array.isArray(repos)) return counts;
19107
+ if (!Array.isArray(repos)) {
19108
+ invalidateSourceBaseline(input.graph);
19109
+ return counts;
19110
+ }
18927
19111
  counts.listed = true;
18928
- for (const r of repos) {
18929
- if (!input.forceResync && !needsSync(r)) continue;
19112
+ const validRepos = repos.filter(validRepoRow);
19113
+ const invalidCount = repos.length - validRepos.length;
19114
+ if (invalidCount) {
19115
+ counts.failed += invalidCount;
19116
+ for (let index = 0; index < invalidCount; index++) {
19117
+ input.onSkip?.("(invalid)", "control plane repo row invalid");
19118
+ }
19119
+ }
19120
+ retainSourceBaselines(input.graph, validRepos.map((r) => `${r.owner}/${r.name}`));
19121
+ for (const r of validRepos) {
19122
+ if (r.syncStatus === "failed") invalidateSourceBaseline(input.graph, `${r.owner}/${r.name}`);
19123
+ }
19124
+ for (const r of validRepos) {
19125
+ const forced = typeof input.forceResync === "function" ? input.forceResync(`${r.owner}/${r.name}`) : input.forceResync === true;
19126
+ if (!forced && !needsSync(r)) continue;
18930
19127
  if (await syncOneRepo(r, input)) counts.synced++;
18931
19128
  else counts.failed++;
18932
19129
  }
@@ -18939,14 +19136,20 @@ async function startRepoSync(input) {
18939
19136
  let inFlight = null;
18940
19137
  let again = false;
18941
19138
  let lastPass;
18942
- let bootResyncDone = false;
19139
+ const bootExtracted = /* @__PURE__ */ new Set();
18943
19140
  const run = async () => {
18944
19141
  try {
18945
19142
  do {
18946
19143
  again = false;
18947
19144
  const startedAt = new Date(now()).toISOString();
18948
- const counts = await runRepoSyncPassCounted({ ...input, forceResync: !bootResyncDone });
18949
- if (counts.listed) bootResyncDone = true;
19145
+ const counts = await runRepoSyncPassCounted({
19146
+ ...input,
19147
+ forceResync: (repo) => !bootExtracted.has(repo),
19148
+ onExtracted: (repo) => {
19149
+ bootExtracted.add(repo);
19150
+ input.onExtracted?.(repo);
19151
+ }
19152
+ });
18950
19153
  lastPass = { ...counts, startedAt, finishedAt: new Date(now()).toISOString() };
18951
19154
  } while (again && !stopped);
18952
19155
  } finally {
@@ -19482,7 +19685,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
19482
19685
  }
19483
19686
 
19484
19687
  // src/connectors/index.ts
19485
- import { NodeType as NodeType33, parseFileId as parseFileId2, Provenance as Provenance28 } from "@neat.is/types";
19688
+ import { NodeType as NodeType34, parseFileId as parseFileId2, Provenance as Provenance28 } from "@neat.is/types";
19486
19689
  var NO_ENV = "unknown";
19487
19690
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
19488
19691
  if (!graph.hasNode(targetNodeId)) return void 0;
@@ -19501,7 +19704,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
19501
19704
  function routeCallSiteFor(graph, targetNodeId) {
19502
19705
  if (!graph.hasNode(targetNodeId)) return void 0;
19503
19706
  const attrs = graph.getNodeAttributes(targetNodeId);
19504
- if (attrs.type !== NodeType33.RouteNode || !attrs.path) return void 0;
19707
+ if (attrs.type !== NodeType34.RouteNode || !attrs.path) return void 0;
19505
19708
  const site = { relPath: attrs.path };
19506
19709
  if (attrs.line !== void 0) site.line = attrs.line;
19507
19710
  return site;
@@ -20317,7 +20520,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
20317
20520
  }
20318
20521
 
20319
20522
  // src/connectors/railway/index.ts
20320
- import { EdgeType as EdgeType28, NodeType as NodeType34, serviceId as serviceId16 } from "@neat.is/types";
20523
+ import { EdgeType as EdgeType28, NodeType as NodeType35, serviceId as serviceId16 } from "@neat.is/types";
20321
20524
 
20322
20525
  // src/connectors/railway/client.ts
20323
20526
  var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
@@ -20467,7 +20670,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
20467
20670
  const out = [];
20468
20671
  graph.forEachNode((_id, attrs) => {
20469
20672
  const node = attrs;
20470
- if (node.type !== NodeType34.RouteNode) return;
20673
+ if (node.type !== NodeType35.RouteNode) return;
20471
20674
  const route = attrs;
20472
20675
  if (route.service !== serviceName) return;
20473
20676
  out.push({
@@ -20758,10 +20961,10 @@ function mapLogEntriesToSignals(entries) {
20758
20961
  }
20759
20962
 
20760
20963
  // src/connectors/firebase/resolve.ts
20761
- import { NodeType as NodeType36, EdgeType as EdgeType29 } from "@neat.is/types";
20964
+ import { NodeType as NodeType37, EdgeType as EdgeType29 } from "@neat.is/types";
20762
20965
 
20763
20966
  // src/connectors/infer-service.ts
20764
- import { NodeType as NodeType35 } from "@neat.is/types";
20967
+ import { NodeType as NodeType36 } from "@neat.is/types";
20765
20968
  function normalizeName(name) {
20766
20969
  return name.toLowerCase().replace(/[^a-z0-9]/g, "");
20767
20970
  }
@@ -20772,11 +20975,11 @@ function inferServiceName(graph, resourceName, method, path76) {
20772
20975
  const owners = /* @__PURE__ */ new Set();
20773
20976
  graph.forEachNode((_id, attrs) => {
20774
20977
  const node = attrs;
20775
- if (node.type === NodeType35.ServiceNode && typeof node.name === "string") {
20978
+ if (node.type === NodeType36.ServiceNode && typeof node.name === "string") {
20776
20979
  services.push(node.name);
20777
20980
  return;
20778
20981
  }
20779
- if (node.type !== NodeType35.RouteNode) return;
20982
+ if (node.type !== NodeType36.RouteNode) return;
20780
20983
  const route = attrs;
20781
20984
  if (!route.pathTemplate || normalizePathTemplate(route.pathTemplate) !== wantedPath) return;
20782
20985
  const routeMethod = route.method.toUpperCase();
@@ -20803,7 +21006,7 @@ function routeEntriesFor(graph, serviceName) {
20803
21006
  const entries = [];
20804
21007
  graph.forEachNode((_id, attrs) => {
20805
21008
  const node = attrs;
20806
- if (node.type !== NodeType36.RouteNode) return;
21009
+ if (node.type !== NodeType37.RouteNode) return;
20807
21010
  const route = attrs;
20808
21011
  if (route.service !== serviceName) return;
20809
21012
  entries.push({
@@ -20864,7 +21067,7 @@ function createFirebaseConnector(graph, serviceMap) {
20864
21067
  }
20865
21068
 
20866
21069
  // src/connectors/cloudflare/connector.ts
20867
- import { EdgeType as EdgeType30, NodeType as NodeType37, fileId as fileId5, infraId as infraId23 } from "@neat.is/types";
21070
+ import { EdgeType as EdgeType30, NodeType as NodeType38, fileId as fileId5, infraId as infraId23 } from "@neat.is/types";
20868
21071
 
20869
21072
  // src/connectors/cloudflare/client.ts
20870
21073
  import { randomUUID } from "crypto";
@@ -21023,7 +21226,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
21023
21226
  graph.forEachNode((id, attrs) => {
21024
21227
  if (found) return;
21025
21228
  const a = attrs;
21026
- if (a.type === NodeType37.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
21229
+ if (a.type === NodeType38.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
21027
21230
  found = id;
21028
21231
  }
21029
21232
  });
@@ -21035,7 +21238,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
21035
21238
  graph.forEachNode((id, attrs) => {
21036
21239
  if (found) return;
21037
21240
  const a = attrs;
21038
- if (a.type !== NodeType37.RouteNode || a.service !== serviceName) return;
21241
+ if (a.type !== NodeType38.RouteNode || a.service !== serviceName) return;
21039
21242
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
21040
21243
  const routeMethod = (a.method ?? "").toUpperCase();
21041
21244
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -21434,14 +21637,14 @@ function mapLogEntriesToSignals2(entries) {
21434
21637
  }
21435
21638
 
21436
21639
  // src/connectors/cloud-run/resolve.ts
21437
- import { EdgeType as EdgeType32, NodeType as NodeType38, infraId as infraId25 } from "@neat.is/types";
21640
+ import { EdgeType as EdgeType32, NodeType as NodeType39, infraId as infraId25 } from "@neat.is/types";
21438
21641
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
21439
21642
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
21440
21643
  let found = null;
21441
21644
  graph.forEachNode((_id, attrs) => {
21442
21645
  if (found) return;
21443
21646
  const node = attrs;
21444
- if (node.type !== NodeType38.RouteNode) return;
21647
+ if (node.type !== NodeType39.RouteNode) return;
21445
21648
  const route = attrs;
21446
21649
  if (route.service !== serviceName || !route.pathTemplate) return;
21447
21650
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -21645,14 +21848,14 @@ function mapLogEntriesToSignals3(entries) {
21645
21848
  }
21646
21849
 
21647
21850
  // src/connectors/gcp-lb/resolve.ts
21648
- import { EdgeType as EdgeType33, NodeType as NodeType39, infraId as infraId26 } from "@neat.is/types";
21851
+ import { EdgeType as EdgeType33, NodeType as NodeType40, infraId as infraId26 } from "@neat.is/types";
21649
21852
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
21650
21853
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
21651
21854
  let found = null;
21652
21855
  graph.forEachNode((_id, attrs) => {
21653
21856
  if (found) return;
21654
21857
  const node = attrs;
21655
- if (node.type !== NodeType39.RouteNode) return;
21858
+ if (node.type !== NodeType40.RouteNode) return;
21656
21859
  const route = attrs;
21657
21860
  if (route.service !== serviceName || !route.pathTemplate) return;
21658
21861
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -21723,7 +21926,7 @@ function createGcpLbConnector(graph, config = {}) {
21723
21926
  }
21724
21927
 
21725
21928
  // src/connectors/render/index.ts
21726
- import { EdgeType as EdgeType34, NodeType as NodeType40 } from "@neat.is/types";
21929
+ import { EdgeType as EdgeType34, NodeType as NodeType41 } from "@neat.is/types";
21727
21930
 
21728
21931
  // src/connectors/render/types.ts
21729
21932
  function readRenderToken(credentials) {
@@ -21799,7 +22002,7 @@ function buildRenderRouteIndex(graph, serviceName) {
21799
22002
  const out = [];
21800
22003
  graph.forEachNode((_id, attrs) => {
21801
22004
  const node = attrs;
21802
- if (node.type !== NodeType40.RouteNode) return;
22005
+ if (node.type !== NodeType41.RouteNode) return;
21803
22006
  const route = attrs;
21804
22007
  if (route.service !== serviceName) return;
21805
22008
  out.push({
@@ -22283,7 +22486,7 @@ function mapBuildsToSignals(builds, serviceName) {
22283
22486
  }
22284
22487
 
22285
22488
  // src/connectors/eas/resolve.ts
22286
- import { EdgeType as EdgeType36, NodeType as NodeType41, parseFileId as parseFileId3 } from "@neat.is/types";
22489
+ import { EdgeType as EdgeType36, NodeType as NodeType42, parseFileId as parseFileId3 } from "@neat.is/types";
22287
22490
  var NO_ENV2 = "unknown";
22288
22491
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
22289
22492
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -22311,7 +22514,7 @@ function findConfigNode(graph, basenames, serviceName) {
22311
22514
  graph.forEachNode((id, attrs) => {
22312
22515
  if (scoped) return;
22313
22516
  const node = attrs;
22314
- if (node.type !== NodeType41.ConfigNode) return;
22517
+ if (node.type !== NodeType42.ConfigNode) return;
22315
22518
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
22316
22519
  if (anyMatch === null) anyMatch = id;
22317
22520
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -23056,7 +23259,7 @@ function serializeGraph(graph) {
23056
23259
  graph.forEachEdge((_id, attrs) => {
23057
23260
  edges.push(attrs);
23058
23261
  });
23059
- return { nodes, edges };
23262
+ return { nodes, edges, sourceBaselines: readSourceBaselines(graph) };
23060
23263
  }
23061
23264
  function projectFromReq(req, singleProject, registry) {
23062
23265
  const params = req.params;
@@ -24157,4 +24360,4 @@ export {
24157
24360
  deprovisionConnector,
24158
24361
  buildApi
24159
24362
  };
24160
- //# sourceMappingURL=chunk-H2BAPN33.js.map
24363
+ //# sourceMappingURL=chunk-MW2XYLCS.js.map