@neat.is/core 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req, reply, done) => {
64
- const path72 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path72) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path72)) {
64
+ const path74 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path74) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path74)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -415,8 +415,8 @@ function websocketChannelPathOf(attrs) {
415
415
  const v = attrs[key];
416
416
  if (typeof v === "string" && v.length > 0) {
417
417
  const q = v.indexOf("?");
418
- const path72 = q === -1 ? v : v.slice(0, q);
419
- if (path72.length > 0) return path72;
418
+ const path74 = q === -1 ? v : v.slice(0, q);
419
+ if (path74.length > 0) return path74;
420
420
  }
421
421
  }
422
422
  return void 0;
@@ -748,13 +748,13 @@ __export(cli_exports, {
748
748
  runMonitorVerb: () => runMonitorVerb,
749
749
  runQueryVerb: () => runQueryVerb,
750
750
  runSkill: () => runSkill,
751
- usage: () => usage2
751
+ usage: () => usage4
752
752
  });
753
753
  module.exports = __toCommonJS(cli_exports);
754
754
  init_cjs_shims();
755
- var import_node_path71 = __toESM(require("path"), 1);
756
- var import_node_os6 = __toESM(require("os"), 1);
757
- var import_node_fs43 = require("fs");
755
+ var import_node_path73 = __toESM(require("path"), 1);
756
+ var import_node_os8 = __toESM(require("os"), 1);
757
+ var import_node_fs45 = require("fs");
758
758
 
759
759
  // src/banner.ts
760
760
  init_cjs_shims();
@@ -1322,19 +1322,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1322
1322
  function longestIncomingWalk(graph, start, maxDepth) {
1323
1323
  let best = { path: [start], edges: [] };
1324
1324
  const visited = /* @__PURE__ */ new Set([start]);
1325
- function step(node, path72, edges) {
1326
- if (path72.length > best.path.length) {
1327
- best = { path: [...path72], edges: [...edges] };
1325
+ function step(node, path74, edges) {
1326
+ if (path74.length > best.path.length) {
1327
+ best = { path: [...path74], edges: [...edges] };
1328
1328
  }
1329
- if (path72.length - 1 >= maxDepth) return;
1329
+ if (path74.length - 1 >= maxDepth) return;
1330
1330
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1331
1331
  for (const [srcId, edge] of incoming) {
1332
1332
  if (visited.has(srcId)) continue;
1333
1333
  visited.add(srcId);
1334
- path72.push(srcId);
1334
+ path74.push(srcId);
1335
1335
  edges.push(edge);
1336
- step(srcId, path72, edges);
1337
- path72.pop();
1336
+ step(srcId, path74, edges);
1337
+ path74.pop();
1338
1338
  edges.pop();
1339
1339
  visited.delete(srcId);
1340
1340
  }
@@ -1541,26 +1541,26 @@ function dominantFailingCall(graph, serviceId7, visited) {
1541
1541
  return best;
1542
1542
  }
1543
1543
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1544
- const path72 = [originServiceId];
1544
+ const path74 = [originServiceId];
1545
1545
  const edges = [];
1546
1546
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1547
1547
  let current = originServiceId;
1548
1548
  for (let depth = 0; depth < maxDepth; depth++) {
1549
1549
  const hop = dominantFailingCall(graph, current, visited);
1550
1550
  if (!hop) break;
1551
- path72.push(hop.nextService);
1551
+ path74.push(hop.nextService);
1552
1552
  edges.push(hop.edge);
1553
1553
  visited.add(hop.nextService);
1554
1554
  current = hop.nextService;
1555
1555
  }
1556
1556
  if (edges.length === 0) return null;
1557
- return { path: path72, edges, culprit: current };
1557
+ return { path: path74, edges, culprit: current };
1558
1558
  }
1559
1559
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1560
1560
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1561
1561
  if (!chain) return null;
1562
1562
  const culprit = chain.culprit;
1563
- const path72 = [...chain.path];
1563
+ const path74 = [...chain.path];
1564
1564
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1565
1565
  const baseConfidence = confidenceFromMix(chain.edges);
1566
1566
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1568,14 +1568,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1568
1568
  if (loc) {
1569
1569
  let rootCauseNode = culprit;
1570
1570
  if (loc.fileNode) {
1571
- path72.push(loc.fileNode);
1571
+ path74.push(loc.fileNode);
1572
1572
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1573
1573
  rootCauseNode = loc.fileNode;
1574
1574
  }
1575
1575
  return import_types.RootCauseResultSchema.parse({
1576
1576
  rootCauseNode,
1577
1577
  rootCauseReason: loc.rootCauseReason,
1578
- traversalPath: path72,
1578
+ traversalPath: path74,
1579
1579
  edgeProvenances,
1580
1580
  confidence,
1581
1581
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1587,7 +1587,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1587
1587
  return import_types.RootCauseResultSchema.parse({
1588
1588
  rootCauseNode: culprit,
1589
1589
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1590
- traversalPath: path72,
1590
+ traversalPath: path74,
1591
1591
  edgeProvenances,
1592
1592
  confidence,
1593
1593
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -8393,6 +8393,55 @@ function columnsFromClassBody(body) {
8393
8393
  }
8394
8394
  return out;
8395
8395
  }
8396
+ function foreignKeyParentTable(call) {
8397
+ const fn = call.childForFieldName("function");
8398
+ const t = fn?.text;
8399
+ if (!t) return null;
8400
+ const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
8401
+ if (base !== "ForeignKey") return null;
8402
+ const target = firstPositionalString(call);
8403
+ if (!target) return null;
8404
+ const parts = target.split(".");
8405
+ if (parts.length < 2) return null;
8406
+ return parts[parts.length - 2];
8407
+ }
8408
+ function sqlalchemyForeignKeys(file, serviceDir) {
8409
+ if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
8410
+ const tree = parseSource6(makePyParser4(), file.content);
8411
+ const out = [];
8412
+ const seen = /* @__PURE__ */ new Set();
8413
+ walk3(tree.rootNode, (node) => {
8414
+ if (node.type !== "class_definition") return;
8415
+ const body = node.childForFieldName("body");
8416
+ const nameNode = node.childForFieldName("name");
8417
+ if (!body || !nameNode) return;
8418
+ const explicit = explicitTablename(body);
8419
+ if (explicit === "computed") return;
8420
+ let childTable = null;
8421
+ if (explicit) childTable = explicit.name;
8422
+ else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text);
8423
+ if (!childTable) return;
8424
+ walk3(body, (n) => {
8425
+ if (n.type !== "call") return;
8426
+ const parentTable = foreignKeyParentTable(n);
8427
+ if (!parentTable) return;
8428
+ const key = `${childTable}->${parentTable}`;
8429
+ if (seen.has(key)) return;
8430
+ seen.add(key);
8431
+ const line = n.startPosition.row + 1;
8432
+ out.push({
8433
+ childTable,
8434
+ parentTable,
8435
+ evidence: {
8436
+ file: import_node_path36.default.relative(serviceDir, file.path),
8437
+ line,
8438
+ snippet: snippet(file.content, line)
8439
+ }
8440
+ });
8441
+ });
8442
+ });
8443
+ return out;
8444
+ }
8396
8445
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
8397
8446
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
8398
8447
  const tree = parseSource6(makePyParser4(), file.content);
@@ -8711,15 +8760,15 @@ function drizzleEndpointsFromFile(file, serviceDir) {
8711
8760
  if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
8712
8761
  const args = node.childForFieldName("arguments");
8713
8762
  const argNodes = args ? namedChildren3(args) : [];
8714
- const tableName = stringLiteralText2(argNodes[0] ?? null);
8763
+ const tableName2 = stringLiteralText2(argNodes[0] ?? null);
8715
8764
  const obj = argNodes[1];
8716
- if (tableName && obj?.type === "object" && !seen.has(tableName)) {
8717
- seen.add(tableName);
8765
+ if (tableName2 && obj?.type === "object" && !seen.has(tableName2)) {
8766
+ seen.add(tableName2);
8718
8767
  const columns = columnsFromObject(obj);
8719
8768
  const line = node.startPosition.row + 1;
8720
8769
  out.push({
8721
- infraId: (0, import_types27.infraId)("sql-table", tableName),
8722
- name: tableName,
8770
+ infraId: (0, import_types27.infraId)("sql-table", tableName2),
8771
+ name: tableName2,
8723
8772
  kind: "sql-table",
8724
8773
  edgeType: "CALLS",
8725
8774
  confidenceKind: "structural",
@@ -8738,6 +8787,92 @@ function drizzleEndpointsFromFile(file, serviceDir) {
8738
8787
  walk6(tree.rootNode);
8739
8788
  return out;
8740
8789
  }
8790
+ function enclosingVarName(call) {
8791
+ let node = call;
8792
+ while (node?.parent) {
8793
+ const parent = node.parent;
8794
+ if (parent.type === "variable_declarator") {
8795
+ const name = parent.childForFieldName("name");
8796
+ return name?.type === "identifier" ? name.text : null;
8797
+ }
8798
+ if (parent.type === "call_expression" || parent.type === "member_expression") {
8799
+ node = parent;
8800
+ continue;
8801
+ }
8802
+ return null;
8803
+ }
8804
+ return null;
8805
+ }
8806
+ function collectDrizzleTables(root) {
8807
+ const tables = [];
8808
+ const varToTable = /* @__PURE__ */ new Map();
8809
+ const walk6 = (node) => {
8810
+ if (node.type === "call_expression") {
8811
+ const fn = node.childForFieldName("function");
8812
+ if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
8813
+ const args = node.childForFieldName("arguments");
8814
+ const argNodes = args ? namedChildren3(args) : [];
8815
+ const tableName2 = stringLiteralText2(argNodes[0] ?? null);
8816
+ const obj = argNodes[1]?.type === "object" ? argNodes[1] : null;
8817
+ if (tableName2) {
8818
+ tables.push({ tableName: tableName2, object: obj });
8819
+ const varName = enclosingVarName(node);
8820
+ if (varName) varToTable.set(varName, tableName2);
8821
+ }
8822
+ }
8823
+ }
8824
+ for (const c of namedChildren3(node)) walk6(c);
8825
+ };
8826
+ walk6(root);
8827
+ return { tables, varToTable };
8828
+ }
8829
+ function referencesTargetVar(call) {
8830
+ const fn = call.childForFieldName("function");
8831
+ if (fn?.type !== "member_expression") return null;
8832
+ if (fn.childForFieldName("property")?.text !== "references") return null;
8833
+ const args = call.childForFieldName("arguments");
8834
+ const first = args ? namedChildren3(args)[0] ?? null : null;
8835
+ if (first?.type !== "arrow_function") return null;
8836
+ const body = first.childForFieldName("body");
8837
+ if (body?.type !== "member_expression") return null;
8838
+ const obj = body.childForFieldName("object");
8839
+ return obj?.type === "identifier" ? obj.text : null;
8840
+ }
8841
+ function drizzleForeignKeys(file, serviceDir) {
8842
+ if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
8843
+ const tree = parseSource3(parserForExt(import_node_path38.default.extname(file.path)), file.content);
8844
+ const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
8845
+ const out = [];
8846
+ const seen = /* @__PURE__ */ new Set();
8847
+ for (const table of tables) {
8848
+ if (!table.object) continue;
8849
+ const walk6 = (node) => {
8850
+ if (node.type === "call_expression") {
8851
+ const targetVar = referencesTargetVar(node);
8852
+ const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
8853
+ if (parentTable) {
8854
+ const key = `${table.tableName}->${parentTable}`;
8855
+ if (!seen.has(key)) {
8856
+ seen.add(key);
8857
+ const line = node.startPosition.row + 1;
8858
+ out.push({
8859
+ childTable: table.tableName,
8860
+ parentTable,
8861
+ evidence: {
8862
+ file: import_node_path38.default.relative(serviceDir, file.path),
8863
+ line,
8864
+ snippet: snippet(file.content, line)
8865
+ }
8866
+ });
8867
+ }
8868
+ }
8869
+ }
8870
+ for (const c of namedChildren3(node)) walk6(c);
8871
+ };
8872
+ walk6(table.object);
8873
+ }
8874
+ return out;
8875
+ }
8741
8876
 
8742
8877
  // src/extract/calls/prisma.ts
8743
8878
  init_cjs_shims();
@@ -8866,6 +9001,96 @@ async function prismaColumnEndpoints(serviceDir) {
8866
9001
  if (!content) return [];
8867
9002
  return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir);
8868
9003
  }
9004
+ function buildModelTableMap(lines) {
9005
+ const map = /* @__PURE__ */ new Map();
9006
+ let current = null;
9007
+ let depth = 0;
9008
+ for (const raw of lines) {
9009
+ if (current === null) {
9010
+ const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
9011
+ if (header && raw.includes("{")) {
9012
+ current = { model: header[1], table: header[1] };
9013
+ depth = netBraces(raw);
9014
+ if (depth <= 0) {
9015
+ map.set(current.model, current.table);
9016
+ current = null;
9017
+ }
9018
+ }
9019
+ continue;
9020
+ }
9021
+ depth += netBraces(raw);
9022
+ const trimmed = stripLineComment(raw).trim();
9023
+ if (trimmed.startsWith("@@")) {
9024
+ const m = trimmed.match(/@@map\(\s*"([^"]+)"\s*\)/);
9025
+ if (m) current.table = m[1];
9026
+ }
9027
+ if (depth <= 0) {
9028
+ map.set(current.model, current.table);
9029
+ current = null;
9030
+ }
9031
+ }
9032
+ if (current) map.set(current.model, current.table);
9033
+ return map;
9034
+ }
9035
+ function prismaForeignKeysFromSchema(file, serviceDir) {
9036
+ const content = file.content;
9037
+ if (!/\bmodel\s+[A-Za-z_]\w*\s*\{/.test(content)) return [];
9038
+ const lines = content.split("\n");
9039
+ const modelToTable = buildModelTableMap(lines);
9040
+ const out = [];
9041
+ const seen = /* @__PURE__ */ new Set();
9042
+ let current = null;
9043
+ let depth = 0;
9044
+ for (let i = 0; i < lines.length; i++) {
9045
+ const raw = lines[i];
9046
+ const lineNo = i + 1;
9047
+ if (current === null) {
9048
+ const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
9049
+ if (header && raw.includes("{")) {
9050
+ current = { table: modelToTable.get(header[1]) ?? header[1] };
9051
+ depth = netBraces(raw);
9052
+ if (depth <= 0) current = null;
9053
+ }
9054
+ continue;
9055
+ }
9056
+ depth += netBraces(raw);
9057
+ const closing = depth <= 0;
9058
+ const trimmed = stripLineComment(raw).trim();
9059
+ if (trimmed && !trimmed.startsWith("@@") && !trimmed.startsWith("}")) {
9060
+ const fm = trimmed.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)/);
9061
+ if (fm && /@relation\b[^)]*\bfields\s*:/.test(trimmed)) {
9062
+ const parentTable = modelToTable.get(fm[2]);
9063
+ if (parentTable) {
9064
+ const key = `${current.table}->${parentTable}`;
9065
+ if (!seen.has(key)) {
9066
+ seen.add(key);
9067
+ out.push({
9068
+ childTable: current.table,
9069
+ parentTable,
9070
+ evidence: {
9071
+ file: import_node_path39.default.relative(serviceDir, file.path),
9072
+ line: lineNo,
9073
+ snippet: snippet(content, lineNo)
9074
+ }
9075
+ });
9076
+ }
9077
+ }
9078
+ }
9079
+ }
9080
+ if (closing) current = null;
9081
+ }
9082
+ return out;
9083
+ }
9084
+ async function prismaForeignKeys(serviceDir) {
9085
+ const schemaPath = await findFirst(serviceDir, [
9086
+ import_node_path39.default.join("prisma", "schema.prisma"),
9087
+ "schema.prisma"
9088
+ ]);
9089
+ if (!schemaPath) return [];
9090
+ const content = await readIfExists(schemaPath);
9091
+ if (!content) return [];
9092
+ return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir);
9093
+ }
8869
9094
 
8870
9095
  // src/extract/calls/go.ts
8871
9096
  init_cjs_shims();
@@ -9042,21 +9267,79 @@ async function addCallEdges(graph, services) {
9042
9267
  };
9043
9268
  }
9044
9269
 
9270
+ // src/extract/table-edges.ts
9271
+ init_cjs_shims();
9272
+ var import_types31 = require("@neat.is/types");
9273
+ async function addTableEdges(graph, services) {
9274
+ let nodesAdded = 0;
9275
+ let edgesAdded = 0;
9276
+ for (const service of services) {
9277
+ const files = await loadSourceFiles(service.dir);
9278
+ const refs = [];
9279
+ for (const file of files) {
9280
+ try {
9281
+ refs.push(...drizzleForeignKeys(file, service.dir));
9282
+ refs.push(...sqlalchemyForeignKeys(file, service.dir));
9283
+ } catch (err) {
9284
+ recordExtractionError("foreign-key extraction", file.path, err);
9285
+ }
9286
+ }
9287
+ try {
9288
+ refs.push(...await prismaForeignKeys(service.dir));
9289
+ } catch (err) {
9290
+ recordExtractionError("prisma foreign-key extraction", service.dir, err);
9291
+ }
9292
+ for (const ref of refs) {
9293
+ const childId = (0, import_types31.infraId)("sql-table", ref.childTable);
9294
+ const parentId = (0, import_types31.infraId)("sql-table", ref.parentTable);
9295
+ if (childId === parentId) continue;
9296
+ nodesAdded += ensureTableNode(graph, childId, ref.childTable);
9297
+ nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
9298
+ const edgeId = (0, import_types31.extractedEdgeId)(childId, parentId, import_types31.EdgeType.REFERENCES);
9299
+ if (graph.hasEdge(edgeId)) continue;
9300
+ const edge = {
9301
+ id: edgeId,
9302
+ source: childId,
9303
+ target: parentId,
9304
+ type: import_types31.EdgeType.REFERENCES,
9305
+ provenance: import_types31.Provenance.EXTRACTED,
9306
+ confidence: (0, import_types31.confidenceForExtracted)("structural"),
9307
+ evidence: ref.evidence
9308
+ };
9309
+ graph.addEdgeWithKey(edgeId, childId, parentId, edge);
9310
+ edgesAdded++;
9311
+ }
9312
+ }
9313
+ return { nodesAdded, edgesAdded };
9314
+ }
9315
+ function ensureTableNode(graph, id, name) {
9316
+ if (graph.hasNode(id)) return 0;
9317
+ const node = {
9318
+ id,
9319
+ type: import_types31.NodeType.InfraNode,
9320
+ name,
9321
+ provider: "self",
9322
+ kind: "sql-table"
9323
+ };
9324
+ graph.addNode(id, node);
9325
+ return 1;
9326
+ }
9327
+
9045
9328
  // src/extract/infra/index.ts
9046
9329
  init_cjs_shims();
9047
9330
 
9048
9331
  // src/extract/infra/docker-compose.ts
9049
9332
  init_cjs_shims();
9050
9333
  var import_node_path43 = __toESM(require("path"), 1);
9051
- var import_types32 = require("@neat.is/types");
9334
+ var import_types33 = require("@neat.is/types");
9052
9335
 
9053
9336
  // src/extract/infra/shared.ts
9054
9337
  init_cjs_shims();
9055
- var import_types31 = require("@neat.is/types");
9338
+ var import_types32 = require("@neat.is/types");
9056
9339
  function makeInfraNode(kind, name, provider = "self", extras) {
9057
9340
  return {
9058
- id: (0, import_types31.infraId)(kind, name),
9059
- type: import_types31.NodeType.InfraNode,
9341
+ id: (0, import_types32.infraId)(kind, name),
9342
+ type: import_types32.NodeType.InfraNode,
9060
9343
  name,
9061
9344
  provider,
9062
9345
  kind,
@@ -9100,8 +9383,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
9100
9383
  source: anchorId,
9101
9384
  target: node.id,
9102
9385
  type: edgeType,
9103
- provenance: import_types31.Provenance.EXTRACTED,
9104
- confidence: (0, import_types31.confidenceForExtracted)("structural"),
9386
+ provenance: import_types32.Provenance.EXTRACTED,
9387
+ confidence: (0, import_types32.confidenceForExtracted)("structural"),
9105
9388
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
9106
9389
  };
9107
9390
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9168,15 +9451,15 @@ async function addComposeInfra(graph, scanPath, services) {
9168
9451
  for (const dep of dependsOnList(svc.depends_on)) {
9169
9452
  const targetId = composeNameToNodeId.get(dep);
9170
9453
  if (!targetId) continue;
9171
- const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types32.EdgeType.DEPENDS_ON);
9454
+ const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types33.EdgeType.DEPENDS_ON);
9172
9455
  if (graph.hasEdge(edgeId)) continue;
9173
9456
  const edge = {
9174
9457
  id: edgeId,
9175
9458
  source: sourceId,
9176
9459
  target: targetId,
9177
- type: import_types32.EdgeType.DEPENDS_ON,
9178
- provenance: import_types32.Provenance.EXTRACTED,
9179
- confidence: (0, import_types32.confidenceForExtracted)("structural"),
9460
+ type: import_types33.EdgeType.DEPENDS_ON,
9461
+ provenance: import_types33.Provenance.EXTRACTED,
9462
+ confidence: (0, import_types33.confidenceForExtracted)("structural"),
9180
9463
  evidence: { file: evidenceFile }
9181
9464
  };
9182
9465
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9190,7 +9473,7 @@ async function addComposeInfra(graph, scanPath, services) {
9190
9473
  init_cjs_shims();
9191
9474
  var import_node_path44 = __toESM(require("path"), 1);
9192
9475
  var import_node_fs18 = require("fs");
9193
- var import_types33 = require("@neat.is/types");
9476
+ var import_types34 = require("@neat.is/types");
9194
9477
  function readDockerfile(content) {
9195
9478
  let image = null;
9196
9479
  const ports = [];
@@ -9249,15 +9532,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
9249
9532
  );
9250
9533
  nodesAdded += fn;
9251
9534
  edgesAdded += fe;
9252
- const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types33.EdgeType.RUNS_ON);
9535
+ const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types34.EdgeType.RUNS_ON);
9253
9536
  if (!graph.hasEdge(edgeId)) {
9254
9537
  const edge = {
9255
9538
  id: edgeId,
9256
9539
  source: fileNodeId,
9257
9540
  target: node.id,
9258
- type: import_types33.EdgeType.RUNS_ON,
9259
- provenance: import_types33.Provenance.EXTRACTED,
9260
- confidence: (0, import_types33.confidenceForExtracted)("structural"),
9541
+ type: import_types34.EdgeType.RUNS_ON,
9542
+ provenance: import_types34.Provenance.EXTRACTED,
9543
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
9261
9544
  evidence: {
9262
9545
  file: evidenceFile,
9263
9546
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -9272,15 +9555,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
9272
9555
  graph.addNode(portNode.id, portNode);
9273
9556
  nodesAdded++;
9274
9557
  }
9275
- const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types33.EdgeType.CONNECTS_TO);
9558
+ const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types34.EdgeType.CONNECTS_TO);
9276
9559
  if (graph.hasEdge(portEdgeId)) continue;
9277
9560
  const portEdge = {
9278
9561
  id: portEdgeId,
9279
9562
  source: fileNodeId,
9280
9563
  target: portNode.id,
9281
- type: import_types33.EdgeType.CONNECTS_TO,
9282
- provenance: import_types33.Provenance.EXTRACTED,
9283
- confidence: (0, import_types33.confidenceForExtracted)("structural"),
9564
+ type: import_types34.EdgeType.CONNECTS_TO,
9565
+ provenance: import_types34.Provenance.EXTRACTED,
9566
+ confidence: (0, import_types34.confidenceForExtracted)("structural"),
9284
9567
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
9285
9568
  };
9286
9569
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -9294,7 +9577,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
9294
9577
  init_cjs_shims();
9295
9578
  var import_node_fs19 = require("fs");
9296
9579
  var import_node_path45 = __toESM(require("path"), 1);
9297
- var import_types34 = require("@neat.is/types");
9580
+ var import_types35 = require("@neat.is/types");
9298
9581
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
9299
9582
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
9300
9583
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -9375,16 +9658,16 @@ async function addTerraformResources(graph, scanPath) {
9375
9658
  if (!target) continue;
9376
9659
  if (seen.has(target.nodeId)) continue;
9377
9660
  seen.add(target.nodeId);
9378
- const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types34.EdgeType.DEPENDS_ON);
9661
+ const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types35.EdgeType.DEPENDS_ON);
9379
9662
  if (graph.hasEdge(edgeId)) continue;
9380
9663
  const line = lineAt2(content, resource.bodyOffset + ref.index);
9381
9664
  const edge = {
9382
9665
  id: edgeId,
9383
9666
  source: resource.nodeId,
9384
9667
  target: target.nodeId,
9385
- type: import_types34.EdgeType.DEPENDS_ON,
9386
- provenance: import_types34.Provenance.EXTRACTED,
9387
- confidence: (0, import_types34.confidenceForExtracted)("structural"),
9668
+ type: import_types35.EdgeType.DEPENDS_ON,
9669
+ provenance: import_types35.Provenance.EXTRACTED,
9670
+ confidence: (0, import_types35.confidenceForExtracted)("structural"),
9388
9671
  evidence: { file: evidenceFile, line, snippet: key }
9389
9672
  };
9390
9673
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9456,7 +9739,7 @@ init_cjs_shims();
9456
9739
  var import_node_fs21 = require("fs");
9457
9740
  var import_node_path47 = __toESM(require("path"), 1);
9458
9741
  var import_smol_toml2 = require("smol-toml");
9459
- var import_types35 = require("@neat.is/types");
9742
+ var import_types36 = require("@neat.is/types");
9460
9743
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
9461
9744
  async function readWranglerConfig(dir) {
9462
9745
  for (const filename of WRANGLER_FILENAMES) {
@@ -9504,8 +9787,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
9504
9787
  source: anchorId,
9505
9788
  target: node.id,
9506
9789
  type: edgeType,
9507
- provenance: import_types35.Provenance.EXTRACTED,
9508
- confidence: (0, import_types35.confidenceForExtracted)("structural"),
9790
+ provenance: import_types36.Provenance.EXTRACTED,
9791
+ confidence: (0, import_types36.confidenceForExtracted)("structural"),
9509
9792
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
9510
9793
  };
9511
9794
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9566,15 +9849,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9566
9849
  nodesAdded++;
9567
9850
  }
9568
9851
  if (runtimeNode.id !== anchorId) {
9569
- const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types35.EdgeType.RUNS_ON);
9852
+ const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types36.EdgeType.RUNS_ON);
9570
9853
  if (!graph.hasEdge(runsOnId)) {
9571
9854
  const edge = {
9572
9855
  id: runsOnId,
9573
9856
  source: anchorId,
9574
9857
  target: runtimeNode.id,
9575
- type: import_types35.EdgeType.RUNS_ON,
9576
- provenance: import_types35.Provenance.EXTRACTED,
9577
- confidence: (0, import_types35.confidenceForExtracted)("structural"),
9858
+ type: import_types36.EdgeType.RUNS_ON,
9859
+ provenance: import_types36.Provenance.EXTRACTED,
9860
+ confidence: (0, import_types36.confidenceForExtracted)("structural"),
9578
9861
  evidence: {
9579
9862
  file: evidenceFile,
9580
9863
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -9588,7 +9871,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9588
9871
  const result = addResourceEdge(
9589
9872
  graph,
9590
9873
  anchorId,
9591
- import_types35.EdgeType.CONNECTS_TO,
9874
+ import_types36.EdgeType.CONNECTS_TO,
9592
9875
  "cloudflare-route",
9593
9876
  route,
9594
9877
  evidenceFile,
@@ -9612,7 +9895,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9612
9895
  const result = addResourceEdge(
9613
9896
  graph,
9614
9897
  anchorId,
9615
- import_types35.EdgeType.DEPENDS_ON,
9898
+ import_types36.EdgeType.DEPENDS_ON,
9616
9899
  group.kind,
9617
9900
  name,
9618
9901
  evidenceFile,
@@ -9626,7 +9909,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9626
9909
  const result = addResourceEdge(
9627
9910
  graph,
9628
9911
  anchorId,
9629
- import_types35.EdgeType.DEPENDS_ON,
9912
+ import_types36.EdgeType.DEPENDS_ON,
9630
9913
  "cloudflare-cron",
9631
9914
  cron,
9632
9915
  evidenceFile,
@@ -9639,7 +9922,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9639
9922
  const result = addResourceEdge(
9640
9923
  graph,
9641
9924
  anchorId,
9642
- import_types35.EdgeType.DEPENDS_ON,
9925
+ import_types36.EdgeType.DEPENDS_ON,
9643
9926
  "cloudflare-env-var",
9644
9927
  varName,
9645
9928
  evidenceFile,
@@ -9652,15 +9935,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9652
9935
  if (!svc.service) continue;
9653
9936
  const target = workerIndex.get(svc.service);
9654
9937
  if (target && target.anchorId !== anchorId) {
9655
- const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types35.EdgeType.CALLS);
9938
+ const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types36.EdgeType.CALLS);
9656
9939
  if (!graph.hasEdge(edgeId)) {
9657
9940
  const edge = {
9658
9941
  id: edgeId,
9659
9942
  source: anchorId,
9660
9943
  target: target.anchorId,
9661
- type: import_types35.EdgeType.CALLS,
9662
- provenance: import_types35.Provenance.EXTRACTED,
9663
- confidence: (0, import_types35.confidenceForExtracted)("structural"),
9944
+ type: import_types36.EdgeType.CALLS,
9945
+ provenance: import_types36.Provenance.EXTRACTED,
9946
+ confidence: (0, import_types36.confidenceForExtracted)("structural"),
9664
9947
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
9665
9948
  };
9666
9949
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -9671,7 +9954,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9671
9954
  const result = addResourceEdge(
9672
9955
  graph,
9673
9956
  anchorId,
9674
- import_types35.EdgeType.DEPENDS_ON,
9957
+ import_types36.EdgeType.DEPENDS_ON,
9675
9958
  "cloudflare-service-binding",
9676
9959
  svc.service,
9677
9960
  evidenceFile,
@@ -9688,7 +9971,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
9688
9971
  init_cjs_shims();
9689
9972
  var import_node_fs22 = require("fs");
9690
9973
  var import_node_path48 = __toESM(require("path"), 1);
9691
- var import_types36 = require("@neat.is/types");
9974
+ var import_types37 = require("@neat.is/types");
9692
9975
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
9693
9976
  async function readVercelConfig(dir) {
9694
9977
  for (const filename of VERCEL_CONFIG_FILENAMES) {
@@ -9751,12 +10034,12 @@ async function addVercelServices(graph, services, scanPath) {
9751
10034
  nodesAdded += result.nodesAdded;
9752
10035
  edgesAdded += result.edgesAdded;
9753
10036
  };
9754
- add(import_types36.EdgeType.RUNS_ON, "vercel", "vercel");
9755
- for (const cron of config.crons ?? []) add(import_types36.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
9756
- for (const varName of Object.keys(config.env ?? {})) add(import_types36.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9757
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types36.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
10037
+ add(import_types37.EdgeType.RUNS_ON, "vercel", "vercel");
10038
+ for (const cron of config.crons ?? []) add(import_types37.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
10039
+ for (const varName of Object.keys(config.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
10040
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
9758
10041
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
9759
- add(import_types36.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
10042
+ add(import_types37.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
9760
10043
  }
9761
10044
  }
9762
10045
  return { nodesAdded, edgesAdded };
@@ -9767,7 +10050,7 @@ init_cjs_shims();
9767
10050
  var import_node_fs23 = require("fs");
9768
10051
  var import_node_path49 = __toESM(require("path"), 1);
9769
10052
  var import_smol_toml3 = require("smol-toml");
9770
- var import_types37 = require("@neat.is/types");
10053
+ var import_types38 = require("@neat.is/types");
9771
10054
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
9772
10055
  async function readRailwayConfig(dir) {
9773
10056
  for (const filename of RAILWAY_FILENAMES) {
@@ -9813,9 +10096,9 @@ async function addRailwayServices(graph, services, scanPath) {
9813
10096
  nodesAdded += result.nodesAdded;
9814
10097
  edgesAdded += result.edgesAdded;
9815
10098
  };
9816
- add(import_types37.EdgeType.RUNS_ON, "railway", "railway");
9817
- add(import_types37.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
9818
- add(import_types37.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
10099
+ add(import_types38.EdgeType.RUNS_ON, "railway", "railway");
10100
+ add(import_types38.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
10101
+ add(import_types38.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
9819
10102
  }
9820
10103
  return { nodesAdded, edgesAdded };
9821
10104
  }
@@ -9825,7 +10108,7 @@ init_cjs_shims();
9825
10108
  var import_node_fs24 = require("fs");
9826
10109
  var import_node_path50 = __toESM(require("path"), 1);
9827
10110
  var import_smol_toml4 = require("smol-toml");
9828
- var import_types38 = require("@neat.is/types");
10111
+ var import_types39 = require("@neat.is/types");
9829
10112
  async function readSupabaseConfig(dir) {
9830
10113
  const relFile = import_node_path50.default.join("supabase", "config.toml");
9831
10114
  const abs = import_node_path50.default.join(dir, relFile);
@@ -9873,10 +10156,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
9873
10156
  nodesAdded += result.nodesAdded;
9874
10157
  edgesAdded += result.edgesAdded;
9875
10158
  };
9876
- add(import_types38.EdgeType.RUNS_ON, "supabase", "supabase");
9877
- for (const fn of Object.keys(config.functions ?? {})) add(import_types38.EdgeType.DEPENDS_ON, "supabase-function", fn);
9878
- if (config.storage) add(import_types38.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
9879
- if (config.auth) add(import_types38.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
10159
+ add(import_types39.EdgeType.RUNS_ON, "supabase", "supabase");
10160
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types39.EdgeType.DEPENDS_ON, "supabase-function", fn);
10161
+ if (config.storage) add(import_types39.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
10162
+ if (config.auth) add(import_types39.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
9880
10163
  }
9881
10164
  return { nodesAdded, edgesAdded };
9882
10165
  }
@@ -9904,11 +10187,11 @@ var import_node_path52 = __toESM(require("path"), 1);
9904
10187
  init_cjs_shims();
9905
10188
  var import_node_fs25 = require("fs");
9906
10189
  var import_node_path51 = __toESM(require("path"), 1);
9907
- var import_types39 = require("@neat.is/types");
10190
+ var import_types40 = require("@neat.is/types");
9908
10191
  function dropOrphanedFileNodes(graph) {
9909
10192
  const orphans = [];
9910
10193
  graph.forEachNode((id, attrs) => {
9911
- if (attrs.type !== import_types39.NodeType.FileNode) return;
10194
+ if (attrs.type !== import_types40.NodeType.FileNode) return;
9912
10195
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
9913
10196
  orphans.push(id);
9914
10197
  }
@@ -9921,7 +10204,7 @@ function retireEdgesByFile(graph, file) {
9921
10204
  const toDrop = [];
9922
10205
  graph.forEachEdge((id, attrs) => {
9923
10206
  const edge = attrs;
9924
- if (edge.provenance !== import_types39.Provenance.EXTRACTED) return;
10207
+ if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
9925
10208
  if (!edge.evidence?.file) return;
9926
10209
  if (edge.evidence.file === normalized) toDrop.push(id);
9927
10210
  });
@@ -9934,7 +10217,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
9934
10217
  const bases = [scanPath, ...serviceDirs];
9935
10218
  graph.forEachEdge((id, attrs) => {
9936
10219
  const edge = attrs;
9937
- if (edge.provenance !== import_types39.Provenance.EXTRACTED) return;
10220
+ if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
9938
10221
  const evidenceFile = edge.evidence?.file;
9939
10222
  if (!evidenceFile) return;
9940
10223
  if (import_node_path51.default.isAbsolute(evidenceFile)) {
@@ -9965,6 +10248,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
9965
10248
  const routePhase = await addRoutes(graph, services);
9966
10249
  const grpcPhase = await addGrpcMethods(graph, services);
9967
10250
  const phase4 = await addCallEdges(graph, services);
10251
+ const tableEdges = await addTableEdges(graph, services);
9968
10252
  const phase5 = await addInfra(graph, scanPath, services);
9969
10253
  const ghostsRetired = retireExtractedEdgesByMissingFile(
9970
10254
  graph,
@@ -10004,8 +10288,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
10004
10288
  }
10005
10289
  }
10006
10290
  const result = {
10007
- nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
10008
- edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
10291
+ nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded,
10292
+ edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded,
10009
10293
  frontiersPromoted,
10010
10294
  extractionErrors: errorEntries.length,
10011
10295
  errorEntries,
@@ -10028,39 +10312,39 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
10028
10312
 
10029
10313
  // src/divergences.ts
10030
10314
  init_cjs_shims();
10031
- var import_types40 = require("@neat.is/types");
10315
+ var import_types41 = require("@neat.is/types");
10032
10316
  function bucketKey(source, target, type) {
10033
10317
  return `${type}|${source}|${target}`;
10034
10318
  }
10035
10319
  function bucketSourceFor(graph, edge) {
10036
- if (edge.type !== import_types40.EdgeType.CONNECTS_TO) return edge.source;
10037
- const parsed = (0, import_types40.parseFileId)(edge.source);
10320
+ if (edge.type !== import_types41.EdgeType.CONNECTS_TO) return edge.source;
10321
+ const parsed = (0, import_types41.parseFileId)(edge.source);
10038
10322
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
10039
10323
  const target = graph.getNodeAttributes(edge.target);
10040
- if (target.type !== import_types40.NodeType.DatabaseNode) return edge.source;
10041
- return (0, import_types40.serviceId)(parsed.service);
10324
+ if (target.type !== import_types41.NodeType.DatabaseNode) return edge.source;
10325
+ return (0, import_types41.serviceId)(parsed.service);
10042
10326
  }
10043
10327
  function bucketEdges(graph) {
10044
10328
  const buckets2 = /* @__PURE__ */ new Map();
10045
10329
  graph.forEachEdge((id, attrs) => {
10046
10330
  const e = attrs;
10047
- const parsed = (0, import_types40.parseEdgeId)(id);
10331
+ const parsed = (0, import_types41.parseEdgeId)(id);
10048
10332
  const provenance = parsed?.provenance ?? e.provenance;
10049
10333
  const source = bucketSourceFor(graph, e);
10050
10334
  const key = bucketKey(source, e.target, e.type);
10051
10335
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
10052
10336
  switch (provenance) {
10053
- case import_types40.Provenance.EXTRACTED:
10337
+ case import_types41.Provenance.EXTRACTED:
10054
10338
  cur.extracted = e;
10055
10339
  break;
10056
- case import_types40.Provenance.OBSERVED:
10340
+ case import_types41.Provenance.OBSERVED:
10057
10341
  cur.observed = e;
10058
10342
  break;
10059
- case import_types40.Provenance.INFERRED:
10343
+ case import_types41.Provenance.INFERRED:
10060
10344
  cur.inferred = e;
10061
10345
  break;
10062
10346
  default:
10063
- if (e.provenance === import_types40.Provenance.STALE) cur.stale = e;
10347
+ if (e.provenance === import_types41.Provenance.STALE) cur.stale = e;
10064
10348
  }
10065
10349
  buckets2.set(key, cur);
10066
10350
  });
@@ -10069,17 +10353,17 @@ function bucketEdges(graph) {
10069
10353
  function nodeIsFrontier(graph, nodeId) {
10070
10354
  if (!graph.hasNode(nodeId)) return false;
10071
10355
  const attrs = graph.getNodeAttributes(nodeId);
10072
- return attrs.type === import_types40.NodeType.FrontierNode;
10356
+ return attrs.type === import_types41.NodeType.FrontierNode;
10073
10357
  }
10074
10358
  function nodeIsWebsocketChannel(graph, nodeId) {
10075
10359
  if (!graph.hasNode(nodeId)) return false;
10076
10360
  const attrs = graph.getNodeAttributes(nodeId);
10077
- return attrs.type === import_types40.NodeType.WebSocketChannelNode;
10361
+ return attrs.type === import_types41.NodeType.WebSocketChannelNode;
10078
10362
  }
10079
10363
  function nodeIsSymbol(graph, nodeId) {
10080
10364
  if (!graph.hasNode(nodeId)) return false;
10081
10365
  const attrs = graph.getNodeAttributes(nodeId);
10082
- return attrs.type === import_types40.NodeType.SymbolNode;
10366
+ return attrs.type === import_types41.NodeType.SymbolNode;
10083
10367
  }
10084
10368
  function clampConfidence(n) {
10085
10369
  if (!Number.isFinite(n)) return 0;
@@ -10099,14 +10383,14 @@ function gradedConfidence(edge) {
10099
10383
  return clampConfidence(confidenceForEdge(edge));
10100
10384
  }
10101
10385
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
10102
- import_types40.EdgeType.CALLS,
10103
- import_types40.EdgeType.CONNECTS_TO,
10104
- import_types40.EdgeType.PUBLISHES_TO,
10105
- import_types40.EdgeType.CONSUMES_FROM
10386
+ import_types41.EdgeType.CALLS,
10387
+ import_types41.EdgeType.CONNECTS_TO,
10388
+ import_types41.EdgeType.PUBLISHES_TO,
10389
+ import_types41.EdgeType.CONSUMES_FROM
10106
10390
  ]);
10107
10391
  function detectMissingDivergences(graph, bucket) {
10108
10392
  const out = [];
10109
- if (bucket.type === import_types40.EdgeType.CONTAINS) return out;
10393
+ if (bucket.type === import_types41.EdgeType.CONTAINS) return out;
10110
10394
  if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
10111
10395
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
10112
10396
  if (!nodeIsFrontier(graph, bucket.target)) {
@@ -10148,7 +10432,7 @@ function declaredHostFor(svc) {
10148
10432
  function hasExtractedConfiguredBy(graph, svcId) {
10149
10433
  for (const edgeId of graph.outboundEdges(svcId)) {
10150
10434
  const e = graph.getEdgeAttributes(edgeId);
10151
- if (e.type === import_types40.EdgeType.CONFIGURED_BY && e.provenance === import_types40.Provenance.EXTRACTED) {
10435
+ if (e.type === import_types41.EdgeType.CONFIGURED_BY && e.provenance === import_types41.Provenance.EXTRACTED) {
10152
10436
  return true;
10153
10437
  }
10154
10438
  }
@@ -10161,10 +10445,10 @@ function detectHostMismatch(graph, svcId, svc) {
10161
10445
  const out = [];
10162
10446
  for (const edgeId of graph.outboundEdges(svcId)) {
10163
10447
  const edge = graph.getEdgeAttributes(edgeId);
10164
- if (edge.type !== import_types40.EdgeType.CONNECTS_TO) continue;
10165
- if (edge.provenance !== import_types40.Provenance.OBSERVED) continue;
10448
+ if (edge.type !== import_types41.EdgeType.CONNECTS_TO) continue;
10449
+ if (edge.provenance !== import_types41.Provenance.OBSERVED) continue;
10166
10450
  const target = graph.getNodeAttributes(edge.target);
10167
- if (target.type !== import_types40.NodeType.DatabaseNode) continue;
10451
+ if (target.type !== import_types41.NodeType.DatabaseNode) continue;
10168
10452
  const observedHost = target.host?.trim();
10169
10453
  if (!observedHost) continue;
10170
10454
  if (observedHost === declaredHost) continue;
@@ -10186,10 +10470,10 @@ function detectCompatDivergences(graph, svcId, svc) {
10186
10470
  const deps = svc.dependencies ?? {};
10187
10471
  for (const edgeId of graph.outboundEdges(svcId)) {
10188
10472
  const edge = graph.getEdgeAttributes(edgeId);
10189
- if (edge.type !== import_types40.EdgeType.CONNECTS_TO) continue;
10190
- if (edge.provenance !== import_types40.Provenance.OBSERVED) continue;
10473
+ if (edge.type !== import_types41.EdgeType.CONNECTS_TO) continue;
10474
+ if (edge.provenance !== import_types41.Provenance.OBSERVED) continue;
10191
10475
  const target = graph.getNodeAttributes(edge.target);
10192
- if (target.type !== import_types40.NodeType.DatabaseNode) continue;
10476
+ if (target.type !== import_types41.NodeType.DatabaseNode) continue;
10193
10477
  for (const pair of compatPairs()) {
10194
10478
  if (pair.engine !== target.engine) continue;
10195
10479
  const declared = deps[pair.driver];
@@ -10286,7 +10570,7 @@ function suppressHostMismatchHalves(all) {
10286
10570
  for (const d of all) {
10287
10571
  if (d.type !== "host-mismatch") continue;
10288
10572
  observedHalf.add(`${d.source}->${d.target}`);
10289
- declaredHalf.add((0, import_types40.databaseId)(d.extractedHost));
10573
+ declaredHalf.add((0, import_types41.databaseId)(d.extractedHost));
10290
10574
  }
10291
10575
  if (observedHalf.size === 0) return all;
10292
10576
  return all.filter((d) => {
@@ -10305,13 +10589,13 @@ function computeDivergences(graph, opts = {}) {
10305
10589
  }
10306
10590
  graph.forEachNode((nodeId, attrs) => {
10307
10591
  const n = attrs;
10308
- if (n.type === import_types40.NodeType.ServiceNode) {
10592
+ if (n.type === import_types41.NodeType.ServiceNode) {
10309
10593
  const svc = n;
10310
10594
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
10311
10595
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
10312
10596
  return;
10313
10597
  }
10314
- if (n.type === import_types40.NodeType.InfraNode && n.kind === "sql-table") {
10598
+ if (n.type === import_types41.NodeType.InfraNode && n.kind === "sql-table") {
10315
10599
  for (const d of detectColumnDrift(n)) all.push(d);
10316
10600
  }
10317
10601
  });
@@ -10347,7 +10631,7 @@ function computeDivergences(graph, opts = {}) {
10347
10631
  const bc = "column" in b && b.column ? b.column : "";
10348
10632
  return ac.localeCompare(bc);
10349
10633
  });
10350
- return import_types40.DivergenceResultSchema.parse({
10634
+ return import_types41.DivergenceResultSchema.parse({
10351
10635
  divergences: filtered,
10352
10636
  totalAffected: filtered.length,
10353
10637
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -10358,7 +10642,7 @@ function computeDivergences(graph, opts = {}) {
10358
10642
  init_cjs_shims();
10359
10643
  var import_node_fs26 = require("fs");
10360
10644
  var import_node_path53 = __toESM(require("path"), 1);
10361
- var import_types41 = require("@neat.is/types");
10645
+ var import_types42 = require("@neat.is/types");
10362
10646
  var SCHEMA_VERSION = 6;
10363
10647
  function migrateV1ToV2(payload) {
10364
10648
  const nodes = payload.graph.nodes;
@@ -10382,7 +10666,7 @@ function migrateV5ToV6(payload) {
10382
10666
  if (Array.isArray(nodes)) {
10383
10667
  for (const node of nodes) {
10384
10668
  const attrs = node.attributes;
10385
- if (!attrs || attrs.type !== import_types41.NodeType.InfraNode) continue;
10669
+ if (!attrs || attrs.type !== import_types42.NodeType.InfraNode) continue;
10386
10670
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
10387
10671
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
10388
10672
  }
@@ -10395,12 +10679,12 @@ function migrateV2ToV3(payload) {
10395
10679
  for (const edge of edges) {
10396
10680
  const attrs = edge.attributes;
10397
10681
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
10398
- attrs.provenance = import_types41.Provenance.OBSERVED;
10682
+ attrs.provenance = import_types42.Provenance.OBSERVED;
10399
10683
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
10400
10684
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
10401
10685
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
10402
10686
  if (type && source && target) {
10403
- const newId = (0, import_types41.observedEdgeId)(source, target, type);
10687
+ const newId = (0, import_types42.observedEdgeId)(source, target, type);
10404
10688
  attrs.id = newId;
10405
10689
  if (edge.key) edge.key = newId;
10406
10690
  }
@@ -10532,7 +10816,7 @@ ${NEAT_OUT_LINE}
10532
10816
 
10533
10817
  // src/summary.ts
10534
10818
  init_cjs_shims();
10535
- var import_types42 = require("@neat.is/types");
10819
+ var import_types43 = require("@neat.is/types");
10536
10820
  function renderOtelEnvBlock() {
10537
10821
  return [
10538
10822
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -10542,19 +10826,19 @@ function renderOtelEnvBlock() {
10542
10826
  }
10543
10827
  function findIncompatServices(nodes) {
10544
10828
  return nodes.filter(
10545
- (n) => n.type === import_types42.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
10829
+ (n) => n.type === import_types43.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
10546
10830
  );
10547
10831
  }
10548
10832
  function servicesWithoutObserved(nodes, edges) {
10549
10833
  const seen = /* @__PURE__ */ new Set();
10550
10834
  for (const e of edges) {
10551
- if (e.provenance === import_types42.Provenance.OBSERVED) {
10835
+ if (e.provenance === import_types43.Provenance.OBSERVED) {
10552
10836
  seen.add(e.source);
10553
10837
  seen.add(e.target);
10554
10838
  }
10555
10839
  }
10556
10840
  return nodes.filter(
10557
- (n) => n.type === import_types42.NodeType.ServiceNode && !seen.has(n.id)
10841
+ (n) => n.type === import_types43.NodeType.ServiceNode && !seen.has(n.id)
10558
10842
  );
10559
10843
  }
10560
10844
  function formatDivergence(d) {
@@ -10636,7 +10920,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
10636
10920
  init_cjs_shims();
10637
10921
  var import_fastify2 = __toESM(require("fastify"), 1);
10638
10922
  var import_cors = __toESM(require("@fastify/cors"), 1);
10639
- var import_types60 = require("@neat.is/types");
10923
+ var import_types67 = require("@neat.is/types");
10640
10924
 
10641
10925
  // src/extend/index.ts
10642
10926
  init_cjs_shims();
@@ -11160,7 +11444,7 @@ init_cjs_shims();
11160
11444
  var import_node_fs31 = require("fs");
11161
11445
  var import_node_os3 = __toESM(require("os"), 1);
11162
11446
  var import_node_path58 = __toESM(require("path"), 1);
11163
- var import_types43 = require("@neat.is/types");
11447
+ var import_types44 = require("@neat.is/types");
11164
11448
  var LOCK_TIMEOUT_MS = 5e3;
11165
11449
  var LOCK_RETRY_MS = 50;
11166
11450
  function neatHome() {
@@ -11414,10 +11698,10 @@ async function readRegistry() {
11414
11698
  throw err;
11415
11699
  }
11416
11700
  const parsed = JSON.parse(raw);
11417
- return import_types43.RegistryFileSchema.parse(parsed);
11701
+ return import_types44.RegistryFileSchema.parse(parsed);
11418
11702
  }
11419
11703
  async function writeRegistry(reg) {
11420
- const validated = import_types43.RegistryFileSchema.parse(reg);
11704
+ const validated = import_types44.RegistryFileSchema.parse(reg);
11421
11705
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
11422
11706
  }
11423
11707
  var ProjectNameCollisionError = class extends Error {
@@ -11930,15 +12214,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
11930
12214
 
11931
12215
  // src/connectors/index.ts
11932
12216
  init_cjs_shims();
11933
- var import_types44 = require("@neat.is/types");
12217
+ var import_types45 = require("@neat.is/types");
11934
12218
  var NO_ENV = "unknown";
11935
12219
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
11936
12220
  if (!graph.hasNode(targetNodeId)) return void 0;
11937
12221
  const sites = [];
11938
12222
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
11939
12223
  const edge = graph.getEdgeAttributes(edgeId);
11940
- if (edge.provenance !== import_types44.Provenance.EXTRACTED) continue;
11941
- const parsed = (0, import_types44.parseFileId)(edge.source);
12224
+ if (edge.provenance !== import_types45.Provenance.EXTRACTED) continue;
12225
+ const parsed = (0, import_types45.parseFileId)(edge.source);
11942
12226
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
11943
12227
  const site = { relPath: edge.evidence.file };
11944
12228
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -11949,7 +12233,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
11949
12233
  function routeCallSiteFor(graph, targetNodeId) {
11950
12234
  if (!graph.hasNode(targetNodeId)) return void 0;
11951
12235
  const attrs = graph.getNodeAttributes(targetNodeId);
11952
- if (attrs.type !== import_types44.NodeType.RouteNode || !attrs.path) return void 0;
12236
+ if (attrs.type !== import_types45.NodeType.RouteNode || !attrs.path) return void 0;
11953
12237
  const site = { relPath: attrs.path };
11954
12238
  if (attrs.line !== void 0) site.line = attrs.line;
11955
12239
  return site;
@@ -12133,7 +12417,11 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
12133
12417
  // connector add/remove/test` (provision/deprovision/validate), never a poll
12134
12418
  // loop, so this bucket is exercised a handful of times per command. Kept
12135
12419
  // conservative pending a documented Drains-API rate limit.
12136
- vercel: { capacity: 20, refillMs: 5e3 }
12420
+ vercel: { capacity: 20, refillMs: 5e3 },
12421
+ // Render's REST API (api-docs.render.com/reference/rate-limiting) isn't
12422
+ // pinned here to a single confirmed number — this is a conservative
12423
+ // placeholder pending a live project, matching the other pull providers.
12424
+ render: { capacity: 30, refillMs: 1e4 }
12137
12425
  };
12138
12426
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
12139
12427
  function defaultRateLimitFor(provider) {
@@ -12426,10 +12714,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
12426
12714
  // src/connectors/supabase/map.ts
12427
12715
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
12428
12716
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
12429
- function targetFromRestPath(path72) {
12430
- const rpcMatch = REST_RPC_PATH_RE.exec(path72);
12717
+ function targetFromRestPath(path74) {
12718
+ const rpcMatch = REST_RPC_PATH_RE.exec(path74);
12431
12719
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
12432
- const tableMatch = REST_TABLE_PATH_RE.exec(path72);
12720
+ const tableMatch = REST_TABLE_PATH_RE.exec(path74);
12433
12721
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
12434
12722
  return null;
12435
12723
  }
@@ -12540,23 +12828,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
12540
12828
 
12541
12829
  // src/connectors/supabase/resolve.ts
12542
12830
  init_cjs_shims();
12543
- var import_types46 = require("@neat.is/types");
12831
+ var import_types47 = require("@neat.is/types");
12544
12832
  function createSupabaseResolveTarget(graph, config) {
12545
12833
  return (signal, _ctx) => {
12546
12834
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
12547
12835
  return null;
12548
12836
  }
12549
- const subResourceId = (0, import_types46.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
12837
+ const subResourceId = (0, import_types47.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
12550
12838
  if (graph.hasNode(subResourceId)) {
12551
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
12839
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types47.EdgeType.CALLS };
12552
12840
  }
12553
- const bareResourceId = (0, import_types46.infraId)(signal.targetKind, signal.targetName);
12841
+ const bareResourceId = (0, import_types47.infraId)(signal.targetKind, signal.targetName);
12554
12842
  if (graph.hasNode(bareResourceId)) {
12555
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
12843
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types47.EdgeType.CALLS };
12556
12844
  }
12557
- const projectLevelId = (0, import_types46.infraId)("supabase", config.nodeRef);
12845
+ const projectLevelId = (0, import_types47.infraId)("supabase", config.nodeRef);
12558
12846
  if (graph.hasNode(projectLevelId)) {
12559
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types46.EdgeType.CALLS };
12847
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types47.EdgeType.CALLS };
12560
12848
  }
12561
12849
  return null;
12562
12850
  };
@@ -12649,7 +12937,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
12649
12937
 
12650
12938
  // src/connectors/railway/index.ts
12651
12939
  init_cjs_shims();
12652
- var import_types50 = require("@neat.is/types");
12940
+ var import_types51 = require("@neat.is/types");
12653
12941
 
12654
12942
  // src/connectors/railway/client.ts
12655
12943
  init_cjs_shims();
@@ -12800,7 +13088,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
12800
13088
  const out = [];
12801
13089
  graph.forEachNode((_id, attrs) => {
12802
13090
  const node = attrs;
12803
- if (node.type !== import_types50.NodeType.RouteNode) return;
13091
+ if (node.type !== import_types51.NodeType.RouteNode) return;
12804
13092
  const route = attrs;
12805
13093
  if (route.service !== serviceName) return;
12806
13094
  out.push({
@@ -12904,12 +13192,12 @@ function createRailwayResolveTarget(config) {
12904
13192
  const serviceName = config.serviceNameById[config.serviceId];
12905
13193
  if (!serviceName) return null;
12906
13194
  if (signal.targetKind === ROUTE_TARGET_KIND) {
12907
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types50.EdgeType.CALLS };
13195
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types51.EdgeType.CALLS };
12908
13196
  }
12909
13197
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
12910
13198
  const peerName = config.serviceNameById[signal.targetName];
12911
13199
  if (!peerName) return null;
12912
- return { targetNodeId: (0, import_types50.serviceId)(peerName), serviceName, edgeType: import_types50.EdgeType.CONNECTS_TO };
13200
+ return { targetNodeId: (0, import_types51.serviceId)(peerName), serviceName, edgeType: import_types51.EdgeType.CONNECTS_TO };
12913
13201
  }
12914
13202
  return null;
12915
13203
  };
@@ -13033,9 +13321,9 @@ function parseFirebaseTargetName(targetName) {
13033
13321
  const secondSep = rest.indexOf(FIELD_SEP);
13034
13322
  if (secondSep === -1) return null;
13035
13323
  const method = rest.slice(0, secondSep);
13036
- const path72 = rest.slice(secondSep + 1);
13037
- if (!resourceName || !method || !path72) return null;
13038
- return { resourceName, method, path: path72 };
13324
+ const path74 = rest.slice(secondSep + 1);
13325
+ if (!resourceName || !method || !path74) return null;
13326
+ return { resourceName, method, path: path74 };
13039
13327
  }
13040
13328
  function resourceNameFor(type, labels) {
13041
13329
  if (!labels) return null;
@@ -13073,14 +13361,14 @@ function mapLogEntryToSignal(entry2) {
13073
13361
  if (!req) return null;
13074
13362
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
13075
13363
  const method = req.requestMethod.toUpperCase();
13076
- const path72 = pathFromRequestUrl(req.requestUrl);
13077
- if (path72 === null) return null;
13364
+ const path74 = pathFromRequestUrl(req.requestUrl);
13365
+ if (path74 === null) return null;
13078
13366
  const timestamp = entry2.timestamp;
13079
13367
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
13080
13368
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
13081
13369
  return {
13082
13370
  targetKind: resourceType,
13083
- targetName: packFirebaseTargetName({ resourceName, method, path: path72 }),
13371
+ targetName: packFirebaseTargetName({ resourceName, method, path: path74 }),
13084
13372
  callCount: 1,
13085
13373
  errorCount: isError ? 1 : 0,
13086
13374
  lastObservedIso: timestamp
@@ -13097,7 +13385,7 @@ function mapLogEntriesToSignals(entries) {
13097
13385
 
13098
13386
  // src/connectors/firebase/resolve.ts
13099
13387
  init_cjs_shims();
13100
- var import_types51 = require("@neat.is/types");
13388
+ var import_types52 = require("@neat.is/types");
13101
13389
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
13102
13390
  switch (resourceType) {
13103
13391
  case "cloud_function":
@@ -13112,7 +13400,7 @@ function routeEntriesFor(graph, serviceName) {
13112
13400
  const entries = [];
13113
13401
  graph.forEachNode((_id, attrs) => {
13114
13402
  const node = attrs;
13115
- if (node.type !== import_types51.NodeType.RouteNode) return;
13403
+ if (node.type !== import_types52.NodeType.RouteNode) return;
13116
13404
  const route = attrs;
13117
13405
  if (route.service !== serviceName) return;
13118
13406
  entries.push({
@@ -13144,7 +13432,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
13144
13432
  return {
13145
13433
  targetNodeId: match.routeNodeId,
13146
13434
  serviceName,
13147
- edgeType: import_types51.EdgeType.CALLS
13435
+ edgeType: import_types52.EdgeType.CALLS
13148
13436
  };
13149
13437
  };
13150
13438
  }
@@ -13171,7 +13459,7 @@ init_cjs_shims();
13171
13459
 
13172
13460
  // src/connectors/cloudflare/connector.ts
13173
13461
  init_cjs_shims();
13174
- var import_types53 = require("@neat.is/types");
13462
+ var import_types54 = require("@neat.is/types");
13175
13463
 
13176
13464
  // src/connectors/cloudflare/client.ts
13177
13465
  init_cjs_shims();
@@ -13287,7 +13575,7 @@ function mapEventToSignal(event) {
13287
13575
  if (Number.isNaN(observedAt.getTime())) return null;
13288
13576
  const statusCode = metadata?.statusCode;
13289
13577
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
13290
- const path72 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
13578
+ const path74 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
13291
13579
  return {
13292
13580
  targetKind: CLOUDFLARE_TARGET_KIND,
13293
13581
  targetName: scriptName,
@@ -13295,7 +13583,7 @@ function mapEventToSignal(event) {
13295
13583
  errorCount: isError ? 1 : 0,
13296
13584
  lastObservedIso: observedAt.toISOString(),
13297
13585
  method,
13298
- ...path72 ? { path: path72 } : {},
13586
+ ...path74 ? { path: path74 } : {},
13299
13587
  ...typeof statusCode === "number" ? { statusCode } : {},
13300
13588
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
13301
13589
  };
@@ -13335,19 +13623,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
13335
13623
  graph.forEachNode((id, attrs) => {
13336
13624
  if (found) return;
13337
13625
  const a = attrs;
13338
- if (a.type === import_types53.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
13626
+ if (a.type === import_types54.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
13339
13627
  found = id;
13340
13628
  }
13341
13629
  });
13342
13630
  return found;
13343
13631
  }
13344
- function findMatchingRouteNode(graph, serviceName, method, path72) {
13345
- const normalizedPath = normalizePathTemplate(path72);
13632
+ function findMatchingRouteNode(graph, serviceName, method, path74) {
13633
+ const normalizedPath = normalizePathTemplate(path74);
13346
13634
  let found = null;
13347
13635
  graph.forEachNode((id, attrs) => {
13348
13636
  if (found) return;
13349
13637
  const a = attrs;
13350
- if (a.type !== import_types53.NodeType.RouteNode || a.service !== serviceName) return;
13638
+ if (a.type !== import_types54.NodeType.RouteNode || a.service !== serviceName) return;
13351
13639
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
13352
13640
  const routeMethod = (a.method ?? "").toUpperCase();
13353
13641
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -13359,18 +13647,18 @@ function createCloudflareResolveTarget(config, graph) {
13359
13647
  return (signal) => {
13360
13648
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
13361
13649
  const scriptName = signal.targetName;
13362
- const { method, path: path72 } = signal;
13650
+ const { method, path: path74 } = signal;
13363
13651
  const resolveRouteGrain = (serviceName, wholeFileId) => {
13364
- if (!method || !path72) return wholeFileId;
13365
- return findMatchingRouteNode(graph, serviceName, method, path72) ?? wholeFileId;
13652
+ if (!method || !path74) return wholeFileId;
13653
+ return findMatchingRouteNode(graph, serviceName, method, path74) ?? wholeFileId;
13366
13654
  };
13367
13655
  const mapping = config.workers?.[scriptName];
13368
13656
  if (mapping) {
13369
- const wholeFileId = (0, import_types53.fileId)(mapping.service, mapping.entryFile);
13657
+ const wholeFileId = (0, import_types54.fileId)(mapping.service, mapping.entryFile);
13370
13658
  return {
13371
13659
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
13372
13660
  serviceName: mapping.service,
13373
- edgeType: import_types53.EdgeType.CALLS
13661
+ edgeType: import_types54.EdgeType.CALLS
13374
13662
  };
13375
13663
  }
13376
13664
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -13379,13 +13667,13 @@ function createCloudflareResolveTarget(config, graph) {
13379
13667
  return {
13380
13668
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
13381
13669
  serviceName: fileNode.service,
13382
- edgeType: import_types53.EdgeType.CALLS
13670
+ edgeType: import_types54.EdgeType.CALLS
13383
13671
  };
13384
13672
  }
13385
13673
  return {
13386
- targetNodeId: (0, import_types53.infraId)("cloudflare-worker", scriptName),
13674
+ targetNodeId: (0, import_types54.infraId)("cloudflare-worker", scriptName),
13387
13675
  serviceName: scriptName,
13388
- edgeType: import_types53.EdgeType.CALLS,
13676
+ edgeType: import_types54.EdgeType.CALLS,
13389
13677
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
13390
13678
  };
13391
13679
  };
@@ -13581,14 +13869,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
13581
13869
 
13582
13870
  // src/connectors/neon/resolve.ts
13583
13871
  init_cjs_shims();
13584
- var import_types57 = require("@neat.is/types");
13872
+ var import_types58 = require("@neat.is/types");
13585
13873
  function createNeonResolveTarget(config) {
13586
13874
  return (signal) => {
13587
13875
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
13588
13876
  return {
13589
- targetNodeId: (0, import_types57.infraId)("sql-table", signal.targetName),
13877
+ targetNodeId: (0, import_types58.infraId)("sql-table", signal.targetName),
13590
13878
  serviceName: config.serviceName,
13591
- edgeType: import_types57.EdgeType.CALLS,
13879
+ edgeType: import_types58.EdgeType.CALLS,
13592
13880
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
13593
13881
  };
13594
13882
  };
@@ -13626,45 +13914,451 @@ function createNeonConnector(config, deps = {}) {
13626
13914
  };
13627
13915
  }
13628
13916
 
13629
- // src/connectors/registry.ts
13630
- var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
13631
- async function authProbe(input) {
13632
- const { provider, accountKey, url, token, init, fetchImpl } = input;
13633
- try {
13917
+ // src/connectors/cloud-run/index.ts
13918
+ init_cjs_shims();
13919
+
13920
+ // src/connectors/cloud-run/client.ts
13921
+ init_cjs_shims();
13922
+ function cloudRunRequestLogName(projectId) {
13923
+ return `projects/${projectId}/logs/run.googleapis.com%2Frequests`;
13924
+ }
13925
+ function buildCloudRunEntriesFilter(projectId, sinceIso) {
13926
+ return [
13927
+ `logName = "${cloudRunRequestLogName(projectId)}"`,
13928
+ `resource.type = "${CLOUD_RUN_RESOURCE_TYPE}"`,
13929
+ 'httpRequest.requestMethod != ""',
13930
+ `timestamp >= "${sinceIso}"`
13931
+ ].join(" AND ");
13932
+ }
13933
+ var CLOUD_RUN_RESOURCE_TYPE = "cloud_run_revision";
13934
+ var DEFAULT_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
13935
+ var ENTRIES_LIST_URL2 = "https://logging.googleapis.com/v2/entries:list";
13936
+ var PAGE_SIZE2 = 1e3;
13937
+ var MAX_PAGES2 = 20;
13938
+ async function fetchCloudRunRequestLogEntries(creds, sinceIso, apiUrl = ENTRIES_LIST_URL2) {
13939
+ const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso);
13940
+ const out = [];
13941
+ let pageToken;
13942
+ for (let page = 0; page < MAX_PAGES2; page++) {
13943
+ const body = {
13944
+ resourceNames: [`projects/${creds.projectId}`],
13945
+ filter,
13946
+ orderBy: "timestamp asc",
13947
+ pageSize: PAGE_SIZE2,
13948
+ ...pageToken ? { pageToken } : {}
13949
+ };
13634
13950
  const res = await junctionFetch(
13635
- url,
13951
+ apiUrl,
13636
13952
  {
13637
- ...init ?? {},
13638
- headers: { ...bearerAuthHeader(token), ...init?.headers ?? {} }
13953
+ method: "POST",
13954
+ headers: {
13955
+ ...bearerAuthHeader(creds.accessToken),
13956
+ "Content-Type": "application/json"
13957
+ },
13958
+ body: JSON.stringify(body)
13639
13959
  },
13640
- { provider, accountKey, ...fetchImpl ? { fetchImpl } : {} }
13960
+ // accountKey: the GCP project id one customer's Cloud Logging quota is
13961
+ // scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit
13962
+ // bucket), the same key Firebase's connector uses.
13963
+ { provider: "cloud-run", accountKey: creds.projectId }
13641
13964
  );
13642
- if (res.ok) return { ok: true };
13643
- if (res.status === 401 || res.status === 403) {
13644
- return { ok: false, reason: `${provider} rejected the credential (HTTP ${res.status})` };
13965
+ if (!res.ok) {
13966
+ throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
13645
13967
  }
13646
- return {
13647
- ok: false,
13648
- reason: `${provider} auth check returned HTTP ${res.status} ${res.statusText} \u2014 could not confirm the credential`
13649
- };
13650
- } catch (err) {
13651
- return {
13652
- ok: false,
13653
- reason: `${provider} auth check could not reach the provider: ${err.message}`
13654
- };
13968
+ const json = await res.json();
13969
+ if (Array.isArray(json.entries)) out.push(...json.entries);
13970
+ if (!json.nextPageToken) break;
13971
+ pageToken = json.nextPageToken;
13655
13972
  }
13973
+ return out;
13656
13974
  }
13657
- var PROVIDER_DISPATCH = {
13658
- supabase: {
13659
- provider: "supabase",
13660
- primaryCredentialKey: "managementToken",
13661
- requiredCredentialFields: ["managementToken"],
13662
- requiredOptionFields: ["apiProjectRef", "nodeRef", "serviceName"],
13663
- build(graph, options) {
13664
- return createSupabaseConnector(graph, options);
13665
- },
13666
- // GET /v1/projects the Management API's own auth-gated list endpoint, the
13667
- // cheapest confirmation the management token is live (the same surface
13975
+
13976
+ // src/connectors/cloud-run/map.ts
13977
+ init_cjs_shims();
13978
+
13979
+ // src/connectors/cloud-run/types.ts
13980
+ init_cjs_shims();
13981
+ function readCloudRunCredentials(raw) {
13982
+ const projectId = raw["projectId"];
13983
+ const accessToken = raw["accessToken"];
13984
+ if (typeof projectId !== "string" || projectId.length === 0) {
13985
+ throw new Error("cloud-run connector: credentials.projectId must be a non-empty string");
13986
+ }
13987
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
13988
+ throw new Error("cloud-run connector: credentials.accessToken must be a non-empty string");
13989
+ }
13990
+ return { projectId, accessToken };
13991
+ }
13992
+ var CLOUD_RUN_TARGET_KIND = "cloud_run_revision";
13993
+ var FIELD_SEP2 = "\0";
13994
+ function packCloudRunTargetName(identity) {
13995
+ return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP2);
13996
+ }
13997
+ function parseCloudRunTargetName(targetName) {
13998
+ const firstSep = targetName.indexOf(FIELD_SEP2);
13999
+ if (firstSep === -1) return null;
14000
+ const serviceName = targetName.slice(0, firstSep);
14001
+ const rest = targetName.slice(firstSep + 1);
14002
+ const secondSep = rest.indexOf(FIELD_SEP2);
14003
+ if (secondSep === -1) return null;
14004
+ const method = rest.slice(0, secondSep);
14005
+ const path74 = rest.slice(secondSep + 1);
14006
+ if (!serviceName || !method || !path74) return null;
14007
+ return { serviceName, method, path: path74 };
14008
+ }
14009
+
14010
+ // src/connectors/cloud-run/map.ts
14011
+ var CLOUD_RUN_RESOURCE_TYPE2 = "cloud_run_revision";
14012
+ function pathFromRequestUrl2(requestUrl) {
14013
+ if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
14014
+ if (requestUrl.startsWith("/")) {
14015
+ const withoutQuery = requestUrl.split("?")[0];
14016
+ return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
14017
+ }
14018
+ try {
14019
+ const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
14020
+ const parsed = new URL(candidate);
14021
+ return parsed.pathname || "/";
14022
+ } catch {
14023
+ return null;
14024
+ }
14025
+ }
14026
+ var ERROR_STATUS_THRESHOLD4 = 500;
14027
+ function mapLogEntryToSignal2(entry2) {
14028
+ if (!entry2 || typeof entry2 !== "object") return null;
14029
+ if (entry2.resource?.type !== CLOUD_RUN_RESOURCE_TYPE2) return null;
14030
+ const serviceName = entry2.resource?.labels?.["service_name"];
14031
+ if (typeof serviceName !== "string" || serviceName.length === 0) return null;
14032
+ const req = entry2.httpRequest;
14033
+ if (!req) return null;
14034
+ if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
14035
+ const method = req.requestMethod.toUpperCase();
14036
+ const path74 = pathFromRequestUrl2(req.requestUrl);
14037
+ if (path74 === null) return null;
14038
+ const timestamp = entry2.timestamp;
14039
+ if (typeof timestamp !== "string" || timestamp.length === 0) return null;
14040
+ const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
14041
+ return {
14042
+ targetKind: CLOUD_RUN_TARGET_KIND,
14043
+ targetName: packCloudRunTargetName({ serviceName, method, path: path74 }),
14044
+ callCount: 1,
14045
+ errorCount: isError ? 1 : 0,
14046
+ lastObservedIso: timestamp
14047
+ };
14048
+ }
14049
+ function mapLogEntriesToSignals2(entries) {
14050
+ const out = [];
14051
+ for (const entry2 of entries) {
14052
+ const signal = mapLogEntryToSignal2(entry2);
14053
+ if (signal) out.push(signal);
14054
+ }
14055
+ return out;
14056
+ }
14057
+
14058
+ // src/connectors/cloud-run/resolve.ts
14059
+ init_cjs_shims();
14060
+ var import_types62 = require("@neat.is/types");
14061
+ var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
14062
+ function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
14063
+ let found = null;
14064
+ graph.forEachNode((_id, attrs) => {
14065
+ if (found) return;
14066
+ const node = attrs;
14067
+ if (node.type !== import_types62.NodeType.RouteNode) return;
14068
+ const route = attrs;
14069
+ if (route.service !== serviceName || !route.pathTemplate) return;
14070
+ if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
14071
+ const routeMethod = route.method.toUpperCase();
14072
+ if (routeMethod !== "ALL" && routeMethod !== method) return;
14073
+ found = route.id;
14074
+ });
14075
+ return found;
14076
+ }
14077
+ function createCloudRunResolveTarget(graph, config) {
14078
+ return (signal) => {
14079
+ if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
14080
+ const identity = parseCloudRunTargetName(signal.targetName);
14081
+ if (!identity) return null;
14082
+ const { serviceName: gcpServiceName, method, path: path74 } = identity;
14083
+ const mappedService = config.serviceMap?.[gcpServiceName];
14084
+ if (mappedService) {
14085
+ const routeNodeId = findMatchingRouteNode2(
14086
+ graph,
14087
+ mappedService,
14088
+ method,
14089
+ normalizePathTemplate(path74)
14090
+ );
14091
+ if (routeNodeId) {
14092
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types62.EdgeType.CALLS };
14093
+ }
14094
+ }
14095
+ return {
14096
+ targetNodeId: (0, import_types62.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
14097
+ serviceName: mappedService ?? gcpServiceName,
14098
+ edgeType: import_types62.EdgeType.CALLS,
14099
+ ensureInfraNode: {
14100
+ kind: CLOUD_RUN_SERVICE_INFRA_KIND,
14101
+ name: gcpServiceName,
14102
+ provider: "cloud-run"
14103
+ }
14104
+ };
14105
+ };
14106
+ }
14107
+
14108
+ // src/connectors/cloud-run/index.ts
14109
+ var CloudRunConnector = class {
14110
+ constructor(config = {}) {
14111
+ this.config = config;
14112
+ }
14113
+ config;
14114
+ provider = "cloud-run";
14115
+ async poll(ctx) {
14116
+ const creds = readCloudRunCredentials(ctx.credentials);
14117
+ const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS2;
14118
+ const sinceIso = boundedSinceIso(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
14119
+ const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl);
14120
+ return mapLogEntriesToSignals2(entries);
14121
+ }
14122
+ };
14123
+ function boundedSinceIso(since, now, maxLookbackMs) {
14124
+ const floor = new Date(now.getTime() - maxLookbackMs);
14125
+ if (!since) return floor.toISOString();
14126
+ const sinceMs = new Date(since).getTime();
14127
+ if (Number.isNaN(sinceMs)) return floor.toISOString();
14128
+ return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
14129
+ }
14130
+ function createCloudRunConnector(graph, config = {}) {
14131
+ return {
14132
+ connector: new CloudRunConnector(config),
14133
+ resolveTarget: createCloudRunResolveTarget(graph, config)
14134
+ };
14135
+ }
14136
+
14137
+ // src/connectors/render/index.ts
14138
+ init_cjs_shims();
14139
+ var import_types65 = require("@neat.is/types");
14140
+
14141
+ // src/connectors/render/types.ts
14142
+ init_cjs_shims();
14143
+ function readRenderToken(credentials) {
14144
+ const token = credentials.token;
14145
+ if (typeof token !== "string" || token.length === 0) {
14146
+ throw new Error("Render connector requires ctx.credentials.token (a Render API key)");
14147
+ }
14148
+ return token;
14149
+ }
14150
+ function renderLabelValue(entry2, name) {
14151
+ if (!Array.isArray(entry2.labels)) return void 0;
14152
+ const label = entry2.labels.find((l) => l && typeof l === "object" && l.name === name);
14153
+ return label && typeof label.value === "string" ? label.value : void 0;
14154
+ }
14155
+
14156
+ // src/connectors/render/client.ts
14157
+ init_cjs_shims();
14158
+ var DEFAULT_RENDER_API_URL = "https://api.render.com/v1";
14159
+ var DEFAULT_RENDER_LOG_LIMIT = 100;
14160
+ var RENDER_MAX_LOG_LIMIT = 100;
14161
+ var DEFAULT_RENDER_MAX_PAGES = 20;
14162
+ var DEFAULT_MAX_LOOKBACK_MS4 = 24 * 60 * 60 * 1e3;
14163
+ function clampLimit(limit) {
14164
+ const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT);
14165
+ if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT;
14166
+ return Math.min(raw, RENDER_MAX_LOG_LIMIT);
14167
+ }
14168
+ function boundedRenderStartTime(since, now, maxLookbackMs) {
14169
+ const floor = new Date(now.getTime() - maxLookbackMs);
14170
+ if (!since) return floor.toISOString();
14171
+ const sinceMs = new Date(since).getTime();
14172
+ if (Number.isNaN(sinceMs)) return floor.toISOString();
14173
+ return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
14174
+ }
14175
+ async function fetchRenderLogPage(config, token, startTime, endTime, limit, fetchImpl) {
14176
+ const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`);
14177
+ url.searchParams.set("ownerId", config.ownerId);
14178
+ url.searchParams.set("resource", config.resourceId);
14179
+ url.searchParams.set("type", "request");
14180
+ url.searchParams.set("startTime", startTime);
14181
+ url.searchParams.set("endTime", endTime);
14182
+ url.searchParams.set("direction", "backward");
14183
+ url.searchParams.set("limit", String(limit));
14184
+ const res = await junctionFetch(
14185
+ url,
14186
+ { method: "GET", headers: { ...bearerAuthHeader(token) } },
14187
+ { provider: "render", accountKey: config.ownerId, ...fetchImpl ? { fetchImpl } : {} }
14188
+ );
14189
+ if (!res.ok) {
14190
+ throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`);
14191
+ }
14192
+ return await res.json();
14193
+ }
14194
+ async function fetchRenderRequestLogs(config, token, startTime, endTime, fetchImpl) {
14195
+ const limit = clampLimit(config.limit);
14196
+ const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES));
14197
+ const out = [];
14198
+ let pageStart = startTime;
14199
+ let pageEnd = endTime;
14200
+ for (let page = 0; page < maxPages; page++) {
14201
+ const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl);
14202
+ if (Array.isArray(body.logs)) out.push(...body.logs);
14203
+ if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break;
14204
+ pageStart = body.nextStartTime;
14205
+ pageEnd = body.nextEndTime;
14206
+ }
14207
+ return out;
14208
+ }
14209
+
14210
+ // src/connectors/render/index.ts
14211
+ var ROUTE_TARGET_KIND2 = "route";
14212
+ var UNMATCHED_ROUTE_TARGET_KIND2 = "unmatched-route";
14213
+ function buildRenderRouteIndex(graph, serviceName) {
14214
+ const out = [];
14215
+ graph.forEachNode((_id, attrs) => {
14216
+ const node = attrs;
14217
+ if (node.type !== import_types65.NodeType.RouteNode) return;
14218
+ const route = attrs;
14219
+ if (route.service !== serviceName) return;
14220
+ out.push({
14221
+ method: route.method.toUpperCase(),
14222
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
14223
+ routeNodeId: route.id,
14224
+ path: route.path,
14225
+ line: route.line
14226
+ });
14227
+ });
14228
+ return out;
14229
+ }
14230
+ function findRenderRoute(entries, method, normalizedPath) {
14231
+ return entries.find(
14232
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
14233
+ );
14234
+ }
14235
+ function bucketKey3(method, normalizedPath) {
14236
+ return `${method} ${normalizedPath}`;
14237
+ }
14238
+ function isHttpErrorStatus2(status2) {
14239
+ return status2 >= 400;
14240
+ }
14241
+ function upsertBucket2(buckets2, key, isError, timestamp, build) {
14242
+ const existing = buckets2.get(key);
14243
+ if (existing) {
14244
+ existing.callCount += 1;
14245
+ if (isError) existing.errorCount += 1;
14246
+ if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
14247
+ return;
14248
+ }
14249
+ buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
14250
+ }
14251
+ function mapRenderRequestLogsToSignals(entries, routeIndex) {
14252
+ const buckets2 = /* @__PURE__ */ new Map();
14253
+ if (!Array.isArray(entries)) return [];
14254
+ for (const entry2 of entries) {
14255
+ if (!entry2 || typeof entry2 !== "object") continue;
14256
+ if (typeof entry2.timestamp !== "string") continue;
14257
+ const method = renderLabelValue(entry2, "method");
14258
+ const rawPath = renderLabelValue(entry2, "path");
14259
+ if (typeof method !== "string" || method.length === 0) continue;
14260
+ if (typeof rawPath !== "string" || rawPath.length === 0) continue;
14261
+ const methodUpper = method.toUpperCase();
14262
+ const pathOnly = rawPath.split("?")[0];
14263
+ const normalizedPath = normalizePathTemplate(pathOnly);
14264
+ const statusCode = Number.parseInt(renderLabelValue(entry2, "statusCode") ?? "", 10);
14265
+ const isError = Number.isFinite(statusCode) && isHttpErrorStatus2(statusCode);
14266
+ const match = findRenderRoute(routeIndex, methodUpper, normalizedPath);
14267
+ if (match) {
14268
+ upsertBucket2(buckets2, `route:${match.routeNodeId}`, isError, entry2.timestamp, () => ({
14269
+ targetKind: ROUTE_TARGET_KIND2,
14270
+ targetName: match.routeNodeId,
14271
+ // RouteNode.line is optional in the schema (packages/types/src/
14272
+ // nodes.ts) even though routes.ts always sets it today — skip the
14273
+ // callSite rather than fabricate a line when it's ever absent
14274
+ // (file-awareness.md §6).
14275
+ ...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
14276
+ }));
14277
+ } else {
14278
+ upsertBucket2(
14279
+ buckets2,
14280
+ `unmatched:${bucketKey3(methodUpper, normalizedPath)}`,
14281
+ isError,
14282
+ entry2.timestamp,
14283
+ () => ({
14284
+ targetKind: UNMATCHED_ROUTE_TARGET_KIND2,
14285
+ targetName: bucketKey3(methodUpper, normalizedPath)
14286
+ })
14287
+ );
14288
+ }
14289
+ }
14290
+ return [...buckets2.values()].map((b) => ({
14291
+ targetKind: b.targetKind,
14292
+ targetName: b.targetName,
14293
+ callCount: b.callCount,
14294
+ errorCount: b.errorCount,
14295
+ lastObservedIso: b.lastObservedIso,
14296
+ ...b.callSite ? { callSite: b.callSite } : {}
14297
+ }));
14298
+ }
14299
+ function createRenderResolveTarget(config) {
14300
+ return (signal) => {
14301
+ if (signal.targetKind === ROUTE_TARGET_KIND2) {
14302
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
14303
+ }
14304
+ return null;
14305
+ };
14306
+ }
14307
+ function createRenderConnector(graph, config) {
14308
+ return {
14309
+ provider: "render",
14310
+ async poll(ctx) {
14311
+ const token = readRenderToken(ctx.credentials);
14312
+ const now = /* @__PURE__ */ new Date();
14313
+ const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS4;
14314
+ const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs);
14315
+ const endTime = now.toISOString();
14316
+ const logs = await fetchRenderRequestLogs(config, token, startTime, endTime);
14317
+ const routeIndex = buildRenderRouteIndex(graph, config.serviceName);
14318
+ return mapRenderRequestLogsToSignals(logs, routeIndex);
14319
+ }
14320
+ };
14321
+ }
14322
+
14323
+ // src/connectors/registry.ts
14324
+ var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
14325
+ async function authProbe(input) {
14326
+ const { provider, accountKey, url, token, init, fetchImpl } = input;
14327
+ try {
14328
+ const res = await junctionFetch(
14329
+ url,
14330
+ {
14331
+ ...init ?? {},
14332
+ headers: { ...bearerAuthHeader(token), ...init?.headers ?? {} }
14333
+ },
14334
+ { provider, accountKey, ...fetchImpl ? { fetchImpl } : {} }
14335
+ );
14336
+ if (res.ok) return { ok: true };
14337
+ if (res.status === 401 || res.status === 403) {
14338
+ return { ok: false, reason: `${provider} rejected the credential (HTTP ${res.status})` };
14339
+ }
14340
+ return {
14341
+ ok: false,
14342
+ reason: `${provider} auth check returned HTTP ${res.status} ${res.statusText} \u2014 could not confirm the credential`
14343
+ };
14344
+ } catch (err) {
14345
+ return {
14346
+ ok: false,
14347
+ reason: `${provider} auth check could not reach the provider: ${err.message}`
14348
+ };
14349
+ }
14350
+ }
14351
+ var PROVIDER_DISPATCH = {
14352
+ supabase: {
14353
+ provider: "supabase",
14354
+ primaryCredentialKey: "managementToken",
14355
+ requiredCredentialFields: ["managementToken"],
14356
+ requiredOptionFields: ["apiProjectRef", "nodeRef", "serviceName"],
14357
+ build(graph, options) {
14358
+ return createSupabaseConnector(graph, options);
14359
+ },
14360
+ // GET /v1/projects — the Management API's own auth-gated list endpoint, the
14361
+ // cheapest confirmation the management token is live (the same surface
13668
14362
  // client.ts polls, minus the heavy log query).
13669
14363
  validate({ credentials, options, fetchImpl }) {
13670
14364
  const cfg = options;
@@ -13801,6 +14495,71 @@ var PROVIDER_DISPATCH = {
13801
14495
  return { ok: false, reason: `neon telemetry read failed: ${err.message}` };
13802
14496
  }
13803
14497
  }
14498
+ },
14499
+ "cloud-run": {
14500
+ provider: "cloud-run",
14501
+ // Cloud Run reads both projectId and accessToken from the credential; the
14502
+ // single-string form maps to the secret (the token), and the required-fields
14503
+ // check below catches a projectId that was never supplied.
14504
+ primaryCredentialKey: "accessToken",
14505
+ requiredCredentialFields: ["projectId", "accessToken"],
14506
+ requiredOptionFields: [],
14507
+ build(graph, options) {
14508
+ return createCloudRunConnector(graph, options);
14509
+ },
14510
+ // POST entries:list with pageSize 1 — the exact surface poll() reads, so the
14511
+ // probe checks the actual `logging.logEntries.list` permission the connector
14512
+ // needs. A GET on the lighter logs.list endpoint (as Firebase probes) would
14513
+ // instead check `logging.logs.list`, falsely rejecting a correctly-scoped
14514
+ // custom role that carries only `logging.logEntries.list` (the narrowest
14515
+ // grant docs/connectors/cloud-run.md documents) — the same false-negative
14516
+ // trap Railway's validate avoids by probing its real query. A 2xx means the
14517
+ // token can list log entries; 401/403 means the provider rejected it.
14518
+ validate({ credentials, fetchImpl }) {
14519
+ const projectId = String(credentials.projectId ?? "");
14520
+ return authProbe({
14521
+ provider: "cloud-run",
14522
+ accountKey: projectId || "validate",
14523
+ url: "https://logging.googleapis.com/v2/entries:list",
14524
+ token: String(credentials.accessToken ?? ""),
14525
+ init: {
14526
+ method: "POST",
14527
+ headers: { "Content-Type": "application/json" },
14528
+ body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 })
14529
+ },
14530
+ ...fetchImpl ? { fetchImpl } : {}
14531
+ });
14532
+ }
14533
+ },
14534
+ render: {
14535
+ provider: "render",
14536
+ primaryCredentialKey: "token",
14537
+ requiredCredentialFields: ["token"],
14538
+ requiredOptionFields: ["ownerId", "resourceId", "serviceName"],
14539
+ build(graph, options) {
14540
+ const config = options;
14541
+ return {
14542
+ connector: createRenderConnector(graph, config),
14543
+ resolveTarget: createRenderResolveTarget(config)
14544
+ };
14545
+ },
14546
+ // GET /v1/services?limit=1 — the cheapest read the Render API key
14547
+ // authenticates against (render.com/docs/api). Unlike Railway's GraphQL
14548
+ // gateway, Render is a plain REST API: a live key returns 2xx, a bad one a
14549
+ // 401/403, so authProbe's status-code check is a true verdict here. The
14550
+ // logs query itself also needs an ownerId + resource; `services` needs
14551
+ // neither and still fails 401 on a bad token, so it's the honest probe.
14552
+ validate({ credentials, options, fetchImpl }) {
14553
+ const cfg = options;
14554
+ const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL;
14555
+ return authProbe({
14556
+ provider: "render",
14557
+ accountKey: cfg.ownerId ?? "validate",
14558
+ url: `${baseUrl}/services?limit=1`,
14559
+ token: String(credentials.token ?? ""),
14560
+ ...fetchImpl ? { fetchImpl } : {}
14561
+ });
14562
+ }
13804
14563
  }
13805
14564
  };
13806
14565
  function vercelCredsFrom(credentials) {
@@ -14231,11 +14990,11 @@ function registerRoutes(scope, ctx) {
14231
14990
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
14232
14991
  const parsed = [];
14233
14992
  for (const c of candidates) {
14234
- const r = import_types60.DivergenceTypeSchema.safeParse(c);
14993
+ const r = import_types67.DivergenceTypeSchema.safeParse(c);
14235
14994
  if (!r.success) {
14236
14995
  return reply.code(400).send({
14237
14996
  error: `unknown divergence type "${c}"`,
14238
- allowed: import_types60.DivergenceTypeSchema.options
14997
+ allowed: import_types67.DivergenceTypeSchema.options
14239
14998
  });
14240
14999
  }
14241
15000
  parsed.push(r.data);
@@ -14544,7 +15303,7 @@ function registerRoutes(scope, ctx) {
14544
15303
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
14545
15304
  let violations = await log.readAll();
14546
15305
  if (req.query.severity) {
14547
- const sev = import_types60.PolicySeveritySchema.safeParse(req.query.severity);
15306
+ const sev = import_types67.PolicySeveritySchema.safeParse(req.query.severity);
14548
15307
  if (!sev.success) {
14549
15308
  return reply.code(400).send({
14550
15309
  error: "invalid severity",
@@ -14583,7 +15342,7 @@ function registerRoutes(scope, ctx) {
14583
15342
  scope.post("/policies/check", async (req, reply) => {
14584
15343
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
14585
15344
  if (!proj) return;
14586
- const parsed = import_types60.PoliciesCheckBodySchema.safeParse(req.body ?? {});
15345
+ const parsed = import_types67.PoliciesCheckBodySchema.safeParse(req.body ?? {});
14587
15346
  if (!parsed.success) {
14588
15347
  return reply.code(400).send({
14589
15348
  error: "invalid /policies/check body",
@@ -14916,7 +15675,7 @@ var import_node_fs33 = require("fs");
14916
15675
  var import_node_path60 = __toESM(require("path"), 1);
14917
15676
 
14918
15677
  // src/daemon.ts
14919
- var import_types61 = require("@neat.is/types");
15678
+ var import_types68 = require("@neat.is/types");
14920
15679
  function daemonJsonPath(scanPath) {
14921
15680
  return import_node_path61.default.join(scanPath, "neat-out", "daemon.json");
14922
15681
  }
@@ -19536,13 +20295,462 @@ async function runHooksCommand(args) {
19536
20295
  }
19537
20296
  }
19538
20297
 
20298
+ // src/codex-cli.ts
20299
+ init_cjs_shims();
20300
+ var import_node_path70 = __toESM(require("path"), 1);
20301
+ var import_node_os6 = __toESM(require("os"), 1);
20302
+ var import_node_fs43 = require("fs");
20303
+ var import_node_util = require("util");
20304
+ var import_smol_toml5 = require("smol-toml");
20305
+ var CODEX_MCP_SERVER = {
20306
+ command: "npx",
20307
+ args: ["-y", "@neat.is/mcp"],
20308
+ env: { NEAT_CORE_URL: "http://localhost:8080" }
20309
+ };
20310
+ var CODEX_NEAT_BLOCK = [
20311
+ "[mcp_servers.neat]",
20312
+ 'command = "npx"',
20313
+ 'args = ["-y", "@neat.is/mcp"]',
20314
+ 'env = { NEAT_CORE_URL = "http://localhost:8080" }'
20315
+ ].join("\n");
20316
+ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
20317
+ var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
20318
+ function codexConfigPath() {
20319
+ const override = process.env.NEAT_CODEX_CONFIG;
20320
+ if (override && override.length > 0) return import_node_path70.default.resolve(override);
20321
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
20322
+ return import_node_path70.default.join(home, ".codex", "config.toml");
20323
+ }
20324
+ function agentsFilePath() {
20325
+ const override = process.env.NEAT_CODEX_AGENTS;
20326
+ if (override && override.length > 0) return import_node_path70.default.resolve(override);
20327
+ return import_node_path70.default.join(process.cwd(), "AGENTS.md");
20328
+ }
20329
+ function isTableHeader(line) {
20330
+ return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
20331
+ }
20332
+ function tableName(line) {
20333
+ return line.trim().replace(/^\[\[?/, "").replace(/\]\]?$/, "").trim();
20334
+ }
20335
+ function isNeatHeader(line) {
20336
+ return isTableHeader(line) && tableName(line) === "mcp_servers.neat";
20337
+ }
20338
+ function isNeatChildHeader(line) {
20339
+ if (!isTableHeader(line)) return false;
20340
+ const name = tableName(line);
20341
+ return name === "mcp_servers.neat" || name.startsWith("mcp_servers.neat.");
20342
+ }
20343
+ function upsertCodexConfig(raw) {
20344
+ const trimmed = raw.trim();
20345
+ const parsed = trimmed.length > 0 ? (0, import_smol_toml5.parse)(raw) : {};
20346
+ const existingServers = parsed.mcp_servers ?? {};
20347
+ const spliced = spliceNeatBlock(raw);
20348
+ let text;
20349
+ if (verifyPreserved(raw, spliced, parsed)) {
20350
+ text = spliced;
20351
+ } else {
20352
+ const merged = {
20353
+ ...parsed,
20354
+ mcp_servers: { ...existingServers, neat: CODEX_MCP_SERVER }
20355
+ };
20356
+ text = (0, import_smol_toml5.stringify)(merged);
20357
+ if (!text.endsWith("\n")) text += "\n";
20358
+ }
20359
+ return { text, changed: text !== raw };
20360
+ }
20361
+ function spliceNeatBlock(raw) {
20362
+ const block = CODEX_NEAT_BLOCK;
20363
+ const lines = raw.length > 0 ? raw.split("\n") : [];
20364
+ const start = lines.findIndex(isNeatHeader);
20365
+ if (start === -1) {
20366
+ const base = raw.replace(/\n+$/, "");
20367
+ return base.length > 0 ? `${base}
20368
+
20369
+ ${block}
20370
+ ` : `${block}
20371
+ `;
20372
+ }
20373
+ let end = start + 1;
20374
+ for (; ; ) {
20375
+ while (end < lines.length && !isTableHeader(lines[end])) end++;
20376
+ if (end < lines.length && isNeatChildHeader(lines[end])) {
20377
+ end++;
20378
+ continue;
20379
+ }
20380
+ break;
20381
+ }
20382
+ const before = lines.slice(0, start).join("\n").replace(/\n*$/, "");
20383
+ const after = lines.slice(end).join("\n").replace(/^\n*/, "");
20384
+ let text = "";
20385
+ if (before.length > 0) text += `${before}
20386
+
20387
+ `;
20388
+ text += `${block}
20389
+ `;
20390
+ if (after.length > 0) text += `
20391
+ ${after}`;
20392
+ return `${text.replace(/\n*$/, "")}
20393
+ `;
20394
+ }
20395
+ function verifyPreserved(raw, spliced, original) {
20396
+ let next;
20397
+ try {
20398
+ next = (0, import_smol_toml5.parse)(spliced);
20399
+ } catch {
20400
+ return false;
20401
+ }
20402
+ const origServers = original.mcp_servers ?? {};
20403
+ const nextServers = next.mcp_servers ?? {};
20404
+ if (!(0, import_node_util.isDeepStrictEqual)(nextServers.neat, CODEX_MCP_SERVER)) return false;
20405
+ for (const name of Object.keys(origServers)) {
20406
+ if (name === "neat") continue;
20407
+ if (!(0, import_node_util.isDeepStrictEqual)(nextServers[name], origServers[name])) return false;
20408
+ }
20409
+ for (const name of Object.keys(nextServers)) {
20410
+ if (name !== "neat" && !(name in origServers)) return false;
20411
+ }
20412
+ for (const key of Object.keys(original)) {
20413
+ if (key === "mcp_servers") continue;
20414
+ if (!(0, import_node_util.isDeepStrictEqual)(next[key], original[key])) return false;
20415
+ }
20416
+ for (const key of Object.keys(next)) {
20417
+ if (key !== "mcp_servers" && !(key in original)) return false;
20418
+ }
20419
+ return true;
20420
+ }
20421
+ function agentsBlock(guide) {
20422
+ return `${NEAT_GRAPH_FIRST_START}
20423
+ ${guide.replace(/\s+$/, "")}
20424
+ ${NEAT_GRAPH_FIRST_END}
20425
+ `;
20426
+ }
20427
+ function upsertAgents(raw, guide) {
20428
+ const block = agentsBlock(guide);
20429
+ if (raw.length === 0) return { text: block, changed: true };
20430
+ const startIdx = raw.indexOf(NEAT_GRAPH_FIRST_START);
20431
+ const endIdx = raw.indexOf(NEAT_GRAPH_FIRST_END);
20432
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
20433
+ const before = raw.slice(0, startIdx);
20434
+ const after = raw.slice(endIdx + NEAT_GRAPH_FIRST_END.length);
20435
+ const text2 = `${before}${block.replace(/\n+$/, "")}${after}`;
20436
+ return { text: text2, changed: text2 !== raw };
20437
+ }
20438
+ const base = raw.replace(/\n+$/, "");
20439
+ const text = base.length > 0 ? `${base}
20440
+
20441
+ ${block}` : block;
20442
+ return { text, changed: text !== raw };
20443
+ }
20444
+ async function readGuide() {
20445
+ return readSkillAsset(GUIDE_FILENAME);
20446
+ }
20447
+ async function runCodex(opts) {
20448
+ if (opts.printConfig) {
20449
+ process.stdout.write(`${CODEX_NEAT_BLOCK}
20450
+ `);
20451
+ return { exitCode: 0 };
20452
+ }
20453
+ if (opts.printGuide) {
20454
+ process.stdout.write(agentsBlock(await readGuide()));
20455
+ return { exitCode: 0 };
20456
+ }
20457
+ const configPath = codexConfigPath();
20458
+ const agentsPath = agentsFilePath();
20459
+ let configRaw = "";
20460
+ try {
20461
+ configRaw = await import_node_fs43.promises.readFile(configPath, "utf8");
20462
+ } catch (err) {
20463
+ if (err.code !== "ENOENT") {
20464
+ console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
20465
+ return { exitCode: 1 };
20466
+ }
20467
+ }
20468
+ let agentsRaw = "";
20469
+ try {
20470
+ agentsRaw = await import_node_fs43.promises.readFile(agentsPath, "utf8");
20471
+ } catch (err) {
20472
+ if (err.code !== "ENOENT") {
20473
+ console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
20474
+ return { exitCode: 1 };
20475
+ }
20476
+ }
20477
+ let config;
20478
+ try {
20479
+ config = upsertCodexConfig(configRaw);
20480
+ } catch (err) {
20481
+ console.error(
20482
+ `neat codex: ${configPath} is not valid TOML \u2014 ${err.message}`
20483
+ );
20484
+ console.error("neat codex: fix the file and re-run; nothing was written.");
20485
+ return { exitCode: 1 };
20486
+ }
20487
+ const guide = await readGuide();
20488
+ const agents = upsertAgents(agentsRaw, guide);
20489
+ if (!opts.apply) {
20490
+ console.log("neat codex \u2014 plan (nothing written; re-run with --apply to write)");
20491
+ console.log("");
20492
+ console.log(` Codex MCP config: ${configPath}`);
20493
+ console.log(
20494
+ config.changed ? ` ${configRaw ? "update" : "create"} the [mcp_servers.neat] table:` : " already up to date \u2014 [mcp_servers.neat] matches"
20495
+ );
20496
+ if (config.changed) {
20497
+ for (const line of CODEX_NEAT_BLOCK.split("\n")) console.log(` ${line}`);
20498
+ }
20499
+ console.log("");
20500
+ console.log(` Project instructions: ${agentsPath}`);
20501
+ console.log(
20502
+ agents.changed ? ` ${agentsRaw ? "update" : "create"} the graph-first block (between ${NEAT_GRAPH_FIRST_START} markers)` : " already up to date \u2014 graph-first block matches"
20503
+ );
20504
+ console.log("");
20505
+ console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 edit that value in");
20506
+ console.log("the generated table to point Codex at a non-default daemon.");
20507
+ return { exitCode: 0 };
20508
+ }
20509
+ if (config.changed) {
20510
+ await import_node_fs43.promises.mkdir(import_node_path70.default.dirname(configPath), { recursive: true });
20511
+ await import_node_fs43.promises.writeFile(configPath, config.text, "utf8");
20512
+ console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
20513
+ } else {
20514
+ console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
20515
+ }
20516
+ if (agents.changed) {
20517
+ await import_node_fs43.promises.mkdir(import_node_path70.default.dirname(agentsPath), { recursive: true });
20518
+ await import_node_fs43.promises.writeFile(agentsPath, agents.text, "utf8");
20519
+ console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
20520
+ } else {
20521
+ console.log(`neat codex: ${agentsPath} already has the graph-first block`);
20522
+ }
20523
+ console.log("");
20524
+ console.log("restart Codex to pick up the new MCP server. NEAT_CORE_URL in the table");
20525
+ console.log("points the server at the local daemon \u2014 edit it for a non-default one.");
20526
+ return { exitCode: 0 };
20527
+ }
20528
+ function usage2() {
20529
+ console.log("neat codex \u2014 install NEAT into the OpenAI Codex CLI (MCP server + AGENTS.md)");
20530
+ console.log("");
20531
+ console.log(" (no flag) plan: print what would change, write nothing");
20532
+ console.log(" --apply add [mcp_servers.neat] to ~/.codex/config.toml and write");
20533
+ console.log(" the graph-first block into ./AGENTS.md, merging into both");
20534
+ console.log(" without touching your other servers or instructions");
20535
+ console.log(" --print-config print the [mcp_servers.neat] TOML block to stdout");
20536
+ console.log(" --print-guide print the AGENTS.md graph-first block to stdout");
20537
+ console.log("");
20538
+ console.log("Existing config is preserved and a re-run is a no-op. A malformed");
20539
+ console.log("config.toml is a clear error with no partial write.");
20540
+ }
20541
+ async function runCodexCommand(args) {
20542
+ const opts = { apply: false, printConfig: false, printGuide: false };
20543
+ for (const arg of args) {
20544
+ switch (arg) {
20545
+ case "--apply":
20546
+ opts.apply = true;
20547
+ break;
20548
+ case "--print-config":
20549
+ opts.printConfig = true;
20550
+ break;
20551
+ case "--print-guide":
20552
+ opts.printGuide = true;
20553
+ break;
20554
+ case "-h":
20555
+ case "--help":
20556
+ usage2();
20557
+ return 0;
20558
+ default:
20559
+ console.error(`neat codex: unknown flag "${arg}"`);
20560
+ usage2();
20561
+ return 2;
20562
+ }
20563
+ }
20564
+ try {
20565
+ const { exitCode } = await runCodex(opts);
20566
+ return exitCode;
20567
+ } catch (err) {
20568
+ console.error(err.message);
20569
+ return 1;
20570
+ }
20571
+ }
20572
+
20573
+ // src/editors-cli.ts
20574
+ init_cjs_shims();
20575
+ var import_node_path71 = __toESM(require("path"), 1);
20576
+ var import_node_os7 = __toESM(require("os"), 1);
20577
+ var import_node_fs44 = require("fs");
20578
+ var NEAT_MCP_SERVER = {
20579
+ command: "npx",
20580
+ args: ["-y", "@neat.is/mcp"]
20581
+ };
20582
+ var GRAPH_FIRST_MARKER_OPEN = "<!-- neat:graph-first -->";
20583
+ var GRAPH_FIRST_MARKER_CLOSE = "<!-- /neat:graph-first -->";
20584
+ function homeDir() {
20585
+ return process.env.HOME ?? process.env.USERPROFILE ?? import_node_os7.default.homedir();
20586
+ }
20587
+ var CURSOR_CLIENT = {
20588
+ id: "cursor",
20589
+ label: "Cursor",
20590
+ docsUrl: "https://docs.cursor.com/context/mcp",
20591
+ mcpConfigPath: () => {
20592
+ const override = process.env.NEAT_CURSOR_CONFIG;
20593
+ if (override && override.length > 0) return import_node_path71.default.resolve(override);
20594
+ return import_node_path71.default.join(homeDir(), ".cursor", "mcp.json");
20595
+ },
20596
+ // Cursor still reads a single `.cursorrules` at the project root (the modern
20597
+ // `.cursor/rules/*.mdc` split is one-rule-per-file with frontmatter — a worse
20598
+ // fit for a marker-fenced block). GRAPH_FIRST.md names this file directly.
20599
+ rulesFileName: ".cursorrules"
20600
+ };
20601
+ var DEVIN_CLIENT = {
20602
+ id: "devin",
20603
+ label: "Devin Desktop (Cascade)",
20604
+ docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
20605
+ mcpConfigPath: () => {
20606
+ const override = process.env.NEAT_DEVIN_CONFIG;
20607
+ if (override && override.length > 0) return import_node_path71.default.resolve(override);
20608
+ return import_node_path71.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json");
20609
+ },
20610
+ rulesFileName: ".windsurfrules"
20611
+ };
20612
+ function mergeMcpConfig(existing) {
20613
+ const servers = existing.mcpServers ?? {};
20614
+ const already = JSON.stringify(servers.neat) === JSON.stringify(NEAT_MCP_SERVER);
20615
+ const merged = {
20616
+ ...existing,
20617
+ mcpServers: { ...servers, neat: NEAT_MCP_SERVER }
20618
+ };
20619
+ return { merged, changed: !already };
20620
+ }
20621
+ function escapeRegExp(s) {
20622
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20623
+ }
20624
+ function buildGuidanceBlock(guide) {
20625
+ return `${GRAPH_FIRST_MARKER_OPEN}
20626
+ ${guide.trim()}
20627
+ ${GRAPH_FIRST_MARKER_CLOSE}
20628
+ `;
20629
+ }
20630
+ function mergeRulesFile(existing, block) {
20631
+ const region = new RegExp(
20632
+ `${escapeRegExp(GRAPH_FIRST_MARKER_OPEN)}[\\s\\S]*?${escapeRegExp(GRAPH_FIRST_MARKER_CLOSE)}\\n?`
20633
+ );
20634
+ if (region.test(existing)) return existing.replace(region, block);
20635
+ if (existing.trim().length === 0) return block;
20636
+ return `${existing.replace(/\s+$/, "")}
20637
+
20638
+ ${block}`;
20639
+ }
20640
+ async function runEditorInstall(client, opts) {
20641
+ const mcpPath = client.mcpConfigPath();
20642
+ const rulesPath = import_node_path71.default.join(opts.projectDir, client.rulesFileName);
20643
+ let existingMcp = {};
20644
+ try {
20645
+ existingMcp = JSON.parse(await import_node_fs44.promises.readFile(mcpPath, "utf8"));
20646
+ } catch (err) {
20647
+ const e = err;
20648
+ if (e.code === "ENOENT") {
20649
+ existingMcp = {};
20650
+ } else if (err instanceof SyntaxError) {
20651
+ console.error(
20652
+ `neat ${client.id}: ${mcpPath} is not valid JSON \u2014 ${e.message}. Fix it (or move it aside) and re-run; nothing was written.`
20653
+ );
20654
+ return { exitCode: 1 };
20655
+ } else {
20656
+ console.error(`neat ${client.id}: failed to read ${mcpPath} \u2014 ${e.message}`);
20657
+ return { exitCode: 1 };
20658
+ }
20659
+ }
20660
+ let existingRules = "";
20661
+ try {
20662
+ existingRules = await import_node_fs44.promises.readFile(rulesPath, "utf8");
20663
+ } catch (err) {
20664
+ if (err.code !== "ENOENT") {
20665
+ console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
20666
+ return { exitCode: 1 };
20667
+ }
20668
+ }
20669
+ const guide = await readSkillAsset(GUIDE_FILENAME);
20670
+ const block = buildGuidanceBlock(guide);
20671
+ const { merged, changed: mcpChanged } = mergeMcpConfig(existingMcp);
20672
+ const mcpJson = JSON.stringify(merged, null, 2) + "\n";
20673
+ const newRules = mergeRulesFile(existingRules, block);
20674
+ const rulesChanged = newRules !== existingRules;
20675
+ if (!opts.apply) {
20676
+ console.log(`neat ${client.id} \u2014 wire NEAT into ${client.label} (plan; nothing written)`);
20677
+ console.log("");
20678
+ console.log(`MCP server \u2192 ${mcpPath}`);
20679
+ console.log(
20680
+ mcpChanged ? " would add mcpServers.neat:" : " mcpServers.neat already present and current \u2014 no change:"
20681
+ );
20682
+ console.log(indent(JSON.stringify({ neat: NEAT_MCP_SERVER }, null, 2)));
20683
+ console.log("");
20684
+ console.log(`Graph-first guidance \u2192 ${rulesPath}`);
20685
+ console.log(
20686
+ rulesChanged ? existingRules.includes(GRAPH_FIRST_MARKER_OPEN) ? " would refresh the neat:graph-first block:" : " would add the neat:graph-first block:" : " neat:graph-first block already present and current \u2014 no change."
20687
+ );
20688
+ if (rulesChanged) console.log(indent(block.trimEnd()));
20689
+ console.log("");
20690
+ console.log(`Re-run with --apply to write both files. Existing servers and rules are kept.`);
20691
+ return { exitCode: 0 };
20692
+ }
20693
+ await import_node_fs44.promises.mkdir(import_node_path71.default.dirname(mcpPath), { recursive: true });
20694
+ await import_node_fs44.promises.writeFile(mcpPath, mcpJson, "utf8");
20695
+ await import_node_fs44.promises.mkdir(import_node_path71.default.dirname(rulesPath), { recursive: true });
20696
+ await import_node_fs44.promises.writeFile(rulesPath, newRules, "utf8");
20697
+ console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
20698
+ console.log(` MCP server: ${mcpPath} (mcpServers.neat \u2192 npx -y @neat.is/mcp)`);
20699
+ console.log(` guidance: ${rulesPath} (neat:graph-first block)`);
20700
+ console.log("");
20701
+ console.log(`restart ${client.label} to pick up the MCP server. Point it at a non-default`);
20702
+ console.log(`daemon by setting NEAT_CORE_URL in the neat server's env in that config.`);
20703
+ return { exitCode: 0 };
20704
+ }
20705
+ function indent(text) {
20706
+ return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
20707
+ }
20708
+ function usage3(client) {
20709
+ console.log(`neat ${client.id} \u2014 install NEAT's MCP server + graph-first guidance into ${client.label}`);
20710
+ console.log("");
20711
+ console.log(" --apply write the MCP config and the rules file (default: plan only)");
20712
+ console.log("");
20713
+ console.log("Writes NEAT's stdio MCP server (npx -y @neat.is/mcp) into");
20714
+ console.log(` ${client.mcpConfigPath()}`);
20715
+ console.log(`and the graph-first guidance block into ./${client.rulesFileName}, both`);
20716
+ console.log("additively \u2014 existing servers and rules are preserved, a re-run is a no-op.");
20717
+ console.log("");
20718
+ console.log(`See ${client.docsUrl} for ${client.label}'s MCP config format.`);
20719
+ }
20720
+ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
20721
+ const client = clientId === "cursor" ? CURSOR_CLIENT : DEVIN_CLIENT;
20722
+ let apply4 = false;
20723
+ for (const arg of args) {
20724
+ switch (arg) {
20725
+ case "--apply":
20726
+ apply4 = true;
20727
+ break;
20728
+ case "-h":
20729
+ case "--help":
20730
+ usage3(client);
20731
+ return 0;
20732
+ default:
20733
+ console.error(`neat ${client.id}: unknown flag "${arg}"`);
20734
+ usage3(client);
20735
+ return 2;
20736
+ }
20737
+ }
20738
+ try {
20739
+ const { exitCode } = await runEditorInstall(client, { apply: apply4, projectDir });
20740
+ return exitCode;
20741
+ } catch (err) {
20742
+ console.error(err.message);
20743
+ return 1;
20744
+ }
20745
+ }
20746
+
19539
20747
  // src/monitor.ts
19540
20748
  init_cjs_shims();
19541
- var import_types63 = require("@neat.is/types");
20749
+ var import_types70 = require("@neat.is/types");
19542
20750
 
19543
20751
  // src/cli-client.ts
19544
20752
  init_cjs_shims();
19545
- var import_types62 = require("@neat.is/types");
20753
+ var import_types69 = require("@neat.is/types");
19546
20754
  var HttpError = class extends Error {
19547
20755
  constructor(status2, message, responseBody = "") {
19548
20756
  super(message);
@@ -19567,10 +20775,10 @@ function createHttpClient(baseUrl, bearerToken) {
19567
20775
  const root = baseUrl.replace(/\/$/, "");
19568
20776
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
19569
20777
  return {
19570
- async get(path72) {
20778
+ async get(path74) {
19571
20779
  let res;
19572
20780
  try {
19573
- res = await fetch(`${root}${path72}`, {
20781
+ res = await fetch(`${root}${path74}`, {
19574
20782
  headers: { ...authHeader }
19575
20783
  });
19576
20784
  } catch (err) {
@@ -19582,16 +20790,16 @@ function createHttpClient(baseUrl, bearerToken) {
19582
20790
  const body = await res.text().catch(() => "");
19583
20791
  throw new HttpError(
19584
20792
  res.status,
19585
- `${res.status} ${res.statusText} on GET ${path72}: ${body}`,
20793
+ `${res.status} ${res.statusText} on GET ${path74}: ${body}`,
19586
20794
  body
19587
20795
  );
19588
20796
  }
19589
20797
  return await res.json();
19590
20798
  },
19591
- async post(path72, body) {
20799
+ async post(path74, body) {
19592
20800
  let res;
19593
20801
  try {
19594
- res = await fetch(`${root}${path72}`, {
20802
+ res = await fetch(`${root}${path74}`, {
19595
20803
  method: "POST",
19596
20804
  headers: { "content-type": "application/json", ...authHeader },
19597
20805
  body: JSON.stringify(body)
@@ -19605,7 +20813,7 @@ function createHttpClient(baseUrl, bearerToken) {
19605
20813
  const text = await res.text().catch(() => "");
19606
20814
  throw new HttpError(
19607
20815
  res.status,
19608
- `${res.status} ${res.statusText} on POST ${path72}: ${text}`,
20816
+ `${res.status} ${res.statusText} on POST ${path74}: ${text}`,
19609
20817
  text
19610
20818
  );
19611
20819
  }
@@ -19619,12 +20827,12 @@ function projectPath(project, suffix) {
19619
20827
  }
19620
20828
  async function runRootCause(client, input) {
19621
20829
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
19622
- const path72 = projectPath(
20830
+ const path74 = projectPath(
19623
20831
  input.project,
19624
20832
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
19625
20833
  );
19626
20834
  try {
19627
- const result = await client.get(path72);
20835
+ const result = await client.get(path74);
19628
20836
  const arrowPath = result.traversalPath.join(" \u2190 ");
19629
20837
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
19630
20838
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -19650,12 +20858,12 @@ async function runRootCause(client, input) {
19650
20858
  }
19651
20859
  async function runBlastRadius(client, input) {
19652
20860
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
19653
- const path72 = projectPath(
20861
+ const path74 = projectPath(
19654
20862
  input.project,
19655
20863
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
19656
20864
  );
19657
20865
  try {
19658
- const result = await client.get(path72);
20866
+ const result = await client.get(path74);
19659
20867
  if (result.totalAffected === 0) {
19660
20868
  return {
19661
20869
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -19684,17 +20892,17 @@ async function runBlastRadius(client, input) {
19684
20892
  }
19685
20893
  }
19686
20894
  function formatBlastEntry(n) {
19687
- const tag = n.edgeProvenance === import_types62.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
20895
+ const tag = n.edgeProvenance === import_types69.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
19688
20896
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
19689
20897
  }
19690
20898
  async function runDependencies(client, input) {
19691
20899
  const depth = input.depth ?? 3;
19692
- const path72 = projectPath(
20900
+ const path74 = projectPath(
19693
20901
  input.project,
19694
20902
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
19695
20903
  );
19696
20904
  try {
19697
- const result = await client.get(path72);
20905
+ const result = await client.get(path74);
19698
20906
  if (result.total === 0) {
19699
20907
  return {
19700
20908
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -19741,7 +20949,7 @@ async function runObservedDependencies(client, input) {
19741
20949
  if (result.observed) {
19742
20950
  return {
19743
20951
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
19744
- provenance: import_types62.Provenance.OBSERVED
20952
+ provenance: import_types69.Provenance.OBSERVED
19745
20953
  };
19746
20954
  }
19747
20955
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -19751,7 +20959,7 @@ async function runObservedDependencies(client, input) {
19751
20959
  return {
19752
20960
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
19753
20961
  block: blockLines.join("\n"),
19754
- provenance: import_types62.Provenance.OBSERVED
20962
+ provenance: import_types69.Provenance.OBSERVED
19755
20963
  };
19756
20964
  } catch (err) {
19757
20965
  if (err instanceof HttpError && err.status === 404) {
@@ -19786,9 +20994,9 @@ function formatDuration(ms) {
19786
20994
  return `${Math.round(h / 24)}d`;
19787
20995
  }
19788
20996
  async function runIncidents(client, input) {
19789
- const path72 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
20997
+ const path74 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
19790
20998
  try {
19791
- const body = await client.get(path72);
20999
+ const body = await client.get(path74);
19792
21000
  const events = body.events;
19793
21001
  if (events.length === 0) {
19794
21002
  return {
@@ -19805,7 +21013,7 @@ async function runIncidents(client, input) {
19805
21013
  return {
19806
21014
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
19807
21015
  block: blockLines.join("\n"),
19808
- provenance: import_types62.Provenance.OBSERVED
21016
+ provenance: import_types69.Provenance.OBSERVED
19809
21017
  };
19810
21018
  } catch (err) {
19811
21019
  if (err instanceof HttpError && err.status === 404) {
@@ -19914,7 +21122,7 @@ async function runStaleEdges(client, input) {
19914
21122
  return {
19915
21123
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
19916
21124
  block: blockLines.join("\n"),
19917
- provenance: import_types62.Provenance.STALE
21125
+ provenance: import_types69.Provenance.STALE
19918
21126
  };
19919
21127
  }
19920
21128
  async function runPolicies(client, input) {
@@ -20073,10 +21281,10 @@ async function pushSnapshotToRemote(input) {
20073
21281
 
20074
21282
  // src/monitor.ts
20075
21283
  var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
20076
- import_types63.EdgeType.CALLS,
20077
- import_types63.EdgeType.CONNECTS_TO,
20078
- import_types63.EdgeType.PUBLISHES_TO,
20079
- import_types63.EdgeType.CONSUMES_FROM
21284
+ import_types70.EdgeType.CALLS,
21285
+ import_types70.EdgeType.CONNECTS_TO,
21286
+ import_types70.EdgeType.PUBLISHES_TO,
21287
+ import_types70.EdgeType.CONSUMES_FROM
20080
21288
  ]);
20081
21289
  function divergenceKey(d) {
20082
21290
  const column = "column" in d && d.column ? d.column : "";
@@ -20121,7 +21329,7 @@ function formatDivergenceLine2(d) {
20121
21329
  }
20122
21330
  }
20123
21331
  function formatStaleLine(edgeId) {
20124
- const parsed = (0, import_types63.parseEdgeId)(edgeId);
21332
+ const parsed = (0, import_types70.parseEdgeId)(edgeId);
20125
21333
  if (parsed) {
20126
21334
  return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
20127
21335
  }
@@ -20134,7 +21342,7 @@ function divergenceJson(d) {
20134
21342
  return JSON.stringify({ kind: "divergence", ...d });
20135
21343
  }
20136
21344
  function staleJson(edgeId) {
20137
- const parsed = (0, import_types63.parseEdgeId)(edgeId);
21345
+ const parsed = (0, import_types70.parseEdgeId)(edgeId);
20138
21346
  return JSON.stringify({
20139
21347
  kind: "stale",
20140
21348
  edgeId,
@@ -20151,6 +21359,24 @@ function observedEdgeJson(edge) {
20151
21359
  provenance: edge.provenance
20152
21360
  });
20153
21361
  }
21362
+ function policyKey(v) {
21363
+ return `policy|${v.id}`;
21364
+ }
21365
+ function policySubject(v) {
21366
+ const s = v.subject;
21367
+ if (s.nodeId) return s.nodeId;
21368
+ if (s.edgeId) return s.edgeId;
21369
+ if (s.path && s.path.length > 0) return s.path.join(" \u2192 ");
21370
+ return "";
21371
+ }
21372
+ function formatPolicyLine(v) {
21373
+ const subject = policySubject(v);
21374
+ const where = subject ? ` (${subject})` : "";
21375
+ return `\u26A0 policy [${v.severity}] ${v.policyName} \u2014 ${v.message}${where}`;
21376
+ }
21377
+ function policyJson(v) {
21378
+ return JSON.stringify({ kind: "policy", ...v });
21379
+ }
20154
21380
  var MonitorEmitter = class {
20155
21381
  constructor(opts) {
20156
21382
  this.opts = opts;
@@ -20186,7 +21412,7 @@ var MonitorEmitter = class {
20186
21412
  // ignores non-OBSERVED edges and non-dependency edge types (structural
20187
21413
  // ownership), so only real runtime dependencies reach stdout.
20188
21414
  emitObservedEdge(edge) {
20189
- if (edge.provenance !== import_types63.Provenance.OBSERVED) return false;
21415
+ if (edge.provenance !== import_types70.Provenance.OBSERVED) return false;
20190
21416
  if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
20191
21417
  const key = `edge|${edge.id}`;
20192
21418
  if (this.seen.has(key)) return false;
@@ -20194,6 +21420,20 @@ var MonitorEmitter = class {
20194
21420
  this.out(this.opts.json ? observedEdgeJson(edge) : formatObservedEdgeLine(edge));
20195
21421
  return true;
20196
21422
  }
21423
+ // Emit every not-yet-seen policy violation in a fresh /policies/violations
21424
+ // read. Returns the count newly emitted (0 → nothing printed, the silent
21425
+ // path). Idempotent across re-reads: the seen-set keys off the violation id.
21426
+ emitPolicies(response) {
21427
+ let emitted = 0;
21428
+ for (const v of response.violations) {
21429
+ const key = policyKey(v);
21430
+ if (this.seen.has(key)) continue;
21431
+ this.seen.add(key);
21432
+ this.out(this.opts.json ? policyJson(v) : formatPolicyLine(v));
21433
+ emitted++;
21434
+ }
21435
+ return emitted;
21436
+ }
20197
21437
  };
20198
21438
  function parseFrame(raw) {
20199
21439
  let event = "message";
@@ -20248,65 +21488,97 @@ function projectPath2(project, suffix) {
20248
21488
  function backoffDelay(attempt, capMs) {
20249
21489
  return Math.min(capMs, 500 * 2 ** Math.min(attempt, 6));
20250
21490
  }
20251
- async function runMonitor(opts) {
20252
- const write = opts.write ?? ((line) => process.stdout.write(line));
20253
- const debounceMs = opts.debounceMs ?? 400;
20254
- const backoffCapMs = opts.backoffCapMs ?? 1e4;
20255
- const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
20256
- const client = createHttpClient(opts.baseUrl, opts.authToken);
20257
- const emitter = new MonitorEmitter({ json: opts.json, write });
20258
- const divergencesPath = projectPath2(opts.project, "/graph/divergences");
20259
- const eventsUrl = `${opts.baseUrl.replace(/\/$/, "")}${projectPath2(opts.project, "/events")}`;
20260
- let readTimer = null;
20261
- let reading = false;
20262
- let readPending = false;
20263
- const doRead = async () => {
20264
- if (reading) {
20265
- readPending = true;
21491
+ var defaultOpenEvents = async (url, headers, signal) => {
21492
+ const res = await fetch(url, { headers, signal });
21493
+ return { ok: res.ok, status: res.status, body: res.body };
21494
+ };
21495
+ function makeDebouncedReader(read, debounceMs) {
21496
+ let timer = null;
21497
+ let running = false;
21498
+ let pending = false;
21499
+ const cancel = () => {
21500
+ if (timer) {
21501
+ clearTimeout(timer);
21502
+ timer = null;
21503
+ }
21504
+ };
21505
+ const run = async () => {
21506
+ if (running) {
21507
+ pending = true;
20266
21508
  return;
20267
21509
  }
20268
- reading = true;
21510
+ running = true;
20269
21511
  try {
20270
- const result = await client.get(divergencesPath);
20271
- emitter.emitDivergences(result);
21512
+ await read();
20272
21513
  } catch {
20273
21514
  } finally {
20274
- reading = false;
20275
- if (readPending) {
20276
- readPending = false;
20277
- scheduleRead();
21515
+ running = false;
21516
+ if (pending) {
21517
+ pending = false;
21518
+ schedule();
20278
21519
  }
20279
21520
  }
20280
21521
  };
20281
- const scheduleRead = () => {
20282
- if (readTimer) clearTimeout(readTimer);
20283
- readTimer = setTimeout(() => {
20284
- readTimer = null;
20285
- void doRead();
21522
+ const schedule = () => {
21523
+ cancel();
21524
+ timer = setTimeout(() => {
21525
+ timer = null;
21526
+ void run();
20286
21527
  }, debounceMs);
20287
- if (typeof readTimer.unref === "function") readTimer.unref();
21528
+ if (typeof timer.unref === "function") timer.unref();
21529
+ };
21530
+ return {
21531
+ schedule,
21532
+ runNow: async () => {
21533
+ cancel();
21534
+ await run();
21535
+ },
21536
+ cancel
20288
21537
  };
21538
+ }
21539
+ async function runMonitor(opts) {
21540
+ const write = opts.write ?? ((line) => process.stdout.write(line));
21541
+ const debounceMs = opts.debounceMs ?? 400;
21542
+ const backoffCapMs = opts.backoffCapMs ?? 1e4;
21543
+ const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
21544
+ const client = opts.httpClient ?? createHttpClient(opts.baseUrl, opts.authToken);
21545
+ const openEvents = opts.openEvents ?? defaultOpenEvents;
21546
+ const emitter = new MonitorEmitter({ json: opts.json, write });
21547
+ const divergencesPath = projectPath2(opts.project, "/graph/divergences");
21548
+ const policiesPath = projectPath2(opts.project, "/policies/violations");
21549
+ const eventsUrl = `${opts.baseUrl.replace(/\/$/, "")}${projectPath2(opts.project, "/events")}`;
21550
+ const divergences = makeDebouncedReader(async () => {
21551
+ const result = await client.get(divergencesPath);
21552
+ emitter.emitDivergences(result);
21553
+ }, debounceMs);
21554
+ const policies = makeDebouncedReader(async () => {
21555
+ const result = await client.get(policiesPath);
21556
+ emitter.emitPolicies(result);
21557
+ }, debounceMs);
20289
21558
  const onFrame = (frame) => {
20290
21559
  switch (frame.event) {
20291
21560
  case "extraction-complete":
20292
- scheduleRead();
21561
+ divergences.schedule();
20293
21562
  break;
20294
21563
  case "stale-transition": {
20295
21564
  const payload = safeParse(frame.data);
20296
21565
  const edgeId = payload && typeof payload.edgeId === "string" ? payload.edgeId : void 0;
20297
21566
  if (edgeId) emitter.emitStale(edgeId);
20298
- scheduleRead();
21567
+ divergences.schedule();
20299
21568
  break;
20300
21569
  }
20301
21570
  case "edge-added": {
20302
21571
  const payload = safeParse(frame.data);
20303
21572
  const edge = payload?.edge;
20304
- if (edge && edge.provenance === import_types63.Provenance.OBSERVED) {
21573
+ if (edge && edge.provenance === import_types70.Provenance.OBSERVED) {
20305
21574
  emitter.emitObservedEdge(edge);
20306
- scheduleRead();
21575
+ divergences.schedule();
20307
21576
  }
20308
21577
  break;
20309
21578
  }
21579
+ case "policy-violation":
21580
+ policies.schedule();
21581
+ break;
20310
21582
  default:
20311
21583
  break;
20312
21584
  }
@@ -20319,17 +21591,17 @@ async function runMonitor(opts) {
20319
21591
  let firstConnect = true;
20320
21592
  for (let attempt = 0; ; attempt++) {
20321
21593
  if (opts.signal?.aborted) break;
20322
- let res;
21594
+ let conn;
20323
21595
  try {
20324
- res = await fetch(eventsUrl, { headers, signal: opts.signal });
21596
+ conn = await openEvents(eventsUrl, headers, opts.signal);
20325
21597
  } catch (err) {
20326
21598
  if (err.name === "AbortError") break;
20327
21599
  if (!connectedOnce || attempt >= maxReconnects) break;
20328
21600
  await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
20329
21601
  continue;
20330
21602
  }
20331
- if (!res.ok || !res.body) {
20332
- await res.body?.cancel().catch(() => {
21603
+ if (!conn.ok || !conn.body) {
21604
+ await conn.body?.cancel().catch(() => {
20333
21605
  });
20334
21606
  if (!connectedOnce || attempt >= maxReconnects) break;
20335
21607
  await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
@@ -20339,19 +21611,22 @@ async function runMonitor(opts) {
20339
21611
  attempt = 0;
20340
21612
  if (firstConnect) {
20341
21613
  firstConnect = false;
20342
- await doRead();
21614
+ await divergences.runNow();
21615
+ await policies.runNow();
20343
21616
  } else {
20344
- scheduleRead();
21617
+ divergences.schedule();
21618
+ policies.schedule();
20345
21619
  }
20346
21620
  try {
20347
- await drainSse(res.body, onFrame);
21621
+ await drainSse(conn.body, onFrame);
20348
21622
  } catch {
20349
21623
  }
20350
21624
  if (opts.signal?.aborted) break;
20351
21625
  if (attempt >= maxReconnects) break;
20352
21626
  await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
20353
21627
  }
20354
- if (readTimer) clearTimeout(readTimer);
21628
+ divergences.cancel();
21629
+ policies.cancel();
20355
21630
  return 0;
20356
21631
  }
20357
21632
  function sleep(ms, signal) {
@@ -20375,7 +21650,7 @@ function sleep(ms, signal) {
20375
21650
 
20376
21651
  // src/cli-verbs.ts
20377
21652
  init_cjs_shims();
20378
- var import_node_path70 = __toESM(require("path"), 1);
21653
+ var import_node_path72 = __toESM(require("path"), 1);
20379
21654
  async function resolveProjectEntry(opts) {
20380
21655
  const entries = await listProjects();
20381
21656
  if (opts.project) {
@@ -20385,7 +21660,7 @@ async function resolveProjectEntry(opts) {
20385
21660
  const cwd = opts.cwd ?? process.cwd();
20386
21661
  const resolvedCwd = await normalizeProjectPath(cwd);
20387
21662
  for (const entry2 of entries) {
20388
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path70.default.sep}`)) {
21663
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path72.default.sep}`)) {
20389
21664
  return entry2;
20390
21665
  }
20391
21666
  }
@@ -20538,7 +21813,7 @@ async function runSync(opts) {
20538
21813
  }
20539
21814
 
20540
21815
  // src/cli.ts
20541
- var import_types64 = require("@neat.is/types");
21816
+ var import_types71 = require("@neat.is/types");
20542
21817
  function isNpxInvocation() {
20543
21818
  if (process.env.npm_command === "exec") return true;
20544
21819
  const execpath = process.env.npm_execpath ?? "";
@@ -20550,7 +21825,7 @@ function isNpxInvocation() {
20550
21825
  function commandPrefix() {
20551
21826
  return isNpxInvocation() ? "npx neat.is" : "neat";
20552
21827
  }
20553
- function usage2() {
21828
+ function usage4() {
20554
21829
  const neat = commandPrefix();
20555
21830
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
20556
21831
  console.log("");
@@ -20570,10 +21845,12 @@ function usage2() {
20570
21845
  console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
20571
21846
  console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
20572
21847
  console.log(" monitor Stream live graph facts to stdout \u2014 one line per new");
20573
- console.log(" fact \u2014 as the daemon learns them: fresh divergences,");
20574
- console.log(" integrations that just went stale, and new observed");
20575
- console.log(" runtime dependencies. Silent when nothing is new; exits");
20576
- console.log(" clean with no output when no daemon is reachable.");
21848
+ console.log(" fact \u2014 as the daemon learns them: fresh divergences");
21849
+ console.log(" (down to the drifting column), integrations that just");
21850
+ console.log(" went stale, new observed runtime dependencies, and");
21851
+ console.log(" freshly-tripped policy violations. Silent when nothing");
21852
+ console.log(" is new; exits clean with no output when no daemon is");
21853
+ console.log(" reachable.");
20577
21854
  console.log(" Flags:");
20578
21855
  console.log(" --project <name> watch a registered project by name");
20579
21856
  console.log(" --json emit one JSON object per line");
@@ -20603,6 +21880,22 @@ function usage2() {
20603
21880
  console.log(" --print-hook print the hook script");
20604
21881
  console.log(" --print-guide print the graph-first guidance");
20605
21882
  console.log(" --print-settings print the settings.json block --apply adds");
21883
+ console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
21884
+ console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
21885
+ console.log(" Plan by default; --apply to write.");
21886
+ console.log(" Flags:");
21887
+ console.log(" --apply add the MCP server + AGENTS.md block");
21888
+ console.log(" --print-config print the config.toml table");
21889
+ console.log(" --print-guide print the AGENTS.md block");
21890
+ console.log(" cursor Wire NEAT into Cursor: add the MCP server to ~/.cursor/mcp.json");
21891
+ console.log(" and the graph-first guidance to ./.cursorrules. Plan by default.");
21892
+ console.log(" Flags:");
21893
+ console.log(" --apply write the MCP config + rules file (default: plan)");
21894
+ console.log(" devin Wire NEAT into Devin Desktop (Cascade): add the MCP server to");
21895
+ console.log(" ~/.codeium/windsurf/mcp_config.json and the graph-first guidance");
21896
+ console.log(" to ./.windsurfrules. Plan by default.");
21897
+ console.log(" Flags:");
21898
+ console.log(" --apply write the MCP config + rules file (default: plan)");
20606
21899
  console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
20607
21900
  console.log(" emit a docker-compose / systemd / docker run artifact, and");
20608
21901
  console.log(" print the OTel env-vars block to paste into your platform.");
@@ -20616,7 +21909,8 @@ function usage2() {
20616
21909
  console.log(" --no-instrument skip the SDK install apply step");
20617
21910
  console.log(" --json emit the delta summary as JSON");
20618
21911
  console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
20619
- console.log(" firebase, cloudflare) and push (vercel, via a trace Drain).");
21912
+ console.log(" firebase, cloudflare, neon, cloud-run) and push (vercel,");
21913
+ console.log(" via a trace Drain).");
20620
21914
  console.log(" Subcommands:");
20621
21915
  console.log(" add <provider> add a connector; validates the credential");
20622
21916
  console.log(" against the provider first (--skip-validate to skip)");
@@ -20848,7 +22142,7 @@ async function buildPatchSections(services, project) {
20848
22142
  }
20849
22143
  async function runInit(opts) {
20850
22144
  const written = [];
20851
- const stat = await import_node_fs43.promises.stat(opts.scanPath).catch(() => null);
22145
+ const stat = await import_node_fs45.promises.stat(opts.scanPath).catch(() => null);
20852
22146
  if (!stat || !stat.isDirectory()) {
20853
22147
  console.error(`neat init: ${opts.scanPath} is not a directory`);
20854
22148
  return { exitCode: 2, writtenFiles: written };
@@ -20857,13 +22151,13 @@ async function runInit(opts) {
20857
22151
  printDiscoveryReport(opts, services);
20858
22152
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
20859
22153
  const patch = renderPatch(sections);
20860
- const patchPath = import_node_path71.default.join(opts.scanPath, "neat.patch");
22154
+ const patchPath = import_node_path73.default.join(opts.scanPath, "neat.patch");
20861
22155
  if (opts.dryRun) {
20862
- await import_node_fs43.promises.writeFile(patchPath, patch, "utf8");
22156
+ await import_node_fs45.promises.writeFile(patchPath, patch, "utf8");
20863
22157
  written.push(patchPath);
20864
22158
  console.log(`dry-run: patch written to ${patchPath}`);
20865
- const gitignorePath = import_node_path71.default.join(opts.scanPath, ".gitignore");
20866
- const gitignoreExists = await import_node_fs43.promises.stat(gitignorePath).then(() => true).catch(() => false);
22159
+ const gitignorePath = import_node_path73.default.join(opts.scanPath, ".gitignore");
22160
+ const gitignoreExists = await import_node_fs45.promises.stat(gitignorePath).then(() => true).catch(() => false);
20867
22161
  const verb = gitignoreExists ? "append" : "create";
20868
22162
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
20869
22163
  console.log("rerun without --dry-run to register and snapshot.");
@@ -20874,9 +22168,9 @@ async function runInit(opts) {
20874
22168
  const graph = getGraph(graphKey);
20875
22169
  const projectPaths = pathsForProject(
20876
22170
  graphKey,
20877
- import_node_path71.default.join(opts.scanPath, "neat-out")
22171
+ import_node_path73.default.join(opts.scanPath, "neat-out")
20878
22172
  );
20879
- const errorsPath = import_node_path71.default.join(import_node_path71.default.dirname(opts.outPath), import_node_path71.default.basename(projectPaths.errorsPath));
22173
+ const errorsPath = import_node_path73.default.join(import_node_path73.default.dirname(opts.outPath), import_node_path73.default.basename(projectPaths.errorsPath));
20880
22174
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
20881
22175
  await saveGraphToDisk(graph, opts.outPath);
20882
22176
  written.push(opts.outPath);
@@ -20955,7 +22249,7 @@ async function runInit(opts) {
20955
22249
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
20956
22250
  }
20957
22251
  } else {
20958
- await import_node_fs43.promises.writeFile(patchPath, patch, "utf8");
22252
+ await import_node_fs45.promises.writeFile(patchPath, patch, "utf8");
20959
22253
  written.push(patchPath);
20960
22254
  }
20961
22255
  }
@@ -20995,9 +22289,9 @@ var CLAUDE_SKILL_CONFIG = {
20995
22289
  };
20996
22290
  function claudeConfigPath() {
20997
22291
  const override = process.env.NEAT_CLAUDE_CONFIG;
20998
- if (override && override.length > 0) return import_node_path71.default.resolve(override);
22292
+ if (override && override.length > 0) return import_node_path73.default.resolve(override);
20999
22293
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
21000
- return import_node_path71.default.join(home, ".claude.json");
22294
+ return import_node_path73.default.join(home, ".claude.json");
21001
22295
  }
21002
22296
  async function runSkill(opts) {
21003
22297
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -21009,7 +22303,7 @@ async function runSkill(opts) {
21009
22303
  const target = claudeConfigPath();
21010
22304
  let existing = {};
21011
22305
  try {
21012
- existing = JSON.parse(await import_node_fs43.promises.readFile(target, "utf8"));
22306
+ existing = JSON.parse(await import_node_fs45.promises.readFile(target, "utf8"));
21013
22307
  } catch (err) {
21014
22308
  if (err.code !== "ENOENT") {
21015
22309
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -21021,8 +22315,8 @@ async function runSkill(opts) {
21021
22315
  ...existing,
21022
22316
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
21023
22317
  };
21024
- await import_node_fs43.promises.mkdir(import_node_path71.default.dirname(target), { recursive: true });
21025
- await import_node_fs43.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
22318
+ await import_node_fs45.promises.mkdir(import_node_path73.default.dirname(target), { recursive: true });
22319
+ await import_node_fs45.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
21026
22320
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
21027
22321
  console.log("restart Claude Code to pick up the new MCP server.");
21028
22322
  console.log("");
@@ -21049,7 +22343,7 @@ async function main() {
21049
22343
  const argv = process.argv.slice(2);
21050
22344
  const cmd0 = argv[0];
21051
22345
  if (cmd0 === "-h" || cmd0 === "--help") {
21052
- usage2();
22346
+ usage4();
21053
22347
  process.exit(0);
21054
22348
  }
21055
22349
  if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
@@ -21066,6 +22360,16 @@ async function main() {
21066
22360
  if (code !== 0) process.exit(code);
21067
22361
  return;
21068
22362
  }
22363
+ if (cmd0 === "codex") {
22364
+ const code = await runCodexCommand(argv.slice(1));
22365
+ if (code !== 0) process.exit(code);
22366
+ return;
22367
+ }
22368
+ if (cmd0 === "cursor" || cmd0 === "devin") {
22369
+ const code = await runEditorCommand(cmd0, argv.slice(1));
22370
+ if (code !== 0) process.exit(code);
22371
+ return;
22372
+ }
21069
22373
  const argvParsed = parseArgs(argv);
21070
22374
  if (argvParsed.positional.length === 0) {
21071
22375
  const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
@@ -21080,19 +22384,19 @@ async function main() {
21080
22384
  const target = positional[0];
21081
22385
  if (!target) {
21082
22386
  console.error("neat init: missing <path>");
21083
- usage2();
22387
+ usage4();
21084
22388
  process.exit(2);
21085
22389
  }
21086
22390
  if (apply4 && dryRun) {
21087
22391
  console.error("neat init: --apply and --dry-run are mutually exclusive");
21088
22392
  process.exit(2);
21089
22393
  }
21090
- const scanPath = import_node_path71.default.resolve(target);
22394
+ const scanPath = import_node_path73.default.resolve(target);
21091
22395
  const projectExplicit = parsed.project !== null;
21092
- const projectName = projectExplicit ? project : import_node_path71.default.basename(scanPath);
22396
+ const projectName = projectExplicit ? project : import_node_path73.default.basename(scanPath);
21093
22397
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
21094
- const fallback = pathsForProject(projectKey, import_node_path71.default.join(scanPath, "neat-out")).snapshotPath;
21095
- const outPath = import_node_path71.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
22398
+ const fallback = pathsForProject(projectKey, import_node_path73.default.join(scanPath, "neat-out")).snapshotPath;
22399
+ const outPath = import_node_path73.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
21096
22400
  const result = await runInit({
21097
22401
  scanPath,
21098
22402
  outPath,
@@ -21110,24 +22414,24 @@ async function main() {
21110
22414
  const target = positional[0];
21111
22415
  if (!target) {
21112
22416
  console.error("neat watch: missing <path>");
21113
- usage2();
22417
+ usage4();
21114
22418
  process.exit(2);
21115
22419
  }
21116
- const scanPath = import_node_path71.default.resolve(target);
21117
- const stat = await import_node_fs43.promises.stat(scanPath).catch(() => null);
22420
+ const scanPath = import_node_path73.default.resolve(target);
22421
+ const stat = await import_node_fs45.promises.stat(scanPath).catch(() => null);
21118
22422
  if (!stat || !stat.isDirectory()) {
21119
22423
  console.error(`neat watch: ${scanPath} is not a directory`);
21120
22424
  process.exit(2);
21121
22425
  }
21122
- const projectPaths = pathsForProject(project, import_node_path71.default.join(scanPath, "neat-out"));
21123
- const outPath = import_node_path71.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
21124
- const errorsPath = import_node_path71.default.resolve(
21125
- process.env.NEAT_ERRORS_PATH ?? import_node_path71.default.join(import_node_path71.default.dirname(outPath), import_node_path71.default.basename(projectPaths.errorsPath))
22426
+ const projectPaths = pathsForProject(project, import_node_path73.default.join(scanPath, "neat-out"));
22427
+ const outPath = import_node_path73.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
22428
+ const errorsPath = import_node_path73.default.resolve(
22429
+ process.env.NEAT_ERRORS_PATH ?? import_node_path73.default.join(import_node_path73.default.dirname(outPath), import_node_path73.default.basename(projectPaths.errorsPath))
21126
22430
  );
21127
- const staleEventsPath = import_node_path71.default.resolve(
21128
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path71.default.join(import_node_path71.default.dirname(outPath), import_node_path71.default.basename(projectPaths.staleEventsPath))
22431
+ const staleEventsPath = import_node_path73.default.resolve(
22432
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path73.default.join(import_node_path73.default.dirname(outPath), import_node_path73.default.basename(projectPaths.staleEventsPath))
21129
22433
  );
21130
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path71.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
22434
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path73.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
21131
22435
  const handle = await startWatch(getGraph(project), {
21132
22436
  scanPath,
21133
22437
  outPath,
@@ -21136,7 +22440,7 @@ async function main() {
21136
22440
  project,
21137
22441
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
21138
22442
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
21139
- neatHome: process.env.NEAT_HOME ? import_node_path71.default.resolve(process.env.NEAT_HOME) : import_node_path71.default.join(import_node_os6.default.homedir(), ".neat"),
22443
+ neatHome: process.env.NEAT_HOME ? import_node_path73.default.resolve(process.env.NEAT_HOME) : import_node_path73.default.join(import_node_os8.default.homedir(), ".neat"),
21140
22444
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
21141
22445
  host: process.env.HOST ?? "0.0.0.0",
21142
22446
  port: Number(process.env.PORT ?? 8080),
@@ -21172,7 +22476,7 @@ async function main() {
21172
22476
  const name = positional[0];
21173
22477
  if (!name) {
21174
22478
  console.error("neat pause: missing <name>");
21175
- usage2();
22479
+ usage4();
21176
22480
  process.exit(2);
21177
22481
  }
21178
22482
  const daemon = await findDaemonByProject(name);
@@ -21197,7 +22501,7 @@ async function main() {
21197
22501
  const name = positional[0];
21198
22502
  if (!name) {
21199
22503
  console.error("neat resume: missing <name>");
21200
- usage2();
22504
+ usage4();
21201
22505
  process.exit(2);
21202
22506
  }
21203
22507
  const daemon = await findDaemonByProject(name);
@@ -21227,7 +22531,7 @@ async function main() {
21227
22531
  const name = positional[0];
21228
22532
  if (!name) {
21229
22533
  console.error("neat uninstall: missing <name>");
21230
- usage2();
22534
+ usage4();
21231
22535
  process.exit(2);
21232
22536
  }
21233
22537
  const daemon = await findDaemonByProject(name);
@@ -21314,15 +22618,15 @@ async function main() {
21314
22618
  return;
21315
22619
  }
21316
22620
  console.error(`neat: unknown command "${cmd}"`);
21317
- usage2();
22621
+ usage4();
21318
22622
  process.exit(1);
21319
22623
  }
21320
22624
  async function tryOrchestrator(cmd, parsed) {
21321
- const scanPath = import_node_path71.default.resolve(cmd);
21322
- const stat = await import_node_fs43.promises.stat(scanPath).catch(() => null);
22625
+ const scanPath = import_node_path73.default.resolve(cmd);
22626
+ const stat = await import_node_fs45.promises.stat(scanPath).catch(() => null);
21323
22627
  if (!stat || !stat.isDirectory()) return null;
21324
22628
  const projectExplicit = parsed.project !== null;
21325
- const projectName = projectExplicit ? parsed.project : import_node_path71.default.basename(scanPath);
22629
+ const projectName = projectExplicit ? parsed.project : import_node_path73.default.basename(scanPath);
21326
22630
  const result = await runOrchestrator({
21327
22631
  scanPath,
21328
22632
  project: projectName,
@@ -21511,10 +22815,10 @@ async function runQueryVerb(cmd, parsed) {
21511
22815
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
21512
22816
  const out = [];
21513
22817
  for (const p of parts) {
21514
- const r = import_types64.DivergenceTypeSchema.safeParse(p);
22818
+ const r = import_types71.DivergenceTypeSchema.safeParse(p);
21515
22819
  if (!r.success) {
21516
22820
  console.error(
21517
- `neat divergences: unknown --type "${p}". allowed: ${import_types64.DivergenceTypeSchema.options.join(", ")}`
22821
+ `neat divergences: unknown --type "${p}". allowed: ${import_types71.DivergenceTypeSchema.options.join(", ")}`
21518
22822
  );
21519
22823
  return 2;
21520
22824
  }