@neat.is/core 0.4.10 → 0.4.12

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
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
58
58
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
59
59
  const publicRead = opts.publicRead === true;
60
60
  app.addHook("preHandler", (req, reply, done) => {
61
- const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
61
+ const path47 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
62
  for (const suffix of suffixes) {
63
- if (path46 === suffix || path46.endsWith(suffix)) {
63
+ if (path47 === suffix || path47.endsWith(suffix)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -186,8 +186,8 @@ function reshapeGrpcRequest(req) {
186
186
  };
187
187
  }
188
188
  function resolveProtoRoot() {
189
- const here = import_node_path35.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
- return import_node_path35.default.resolve(here, "..", "proto");
189
+ const here = import_node_path37.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
190
+ return import_node_path37.default.resolve(here, "..", "proto");
191
191
  }
192
192
  function loadTraceService() {
193
193
  const protoRoot = resolveProtoRoot();
@@ -255,13 +255,13 @@ async function startOtelGrpcReceiver(opts) {
255
255
  })
256
256
  };
257
257
  }
258
- var import_node_url, import_node_path35, import_node_crypto2, grpc, protoLoader;
258
+ var import_node_url, import_node_path37, import_node_crypto2, grpc, protoLoader;
259
259
  var init_otel_grpc = __esm({
260
260
  "src/otel-grpc.ts"() {
261
261
  "use strict";
262
262
  init_cjs_shims();
263
263
  import_node_url = require("url");
264
- import_node_path35 = __toESM(require("path"), 1);
264
+ import_node_path37 = __toESM(require("path"), 1);
265
265
  import_node_crypto2 = require("crypto");
266
266
  grpc = __toESM(require("@grpc/grpc-js"), 1);
267
267
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -371,10 +371,10 @@ function parseOtlpRequest(body) {
371
371
  return out;
372
372
  }
373
373
  function loadProtoRoot() {
374
- const here = import_node_path36.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
- const protoRoot = import_node_path36.default.resolve(here, "..", "proto");
374
+ const here = import_node_path38.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
375
+ const protoRoot = import_node_path38.default.resolve(here, "..", "proto");
376
376
  const root = new import_protobufjs.default.Root();
377
- root.resolvePath = (_origin, target) => import_node_path36.default.resolve(protoRoot, target);
377
+ root.resolvePath = (_origin, target) => import_node_path38.default.resolve(protoRoot, target);
378
378
  root.loadSync(
379
379
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
380
380
  { keepCase: true }
@@ -567,12 +567,12 @@ async function buildOtelReceiver(opts) {
567
567
  };
568
568
  return decorated;
569
569
  }
570
- var import_node_path36, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
570
+ var import_node_path38, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
571
571
  var init_otel = __esm({
572
572
  "src/otel.ts"() {
573
573
  "use strict";
574
574
  init_cjs_shims();
575
- import_node_path36 = __toESM(require("path"), 1);
575
+ import_node_path38 = __toESM(require("path"), 1);
576
576
  import_node_url2 = require("url");
577
577
  import_fastify2 = __toESM(require("fastify"), 1);
578
578
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -600,8 +600,8 @@ __export(cli_exports, {
600
600
  });
601
601
  module.exports = __toCommonJS(cli_exports);
602
602
  init_cjs_shims();
603
- var import_node_path45 = __toESM(require("path"), 1);
604
- var import_node_fs29 = require("fs");
603
+ var import_node_path46 = __toESM(require("path"), 1);
604
+ var import_node_fs30 = require("fs");
605
605
  var import_node_url3 = require("url");
606
606
 
607
607
  // src/graph.ts
@@ -639,6 +639,7 @@ init_cjs_shims();
639
639
  init_cjs_shims();
640
640
  var import_node_fs3 = require("fs");
641
641
  var import_node_path3 = __toESM(require("path"), 1);
642
+ var sourceMapJs = __toESM(require("source-map-js"), 1);
642
643
 
643
644
  // src/policy.ts
644
645
  init_cjs_shims();
@@ -1130,19 +1131,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1130
1131
  function longestIncomingWalk(graph, start, maxDepth) {
1131
1132
  let best = { path: [start], edges: [] };
1132
1133
  const visited = /* @__PURE__ */ new Set([start]);
1133
- function step(node, path46, edges) {
1134
- if (path46.length > best.path.length) {
1135
- best = { path: [...path46], edges: [...edges] };
1134
+ function step(node, path47, edges) {
1135
+ if (path47.length > best.path.length) {
1136
+ best = { path: [...path47], edges: [...edges] };
1136
1137
  }
1137
- if (path46.length - 1 >= maxDepth) return;
1138
+ if (path47.length - 1 >= maxDepth) return;
1138
1139
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1139
1140
  for (const [srcId, edge] of incoming) {
1140
1141
  if (visited.has(srcId)) continue;
1141
1142
  visited.add(srcId);
1142
- path46.push(srcId);
1143
+ path47.push(srcId);
1143
1144
  edges.push(edge);
1144
- step(srcId, path46, edges);
1145
- path46.pop();
1145
+ step(srcId, path47, edges);
1146
+ path47.pop();
1146
1147
  edges.pop();
1147
1148
  visited.delete(srcId);
1148
1149
  }
@@ -1693,6 +1694,14 @@ function warnUnidentifiedSpan(project) {
1693
1694
  `[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
1694
1695
  );
1695
1696
  }
1697
+ var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
1698
+ function warnNoSourceMaps(serviceName) {
1699
+ if (noSourceMapWarnedServices.has(serviceName)) return;
1700
+ noSourceMapWarnedServices.add(serviceName);
1701
+ console.warn(
1702
+ `[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`
1703
+ );
1704
+ }
1696
1705
  function pickAttr(span, ...keys) {
1697
1706
  for (const k of keys) {
1698
1707
  const v = span.attributes[k];
@@ -1735,8 +1744,13 @@ function languageForExt(relPath) {
1735
1744
  return void 0;
1736
1745
  }
1737
1746
  }
1738
- function relPathForRuntimeFile(filepath, serviceNode) {
1747
+ function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
1739
1748
  let p = toPosix(filepath).replace(/^file:\/\//, "");
1749
+ if (scanPath && scanPath.length > 0) {
1750
+ const absRoot = toPosix(import_node_path3.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
1751
+ const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
1752
+ if (p.startsWith(anchor)) return p.slice(anchor.length);
1753
+ }
1740
1754
  const root = serviceNode?.repoPath;
1741
1755
  if (root && root !== "." && root.length > 0) {
1742
1756
  const rootPosix = toPosix(root);
@@ -1753,16 +1767,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
1753
1767
  p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
1754
1768
  return p.length > 0 ? p : null;
1755
1769
  }
1756
- function callSiteFromSpan(span, serviceNode) {
1770
+ var sourceMapCache = /* @__PURE__ */ new Map();
1771
+ function resolveDistToSrc(absFilepath, line) {
1772
+ if (!absFilepath.endsWith(".js")) return null;
1773
+ let entry2 = sourceMapCache.get(absFilepath);
1774
+ if (entry2 === void 0) {
1775
+ entry2 = null;
1776
+ const mapPath = `${absFilepath}.map`;
1777
+ try {
1778
+ if ((0, import_node_fs3.existsSync)(mapPath)) {
1779
+ const raw = JSON.parse((0, import_node_fs3.readFileSync)(mapPath, "utf8"));
1780
+ const consumer = new sourceMapJs.SourceMapConsumer(raw);
1781
+ entry2 = { consumer, dir: import_node_path3.default.dirname(mapPath) };
1782
+ }
1783
+ } catch {
1784
+ entry2 = null;
1785
+ }
1786
+ sourceMapCache.set(absFilepath, entry2);
1787
+ }
1788
+ if (!entry2) return null;
1789
+ try {
1790
+ const pos = entry2.consumer.originalPositionFor({
1791
+ line: line !== void 0 && Number.isFinite(line) ? line : 1,
1792
+ column: 0
1793
+ });
1794
+ if (!pos || !pos.source) return null;
1795
+ const root = entry2.consumer.sourceRoot ?? "";
1796
+ const resolved = import_node_path3.default.resolve(entry2.dir, root, pos.source);
1797
+ return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
1798
+ } catch {
1799
+ return null;
1800
+ }
1801
+ }
1802
+ function callSiteFromSpan(span, serviceNode, scanPath) {
1757
1803
  const filepath = span.attributes[CODE_FILEPATH_ATTR];
1758
1804
  if (typeof filepath !== "string" || filepath.length === 0) return null;
1759
- const relPath = relPathForRuntimeFile(filepath, serviceNode);
1760
- if (!relPath) return null;
1761
1805
  const linenoRaw = span.attributes[CODE_LINENO_ATTR];
1762
- const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
1806
+ let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
1807
+ const abs = toPosix(filepath).replace(/^file:\/\//, "");
1808
+ const resolved = resolveDistToSrc(abs, line);
1809
+ let effectivePath = filepath;
1810
+ let originalRelPath;
1811
+ if (resolved) {
1812
+ originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
1813
+ effectivePath = resolved.filepath;
1814
+ if (resolved.line !== void 0) line = resolved.line;
1815
+ }
1816
+ const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
1817
+ if (!relPath) return null;
1818
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
1819
+ warnNoSourceMaps(serviceNode.name);
1820
+ }
1763
1821
  const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
1764
1822
  const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
1765
- return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
1823
+ return {
1824
+ relPath,
1825
+ ...line !== void 0 ? { line } : {},
1826
+ ...fn ? { fn } : {},
1827
+ ...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
1828
+ };
1766
1829
  }
1767
1830
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
1768
1831
  const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
@@ -1774,6 +1837,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
1774
1837
  service: serviceName,
1775
1838
  path: callSite.relPath,
1776
1839
  ...language ? { language } : {},
1840
+ ...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
1777
1841
  discoveredVia: "otel"
1778
1842
  };
1779
1843
  graph.addNode(fileNodeId, node);
@@ -1799,6 +1863,12 @@ function makeInferredEdgeId(type, source, target) {
1799
1863
  }
1800
1864
  var INFERRED_CONFIDENCE = 0.6;
1801
1865
  var STITCH_MAX_DEPTH = 2;
1866
+ var WIRE_SPAN_KIND_CLIENT = 3;
1867
+ var WIRE_SPAN_KIND_PRODUCER = 4;
1868
+ function spanMintsObservedEdge(kind) {
1869
+ if (kind === void 0 || kind === 0) return true;
1870
+ return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
1871
+ }
1802
1872
  var PARENT_SPAN_CACHE_SIZE = 1e4;
1803
1873
  var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
1804
1874
  var parentSpanCache = /* @__PURE__ */ new Map();
@@ -1905,7 +1975,7 @@ function ensureFrontierNode(graph, host, ts) {
1905
1975
  graph.addNode(id, node);
1906
1976
  return id;
1907
1977
  }
1908
- function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1978
+ function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
1909
1979
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1910
1980
  const id = makeObservedEdgeId(type, source, target);
1911
1981
  if (graph.hasEdge(id)) {
@@ -1942,7 +2012,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1942
2012
  confidence: (0, import_types3.confidenceForObservedSignal)(signal),
1943
2013
  lastObserved: ts,
1944
2014
  callCount: 1,
1945
- signal
2015
+ signal,
2016
+ // Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
2017
+ // Only set when code.filepath was present on the span — never fabricated.
2018
+ ...evidence ? { evidence } : {}
1946
2019
  };
1947
2020
  graph.addEdgeWithKey(id, source, target, edge);
1948
2021
  return { edge, created: true };
@@ -2034,12 +2107,14 @@ async function handleSpan(ctx, span) {
2034
2107
  const isError = span.statusCode === 2;
2035
2108
  cacheSpanService(span, nowMs);
2036
2109
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
2037
- const callSite = callSiteFromSpan(span, sourceServiceNode);
2110
+ const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
2038
2111
  const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
2112
+ const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
2039
2113
  let affectedNode = sourceId;
2114
+ const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2040
2115
  if (span.dbSystem) {
2041
2116
  const host = pickAddress(span);
2042
- if (host) {
2117
+ if (mintsFromCallerSide && host) {
2043
2118
  ensureDatabaseNode(ctx.graph, host, span.dbSystem);
2044
2119
  const targetId = (0, import_types3.databaseId)(host);
2045
2120
  const result = upsertObservedEdge(
@@ -2048,14 +2123,15 @@ async function handleSpan(ctx, span) {
2048
2123
  observedSource(),
2049
2124
  targetId,
2050
2125
  ts,
2051
- isError
2126
+ isError,
2127
+ callSiteEvidence
2052
2128
  );
2053
2129
  if (result) affectedNode = targetId;
2054
2130
  }
2055
2131
  } else {
2056
2132
  const host = pickAddress(span);
2057
2133
  let resolvedViaAddress = false;
2058
- if (host && host !== span.service) {
2134
+ if (mintsFromCallerSide && host && host !== span.service) {
2059
2135
  const targetId = resolveServiceId(ctx.graph, host, env);
2060
2136
  if (targetId && targetId !== sourceId) {
2061
2137
  upsertObservedEdge(
@@ -2064,7 +2140,8 @@ async function handleSpan(ctx, span) {
2064
2140
  observedSource(),
2065
2141
  targetId,
2066
2142
  ts,
2067
- isError
2143
+ isError,
2144
+ callSiteEvidence
2068
2145
  );
2069
2146
  affectedNode = targetId;
2070
2147
  resolvedViaAddress = true;
@@ -2076,7 +2153,8 @@ async function handleSpan(ctx, span) {
2076
2153
  observedSource(),
2077
2154
  frontierNodeId,
2078
2155
  ts,
2079
- isError
2156
+ isError,
2157
+ callSiteEvidence
2080
2158
  );
2081
2159
  affectedNode = frontierNodeId;
2082
2160
  resolvedViaAddress = true;
@@ -3039,14 +3117,111 @@ async function addServiceAliases(graph, scanPath, services) {
3039
3117
 
3040
3118
  // src/extract/databases/index.ts
3041
3119
  init_cjs_shims();
3042
- var import_node_path17 = __toESM(require("path"), 1);
3120
+ var import_node_path18 = __toESM(require("path"), 1);
3121
+ var import_types8 = require("@neat.is/types");
3122
+
3123
+ // src/extract/calls/shared.ts
3124
+ init_cjs_shims();
3125
+ var import_node_fs10 = require("fs");
3126
+ var import_node_path10 = __toESM(require("path"), 1);
3043
3127
  var import_types7 = require("@neat.is/types");
3128
+ async function walkSourceFiles(dir) {
3129
+ const out = [];
3130
+ async function walk(current) {
3131
+ const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3132
+ for (const entry2 of entries) {
3133
+ const full = import_node_path10.default.join(current, entry2.name);
3134
+ if (entry2.isDirectory()) {
3135
+ if (IGNORED_DIRS.has(entry2.name)) continue;
3136
+ if (await isPythonVenvDir(full)) continue;
3137
+ await walk(full);
3138
+ } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
3139
+ out.push(full);
3140
+ }
3141
+ }
3142
+ }
3143
+ await walk(dir);
3144
+ return out;
3145
+ }
3146
+ async function loadSourceFiles(dir) {
3147
+ const paths = await walkSourceFiles(dir);
3148
+ const out = [];
3149
+ for (const p of paths) {
3150
+ try {
3151
+ const content = await import_node_fs10.promises.readFile(p, "utf8");
3152
+ out.push({ path: p, content });
3153
+ } catch {
3154
+ }
3155
+ }
3156
+ return out;
3157
+ }
3158
+ function lineOf(text, needle) {
3159
+ const idx = text.indexOf(needle);
3160
+ if (idx < 0) return 1;
3161
+ return text.slice(0, idx).split("\n").length;
3162
+ }
3163
+ function snippet(text, line) {
3164
+ const lines = text.split("\n");
3165
+ return (lines[line - 1] ?? "").trim();
3166
+ }
3167
+ function toPosix2(p) {
3168
+ return p.split("\\").join("/");
3169
+ }
3170
+ function languageForPath(relPath) {
3171
+ switch (import_node_path10.default.extname(relPath).toLowerCase()) {
3172
+ case ".py":
3173
+ return "python";
3174
+ case ".ts":
3175
+ case ".tsx":
3176
+ return "typescript";
3177
+ case ".js":
3178
+ case ".jsx":
3179
+ case ".mjs":
3180
+ case ".cjs":
3181
+ return "javascript";
3182
+ default:
3183
+ return void 0;
3184
+ }
3185
+ }
3186
+ function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3187
+ let nodesAdded = 0;
3188
+ let edgesAdded = 0;
3189
+ const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
3190
+ if (!graph.hasNode(fileNodeId)) {
3191
+ const language = languageForPath(relPath);
3192
+ const node = {
3193
+ id: fileNodeId,
3194
+ type: import_types7.NodeType.FileNode,
3195
+ service: serviceName,
3196
+ path: relPath,
3197
+ ...language ? { language } : {},
3198
+ discoveredVia: "static"
3199
+ };
3200
+ graph.addNode(fileNodeId, node);
3201
+ nodesAdded++;
3202
+ }
3203
+ const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
3204
+ if (!graph.hasEdge(containsId)) {
3205
+ const edge = {
3206
+ id: containsId,
3207
+ source: serviceNodeId,
3208
+ target: fileNodeId,
3209
+ type: import_types7.EdgeType.CONTAINS,
3210
+ provenance: import_types7.Provenance.EXTRACTED,
3211
+ confidence: (0, import_types7.confidenceForExtracted)("structural"),
3212
+ evidence: { file: relPath }
3213
+ };
3214
+ graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3215
+ edgesAdded++;
3216
+ }
3217
+ return { fileNodeId, nodesAdded, edgesAdded };
3218
+ }
3044
3219
 
3045
3220
  // src/extract/databases/db-config-yaml.ts
3046
3221
  init_cjs_shims();
3047
- var import_node_path10 = __toESM(require("path"), 1);
3222
+ var import_node_path11 = __toESM(require("path"), 1);
3048
3223
  async function parse(serviceDir) {
3049
- const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
3224
+ const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
3050
3225
  if (!await exists(yamlPath)) return [];
3051
3226
  const raw = await readYaml(yamlPath);
3052
3227
  return [
@@ -3064,13 +3239,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3064
3239
 
3065
3240
  // src/extract/databases/dotenv.ts
3066
3241
  init_cjs_shims();
3067
- var import_node_fs11 = require("fs");
3068
- var import_node_path12 = __toESM(require("path"), 1);
3242
+ var import_node_fs12 = require("fs");
3243
+ var import_node_path13 = __toESM(require("path"), 1);
3069
3244
 
3070
3245
  // src/extract/databases/shared.ts
3071
3246
  init_cjs_shims();
3072
- var import_node_fs10 = require("fs");
3073
- var import_node_path11 = __toESM(require("path"), 1);
3247
+ var import_node_fs11 = require("fs");
3248
+ var import_node_path12 = __toESM(require("path"), 1);
3074
3249
  function schemeToEngine(scheme) {
3075
3250
  const s = scheme.toLowerCase().split("+")[0];
3076
3251
  switch (s) {
@@ -3109,14 +3284,14 @@ function parseConnectionString(url) {
3109
3284
  }
3110
3285
  async function readIfExists(filePath) {
3111
3286
  try {
3112
- return await import_node_fs10.promises.readFile(filePath, "utf8");
3287
+ return await import_node_fs11.promises.readFile(filePath, "utf8");
3113
3288
  } catch {
3114
3289
  return null;
3115
3290
  }
3116
3291
  }
3117
3292
  async function findFirst(serviceDir, candidates) {
3118
3293
  for (const rel of candidates) {
3119
- const abs = import_node_path11.default.join(serviceDir, rel);
3294
+ const abs = import_node_path12.default.join(serviceDir, rel);
3120
3295
  const content = await readIfExists(abs);
3121
3296
  if (content !== null) return abs;
3122
3297
  }
@@ -3167,15 +3342,15 @@ function parseDotenvLine(line) {
3167
3342
  return { key, value };
3168
3343
  }
3169
3344
  async function parse2(serviceDir) {
3170
- const entries = await import_node_fs11.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3345
+ const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
3171
3346
  const configs = [];
3172
3347
  const seen = /* @__PURE__ */ new Set();
3173
3348
  for (const entry2 of entries) {
3174
3349
  if (!entry2.isFile()) continue;
3175
3350
  const match = isConfigFile(entry2.name);
3176
3351
  if (!match.match || match.fileType !== "env") continue;
3177
- const filePath = import_node_path12.default.join(serviceDir, entry2.name);
3178
- const content = await import_node_fs11.promises.readFile(filePath, "utf8");
3352
+ const filePath = import_node_path13.default.join(serviceDir, entry2.name);
3353
+ const content = await import_node_fs12.promises.readFile(filePath, "utf8");
3179
3354
  for (const line of content.split("\n")) {
3180
3355
  const parsed = parseDotenvLine(line);
3181
3356
  if (!parsed) continue;
@@ -3194,9 +3369,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3194
3369
 
3195
3370
  // src/extract/databases/prisma.ts
3196
3371
  init_cjs_shims();
3197
- var import_node_path13 = __toESM(require("path"), 1);
3372
+ var import_node_path14 = __toESM(require("path"), 1);
3198
3373
  async function parse3(serviceDir) {
3199
- const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
3374
+ const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
3200
3375
  const content = await readIfExists(schemaPath);
3201
3376
  if (!content) return [];
3202
3377
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3328,10 +3503,10 @@ var knexParser = { name: "knex", parse: parse5 };
3328
3503
 
3329
3504
  // src/extract/databases/ormconfig.ts
3330
3505
  init_cjs_shims();
3331
- var import_node_path14 = __toESM(require("path"), 1);
3506
+ var import_node_path15 = __toESM(require("path"), 1);
3332
3507
  async function parse6(serviceDir) {
3333
3508
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3334
- const abs = import_node_path14.default.join(serviceDir, candidate);
3509
+ const abs = import_node_path15.default.join(serviceDir, candidate);
3335
3510
  if (!await exists(abs)) continue;
3336
3511
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3337
3512
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3391,9 +3566,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3391
3566
 
3392
3567
  // src/extract/databases/sequelize.ts
3393
3568
  init_cjs_shims();
3394
- var import_node_path15 = __toESM(require("path"), 1);
3569
+ var import_node_path16 = __toESM(require("path"), 1);
3395
3570
  async function parse8(serviceDir) {
3396
- const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
3571
+ const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
3397
3572
  if (!await exists(configPath)) return [];
3398
3573
  const raw = await readJson(configPath);
3399
3574
  const out = [];
@@ -3420,7 +3595,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3420
3595
 
3421
3596
  // src/extract/databases/docker-compose.ts
3422
3597
  init_cjs_shims();
3423
- var import_node_path16 = __toESM(require("path"), 1);
3598
+ var import_node_path17 = __toESM(require("path"), 1);
3424
3599
  function portFromService(svc) {
3425
3600
  for (const raw of svc.ports ?? []) {
3426
3601
  const str = String(raw);
@@ -3447,7 +3622,7 @@ function databaseFromEnv(svc) {
3447
3622
  }
3448
3623
  async function parse9(serviceDir) {
3449
3624
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3450
- const abs = import_node_path16.default.join(serviceDir, name);
3625
+ const abs = import_node_path17.default.join(serviceDir, name);
3451
3626
  if (!await exists(abs)) continue;
3452
3627
  const raw = await readYaml(abs);
3453
3628
  if (!raw?.services) return [];
@@ -3488,8 +3663,8 @@ function compatibleDriversFor(engine) {
3488
3663
  }
3489
3664
  function toDatabaseNode(config) {
3490
3665
  return {
3491
- id: (0, import_types7.databaseId)(config.host),
3492
- type: import_types7.NodeType.DatabaseNode,
3666
+ id: (0, import_types8.databaseId)(config.host),
3667
+ type: import_types8.NodeType.DatabaseNode,
3493
3668
  name: config.database || config.host,
3494
3669
  engine: config.engine,
3495
3670
  engineVersion: config.engineVersion,
@@ -3619,19 +3794,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3619
3794
  discoveredVia: mergedDiscoveredVia
3620
3795
  });
3621
3796
  }
3797
+ const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
3798
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3799
+ graph,
3800
+ service.pkg.name,
3801
+ service.node.id,
3802
+ relConfigFile
3803
+ );
3804
+ nodesAdded += fn;
3805
+ edgesAdded += fe;
3806
+ const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
3622
3807
  const edge = {
3623
- id: (0, import_types4.extractedEdgeId)(service.node.id, dbNode.id, import_types7.EdgeType.CONNECTS_TO),
3624
- source: service.node.id,
3808
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
3809
+ source: fileNodeId,
3625
3810
  target: dbNode.id,
3626
- type: import_types7.EdgeType.CONNECTS_TO,
3627
- provenance: import_types7.Provenance.EXTRACTED,
3628
- confidence: (0, import_types7.confidenceForExtracted)("structural"),
3629
- // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3630
- // Ghost-edge cleanup keys retirement on this; the conditional
3631
- // sourceFile spread that used to live here was a v0.1.x leftover.
3632
- evidence: {
3633
- file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
3634
- }
3811
+ type: import_types8.EdgeType.CONNECTS_TO,
3812
+ provenance: import_types8.Provenance.EXTRACTED,
3813
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3814
+ evidence: { file: evidenceFile }
3635
3815
  };
3636
3816
  if (!graph.hasEdge(edge.id)) {
3637
3817
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3657,15 +3837,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3657
3837
 
3658
3838
  // src/extract/configs.ts
3659
3839
  init_cjs_shims();
3660
- var import_node_fs12 = require("fs");
3661
- var import_node_path18 = __toESM(require("path"), 1);
3662
- var import_types8 = require("@neat.is/types");
3840
+ var import_node_fs13 = require("fs");
3841
+ var import_node_path19 = __toESM(require("path"), 1);
3842
+ var import_types9 = require("@neat.is/types");
3663
3843
  async function walkConfigFiles(dir) {
3664
3844
  const out = [];
3665
3845
  async function walk(current) {
3666
- const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
3846
+ const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
3667
3847
  for (const entry2 of entries) {
3668
- const full = import_node_path18.default.join(current, entry2.name);
3848
+ const full = import_node_path19.default.join(current, entry2.name);
3669
3849
  if (entry2.isDirectory()) {
3670
3850
  if (IGNORED_DIRS.has(entry2.name)) continue;
3671
3851
  if (await isPythonVenvDir(full)) continue;
@@ -3684,26 +3864,35 @@ async function addConfigNodes(graph, services, scanPath) {
3684
3864
  for (const service of services) {
3685
3865
  const configFiles = await walkConfigFiles(service.dir);
3686
3866
  for (const file of configFiles) {
3687
- const relPath = import_node_path18.default.relative(scanPath, file);
3867
+ const relPath = import_node_path19.default.relative(scanPath, file);
3688
3868
  const node = {
3689
- id: (0, import_types8.configId)(relPath),
3690
- type: import_types8.NodeType.ConfigNode,
3691
- name: import_node_path18.default.basename(file),
3869
+ id: (0, import_types9.configId)(relPath),
3870
+ type: import_types9.NodeType.ConfigNode,
3871
+ name: import_node_path19.default.basename(file),
3692
3872
  path: relPath,
3693
- fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
3873
+ fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
3694
3874
  };
3695
3875
  if (!graph.hasNode(node.id)) {
3696
3876
  graph.addNode(node.id, node);
3697
3877
  nodesAdded++;
3698
3878
  }
3879
+ const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
3880
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
3881
+ graph,
3882
+ service.pkg.name,
3883
+ service.node.id,
3884
+ relToService
3885
+ );
3886
+ nodesAdded += fn;
3887
+ edgesAdded += fe;
3699
3888
  const edge = {
3700
- id: (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types8.EdgeType.CONFIGURED_BY),
3701
- source: service.node.id,
3889
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
3890
+ source: fileNodeId,
3702
3891
  target: node.id,
3703
- type: import_types8.EdgeType.CONFIGURED_BY,
3704
- provenance: import_types8.Provenance.EXTRACTED,
3705
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
3706
- evidence: { file: relPath.split(import_node_path18.default.sep).join("/") }
3892
+ type: import_types9.EdgeType.CONFIGURED_BY,
3893
+ provenance: import_types9.Provenance.EXTRACTED,
3894
+ confidence: (0, import_types9.confidenceForExtracted)("structural"),
3895
+ evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
3707
3896
  };
3708
3897
  if (!graph.hasEdge(edge.id)) {
3709
3898
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3725,105 +3914,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3725
3914
  var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3726
3915
  var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3727
3916
  var import_types10 = require("@neat.is/types");
3728
-
3729
- // src/extract/calls/shared.ts
3730
- init_cjs_shims();
3731
- var import_node_fs13 = require("fs");
3732
- var import_node_path19 = __toESM(require("path"), 1);
3733
- var import_types9 = require("@neat.is/types");
3734
- async function walkSourceFiles(dir) {
3735
- const out = [];
3736
- async function walk(current) {
3737
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3738
- for (const entry2 of entries) {
3739
- const full = import_node_path19.default.join(current, entry2.name);
3740
- if (entry2.isDirectory()) {
3741
- if (IGNORED_DIRS.has(entry2.name)) continue;
3742
- if (await isPythonVenvDir(full)) continue;
3743
- await walk(full);
3744
- } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
3745
- out.push(full);
3746
- }
3747
- }
3748
- }
3749
- await walk(dir);
3750
- return out;
3751
- }
3752
- async function loadSourceFiles(dir) {
3753
- const paths = await walkSourceFiles(dir);
3754
- const out = [];
3755
- for (const p of paths) {
3756
- try {
3757
- const content = await import_node_fs13.promises.readFile(p, "utf8");
3758
- out.push({ path: p, content });
3759
- } catch {
3760
- }
3761
- }
3762
- return out;
3763
- }
3764
- function lineOf(text, needle) {
3765
- const idx = text.indexOf(needle);
3766
- if (idx < 0) return 1;
3767
- return text.slice(0, idx).split("\n").length;
3768
- }
3769
- function snippet(text, line) {
3770
- const lines = text.split("\n");
3771
- return (lines[line - 1] ?? "").trim();
3772
- }
3773
- function toPosix2(p) {
3774
- return p.split("\\").join("/");
3775
- }
3776
- function languageForPath(relPath) {
3777
- switch (import_node_path19.default.extname(relPath).toLowerCase()) {
3778
- case ".py":
3779
- return "python";
3780
- case ".ts":
3781
- case ".tsx":
3782
- return "typescript";
3783
- case ".js":
3784
- case ".jsx":
3785
- case ".mjs":
3786
- case ".cjs":
3787
- return "javascript";
3788
- default:
3789
- return void 0;
3790
- }
3791
- }
3792
- function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3793
- let nodesAdded = 0;
3794
- let edgesAdded = 0;
3795
- const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
3796
- if (!graph.hasNode(fileNodeId)) {
3797
- const language = languageForPath(relPath);
3798
- const node = {
3799
- id: fileNodeId,
3800
- type: import_types9.NodeType.FileNode,
3801
- service: serviceName,
3802
- path: relPath,
3803
- ...language ? { language } : {},
3804
- discoveredVia: "static"
3805
- };
3806
- graph.addNode(fileNodeId, node);
3807
- nodesAdded++;
3808
- }
3809
- const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
3810
- if (!graph.hasEdge(containsId)) {
3811
- const edge = {
3812
- id: containsId,
3813
- source: serviceNodeId,
3814
- target: fileNodeId,
3815
- type: import_types9.EdgeType.CONTAINS,
3816
- provenance: import_types9.Provenance.EXTRACTED,
3817
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3818
- evidence: { file: relPath }
3819
- };
3820
- graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3821
- edgesAdded++;
3822
- }
3823
- return { fileNodeId, nodesAdded, edgesAdded };
3824
- }
3825
-
3826
- // src/extract/calls/http.ts
3827
3917
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3828
3918
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3829
3919
  function isInsideJsxExternalLink(node) {
@@ -3851,8 +3941,14 @@ function collectStringLiterals(node, out) {
3851
3941
  if (child) collectStringLiterals(child, out);
3852
3942
  }
3853
3943
  }
3944
+ var PARSE_CHUNK = 16384;
3945
+ function parseSource(parser, source) {
3946
+ return parser.parse(
3947
+ (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
3948
+ );
3949
+ }
3854
3950
  function callsFromSource(source, parser, knownHosts) {
3855
- const tree = parser.parse(source);
3951
+ const tree = parseSource(parser, source);
3856
3952
  const literals = [];
3857
3953
  collectStringLiterals(tree.rootNode, literals);
3858
3954
  const out = [];
@@ -4101,6 +4197,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
4101
4197
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
4102
4198
  function isLikelyAddress(value) {
4103
4199
  if (!value) return false;
4200
+ if (/\s/.test(value) || value.startsWith("{")) return false;
4104
4201
  return /:\d{2,5}$/.test(value) || value.includes(".");
4105
4202
  }
4106
4203
  function normaliseForMatch(s) {
@@ -4412,17 +4509,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4412
4509
  graph.addNode(node.id, node);
4413
4510
  nodesAdded++;
4414
4511
  }
4415
- const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
4512
+ const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
4513
+ const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
4514
+ graph,
4515
+ service.pkg.name,
4516
+ service.node.id,
4517
+ relDockerfile
4518
+ );
4519
+ nodesAdded += fn;
4520
+ edgesAdded += fe;
4521
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
4416
4522
  if (!graph.hasEdge(edgeId)) {
4417
4523
  const edge = {
4418
4524
  id: edgeId,
4419
- source: service.node.id,
4525
+ source: fileNodeId,
4420
4526
  target: node.id,
4421
4527
  type: import_types18.EdgeType.RUNS_ON,
4422
4528
  provenance: import_types18.Provenance.EXTRACTED,
4423
4529
  confidence: (0, import_types18.confidenceForExtracted)("structural"),
4424
4530
  evidence: {
4425
- file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
4531
+ file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
4426
4532
  }
4427
4533
  };
4428
4534
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5140,8 +5246,8 @@ function formatIncompat(inc) {
5140
5246
 
5141
5247
  // src/watch.ts
5142
5248
  init_cjs_shims();
5143
- var import_node_fs23 = __toESM(require("fs"), 1);
5144
- var import_node_path38 = __toESM(require("path"), 1);
5249
+ var import_node_fs25 = __toESM(require("fs"), 1);
5250
+ var import_node_path40 = __toESM(require("path"), 1);
5145
5251
  var import_chokidar = __toESM(require("chokidar"), 1);
5146
5252
 
5147
5253
  // src/api.ts
@@ -5150,10 +5256,325 @@ var import_fastify = __toESM(require("fastify"), 1);
5150
5256
  var import_cors = __toESM(require("@fastify/cors"), 1);
5151
5257
  var import_types24 = require("@neat.is/types");
5152
5258
 
5153
- // src/diff.ts
5259
+ // src/extend/index.ts
5260
+ init_cjs_shims();
5261
+ var import_node_fs21 = require("fs");
5262
+ var import_node_path34 = __toESM(require("path"), 1);
5263
+ var import_node_os2 = __toESM(require("os"), 1);
5264
+ var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
5265
+
5266
+ // src/installers/package-manager.ts
5154
5267
  init_cjs_shims();
5155
5268
  var import_node_fs20 = require("fs");
5156
- async function loadSnapshotForDiff(target) {
5269
+ var import_node_path33 = __toESM(require("path"), 1);
5270
+ var import_node_child_process = require("child_process");
5271
+ var LOCKFILE_PRIORITY = [
5272
+ { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
5273
+ { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
5274
+ { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
5275
+ {
5276
+ lockfile: "package-lock.json",
5277
+ pm: "npm",
5278
+ args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
5279
+ }
5280
+ ];
5281
+ var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
5282
+ async function exists2(p) {
5283
+ try {
5284
+ await import_node_fs20.promises.access(p);
5285
+ return true;
5286
+ } catch {
5287
+ return false;
5288
+ }
5289
+ }
5290
+ async function detectPackageManager(serviceDir) {
5291
+ let dir = import_node_path33.default.resolve(serviceDir);
5292
+ const stops = /* @__PURE__ */ new Set();
5293
+ for (let i = 0; i < 64; i++) {
5294
+ if (stops.has(dir)) break;
5295
+ stops.add(dir);
5296
+ for (const candidate of LOCKFILE_PRIORITY) {
5297
+ const lockPath = import_node_path33.default.join(dir, candidate.lockfile);
5298
+ if (await exists2(lockPath)) {
5299
+ return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
5300
+ }
5301
+ }
5302
+ const parent = import_node_path33.default.dirname(dir);
5303
+ if (parent === dir) break;
5304
+ dir = parent;
5305
+ }
5306
+ return { pm: "npm", cwd: import_node_path33.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
5307
+ }
5308
+ async function runPackageManagerInstall(cmd) {
5309
+ return new Promise((resolve) => {
5310
+ const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
5311
+ cwd: cmd.cwd,
5312
+ // Inherit PATH + HOME so the user's installed managers resolve.
5313
+ env: process.env,
5314
+ // `false` keeps the parent in control of cleanup if the orchestrator
5315
+ // exits before install finishes. Cross-platform-safe.
5316
+ shell: false,
5317
+ stdio: ["ignore", "ignore", "pipe"]
5318
+ });
5319
+ let stderr = "";
5320
+ child.stderr?.on("data", (chunk) => {
5321
+ stderr += chunk.toString("utf8");
5322
+ });
5323
+ child.on("error", (err) => {
5324
+ resolve({
5325
+ pm: cmd.pm,
5326
+ cwd: cmd.cwd,
5327
+ args: cmd.args,
5328
+ exitCode: 127,
5329
+ stderr: stderr + `
5330
+ ${err.message}`
5331
+ });
5332
+ });
5333
+ child.on("close", (code) => {
5334
+ resolve({
5335
+ pm: cmd.pm,
5336
+ cwd: cmd.cwd,
5337
+ args: cmd.args,
5338
+ exitCode: code ?? 1,
5339
+ stderr: stderr.trim()
5340
+ });
5341
+ });
5342
+ });
5343
+ }
5344
+
5345
+ // src/extend/index.ts
5346
+ async function fileExists(p) {
5347
+ try {
5348
+ await import_node_fs21.promises.access(p);
5349
+ return true;
5350
+ } catch {
5351
+ return false;
5352
+ }
5353
+ }
5354
+ async function readPackageJson(scanPath) {
5355
+ const pkgPath = import_node_path34.default.join(scanPath, "package.json");
5356
+ const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
5357
+ return JSON.parse(raw);
5358
+ }
5359
+ async function findHookFiles(scanPath) {
5360
+ const entries = await import_node_fs21.promises.readdir(scanPath);
5361
+ return entries.filter(
5362
+ (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
5363
+ ).sort();
5364
+ }
5365
+ function extendLogPath() {
5366
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path34.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
5367
+ }
5368
+ async function appendExtendLog(entry2) {
5369
+ const logPath = extendLogPath();
5370
+ await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(logPath), { recursive: true });
5371
+ await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
5372
+ }
5373
+ function splicedContent(fileContent, snippet2) {
5374
+ if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
5375
+ return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
5376
+ __INSTRUMENTATION_BLOCK__`);
5377
+ }
5378
+ const lines = fileContent.split("\n");
5379
+ let lastPushIdx = -1;
5380
+ for (let i = 0; i < lines.length; i++) {
5381
+ if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
5382
+ }
5383
+ if (lastPushIdx >= 0) {
5384
+ lines.splice(lastPushIdx + 1, 0, snippet2);
5385
+ return lines.join("\n");
5386
+ }
5387
+ for (let i = 0; i < lines.length; i++) {
5388
+ if (lines[i].includes("new NodeSDK(")) {
5389
+ lines.splice(i, 0, snippet2);
5390
+ return lines.join("\n");
5391
+ }
5392
+ }
5393
+ return null;
5394
+ }
5395
+ async function listUninstrumented(ctx) {
5396
+ const pkg = await readPackageJson(ctx.scanPath);
5397
+ const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
5398
+ const results = [];
5399
+ for (const [library, installedVersion] of Object.entries(allDeps2)) {
5400
+ const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
5401
+ if (!entry2) continue;
5402
+ if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
5403
+ results.push({
5404
+ library,
5405
+ coverage: entry2.coverage,
5406
+ installedVersion,
5407
+ instrumentation_package: entry2.instrumentation_package,
5408
+ package_version: entry2.package_version,
5409
+ registration: entry2.registration,
5410
+ notes: entry2.notes
5411
+ });
5412
+ }
5413
+ return results;
5414
+ }
5415
+ function lookupInstrumentation(library, installedVersion) {
5416
+ const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
5417
+ if (!entry2) return null;
5418
+ return {
5419
+ library: entry2.library,
5420
+ coverage: entry2.coverage,
5421
+ instrumentation_package: entry2.instrumentation_package,
5422
+ package_version: entry2.package_version,
5423
+ registration: entry2.registration,
5424
+ notes: entry2.notes
5425
+ };
5426
+ }
5427
+ async function describeProjectInstrumentation(ctx) {
5428
+ const hookFiles = await findHookFiles(ctx.scanPath);
5429
+ const envNeat = await fileExists(import_node_path34.default.join(ctx.scanPath, ".env.neat"));
5430
+ const registryInstrPackages = new Set(
5431
+ (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
5432
+ );
5433
+ const pkg = await readPackageJson(ctx.scanPath);
5434
+ const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
5435
+ const installedDeps = {};
5436
+ for (const [key, version] of Object.entries(allDeps2)) {
5437
+ if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
5438
+ installedDeps[key] = version;
5439
+ }
5440
+ }
5441
+ return { hookFiles, envNeat, installedDeps };
5442
+ }
5443
+ async function applyExtension(ctx, args, options) {
5444
+ const hookFiles = await findHookFiles(ctx.scanPath);
5445
+ if (hookFiles.length === 0) {
5446
+ throw new Error(
5447
+ `No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
5448
+ );
5449
+ }
5450
+ for (const file of hookFiles) {
5451
+ const content = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
5452
+ if (content.includes(args.registration_snippet)) {
5453
+ return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
5454
+ }
5455
+ }
5456
+ const primaryFile = hookFiles[0];
5457
+ const primaryPath = import_node_path34.default.join(ctx.scanPath, primaryFile);
5458
+ const filesTouched = [];
5459
+ const depsAdded = [];
5460
+ const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
5461
+ const pkg = await readPackageJson(ctx.scanPath);
5462
+ if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5463
+ pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
5464
+ await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5465
+ filesTouched.push("package.json");
5466
+ depsAdded.push(`${args.instrumentation_package}@${args.version}`);
5467
+ }
5468
+ const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
5469
+ const patched = splicedContent(hookContent, args.registration_snippet);
5470
+ if (!patched) {
5471
+ throw new Error(
5472
+ `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
5473
+ );
5474
+ }
5475
+ await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
5476
+ filesTouched.push(primaryFile);
5477
+ const cmd = await detectPackageManager(ctx.scanPath);
5478
+ const installer = options?.runInstall ?? runPackageManagerInstall;
5479
+ const install = await installer(cmd);
5480
+ const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
5481
+ await appendExtendLog({
5482
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5483
+ project: ctx.project,
5484
+ library: args.library,
5485
+ instrumentation_package: args.instrumentation_package,
5486
+ version: args.version,
5487
+ registration_snippet: args.registration_snippet,
5488
+ filesTouched,
5489
+ depsAdded,
5490
+ installOutput
5491
+ });
5492
+ return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
5493
+ }
5494
+ async function dryRunExtension(ctx, args) {
5495
+ const hookFiles = await findHookFiles(ctx.scanPath);
5496
+ if (hookFiles.length === 0) {
5497
+ return {
5498
+ library: args.library,
5499
+ filesTouched: [],
5500
+ depsToAdd: [],
5501
+ packageJsonPatch: {},
5502
+ templatePatch: "No hook files found. Run 'neat init' first."
5503
+ };
5504
+ }
5505
+ for (const file of hookFiles) {
5506
+ const content = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
5507
+ if (content.includes(args.registration_snippet)) {
5508
+ return {
5509
+ library: args.library,
5510
+ filesTouched: [],
5511
+ depsToAdd: [],
5512
+ packageJsonPatch: {},
5513
+ templatePatch: "Already applied \u2014 no changes would be made."
5514
+ };
5515
+ }
5516
+ }
5517
+ const primaryFile = hookFiles[0];
5518
+ const filesTouched = [];
5519
+ const depsToAdd = [];
5520
+ let packageJsonPatch = {};
5521
+ let templatePatch = "";
5522
+ const pkg = await readPackageJson(ctx.scanPath);
5523
+ if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
5524
+ packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
5525
+ depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
5526
+ filesTouched.push("package.json");
5527
+ }
5528
+ const hookContent = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, primaryFile), "utf8");
5529
+ const patched = splicedContent(hookContent, args.registration_snippet);
5530
+ if (patched) {
5531
+ filesTouched.push(primaryFile);
5532
+ templatePatch = `+ ${args.registration_snippet}`;
5533
+ } else {
5534
+ templatePatch = "Could not find insertion point in hook file.";
5535
+ }
5536
+ return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
5537
+ }
5538
+ async function rollbackExtension(ctx, args) {
5539
+ const logPath = extendLogPath();
5540
+ if (!await fileExists(logPath)) {
5541
+ return { undone: false, message: "no apply found for library" };
5542
+ }
5543
+ const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
5544
+ const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
5545
+ const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
5546
+ if (!match) {
5547
+ return { undone: false, message: "no apply found for library" };
5548
+ }
5549
+ const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
5550
+ if (await fileExists(pkgPath)) {
5551
+ const pkg = await readPackageJson(ctx.scanPath);
5552
+ if (pkg.dependencies?.[match.instrumentation_package]) {
5553
+ const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
5554
+ pkg.dependencies = rest;
5555
+ await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
5556
+ }
5557
+ }
5558
+ const hookFiles = await findHookFiles(ctx.scanPath);
5559
+ for (const file of hookFiles) {
5560
+ const filePath = import_node_path34.default.join(ctx.scanPath, file);
5561
+ const content = await import_node_fs21.promises.readFile(filePath, "utf8");
5562
+ if (content.includes(match.registration_snippet)) {
5563
+ const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
5564
+ await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
5565
+ break;
5566
+ }
5567
+ }
5568
+ return {
5569
+ undone: true,
5570
+ message: `rolled back ${match.library} (${match.instrumentation_package})`
5571
+ };
5572
+ }
5573
+
5574
+ // src/diff.ts
5575
+ init_cjs_shims();
5576
+ var import_node_fs22 = require("fs");
5577
+ async function loadSnapshotForDiff(target) {
5157
5578
  if (/^https?:\/\//i.test(target)) {
5158
5579
  const res = await fetch(target);
5159
5580
  if (!res.ok) {
@@ -5161,7 +5582,7 @@ async function loadSnapshotForDiff(target) {
5161
5582
  }
5162
5583
  return await res.json();
5163
5584
  }
5164
- const raw = await import_node_fs20.promises.readFile(target, "utf8");
5585
+ const raw = await import_node_fs22.promises.readFile(target, "utf8");
5165
5586
  return JSON.parse(raw);
5166
5587
  }
5167
5588
  function indexEntries(entries) {
@@ -5229,23 +5650,23 @@ function canonicalJson(value) {
5229
5650
 
5230
5651
  // src/projects.ts
5231
5652
  init_cjs_shims();
5232
- var import_node_path33 = __toESM(require("path"), 1);
5653
+ var import_node_path35 = __toESM(require("path"), 1);
5233
5654
  function pathsForProject(project, baseDir) {
5234
5655
  if (project === DEFAULT_PROJECT) {
5235
5656
  return {
5236
- snapshotPath: import_node_path33.default.join(baseDir, "graph.json"),
5237
- errorsPath: import_node_path33.default.join(baseDir, "errors.ndjson"),
5238
- staleEventsPath: import_node_path33.default.join(baseDir, "stale-events.ndjson"),
5239
- embeddingsCachePath: import_node_path33.default.join(baseDir, "embeddings.json"),
5240
- policyViolationsPath: import_node_path33.default.join(baseDir, "policy-violations.ndjson")
5657
+ snapshotPath: import_node_path35.default.join(baseDir, "graph.json"),
5658
+ errorsPath: import_node_path35.default.join(baseDir, "errors.ndjson"),
5659
+ staleEventsPath: import_node_path35.default.join(baseDir, "stale-events.ndjson"),
5660
+ embeddingsCachePath: import_node_path35.default.join(baseDir, "embeddings.json"),
5661
+ policyViolationsPath: import_node_path35.default.join(baseDir, "policy-violations.ndjson")
5241
5662
  };
5242
5663
  }
5243
5664
  return {
5244
- snapshotPath: import_node_path33.default.join(baseDir, `${project}.json`),
5245
- errorsPath: import_node_path33.default.join(baseDir, `errors.${project}.ndjson`),
5246
- staleEventsPath: import_node_path33.default.join(baseDir, `stale-events.${project}.ndjson`),
5247
- embeddingsCachePath: import_node_path33.default.join(baseDir, `embeddings.${project}.json`),
5248
- policyViolationsPath: import_node_path33.default.join(baseDir, `policy-violations.${project}.ndjson`)
5665
+ snapshotPath: import_node_path35.default.join(baseDir, `${project}.json`),
5666
+ errorsPath: import_node_path35.default.join(baseDir, `errors.${project}.ndjson`),
5667
+ staleEventsPath: import_node_path35.default.join(baseDir, `stale-events.${project}.ndjson`),
5668
+ embeddingsCachePath: import_node_path35.default.join(baseDir, `embeddings.${project}.json`),
5669
+ policyViolationsPath: import_node_path35.default.join(baseDir, `policy-violations.${project}.ndjson`)
5249
5670
  };
5250
5671
  }
5251
5672
  var Projects = class {
@@ -5281,65 +5702,135 @@ var Projects = class {
5281
5702
 
5282
5703
  // src/registry.ts
5283
5704
  init_cjs_shims();
5284
- var import_node_fs21 = require("fs");
5285
- var import_node_os2 = __toESM(require("os"), 1);
5286
- var import_node_path34 = __toESM(require("path"), 1);
5705
+ var import_node_fs23 = require("fs");
5706
+ var import_node_os3 = __toESM(require("os"), 1);
5707
+ var import_node_path36 = __toESM(require("path"), 1);
5287
5708
  var import_types23 = require("@neat.is/types");
5288
5709
  var LOCK_TIMEOUT_MS = 5e3;
5289
5710
  var LOCK_RETRY_MS = 50;
5290
5711
  function neatHome() {
5291
5712
  const override = process.env.NEAT_HOME;
5292
- if (override && override.length > 0) return import_node_path34.default.resolve(override);
5293
- return import_node_path34.default.join(import_node_os2.default.homedir(), ".neat");
5713
+ if (override && override.length > 0) return import_node_path36.default.resolve(override);
5714
+ return import_node_path36.default.join(import_node_os3.default.homedir(), ".neat");
5294
5715
  }
5295
5716
  function registryPath() {
5296
- return import_node_path34.default.join(neatHome(), "projects.json");
5717
+ return import_node_path36.default.join(neatHome(), "projects.json");
5297
5718
  }
5298
5719
  function registryLockPath() {
5299
- return import_node_path34.default.join(neatHome(), "projects.json.lock");
5720
+ return import_node_path36.default.join(neatHome(), "projects.json.lock");
5721
+ }
5722
+ function daemonPidPath() {
5723
+ return import_node_path36.default.join(neatHome(), "neatd.pid");
5724
+ }
5725
+ function isPidAliveDefault(pid) {
5726
+ try {
5727
+ process.kill(pid, 0);
5728
+ return true;
5729
+ } catch (err) {
5730
+ return err.code === "EPERM";
5731
+ }
5732
+ }
5733
+ async function readPidFile(file) {
5734
+ try {
5735
+ const raw = await import_node_fs23.promises.readFile(file, "utf8");
5736
+ const pid = Number.parseInt(raw.trim(), 10);
5737
+ return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5738
+ } catch {
5739
+ return void 0;
5740
+ }
5741
+ }
5742
+ var defaultLockHolderProbe = {
5743
+ isPidAlive: isPidAliveDefault,
5744
+ daemonPidFromFile: () => readPidFile(daemonPidPath()),
5745
+ async daemonResponds() {
5746
+ const base = process.env.NEAT_API_URL ?? "http://localhost:8080";
5747
+ try {
5748
+ await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) });
5749
+ return true;
5750
+ } catch {
5751
+ return false;
5752
+ }
5753
+ }
5754
+ };
5755
+ async function readLockPid(lockPath) {
5756
+ return readPidFile(lockPath);
5757
+ }
5758
+ async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
5759
+ const lockPid = await readLockPid(lockPath);
5760
+ const daemonPid = await probe.daemonPidFromFile();
5761
+ if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
5762
+ // Either confirms a live daemon is in the picture (the second guards
5763
+ // against a stale pidfile whose PID got reused).
5764
+ (daemonPid === lockPid || await probe.daemonResponds())) {
5765
+ return { kind: "daemon", pid: daemonPid };
5766
+ }
5767
+ if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
5768
+ return { kind: "command", pid: lockPid };
5769
+ }
5770
+ return { kind: "stale" };
5771
+ }
5772
+ function lockHolderMessage(holder, lockPath, timeoutMs) {
5773
+ switch (holder.kind) {
5774
+ case "daemon":
5775
+ return `The neat daemon (pid ${holder.pid}) is holding the registry lock. Register this project through the daemon, or stop neatd and re-run \`neat init\`.`;
5776
+ case "command":
5777
+ return `Another neat command (pid ${holder.pid}) is holding the registry lock. Wait for it to finish, or check \`ps\` if you're not sure what's running.`;
5778
+ case "stale":
5779
+ return `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`;
5780
+ }
5300
5781
  }
5301
5782
  async function normalizeProjectPath(input) {
5302
- const resolved = import_node_path34.default.resolve(input);
5783
+ const resolved = import_node_path36.default.resolve(input);
5303
5784
  try {
5304
- return await import_node_fs21.promises.realpath(resolved);
5785
+ return await import_node_fs23.promises.realpath(resolved);
5305
5786
  } catch {
5306
5787
  return resolved;
5307
5788
  }
5308
5789
  }
5309
5790
  async function writeAtomically(target, contents) {
5310
- await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(target), { recursive: true });
5791
+ await import_node_fs23.promises.mkdir(import_node_path36.default.dirname(target), { recursive: true });
5311
5792
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
5312
- const fd = await import_node_fs21.promises.open(tmp, "w");
5793
+ const fd = await import_node_fs23.promises.open(tmp, "w");
5313
5794
  try {
5314
5795
  await fd.writeFile(contents, "utf8");
5315
5796
  await fd.sync();
5316
5797
  } finally {
5317
5798
  await fd.close();
5318
5799
  }
5319
- await import_node_fs21.promises.rename(tmp, target);
5800
+ await import_node_fs23.promises.rename(tmp, target);
5320
5801
  }
5321
- async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
5802
+ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
5322
5803
  const deadline = Date.now() + timeoutMs;
5323
- await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(lockPath), { recursive: true });
5804
+ await import_node_fs23.promises.mkdir(import_node_path36.default.dirname(lockPath), { recursive: true });
5805
+ let probedHolder = false;
5324
5806
  while (true) {
5325
5807
  try {
5326
- const fd = await import_node_fs21.promises.open(lockPath, "wx");
5327
- await fd.close();
5808
+ const fd = await import_node_fs23.promises.open(lockPath, "wx");
5809
+ try {
5810
+ await fd.writeFile(`${process.pid}
5811
+ `, "utf8");
5812
+ } finally {
5813
+ await fd.close();
5814
+ }
5328
5815
  return;
5329
5816
  } catch (err) {
5330
5817
  const code = err.code;
5331
5818
  if (code !== "EEXIST") throw err;
5819
+ if (!probedHolder) {
5820
+ probedHolder = true;
5821
+ const holder = await classifyLockHolder(lockPath, probe);
5822
+ if (holder.kind === "daemon") throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
5823
+ }
5332
5824
  if (Date.now() >= deadline) {
5333
- throw new Error(
5334
- `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`
5335
- );
5825
+ const holder = await classifyLockHolder(lockPath, probe);
5826
+ throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
5336
5827
  }
5337
5828
  await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
5338
5829
  }
5339
5830
  }
5340
5831
  }
5341
5832
  async function releaseLock(lockPath) {
5342
- await import_node_fs21.promises.unlink(lockPath).catch(() => {
5833
+ await import_node_fs23.promises.unlink(lockPath).catch(() => {
5343
5834
  });
5344
5835
  }
5345
5836
  async function withLock(fn) {
@@ -5355,7 +5846,7 @@ async function readRegistry() {
5355
5846
  const file = registryPath();
5356
5847
  let raw;
5357
5848
  try {
5358
- raw = await import_node_fs21.promises.readFile(file, "utf8");
5849
+ raw = await import_node_fs23.promises.readFile(file, "utf8");
5359
5850
  } catch (err) {
5360
5851
  if (err.code === "ENOENT") {
5361
5852
  return { version: 1, projects: [] };
@@ -5896,6 +6387,105 @@ function registerRoutes(scope, ctx) {
5896
6387
  violations
5897
6388
  };
5898
6389
  });
6390
+ scope.get("/extend/list-uninstrumented", async (req, reply) => {
6391
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6392
+ if (!proj) return;
6393
+ if (!proj.scanPath) {
6394
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6395
+ }
6396
+ try {
6397
+ const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
6398
+ return { libraries: results };
6399
+ } catch (err) {
6400
+ return reply.code(500).send({ error: err.message });
6401
+ }
6402
+ });
6403
+ scope.get("/extend/lookup", async (req, reply) => {
6404
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6405
+ if (!proj) return;
6406
+ const { library, version } = req.query;
6407
+ if (!library) {
6408
+ return reply.code(400).send({ error: "query parameter `library` is required" });
6409
+ }
6410
+ const result = lookupInstrumentation(library, version);
6411
+ if (!result) {
6412
+ return reply.code(404).send({ error: "library not found in registry", library });
6413
+ }
6414
+ return result;
6415
+ });
6416
+ scope.get("/extend/describe", async (req, reply) => {
6417
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6418
+ if (!proj) return;
6419
+ if (!proj.scanPath) {
6420
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6421
+ }
6422
+ try {
6423
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
6424
+ return state;
6425
+ } catch (err) {
6426
+ return reply.code(500).send({ error: err.message });
6427
+ }
6428
+ });
6429
+ scope.post("/extend/apply", async (req, reply) => {
6430
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6431
+ if (!proj) return;
6432
+ if (!proj.scanPath) {
6433
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6434
+ }
6435
+ const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
6436
+ if (!library || !instrumentation_package || !version || !registration_snippet) {
6437
+ return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
6438
+ }
6439
+ try {
6440
+ const result = await applyExtension(
6441
+ { project: proj.name, scanPath: proj.scanPath },
6442
+ { library, instrumentation_package, version, registration_snippet }
6443
+ );
6444
+ return result;
6445
+ } catch (err) {
6446
+ return reply.code(500).send({ error: err.message });
6447
+ }
6448
+ });
6449
+ scope.post("/extend/dry-run", async (req, reply) => {
6450
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6451
+ if (!proj) return;
6452
+ if (!proj.scanPath) {
6453
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6454
+ }
6455
+ const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
6456
+ if (!library || !instrumentation_package || !version || !registration_snippet) {
6457
+ return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
6458
+ }
6459
+ try {
6460
+ const result = await dryRunExtension(
6461
+ { project: proj.name, scanPath: proj.scanPath },
6462
+ { library, instrumentation_package, version, registration_snippet }
6463
+ );
6464
+ return result;
6465
+ } catch (err) {
6466
+ return reply.code(500).send({ error: err.message });
6467
+ }
6468
+ });
6469
+ scope.post("/extend/rollback", async (req, reply) => {
6470
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap);
6471
+ if (!proj) return;
6472
+ if (!proj.scanPath) {
6473
+ return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
6474
+ }
6475
+ const { library } = req.body ?? {};
6476
+ if (!library) {
6477
+ return reply.code(400).send({ error: "body must include library" });
6478
+ }
6479
+ try {
6480
+ const result = await rollbackExtension(
6481
+ { project: proj.name, scanPath: proj.scanPath },
6482
+ { library }
6483
+ );
6484
+ return result;
6485
+ } catch (err) {
6486
+ return reply.code(500).send({ error: err.message });
6487
+ }
6488
+ });
5899
6489
  }
5900
6490
  async function buildApi(opts) {
5901
6491
  const app = (0, import_fastify.default)({ logger: false });
@@ -6007,8 +6597,8 @@ init_otel_grpc();
6007
6597
 
6008
6598
  // src/search.ts
6009
6599
  init_cjs_shims();
6010
- var import_node_fs22 = require("fs");
6011
- var import_node_path37 = __toESM(require("path"), 1);
6600
+ var import_node_fs24 = require("fs");
6601
+ var import_node_path39 = __toESM(require("path"), 1);
6012
6602
  var import_node_crypto3 = require("crypto");
6013
6603
  var DEFAULT_LIMIT = 10;
6014
6604
  var NOMIC_DIM = 768;
@@ -6138,7 +6728,7 @@ async function pickEmbedder() {
6138
6728
  }
6139
6729
  async function readCache(cachePath) {
6140
6730
  try {
6141
- const raw = await import_node_fs22.promises.readFile(cachePath, "utf8");
6731
+ const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
6142
6732
  const parsed = JSON.parse(raw);
6143
6733
  if (parsed.version !== 1) return null;
6144
6734
  return parsed;
@@ -6147,8 +6737,8 @@ async function readCache(cachePath) {
6147
6737
  }
6148
6738
  }
6149
6739
  async function writeCache(cachePath, cache) {
6150
- await import_node_fs22.promises.mkdir(import_node_path37.default.dirname(cachePath), { recursive: true });
6151
- await import_node_fs22.promises.writeFile(cachePath, JSON.stringify(cache));
6740
+ await import_node_fs24.promises.mkdir(import_node_path39.default.dirname(cachePath), { recursive: true });
6741
+ await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
6152
6742
  }
6153
6743
  var VectorIndex = class {
6154
6744
  constructor(embedder, cachePath) {
@@ -6303,8 +6893,8 @@ var ALL_PHASES = [
6303
6893
  ];
6304
6894
  function classifyChange(relPath) {
6305
6895
  const phases = /* @__PURE__ */ new Set();
6306
- const base = import_node_path38.default.basename(relPath).toLowerCase();
6307
- const segments = relPath.split(import_node_path38.default.sep).map((s) => s.toLowerCase());
6896
+ const base = import_node_path40.default.basename(relPath).toLowerCase();
6897
+ const segments = relPath.split(import_node_path40.default.sep).map((s) => s.toLowerCase());
6308
6898
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
6309
6899
  phases.add("services");
6310
6900
  phases.add("aliases");
@@ -6412,16 +7002,16 @@ function countWatchableDirs(scanPath, limit) {
6412
7002
  if (count >= limit) return;
6413
7003
  let entries;
6414
7004
  try {
6415
- entries = import_node_fs23.default.readdirSync(dir, { withFileTypes: true });
7005
+ entries = import_node_fs25.default.readdirSync(dir, { withFileTypes: true });
6416
7006
  } catch {
6417
7007
  return;
6418
7008
  }
6419
7009
  for (const e of entries) {
6420
7010
  if (count >= limit) return;
6421
7011
  if (!e.isDirectory()) continue;
6422
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path38.default.join(dir, e.name) + import_node_path38.default.sep))) continue;
7012
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path40.default.join(dir, e.name) + import_node_path40.default.sep))) continue;
6423
7013
  count++;
6424
- if (depth < 2) visit(import_node_path38.default.join(dir, e.name), depth + 1);
7014
+ if (depth < 2) visit(import_node_path40.default.join(dir, e.name), depth + 1);
6425
7015
  }
6426
7016
  };
6427
7017
  visit(scanPath, 0);
@@ -6439,8 +7029,8 @@ async function startWatch(graph, opts) {
6439
7029
  const projectName = opts.project ?? DEFAULT_PROJECT;
6440
7030
  await loadGraphFromDisk(graph, opts.outPath);
6441
7031
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
6442
- const policyFilePath = import_node_path38.default.join(opts.scanPath, "policy.json");
6443
- const policyViolationsPath = import_node_path38.default.join(import_node_path38.default.dirname(opts.outPath), "policy-violations.ndjson");
7032
+ const policyFilePath = import_node_path40.default.join(opts.scanPath, "policy.json");
7033
+ const policyViolationsPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), "policy-violations.ndjson");
6444
7034
  let policies = [];
6445
7035
  try {
6446
7036
  policies = await loadPolicyFile(policyFilePath);
@@ -6491,7 +7081,7 @@ async function startWatch(graph, opts) {
6491
7081
  assertBindAuthority(host, auth.authToken);
6492
7082
  const port = opts.port ?? 8080;
6493
7083
  const otelPort = opts.otelPort ?? 4318;
6494
- const cachePath = opts.embeddingsCachePath ?? import_node_path38.default.join(import_node_path38.default.dirname(opts.outPath), "embeddings.json");
7084
+ const cachePath = opts.embeddingsCachePath ?? import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), "embeddings.json");
6495
7085
  let searchIndex;
6496
7086
  try {
6497
7087
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -6509,7 +7099,7 @@ async function startWatch(graph, opts) {
6509
7099
  // Paths are derived from the explicit options the watch caller passes
6510
7100
  // — pathsForProject is only used to fill in the embeddings/snapshot
6511
7101
  // fields so the registry shape is complete.
6512
- ...pathsForProject(projectName, import_node_path38.default.dirname(opts.outPath)),
7102
+ ...pathsForProject(projectName, import_node_path40.default.dirname(opts.outPath)),
6513
7103
  snapshotPath: opts.outPath,
6514
7104
  errorsPath: opts.errorsPath,
6515
7105
  staleEventsPath: opts.staleEventsPath
@@ -6525,6 +7115,7 @@ async function startWatch(graph, opts) {
6525
7115
  const onSpan = makeSpanHandler({
6526
7116
  graph,
6527
7117
  errorsPath: opts.errorsPath,
7118
+ scanPath: opts.scanPath,
6528
7119
  project: projectName,
6529
7120
  writeErrorEventInline: false,
6530
7121
  onPolicyTrigger
@@ -6539,6 +7130,7 @@ async function startWatch(graph, opts) {
6539
7130
  const onSpanGrpc = makeSpanHandler({
6540
7131
  graph,
6541
7132
  errorsPath: opts.errorsPath,
7133
+ scanPath: opts.scanPath,
6542
7134
  project: projectName,
6543
7135
  onPolicyTrigger
6544
7136
  });
@@ -6594,9 +7186,9 @@ async function startWatch(graph, opts) {
6594
7186
  };
6595
7187
  const onPath = (absPath) => {
6596
7188
  if (shouldIgnore(absPath)) return;
6597
- const rel = import_node_path38.default.relative(opts.scanPath, absPath);
7189
+ const rel = import_node_path40.default.relative(opts.scanPath, absPath);
6598
7190
  if (!rel || rel.startsWith("..")) return;
6599
- pendingPaths.add(rel.split(import_node_path38.default.sep).join("/"));
7191
+ pendingPaths.add(rel.split(import_node_path40.default.sep).join("/"));
6600
7192
  const phases = classifyChange(rel);
6601
7193
  if (phases.size === 0) {
6602
7194
  for (const p of ALL_PHASES) pending.add(p);
@@ -6650,16 +7242,16 @@ async function startWatch(graph, opts) {
6650
7242
 
6651
7243
  // src/deploy/detect.ts
6652
7244
  init_cjs_shims();
6653
- var import_node_fs24 = require("fs");
6654
- var import_node_path39 = __toESM(require("path"), 1);
6655
- var import_node_child_process = require("child_process");
7245
+ var import_node_fs26 = require("fs");
7246
+ var import_node_path41 = __toESM(require("path"), 1);
7247
+ var import_node_child_process2 = require("child_process");
6656
7248
  var import_node_crypto4 = require("crypto");
6657
7249
  function generateToken() {
6658
7250
  return (0, import_node_crypto4.randomBytes)(32).toString("base64url");
6659
7251
  }
6660
7252
  async function probeBinary(binary, arg) {
6661
7253
  return new Promise((resolve) => {
6662
- const child = (0, import_node_child_process.spawn)(binary, [arg], { stdio: "ignore" });
7254
+ const child = (0, import_node_child_process2.spawn)(binary, [arg], { stdio: "ignore" });
6663
7255
  const timer = setTimeout(() => {
6664
7256
  child.kill("SIGKILL");
6665
7257
  resolve(false);
@@ -6751,21 +7343,21 @@ async function runDeploy(opts = {}) {
6751
7343
  const token = generateToken();
6752
7344
  switch (substrate) {
6753
7345
  case "docker-compose": {
6754
- const artifactPath = import_node_path39.default.join(cwd, "docker-compose.neat.yml");
7346
+ const artifactPath = import_node_path41.default.join(cwd, "docker-compose.neat.yml");
6755
7347
  const contents = emitDockerCompose(cwd);
6756
- await import_node_fs24.promises.writeFile(artifactPath, contents, "utf8");
7348
+ await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
6757
7349
  return {
6758
7350
  substrate,
6759
7351
  artifactPath,
6760
7352
  token,
6761
7353
  contents,
6762
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path39.default.basename(artifactPath)} up -d`
7354
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path41.default.basename(artifactPath)} up -d`
6763
7355
  };
6764
7356
  }
6765
7357
  case "systemd": {
6766
- const artifactPath = import_node_path39.default.join(cwd, "neat.service");
7358
+ const artifactPath = import_node_path41.default.join(cwd, "neat.service");
6767
7359
  const contents = emitSystemdUnit(cwd);
6768
- await import_node_fs24.promises.writeFile(artifactPath, contents, "utf8");
7360
+ await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
6769
7361
  return {
6770
7362
  substrate,
6771
7363
  artifactPath,
@@ -6798,19 +7390,27 @@ init_cjs_shims();
6798
7390
 
6799
7391
  // src/installers/javascript.ts
6800
7392
  init_cjs_shims();
6801
- var import_node_fs25 = require("fs");
6802
- var import_node_path40 = __toESM(require("path"), 1);
7393
+ var import_node_fs27 = require("fs");
7394
+ var import_node_path42 = __toESM(require("path"), 1);
7395
+ var import_semver2 = __toESM(require("semver"), 1);
6803
7396
 
6804
7397
  // src/installers/templates.ts
6805
7398
  init_cjs_shims();
6806
7399
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
6807
- var OTEL_INIT_STAMP = "// neat-template-version: 3 \u2014 file-first call-site capture (ADR-089) + OTLP bearer auth (#410).";
7400
+ var OTEL_INIT_STAMP = "// neat-template-version: 4 \u2014 layered file-first capture (ADR-090): stack walk + handler-entry + off-stack facades.";
6808
7401
  var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
6809
- function neatCallsiteProcessorSource(ts) {
6810
- const stackT = ts ? ": string" : "";
7402
+ function neatCaptureSource(ts) {
6811
7403
  const spanT = ts ? ": any" : "";
6812
7404
  const strOpt = ts ? ": string | undefined" : "";
6813
- return `function __neatPickUserFrame(stack${stackT}) {
7405
+ const anyT = ts ? ": any" : "";
7406
+ const fnT = ts ? ": any" : "";
7407
+ const arrAny = ts ? ": any[]" : "";
7408
+ return `// Context key shared by the facade/handler wraps (writers) and the
7409
+ // processor fallback (reader). Symbol.for keeps it stable even if the wraps
7410
+ // and the processor end up in different module instances.
7411
+ const NEAT_USER_FRAME = Symbol.for('neat.user-frame')
7412
+
7413
+ function __neatPickUserFrame(stack${strOpt}) {
6814
7414
  const lines = String(stack || '').split('\\n')
6815
7415
  for (let i = 0; i < lines.length; i++) {
6816
7416
  const raw = lines[i].trim()
@@ -6819,6 +7419,9 @@ function neatCallsiteProcessorSource(ts) {
6819
7419
  if (raw.indexOf('@opentelemetry') !== -1) continue
6820
7420
  if (raw.indexOf('node:') !== -1) continue
6821
7421
  if (raw.indexOf('NeatCallSiteSpanProcessor') !== -1) continue
7422
+ // Skip NEAT's own inlined wraps/helpers (all carry the __neat prefix) so a
7423
+ // facade frame never masquerades as the user's call site.
7424
+ if (raw.indexOf('__neat') !== -1) continue
6822
7425
  const bodyText = raw.slice(3)
6823
7426
  const loc = bodyText.match(/:(\\d+):(\\d+)\\)?$/)
6824
7427
  if (!loc) continue
@@ -6840,32 +7443,245 @@ function neatCallsiteProcessorSource(ts) {
6840
7443
  return null
6841
7444
  }
6842
7445
 
7446
+ // Layer 2/3 fallback source: the frame a handler-entry or facade wrap pushed
7447
+ // into context. Reads the span's own parent context first, then the active
7448
+ // context \u2014 the capture spike confirmed both return the value for undici.
7449
+ function __neatFrameFromContext(parentContext${anyT}) {
7450
+ try {
7451
+ const fromParent =
7452
+ parentContext && typeof parentContext.getValue === 'function'
7453
+ ? parentContext.getValue(NEAT_USER_FRAME)
7454
+ : undefined
7455
+ return fromParent || context.active().getValue(NEAT_USER_FRAME) || null
7456
+ } catch (_e) {
7457
+ return null
7458
+ }
7459
+ }
7460
+
7461
+ function __neatSetCodeAttrs(span${spanT}, frame${anyT}) {
7462
+ span.setAttribute('code.filepath', frame.filepath)
7463
+ if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
7464
+ if (frame.function) span.setAttribute('code.function', frame.function)
7465
+ }
7466
+
6843
7467
  class NeatCallSiteSpanProcessor {
6844
- onStart(span${spanT}) {
7468
+ onStart(span${spanT}, parentContext${anyT}) {
6845
7469
  if (!span || (span.kind !== 2 && span.kind !== 3)) return
6846
- const frame = __neatPickUserFrame(new Error().stack)
7470
+ // Layer 1 \u2014 synchronous stack walk (sync-wrapper instrumentations).
7471
+ let frame = __neatPickUserFrame(new Error().stack)
7472
+ // Layer 2/3 \u2014 the handler-entry or off-stack-facade frame from context.
7473
+ if (!frame) frame = __neatFrameFromContext(parentContext)
6847
7474
  if (!frame) return
6848
- span.setAttribute('code.filepath', frame.filepath)
6849
- if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
6850
- if (frame.function) span.setAttribute('code.function', frame.function)
7475
+ __neatSetCodeAttrs(span, frame)
6851
7476
  }
6852
7477
  onEnd() {}
6853
7478
  forceFlush() { return Promise.resolve() }
6854
7479
  shutdown() { return Promise.resolve() }
7480
+ }
7481
+
7482
+ // Capture the caller's synchronous frame at the wrap point and run \`fn\` with
7483
+ // that frame pushed into the active context (file-awareness.md \xA74 layer 3). An
7484
+ // off-stack instrumentation that creates its span inside \`fn\` inherits the
7485
+ // frame; the processor reads it via __neatFrameFromContext.
7486
+ function __neatRunWithUserFrame(fn${fnT}) {
7487
+ const frame = __neatPickUserFrame(new Error().stack)
7488
+ if (!frame) return fn()
7489
+ return context.with(context.active().setValue(NEAT_USER_FRAME, frame), fn)
7490
+ }
7491
+
7492
+ // Handler-entry attribution (file-awareness.md \xA74 layer 2). Stamp the framework
7493
+ // SERVER span with the handler frame captured at route registration, and push
7494
+ // the same frame into context so downstream CLIENT/PRODUCER spans inherit the
7495
+ // handler-file floor when their own stack carries none.
7496
+ function __neatStampHandler(frame${anyT}, run${fnT}) {
7497
+ try {
7498
+ const active = trace.getActiveSpan()
7499
+ if (active && active.kind === 1 && typeof active.setAttribute === 'function') {
7500
+ __neatSetCodeAttrs(active, frame)
7501
+ }
7502
+ } catch (_e) {}
7503
+ try {
7504
+ return context.with(context.active().setValue(NEAT_USER_FRAME, frame), run)
7505
+ } catch (_e) {
7506
+ return run()
7507
+ }
7508
+ }
7509
+
7510
+ // require-in-the-middle is a transitive dependency of the OTel instrumentation
7511
+ // packages NEAT installs, so it resolves in any instrumented CJS service.
7512
+ // Guarded: when it's absent (or the host runs as pure ESM, where require isn't
7513
+ // defined) the off-stack/handler wraps degrade to the stack walk + context
7514
+ // floor, never to a crash.
7515
+ function __neatHook(modules${arrAny}, onload${fnT}) {
7516
+ try {
7517
+ const RITM = require('require-in-the-middle')
7518
+ const Hook = RITM && RITM.Hook ? RITM.Hook : RITM
7519
+ new Hook(modules, { internals: false }, onload)
7520
+ return true
7521
+ } catch (_e) {
7522
+ return false
7523
+ }
7524
+ }
7525
+
7526
+ // Off-stack facade: Node's built-in fetch / undici. The instrumentation creates
7527
+ // the CLIENT span inside a diagnostics_channel handler detached from the
7528
+ // caller's stack, so wrap the global so the user frame is in context when the
7529
+ // span is created. The capture spike (2026-05-28) validated this on real
7530
+ // undici. .name is restored to 'fetch' for ecosystem compatibility; the inner
7531
+ // __neat name is what the frame skip keys on.
7532
+ function __neatWrapFetch() {
7533
+ try {
7534
+ const g${anyT} = globalThis
7535
+ if (typeof g.fetch === 'function' && !g.fetch.__neatWrapped) {
7536
+ const realFetch = g.fetch
7537
+ // The wrapper keeps its __neat-prefixed name so the frame skip in
7538
+ // __neatPickUserFrame never mistakes the wrapper's own frame for the
7539
+ // user's call site (renaming it to 'fetch' would defeat the skip).
7540
+ const __neatFetch${anyT} = function (input${anyT}, init${anyT}) {
7541
+ return __neatRunWithUserFrame(function () { return realFetch(input, init) })
7542
+ }
7543
+ __neatFetch.__neatWrapped = true
7544
+ g.fetch = __neatFetch
7545
+ }
7546
+ } catch (_e) {}
7547
+ }
7548
+
7549
+ // Off-stack facade: @prisma/client. Prisma's query engine backdates its spans
7550
+ // from Rust, off the caller's stack. Wrap the model methods on the client
7551
+ // prototype so the call-site frame (still synchronous at \`prisma.user.find\`)
7552
+ // is pushed into context for the engine dispatch.
7553
+ function __neatWrapPrisma() {
7554
+ __neatHook(['@prisma/client'], function (exports${anyT}) {
7555
+ try {
7556
+ const Client = exports && exports.PrismaClient
7557
+ if (typeof Client === 'function' && !Client.__neatWrapped) {
7558
+ const ops = ['findUnique','findUniqueOrThrow','findFirst','findFirstOrThrow','findMany','create','createMany','update','updateMany','upsert','delete','deleteMany','count','aggregate','groupBy']
7559
+ const __neatPrismaWrapModel = function (model${anyT}) {
7560
+ if (!model || model.__neatWrapped) return model
7561
+ for (let i = 0; i < ops.length; i++) {
7562
+ const op = ops[i]
7563
+ const orig = model[op]
7564
+ if (typeof orig !== 'function') continue
7565
+ model[op] = function __neatPrismaOp(${ts ? "...args: any[]" : "...args"}) {
7566
+ const self = this
7567
+ return __neatRunWithUserFrame(function () { return orig.apply(self, args) })
7568
+ }
7569
+ }
7570
+ model.__neatWrapped = true
7571
+ return model
7572
+ }
7573
+ const proto = Client.prototype
7574
+ const handler = function (model${anyT}) { return __neatPrismaWrapModel(model) }
7575
+ // Model accessors are lazily created getters on the instance; wrap the
7576
+ // \`$extends\`-free common path by trapping property access via a proxy
7577
+ // on each constructed client.
7578
+ exports.PrismaClient = new Proxy(Client, {
7579
+ construct(Target${anyT}, argList${anyT}, NewTarget${anyT}) {
7580
+ const instance = Reflect.construct(Target, argList, NewTarget)
7581
+ return new Proxy(instance, {
7582
+ get(target${anyT}, prop${anyT}, receiver${anyT}) {
7583
+ const value = Reflect.get(target, prop, receiver)
7584
+ if (value && typeof value === 'object' && typeof prop === 'string' && prop[0] !== '$' && prop[0] !== '_') {
7585
+ return handler(value)
7586
+ }
7587
+ return value
7588
+ },
7589
+ })
7590
+ },
7591
+ })
7592
+ exports.PrismaClient.__neatWrapped = true
7593
+ void proto
7594
+ }
7595
+ } catch (_e) {}
7596
+ return exports
7597
+ })
7598
+ }
7599
+
7600
+ // Handler-entry facades. express / connect share the Layer model (each route
7601
+ // handler is a function registered on a Router); the wrap captures the
7602
+ // registration frame and stamps + propagates it when the handler runs. The
7603
+ // registry is the extensibility seam \u2014 koa, fastify (via @fastify/otel),
7604
+ // nestjs, restify, and hapi add an entry as their patch surface is wired.
7605
+ function __neatWrapConnectStyle(mod${anyT}) {
7606
+ try {
7607
+ // express() returns an app whose route verbs live on Router.prototype and
7608
+ // the application proto; connect apps expose \`use\`. Wrap the registration
7609
+ // verbs so the user handler is wound with its registration frame.
7610
+ const verbs = ['use','get','post','put','delete','patch','all','options','head']
7611
+ const wrapTarget = function (target${anyT}) {
7612
+ if (!target || target.__neatVerbsWrapped) return
7613
+ for (let i = 0; i < verbs.length; i++) {
7614
+ const verb = verbs[i]
7615
+ const orig = target[verb]
7616
+ if (typeof orig !== 'function') continue
7617
+ target[verb] = function __neatVerb(${ts ? "...args: any[]" : "...args"}) {
7618
+ const frame = __neatPickUserFrame(new Error().stack)
7619
+ if (frame) {
7620
+ for (let a = 0; a < args.length; a++) {
7621
+ const h = args[a]
7622
+ if (typeof h === 'function' && !h.__neatHandlerWrapped && h.length <= 4) {
7623
+ const inner = h
7624
+ const __neatHandler${anyT} = function (${ts ? "...hargs: any[]" : "...hargs"}) {
7625
+ const self = this
7626
+ return __neatStampHandler(frame, function () { return inner.apply(self, hargs) })
7627
+ }
7628
+ __neatHandler.__neatHandlerWrapped = true
7629
+ args[a] = __neatHandler
7630
+ }
7631
+ }
7632
+ }
7633
+ return orig.apply(this, args)
7634
+ }
7635
+ }
7636
+ target.__neatVerbsWrapped = true
7637
+ }
7638
+ if (mod && mod.Router && mod.Router.prototype) wrapTarget(mod.Router.prototype)
7639
+ if (mod && mod.application) wrapTarget(mod.application)
7640
+ if (mod && mod.prototype) wrapTarget(mod.prototype)
7641
+ } catch (_e) {}
7642
+ }
7643
+
7644
+ function __neatInstallHandlerEntry() {
7645
+ __neatHook(['express'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
7646
+ __neatHook(['connect'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
7647
+ }
7648
+
7649
+ // Install every off-stack and handler-entry wrap. Called once after sdk.start()
7650
+ // \u2014 before user code requires the framework / Prisma modules, so the hooks land
7651
+ // on first require. Each wrap is independently guarded.
7652
+ function __neatInstallFacades() {
7653
+ __neatWrapFetch()
7654
+ __neatWrapPrisma()
7655
+ __neatInstallHandlerEntry()
6855
7656
  }`;
6856
7657
  }
6857
- var CALLSITE_PROCESSOR_JS = neatCallsiteProcessorSource(false);
6858
- var CALLSITE_PROCESSOR_TS = neatCallsiteProcessorSource(true);
6859
- function neatRegisterCallsiteSource(ts) {
7658
+ var CALLSITE_PROCESSOR_JS = neatCaptureSource(false);
7659
+ var CALLSITE_PROCESSOR_TS = neatCaptureSource(true);
7660
+ function neatWireCaptureSource(ts) {
6860
7661
  const providerT = ts ? ": any" : "";
6861
7662
  return `try {
6862
7663
  const provider${providerT} = trace.getTracerProvider()
6863
7664
  const delegate = provider && typeof provider.getDelegate === 'function' ? provider.getDelegate() : provider
6864
- if (delegate && typeof delegate.addSpanProcessor === 'function') {
6865
- delegate.addSpanProcessor(new NeatCallSiteSpanProcessor())
6866
- }
6867
- } catch {
6868
- // capture is best-effort; span export is unaffected
7665
+ if (!delegate || typeof delegate.addSpanProcessor !== 'function') {
7666
+ throw new Error('[neat] could not resolve a TracerProvider to attach the call-site processor; file-first OBSERVED capture would be silent (file-awareness.md \xA74)')
7667
+ }
7668
+ const __neatProcessor = new NeatCallSiteSpanProcessor()
7669
+ delegate.addSpanProcessor(__neatProcessor)
7670
+ // Post-init assertion: confirm attachment on providers that expose their
7671
+ // processor list. An un-introspectable provider is trusted (we just called
7672
+ // its addSpanProcessor); a list that exists and lacks our processor throws.
7673
+ const registered = delegate._registeredSpanProcessors
7674
+ if (Array.isArray(registered) && registered.indexOf(__neatProcessor) === -1) {
7675
+ throw new Error('[neat] call-site processor did not attach to the active TracerProvider (file-awareness.md \xA74)')
7676
+ }
7677
+ } catch (err) {
7678
+ throw err
7679
+ }
7680
+ try {
7681
+ __neatInstallFacades()
7682
+ } catch (_e) {
7683
+ // Facade install is best-effort: the stack-walk + handler-entry floor still
7684
+ // attribute the sync-wrapper majority even if an off-stack wrap fails.
6869
7685
  }`;
6870
7686
  }
6871
7687
  var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
@@ -6876,7 +7692,7 @@ ${OTEL_OTLP_HEADERS_JS}
6876
7692
 
6877
7693
  const { NodeSDK } = require('@opentelemetry/sdk-node')
6878
7694
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
6879
- const { trace } = require('@opentelemetry/api')
7695
+ const { trace, context } = require('@opentelemetry/api')
6880
7696
 
6881
7697
  ${CALLSITE_PROCESSOR_JS}
6882
7698
 
@@ -6884,7 +7700,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
6884
7700
  __INSTRUMENTATION_BLOCK__
6885
7701
  const sdk = new NodeSDK({ instrumentations })
6886
7702
  sdk.start()
6887
- ${neatRegisterCallsiteSource(false)}
7703
+ ${neatWireCaptureSource(false)}
6888
7704
  `;
6889
7705
  var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
6890
7706
  ${OTEL_INIT_STAMP}
@@ -6894,7 +7710,7 @@ ${OTEL_OTLP_HEADERS_JS}
6894
7710
 
6895
7711
  import { NodeSDK } from '@opentelemetry/sdk-node'
6896
7712
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6897
- import { trace } from '@opentelemetry/api'
7713
+ import { trace, context } from '@opentelemetry/api'
6898
7714
 
6899
7715
  ${CALLSITE_PROCESSOR_JS}
6900
7716
 
@@ -6902,17 +7718,22 @@ const instrumentations = [getNodeAutoInstrumentations()]
6902
7718
  __INSTRUMENTATION_BLOCK__
6903
7719
  const sdk = new NodeSDK({ instrumentations })
6904
7720
  sdk.start()
6905
- ${neatRegisterCallsiteSource(false)}
7721
+ ${neatWireCaptureSource(false)}
6906
7722
  `;
6907
7723
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
6908
7724
  ${OTEL_INIT_STAMP}
7725
+ // @ts-nocheck \u2014 generated runtime shim. The layered capture mechanism uses
7726
+ // dynamic patterns (facade wraps, Proxy traps, a createRequire bridge) that a
7727
+ // strict user tsconfig would reject; suppressing type-checking here keeps the
7728
+ // generated file from breaking the host project's \`tsc\` gate (#427) without
7729
+ // constraining the runtime logic. The file is regenerated, never hand-edited.
6909
7730
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6910
7731
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6911
7732
  ${OTEL_OTLP_HEADERS_JS}
6912
7733
 
6913
7734
  import { NodeSDK } from '@opentelemetry/sdk-node'
6914
7735
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6915
- import { trace } from '@opentelemetry/api'
7736
+ import { trace, context } from '@opentelemetry/api'
6916
7737
 
6917
7738
  ${CALLSITE_PROCESSOR_TS}
6918
7739
 
@@ -6920,7 +7741,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
6920
7741
  __INSTRUMENTATION_BLOCK__
6921
7742
  const sdk = new NodeSDK({ instrumentations })
6922
7743
  sdk.start()
6923
- ${neatRegisterCallsiteSource(true)}
7744
+ ${neatWireCaptureSource(true)}
6924
7745
  `;
6925
7746
  function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
6926
7747
  const block = registrations.length === 0 ? "" : `
@@ -7108,15 +7929,15 @@ var OTEL_ENV = {
7108
7929
  value: "http://localhost:4318/projects/<project>/v1/traces"
7109
7930
  };
7110
7931
  function serviceNodeName(pkg, serviceDir) {
7111
- return pkg.name ?? import_node_path40.default.basename(serviceDir);
7932
+ return pkg.name ?? import_node_path42.default.basename(serviceDir);
7112
7933
  }
7113
7934
  function projectToken(pkg, serviceDir, project) {
7114
7935
  if (project && project.length > 0) return project;
7115
- return pkg.name ?? import_node_path40.default.basename(serviceDir);
7936
+ return pkg.name ?? import_node_path42.default.basename(serviceDir);
7116
7937
  }
7117
7938
  async function readJsonFile(p) {
7118
7939
  try {
7119
- const raw = await import_node_fs25.promises.readFile(p, "utf8");
7940
+ const raw = await import_node_fs27.promises.readFile(p, "utf8");
7120
7941
  return JSON.parse(raw);
7121
7942
  } catch {
7122
7943
  return null;
@@ -7125,26 +7946,33 @@ async function readJsonFile(p) {
7125
7946
  async function detectRuntimeKind(pkgRoot, pkg) {
7126
7947
  const deps = allDeps(pkg);
7127
7948
  if ("react-native" in deps || "expo" in deps) return "react-native";
7128
- const appJson = await readJsonFile(import_node_path40.default.join(pkgRoot, "app.json"));
7949
+ const appJson = await readJsonFile(import_node_path42.default.join(pkgRoot, "app.json"));
7129
7950
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
7130
7951
  return "react-native";
7131
7952
  }
7132
- if (await exists2(import_node_path40.default.join(pkgRoot, "vite.config.js")) || await exists2(import_node_path40.default.join(pkgRoot, "vite.config.ts")) || await exists2(import_node_path40.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
7953
+ if (await exists3(import_node_path42.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path42.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path42.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
7133
7954
  return "browser-bundle";
7134
7955
  }
7956
+ if (await exists3(import_node_path42.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
7957
+ if (await exists3(import_node_path42.default.join(pkgRoot, "bun.lockb"))) return "bun";
7958
+ if (await exists3(import_node_path42.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path42.default.join(pkgRoot, "deno.lock"))) {
7959
+ return "deno";
7960
+ }
7961
+ const engines = pkg.engines ?? {};
7962
+ if ("electron" in engines) return "electron";
7135
7963
  return "node";
7136
7964
  }
7137
- async function readPackageJson(serviceDir) {
7965
+ async function readPackageJson2(serviceDir) {
7138
7966
  try {
7139
- const raw = await import_node_fs25.promises.readFile(import_node_path40.default.join(serviceDir, "package.json"), "utf8");
7967
+ const raw = await import_node_fs27.promises.readFile(import_node_path42.default.join(serviceDir, "package.json"), "utf8");
7140
7968
  return JSON.parse(raw);
7141
7969
  } catch {
7142
7970
  return null;
7143
7971
  }
7144
7972
  }
7145
- async function exists2(p) {
7973
+ async function exists3(p) {
7146
7974
  try {
7147
- await import_node_fs25.promises.stat(p);
7975
+ await import_node_fs27.promises.stat(p);
7148
7976
  return true;
7149
7977
  } catch {
7150
7978
  return false;
@@ -7152,7 +7980,7 @@ async function exists2(p) {
7152
7980
  }
7153
7981
  async function readFileMaybe(p) {
7154
7982
  try {
7155
- return await import_node_fs25.promises.readFile(p, "utf8");
7983
+ return await import_node_fs27.promises.readFile(p, "utf8");
7156
7984
  } catch {
7157
7985
  return null;
7158
7986
  }
@@ -7168,14 +7996,20 @@ async function planOtelInitGeneration(file, contents) {
7168
7996
  return null;
7169
7997
  }
7170
7998
  async function detect(serviceDir) {
7171
- const pkg = await readPackageJson(serviceDir);
7999
+ const pkg = await readPackageJson2(serviceDir);
7172
8000
  return pkg !== null && typeof pkg.name === "string";
7173
8001
  }
8002
+ function needsVersionUpgrade(installed, expected) {
8003
+ return !import_semver2.default.satisfies(
8004
+ import_semver2.default.minVersion(installed)?.version ?? installed,
8005
+ expected
8006
+ ) && !import_semver2.default.intersects(installed, expected);
8007
+ }
7174
8008
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
7175
8009
  async function findNextConfig(serviceDir) {
7176
8010
  for (const name of NEXT_CONFIG_CANDIDATES) {
7177
- const candidate = import_node_path40.default.join(serviceDir, name);
7178
- if (await exists2(candidate)) return candidate;
8011
+ const candidate = import_node_path42.default.join(serviceDir, name);
8012
+ if (await exists3(candidate)) return candidate;
7179
8013
  }
7180
8014
  return null;
7181
8015
  }
@@ -7201,8 +8035,8 @@ function hasRemixDependency(pkg) {
7201
8035
  }
7202
8036
  async function findRemixEntry(serviceDir) {
7203
8037
  for (const rel of REMIX_ENTRY_CANDIDATES) {
7204
- const candidate = import_node_path40.default.join(serviceDir, rel);
7205
- if (await exists2(candidate)) return candidate;
8038
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8039
+ if (await exists3(candidate)) return candidate;
7206
8040
  }
7207
8041
  return null;
7208
8042
  }
@@ -7213,15 +8047,15 @@ function hasSvelteKitDependency(pkg) {
7213
8047
  }
7214
8048
  async function findSvelteKitHooks(serviceDir) {
7215
8049
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
7216
- const candidate = import_node_path40.default.join(serviceDir, rel);
7217
- if (await exists2(candidate)) return candidate;
8050
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8051
+ if (await exists3(candidate)) return candidate;
7218
8052
  }
7219
8053
  return null;
7220
8054
  }
7221
8055
  async function findSvelteKitConfig(serviceDir) {
7222
8056
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
7223
- const candidate = import_node_path40.default.join(serviceDir, rel);
7224
- if (await exists2(candidate)) return candidate;
8057
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8058
+ if (await exists3(candidate)) return candidate;
7225
8059
  }
7226
8060
  return null;
7227
8061
  }
@@ -7231,8 +8065,8 @@ function hasNuxtDependency(pkg) {
7231
8065
  }
7232
8066
  async function findNuxtConfig(serviceDir) {
7233
8067
  for (const name of NUXT_CONFIG_CANDIDATES) {
7234
- const candidate = import_node_path40.default.join(serviceDir, name);
7235
- if (await exists2(candidate)) return candidate;
8068
+ const candidate = import_node_path42.default.join(serviceDir, name);
8069
+ if (await exists3(candidate)) return candidate;
7236
8070
  }
7237
8071
  return null;
7238
8072
  }
@@ -7242,8 +8076,8 @@ function hasAstroDependency(pkg) {
7242
8076
  }
7243
8077
  async function findAstroConfig(serviceDir) {
7244
8078
  for (const name of ASTRO_CONFIG_CANDIDATES) {
7245
- const candidate = import_node_path40.default.join(serviceDir, name);
7246
- if (await exists2(candidate)) return candidate;
8079
+ const candidate = import_node_path42.default.join(serviceDir, name);
8080
+ if (await exists3(candidate)) return candidate;
7247
8081
  }
7248
8082
  return null;
7249
8083
  }
@@ -7256,7 +8090,7 @@ function parseNextMajor(range) {
7256
8090
  return Number.isFinite(n) ? n : null;
7257
8091
  }
7258
8092
  async function isTypeScriptProject(serviceDir) {
7259
- return exists2(import_node_path40.default.join(serviceDir, "tsconfig.json"));
8093
+ return exists3(import_node_path42.default.join(serviceDir, "tsconfig.json"));
7260
8094
  }
7261
8095
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
7262
8096
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -7302,8 +8136,8 @@ function entryFromScript(script) {
7302
8136
  }
7303
8137
  async function resolveEntry(serviceDir, pkg) {
7304
8138
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
7305
- const candidate = import_node_path40.default.resolve(serviceDir, pkg.main);
7306
- if (await exists2(candidate)) return candidate;
8139
+ const candidate = import_node_path42.default.resolve(serviceDir, pkg.main);
8140
+ if (await exists3(candidate)) return candidate;
7307
8141
  }
7308
8142
  if (pkg.bin) {
7309
8143
  let binEntry;
@@ -7316,36 +8150,36 @@ async function resolveEntry(serviceDir, pkg) {
7316
8150
  if (typeof first === "string") binEntry = first;
7317
8151
  }
7318
8152
  if (binEntry) {
7319
- const candidate = import_node_path40.default.resolve(serviceDir, binEntry);
7320
- if (await exists2(candidate)) return candidate;
8153
+ const candidate = import_node_path42.default.resolve(serviceDir, binEntry);
8154
+ if (await exists3(candidate)) return candidate;
7321
8155
  }
7322
8156
  }
7323
8157
  const startEntry = entryFromScript(pkg.scripts?.start);
7324
8158
  if (startEntry) {
7325
- const candidate = import_node_path40.default.resolve(serviceDir, startEntry);
7326
- if (await exists2(candidate)) return candidate;
8159
+ const candidate = import_node_path42.default.resolve(serviceDir, startEntry);
8160
+ if (await exists3(candidate)) return candidate;
7327
8161
  }
7328
8162
  const devEntry = entryFromScript(pkg.scripts?.dev);
7329
8163
  if (devEntry) {
7330
- const candidate = import_node_path40.default.resolve(serviceDir, devEntry);
7331
- if (await exists2(candidate)) return candidate;
8164
+ const candidate = import_node_path42.default.resolve(serviceDir, devEntry);
8165
+ if (await exists3(candidate)) return candidate;
7332
8166
  }
7333
8167
  for (const rel of SRC_INDEX_CANDIDATES) {
7334
- const candidate = import_node_path40.default.join(serviceDir, rel);
7335
- if (await exists2(candidate)) return candidate;
8168
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8169
+ if (await exists3(candidate)) return candidate;
7336
8170
  }
7337
8171
  for (const rel of SRC_NAMED_CANDIDATES) {
7338
- const candidate = import_node_path40.default.join(serviceDir, rel);
7339
- if (await exists2(candidate)) return candidate;
8172
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8173
+ if (await exists3(candidate)) return candidate;
7340
8174
  }
7341
8175
  for (const name of INDEX_CANDIDATES) {
7342
- const candidate = import_node_path40.default.join(serviceDir, name);
7343
- if (await exists2(candidate)) return candidate;
8176
+ const candidate = import_node_path42.default.join(serviceDir, name);
8177
+ if (await exists3(candidate)) return candidate;
7344
8178
  }
7345
8179
  return null;
7346
8180
  }
7347
8181
  function dispatchEntry(entryFile, pkg) {
7348
- const ext = import_node_path40.default.extname(entryFile).toLowerCase();
8182
+ const ext = import_node_path42.default.extname(entryFile).toLowerCase();
7349
8183
  if (ext === ".ts" || ext === ".tsx") return "ts";
7350
8184
  if (ext === ".mjs") return "esm";
7351
8185
  if (ext === ".cjs") return "cjs";
@@ -7362,9 +8196,9 @@ function otelInitContents(flavor) {
7362
8196
  return OTEL_INIT_CJS;
7363
8197
  }
7364
8198
  function injectionLine(flavor, entryFile, otelInitFile) {
7365
- let rel = import_node_path40.default.relative(import_node_path40.default.dirname(entryFile), otelInitFile);
8199
+ let rel = import_node_path42.default.relative(import_node_path42.default.dirname(entryFile), otelInitFile);
7366
8200
  if (!rel.startsWith(".")) rel = `./${rel}`;
7367
- rel = rel.split(import_node_path40.default.sep).join("/");
8201
+ rel = rel.split(import_node_path42.default.sep).join("/");
7368
8202
  if (flavor === "cjs") return `require('${rel}')`;
7369
8203
  if (flavor === "esm") return `import '${rel}'`;
7370
8204
  const tsRel = rel.replace(/\.ts$/, "");
@@ -7377,56 +8211,56 @@ function lineIsOtelInjection(line) {
7377
8211
  }
7378
8212
  async function detectsSrcLayout(serviceDir) {
7379
8213
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
7380
- exists2(import_node_path40.default.join(serviceDir, "src", "app")),
7381
- exists2(import_node_path40.default.join(serviceDir, "src", "pages")),
7382
- exists2(import_node_path40.default.join(serviceDir, "app")),
7383
- exists2(import_node_path40.default.join(serviceDir, "pages"))
8214
+ exists3(import_node_path42.default.join(serviceDir, "src", "app")),
8215
+ exists3(import_node_path42.default.join(serviceDir, "src", "pages")),
8216
+ exists3(import_node_path42.default.join(serviceDir, "app")),
8217
+ exists3(import_node_path42.default.join(serviceDir, "pages"))
7384
8218
  ]);
7385
8219
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
7386
8220
  }
7387
8221
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
7388
8222
  const useTs = await isTypeScriptProject(serviceDir);
7389
8223
  const srcLayout = await detectsSrcLayout(serviceDir);
7390
- const baseDir = srcLayout ? import_node_path40.default.join(serviceDir, "src") : serviceDir;
7391
- const instrumentationFile = import_node_path40.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
7392
- const instrumentationNodeFile = import_node_path40.default.join(
8224
+ const baseDir = srcLayout ? import_node_path42.default.join(serviceDir, "src") : serviceDir;
8225
+ const instrumentationFile = import_node_path42.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
8226
+ const instrumentationNodeFile = import_node_path42.default.join(
7393
8227
  baseDir,
7394
8228
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
7395
8229
  );
7396
- const envNeatFile = import_node_path40.default.join(baseDir, ".env.neat");
8230
+ const envNeatFile = import_node_path42.default.join(baseDir, ".env.neat");
7397
8231
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7398
8232
  const dependencyEdits = [];
7399
8233
  for (const sdk of SDK_PACKAGES) {
7400
- if (sdk.name in existingDeps) continue;
7401
- dependencyEdits.push({
7402
- file: manifestPath,
7403
- kind: "add",
7404
- name: sdk.name,
7405
- version: sdk.version
7406
- });
8234
+ if (sdk.name in existingDeps) {
8235
+ if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
8236
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
8237
+ }
8238
+ continue;
8239
+ }
8240
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
7407
8241
  }
7408
8242
  const nonBundled = detectNonBundledInstrumentations(pkg);
7409
8243
  for (const inst of nonBundled) {
7410
- if (inst.pkg in existingDeps) continue;
7411
- dependencyEdits.push({
7412
- file: manifestPath,
7413
- kind: "add",
7414
- name: inst.pkg,
7415
- version: inst.version
7416
- });
8244
+ if (inst.pkg in existingDeps) {
8245
+ if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
8246
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
8247
+ }
8248
+ continue;
8249
+ }
8250
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
7417
8251
  }
7418
8252
  const svcName = serviceNodeName(pkg, serviceDir);
7419
8253
  const projectName = projectToken(pkg, serviceDir, project);
7420
8254
  const registrations = nonBundled.map((i) => i.registration);
7421
8255
  const generatedFiles = [];
7422
- if (!await exists2(instrumentationFile)) {
8256
+ if (!await exists3(instrumentationFile)) {
7423
8257
  generatedFiles.push({
7424
8258
  file: instrumentationFile,
7425
8259
  contents: useTs ? NEXT_INSTRUMENTATION_TS : NEXT_INSTRUMENTATION_JS,
7426
8260
  skipIfExists: true
7427
8261
  });
7428
8262
  }
7429
- if (!await exists2(instrumentationNodeFile)) {
8263
+ if (!await exists3(instrumentationNodeFile)) {
7430
8264
  generatedFiles.push({
7431
8265
  file: instrumentationNodeFile,
7432
8266
  contents: renderNextInstrumentationNode(
@@ -7438,7 +8272,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7438
8272
  skipIfExists: true
7439
8273
  });
7440
8274
  }
7441
- if (!await exists2(envNeatFile)) {
8275
+ if (!await exists3(envNeatFile)) {
7442
8276
  generatedFiles.push({
7443
8277
  file: envNeatFile,
7444
8278
  contents: renderEnvNeat(svcName, projectName),
@@ -7450,7 +8284,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7450
8284
  const nextMajor = parseNextMajor(nextRange);
7451
8285
  if (nextMajor !== null && nextMajor < 15) {
7452
8286
  try {
7453
- const raw = await import_node_fs25.promises.readFile(nextConfigPath, "utf8");
8287
+ const raw = await import_node_fs27.promises.readFile(nextConfigPath, "utf8");
7454
8288
  if (!raw.includes("instrumentationHook")) {
7455
8289
  nextConfigEdit = {
7456
8290
  file: nextConfigPath,
@@ -7498,8 +8332,8 @@ function buildDependencyEdits(pkg, manifestPath) {
7498
8332
  return edits;
7499
8333
  }
7500
8334
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
7501
- const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
7502
- if (!await exists2(envNeatFile)) {
8335
+ const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
8336
+ if (!await exists3(envNeatFile)) {
7503
8337
  generatedFiles.push({
7504
8338
  file: envNeatFile,
7505
8339
  contents: renderEnvNeat(
@@ -7533,13 +8367,13 @@ function fileImportsOtelHook(raw, specifiers) {
7533
8367
  }
7534
8368
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7535
8369
  const useTs = await isTypeScriptProject(serviceDir);
7536
- const otelServerFile = import_node_path40.default.join(
8370
+ const otelServerFile = import_node_path42.default.join(
7537
8371
  serviceDir,
7538
8372
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
7539
8373
  );
7540
8374
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7541
8375
  const generatedFiles = [];
7542
- if (!await exists2(otelServerFile)) {
8376
+ if (!await exists3(otelServerFile)) {
7543
8377
  generatedFiles.push({
7544
8378
  file: otelServerFile,
7545
8379
  contents: renderFrameworkOtelInitForPkg(
@@ -7554,7 +8388,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7554
8388
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
7555
8389
  const entrypointEdits = [];
7556
8390
  try {
7557
- const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
8391
+ const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
7558
8392
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
7559
8393
  const lines = raw.split(/\r?\n/);
7560
8394
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -7590,15 +8424,15 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7590
8424
  }
7591
8425
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
7592
8426
  const useTs = await isTypeScriptProject(serviceDir);
7593
- const otelInitFile = import_node_path40.default.join(
8427
+ const otelInitFile = import_node_path42.default.join(
7594
8428
  serviceDir,
7595
8429
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
7596
8430
  );
7597
- const resolvedHooksFile = hooksFile ?? import_node_path40.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
8431
+ const resolvedHooksFile = hooksFile ?? import_node_path42.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
7598
8432
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7599
8433
  const generatedFiles = [];
7600
8434
  const entrypointEdits = [];
7601
- if (!await exists2(otelInitFile)) {
8435
+ if (!await exists3(otelInitFile)) {
7602
8436
  generatedFiles.push({
7603
8437
  file: otelInitFile,
7604
8438
  contents: renderFrameworkOtelInitForPkg(
@@ -7619,7 +8453,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
7619
8453
  });
7620
8454
  } else {
7621
8455
  try {
7622
- const raw = await import_node_fs25.promises.readFile(hooksFile, "utf8");
8456
+ const raw = await import_node_fs27.promises.readFile(hooksFile, "utf8");
7623
8457
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
7624
8458
  const lines = raw.split(/\r?\n/);
7625
8459
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -7656,17 +8490,17 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
7656
8490
  }
7657
8491
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
7658
8492
  const useTs = await isTypeScriptProject(serviceDir);
7659
- const otelPluginFile = import_node_path40.default.join(
8493
+ const otelPluginFile = import_node_path42.default.join(
7660
8494
  serviceDir,
7661
8495
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
7662
8496
  );
7663
- const otelInitFile = import_node_path40.default.join(
8497
+ const otelInitFile = import_node_path42.default.join(
7664
8498
  serviceDir,
7665
8499
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
7666
8500
  );
7667
8501
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7668
8502
  const generatedFiles = [];
7669
- if (!await exists2(otelInitFile)) {
8503
+ if (!await exists3(otelInitFile)) {
7670
8504
  generatedFiles.push({
7671
8505
  file: otelInitFile,
7672
8506
  contents: renderFrameworkOtelInitForPkg(
@@ -7678,7 +8512,7 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
7678
8512
  skipIfExists: true
7679
8513
  });
7680
8514
  }
7681
- if (!await exists2(otelPluginFile)) {
8515
+ if (!await exists3(otelPluginFile)) {
7682
8516
  generatedFiles.push({
7683
8517
  file: otelPluginFile,
7684
8518
  contents: useTs ? NUXT_OTEL_PLUGIN_TS : NUXT_OTEL_PLUGIN_JS,
@@ -7711,23 +8545,23 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
7711
8545
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
7712
8546
  async function findAstroMiddleware(serviceDir) {
7713
8547
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
7714
- const candidate = import_node_path40.default.join(serviceDir, rel);
7715
- if (await exists2(candidate)) return candidate;
8548
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8549
+ if (await exists3(candidate)) return candidate;
7716
8550
  }
7717
8551
  return null;
7718
8552
  }
7719
8553
  async function planAstro(serviceDir, pkg, manifestPath, project) {
7720
8554
  const useTs = await isTypeScriptProject(serviceDir);
7721
- const otelInitFile = import_node_path40.default.join(
8555
+ const otelInitFile = import_node_path42.default.join(
7722
8556
  serviceDir,
7723
8557
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
7724
8558
  );
7725
8559
  const existingMiddleware = await findAstroMiddleware(serviceDir);
7726
- const middlewareFile = existingMiddleware ?? import_node_path40.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
8560
+ const middlewareFile = existingMiddleware ?? import_node_path42.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
7727
8561
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7728
8562
  const generatedFiles = [];
7729
8563
  const entrypointEdits = [];
7730
- if (!await exists2(otelInitFile)) {
8564
+ if (!await exists3(otelInitFile)) {
7731
8565
  generatedFiles.push({
7732
8566
  file: otelInitFile,
7733
8567
  contents: renderFrameworkOtelInitForPkg(
@@ -7748,7 +8582,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
7748
8582
  });
7749
8583
  } else {
7750
8584
  try {
7751
- const raw = await import_node_fs25.promises.readFile(existingMiddleware, "utf8");
8585
+ const raw = await import_node_fs27.promises.readFile(existingMiddleware, "utf8");
7752
8586
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
7753
8587
  const lines = raw.split(/\r?\n/);
7754
8588
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -7818,8 +8652,8 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
7818
8652
  return null;
7819
8653
  }
7820
8654
  async function plan(serviceDir, opts) {
7821
- const pkg = await readPackageJson(serviceDir);
7822
- const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
8655
+ const pkg = await readPackageJson2(serviceDir);
8656
+ const manifestPath = import_node_path42.default.join(serviceDir, "package.json");
7823
8657
  const project = opts?.project;
7824
8658
  const empty = {
7825
8659
  language: "javascript",
@@ -7854,32 +8688,32 @@ async function plan(serviceDir, opts) {
7854
8688
  return { ...empty, libOnly: true };
7855
8689
  }
7856
8690
  const flavor = dispatchEntry(entryFile, pkg);
7857
- const otelInitFile = import_node_path40.default.join(import_node_path40.default.dirname(entryFile), otelInitFilename(flavor));
7858
- const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
8691
+ const otelInitFile = import_node_path42.default.join(import_node_path42.default.dirname(entryFile), otelInitFilename(flavor));
8692
+ const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
7859
8693
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7860
8694
  const dependencyEdits = [];
7861
8695
  for (const sdk of SDK_PACKAGES) {
7862
- if (sdk.name in existingDeps) continue;
7863
- dependencyEdits.push({
7864
- file: manifestPath,
7865
- kind: "add",
7866
- name: sdk.name,
7867
- version: sdk.version
7868
- });
8696
+ if (sdk.name in existingDeps) {
8697
+ if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
8698
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
8699
+ }
8700
+ continue;
8701
+ }
8702
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
7869
8703
  }
7870
8704
  const nonBundled = detectNonBundledInstrumentations(pkg);
7871
8705
  for (const inst of nonBundled) {
7872
- if (inst.pkg in existingDeps) continue;
7873
- dependencyEdits.push({
7874
- file: manifestPath,
7875
- kind: "add",
7876
- name: inst.pkg,
7877
- version: inst.version
7878
- });
8706
+ if (inst.pkg in existingDeps) {
8707
+ if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
8708
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
8709
+ }
8710
+ continue;
8711
+ }
8712
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
7879
8713
  }
7880
8714
  const entrypointEdits = [];
7881
8715
  try {
7882
- const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
8716
+ const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
7883
8717
  const lines = raw.split(/\r?\n/);
7884
8718
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
7885
8719
  if (!lineIsOtelInjection(firstReal)) {
@@ -7902,7 +8736,7 @@ async function plan(serviceDir, opts) {
7902
8736
  renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
7903
8737
  );
7904
8738
  if (otelInitGen) generatedFiles.push(otelInitGen);
7905
- if (!await exists2(envNeatFile)) {
8739
+ if (!await exists3(envNeatFile)) {
7906
8740
  generatedFiles.push({
7907
8741
  file: envNeatFile,
7908
8742
  contents: renderEnvNeat(svcName, projectName),
@@ -7924,13 +8758,13 @@ async function plan(serviceDir, opts) {
7924
8758
  };
7925
8759
  }
7926
8760
  function isAllowedWritePath(serviceDir, target) {
7927
- const rel = import_node_path40.default.relative(serviceDir, target);
8761
+ const rel = import_node_path42.default.relative(serviceDir, target);
7928
8762
  if (rel.startsWith("..")) return false;
7929
- const base = import_node_path40.default.basename(target);
8763
+ const base = import_node_path42.default.basename(target);
7930
8764
  if (base === "package.json") return true;
7931
8765
  if (base === ".env.neat") return true;
7932
8766
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
7933
- const relPosix = rel.split(import_node_path40.default.sep).join("/");
8767
+ const relPosix = rel.split(import_node_path42.default.sep).join("/");
7934
8768
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
7935
8769
  if (relPosix === base) return true;
7936
8770
  if (relPosix === `src/${base}`) return true;
@@ -7947,10 +8781,10 @@ function isAllowedWritePath(serviceDir, target) {
7947
8781
  return false;
7948
8782
  }
7949
8783
  async function writeAtomic(file, contents) {
7950
- await import_node_fs25.promises.mkdir(import_node_path40.default.dirname(file), { recursive: true });
8784
+ await import_node_fs27.promises.mkdir(import_node_path42.default.dirname(file), { recursive: true });
7951
8785
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
7952
- await import_node_fs25.promises.writeFile(tmp, contents, "utf8");
7953
- await import_node_fs25.promises.rename(tmp, file);
8786
+ await import_node_fs27.promises.writeFile(tmp, contents, "utf8");
8787
+ await import_node_fs27.promises.rename(tmp, file);
7954
8788
  }
7955
8789
  async function apply(installPlan) {
7956
8790
  const { serviceDir } = installPlan;
@@ -7978,6 +8812,28 @@ async function apply(installPlan) {
7978
8812
  writtenFiles: []
7979
8813
  };
7980
8814
  }
8815
+ if (installPlan.runtimeKind === "bun") {
8816
+ return { serviceDir, outcome: "bun", reason: "Bun runtime; use BYO-OTel", writtenFiles: [] };
8817
+ }
8818
+ if (installPlan.runtimeKind === "deno") {
8819
+ return { serviceDir, outcome: "deno", reason: "Deno runtime; use BYO-OTel", writtenFiles: [] };
8820
+ }
8821
+ if (installPlan.runtimeKind === "cloudflare-workers") {
8822
+ return {
8823
+ serviceDir,
8824
+ outcome: "cloudflare-workers",
8825
+ reason: "Cloudflare Workers runtime; use BYO-OTel",
8826
+ writtenFiles: []
8827
+ };
8828
+ }
8829
+ if (installPlan.runtimeKind === "electron") {
8830
+ return {
8831
+ serviceDir,
8832
+ outcome: "electron",
8833
+ reason: "Electron; use BYO-OTel",
8834
+ writtenFiles: []
8835
+ };
8836
+ }
7981
8837
  if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
7982
8838
  return {
7983
8839
  serviceDir,
@@ -8002,9 +8858,9 @@ async function apply(installPlan) {
8002
8858
  const originals = /* @__PURE__ */ new Map();
8003
8859
  const createdFiles = [];
8004
8860
  for (const target of allTargets) {
8005
- if (await exists2(target)) {
8861
+ if (await exists3(target)) {
8006
8862
  try {
8007
- originals.set(target, await import_node_fs25.promises.readFile(target, "utf8"));
8863
+ originals.set(target, await import_node_fs27.promises.readFile(target, "utf8"));
8008
8864
  } catch {
8009
8865
  }
8010
8866
  }
@@ -8028,6 +8884,8 @@ async function apply(installPlan) {
8028
8884
  if (!(dep.name in (pkg.dependencies ?? {}))) {
8029
8885
  pkg.dependencies[dep.name] = dep.version;
8030
8886
  }
8887
+ } else if (dep.kind === "upgrade") {
8888
+ pkg.dependencies[dep.name] = dep.version;
8031
8889
  } else {
8032
8890
  delete pkg.dependencies[dep.name];
8033
8891
  }
@@ -8037,7 +8895,7 @@ async function apply(installPlan) {
8037
8895
  writtenFiles.push(file);
8038
8896
  }
8039
8897
  for (const gen of installPlan.generatedFiles ?? []) {
8040
- if (gen.skipIfExists && await exists2(gen.file)) {
8898
+ if (gen.skipIfExists && await exists3(gen.file)) {
8041
8899
  continue;
8042
8900
  }
8043
8901
  await writeAtomic(gen.file, gen.contents);
@@ -8085,14 +8943,14 @@ async function rollback(installPlan, originals, createdFiles) {
8085
8943
  const removed = [];
8086
8944
  for (const [file, raw] of originals.entries()) {
8087
8945
  try {
8088
- await import_node_fs25.promises.writeFile(file, raw, "utf8");
8946
+ await import_node_fs27.promises.writeFile(file, raw, "utf8");
8089
8947
  restored.push(file);
8090
8948
  } catch {
8091
8949
  }
8092
8950
  }
8093
8951
  for (const file of createdFiles) {
8094
8952
  try {
8095
- await import_node_fs25.promises.unlink(file);
8953
+ await import_node_fs27.promises.unlink(file);
8096
8954
  removed.push(file);
8097
8955
  } catch {
8098
8956
  }
@@ -8107,8 +8965,8 @@ async function rollback(installPlan, originals, createdFiles) {
8107
8965
  ...removed.map((f) => `removed: ${f}`),
8108
8966
  ""
8109
8967
  ];
8110
- const rollbackPath = import_node_path40.default.join(installPlan.serviceDir, "neat-rollback.patch");
8111
- await import_node_fs25.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
8968
+ const rollbackPath = import_node_path42.default.join(installPlan.serviceDir, "neat-rollback.patch");
8969
+ await import_node_fs27.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
8112
8970
  }
8113
8971
  function injectInstrumentationHook(raw) {
8114
8972
  if (raw.includes("instrumentationHook")) return raw;
@@ -8137,8 +8995,8 @@ var javascriptInstaller = {
8137
8995
 
8138
8996
  // src/installers/python.ts
8139
8997
  init_cjs_shims();
8140
- var import_node_fs26 = require("fs");
8141
- var import_node_path41 = __toESM(require("path"), 1);
8998
+ var import_node_fs28 = require("fs");
8999
+ var import_node_path43 = __toESM(require("path"), 1);
8142
9000
  var SDK_PACKAGES2 = [
8143
9001
  { name: "opentelemetry-distro", version: ">=0.49b0" },
8144
9002
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -8148,9 +9006,9 @@ var OTEL_ENV2 = {
8148
9006
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
8149
9007
  value: "http://localhost:4318"
8150
9008
  };
8151
- async function exists3(p) {
9009
+ async function exists4(p) {
8152
9010
  try {
8153
- await import_node_fs26.promises.stat(p);
9011
+ await import_node_fs28.promises.stat(p);
8154
9012
  return true;
8155
9013
  } catch {
8156
9014
  return false;
@@ -8159,7 +9017,7 @@ async function exists3(p) {
8159
9017
  async function detect2(serviceDir) {
8160
9018
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
8161
9019
  for (const m of markers) {
8162
- if (await exists3(import_node_path41.default.join(serviceDir, m))) return true;
9020
+ if (await exists4(import_node_path43.default.join(serviceDir, m))) return true;
8163
9021
  }
8164
9022
  return false;
8165
9023
  }
@@ -8169,9 +9027,9 @@ function reqPackageName(line) {
8169
9027
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
8170
9028
  }
8171
9029
  async function planRequirementsTxtEdits(serviceDir) {
8172
- const file = import_node_path41.default.join(serviceDir, "requirements.txt");
8173
- if (!await exists3(file)) return null;
8174
- const raw = await import_node_fs26.promises.readFile(file, "utf8");
9030
+ const file = import_node_path43.default.join(serviceDir, "requirements.txt");
9031
+ if (!await exists4(file)) return null;
9032
+ const raw = await import_node_fs28.promises.readFile(file, "utf8");
8175
9033
  const presentNames = new Set(
8176
9034
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
8177
9035
  );
@@ -8179,9 +9037,9 @@ async function planRequirementsTxtEdits(serviceDir) {
8179
9037
  return { manifest: file, missing: [...missing] };
8180
9038
  }
8181
9039
  async function planProcfileEdits(serviceDir) {
8182
- const procfile = import_node_path41.default.join(serviceDir, "Procfile");
8183
- if (!await exists3(procfile)) return [];
8184
- const raw = await import_node_fs26.promises.readFile(procfile, "utf8");
9040
+ const procfile = import_node_path43.default.join(serviceDir, "Procfile");
9041
+ if (!await exists4(procfile)) return [];
9042
+ const raw = await import_node_fs28.promises.readFile(procfile, "utf8");
8185
9043
  const edits = [];
8186
9044
  for (const line of raw.split(/\r?\n/)) {
8187
9045
  if (line.length === 0) continue;
@@ -8233,8 +9091,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
8233
9091
  const next = `${original}${trailing}${newlines.join("\n")}
8234
9092
  `;
8235
9093
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
8236
- await import_node_fs26.promises.writeFile(tmp, next, "utf8");
8237
- await import_node_fs26.promises.rename(tmp, manifest);
9094
+ await import_node_fs28.promises.writeFile(tmp, next, "utf8");
9095
+ await import_node_fs28.promises.rename(tmp, manifest);
8238
9096
  }
8239
9097
  async function applyProcfile(procfile, edits, original) {
8240
9098
  let next = original;
@@ -8243,8 +9101,8 @@ async function applyProcfile(procfile, edits, original) {
8243
9101
  next = next.replace(e.before, e.after);
8244
9102
  }
8245
9103
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
8246
- await import_node_fs26.promises.writeFile(tmp, next, "utf8");
8247
- await import_node_fs26.promises.rename(tmp, procfile);
9104
+ await import_node_fs28.promises.writeFile(tmp, next, "utf8");
9105
+ await import_node_fs28.promises.rename(tmp, procfile);
8248
9106
  }
8249
9107
  async function apply2(installPlan) {
8250
9108
  const { serviceDir } = installPlan;
@@ -8257,7 +9115,7 @@ async function apply2(installPlan) {
8257
9115
  const originals = /* @__PURE__ */ new Map();
8258
9116
  for (const file of touched) {
8259
9117
  try {
8260
- originals.set(file, await import_node_fs26.promises.readFile(file, "utf8"));
9118
+ originals.set(file, await import_node_fs28.promises.readFile(file, "utf8"));
8261
9119
  } catch {
8262
9120
  }
8263
9121
  }
@@ -8268,7 +9126,7 @@ async function apply2(installPlan) {
8268
9126
  if (raw === void 0) {
8269
9127
  throw new Error(`python installer: cannot read ${file} during apply`);
8270
9128
  }
8271
- const base = import_node_path41.default.basename(file);
9129
+ const base = import_node_path43.default.basename(file);
8272
9130
  if (base === "requirements.txt") {
8273
9131
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
8274
9132
  if (edits.length > 0) {
@@ -8293,7 +9151,7 @@ async function rollback2(installPlan, originals) {
8293
9151
  const restored = [];
8294
9152
  for (const [file, raw] of originals.entries()) {
8295
9153
  try {
8296
- await import_node_fs26.promises.writeFile(file, raw, "utf8");
9154
+ await import_node_fs28.promises.writeFile(file, raw, "utf8");
8297
9155
  restored.push(file);
8298
9156
  } catch {
8299
9157
  }
@@ -8307,8 +9165,8 @@ async function rollback2(installPlan, originals) {
8307
9165
  ...restored.map((f) => `restored: ${f}`),
8308
9166
  ""
8309
9167
  ];
8310
- const rollbackPath = import_node_path41.default.join(installPlan.serviceDir, "neat-rollback.patch");
8311
- await import_node_fs26.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
9168
+ const rollbackPath = import_node_path43.default.join(installPlan.serviceDir, "neat-rollback.patch");
9169
+ await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
8312
9170
  }
8313
9171
  var pythonInstaller = {
8314
9172
  name: "python",
@@ -8431,100 +9289,19 @@ function renderPatch(sections) {
8431
9289
 
8432
9290
  // src/orchestrator.ts
8433
9291
  init_cjs_shims();
8434
- var import_node_fs28 = require("fs");
9292
+ var import_node_fs29 = require("fs");
8435
9293
  var import_node_http = __toESM(require("http"), 1);
8436
9294
  var import_node_net = __toESM(require("net"), 1);
8437
- var import_node_path43 = __toESM(require("path"), 1);
9295
+ var import_node_path44 = __toESM(require("path"), 1);
8438
9296
  var import_node_child_process3 = require("child_process");
8439
9297
  var import_node_readline = __toESM(require("readline"), 1);
8440
-
8441
- // src/installers/package-manager.ts
8442
- init_cjs_shims();
8443
- var import_node_fs27 = require("fs");
8444
- var import_node_path42 = __toESM(require("path"), 1);
8445
- var import_node_child_process2 = require("child_process");
8446
- var LOCKFILE_PRIORITY = [
8447
- { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
8448
- { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
8449
- { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
8450
- {
8451
- lockfile: "package-lock.json",
8452
- pm: "npm",
8453
- args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
8454
- }
8455
- ];
8456
- var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
8457
- async function exists4(p) {
8458
- try {
8459
- await import_node_fs27.promises.access(p);
8460
- return true;
8461
- } catch {
8462
- return false;
8463
- }
8464
- }
8465
- async function detectPackageManager(serviceDir) {
8466
- let dir = import_node_path42.default.resolve(serviceDir);
8467
- const stops = /* @__PURE__ */ new Set();
8468
- for (let i = 0; i < 64; i++) {
8469
- if (stops.has(dir)) break;
8470
- stops.add(dir);
8471
- for (const candidate of LOCKFILE_PRIORITY) {
8472
- const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
8473
- if (await exists4(lockPath)) {
8474
- return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8475
- }
8476
- }
8477
- const parent = import_node_path42.default.dirname(dir);
8478
- if (parent === dir) break;
8479
- dir = parent;
8480
- }
8481
- return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8482
- }
8483
- async function runPackageManagerInstall(cmd) {
8484
- return new Promise((resolve) => {
8485
- const child = (0, import_node_child_process2.spawn)(cmd.pm, cmd.args, {
8486
- cwd: cmd.cwd,
8487
- // Inherit PATH + HOME so the user's installed managers resolve.
8488
- env: process.env,
8489
- // `false` keeps the parent in control of cleanup if the orchestrator
8490
- // exits before install finishes. Cross-platform-safe.
8491
- shell: false,
8492
- stdio: ["ignore", "ignore", "pipe"]
8493
- });
8494
- let stderr = "";
8495
- child.stderr?.on("data", (chunk) => {
8496
- stderr += chunk.toString("utf8");
8497
- });
8498
- child.on("error", (err) => {
8499
- resolve({
8500
- pm: cmd.pm,
8501
- cwd: cmd.cwd,
8502
- args: cmd.args,
8503
- exitCode: 127,
8504
- stderr: stderr + `
8505
- ${err.message}`
8506
- });
8507
- });
8508
- child.on("close", (code) => {
8509
- resolve({
8510
- pm: cmd.pm,
8511
- cwd: cmd.cwd,
8512
- args: cmd.args,
8513
- exitCode: code ?? 1,
8514
- stderr: stderr.trim()
8515
- });
8516
- });
8517
- });
8518
- }
8519
-
8520
- // src/orchestrator.ts
8521
9298
  async function extractAndPersist(opts) {
8522
9299
  const services = await discoverServices(opts.scanPath);
8523
9300
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
8524
9301
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
8525
9302
  resetGraph(graphKey);
8526
9303
  const graph = getGraph(graphKey);
8527
- const projectPaths = pathsForProject(graphKey, import_node_path43.default.join(opts.scanPath, "neat-out"));
9304
+ const projectPaths = pathsForProject(graphKey, import_node_path44.default.join(opts.scanPath, "neat-out"));
8528
9305
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
8529
9306
  errorsPath: projectPaths.errorsPath
8530
9307
  });
@@ -8550,6 +9327,10 @@ async function applyInstallersOver(services, project, options = {}) {
8550
9327
  let libOnly = 0;
8551
9328
  let browserBundle = 0;
8552
9329
  let reactNative = 0;
9330
+ let bun = 0;
9331
+ let deno = 0;
9332
+ let cloudflareWorkers = 0;
9333
+ let electron = 0;
8553
9334
  const installPlans = /* @__PURE__ */ new Map();
8554
9335
  for (const svc of services) {
8555
9336
  const installer = await pickInstaller(svc.dir);
@@ -8574,7 +9355,59 @@ async function applyInstallersOver(services, project, options = {}) {
8574
9355
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
8575
9356
  } else if (outcome.outcome === "react-native") {
8576
9357
  reactNative++;
8577
- console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
9358
+ const svcName = import_node_path44.default.basename(svc.dir);
9359
+ console.log(
9360
+ `neat: ${svc.dir} detected as React Native / Expo
9361
+ The installer doesn't cover this runtime deterministically.
9362
+ Configure your OTel binding to send spans to:
9363
+ http://localhost:4318/projects/${project}/v1/traces
9364
+ Set OTEL_SERVICE_NAME=${svcName}
9365
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9366
+ );
9367
+ } else if (outcome.outcome === "bun") {
9368
+ bun++;
9369
+ const svcName = import_node_path44.default.basename(svc.dir);
9370
+ console.log(
9371
+ `neat: ${svc.dir} detected as Bun
9372
+ The installer doesn't cover this runtime deterministically.
9373
+ Configure your OTel binding to send spans to:
9374
+ http://localhost:4318/projects/${project}/v1/traces
9375
+ Set OTEL_SERVICE_NAME=${svcName}
9376
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9377
+ );
9378
+ } else if (outcome.outcome === "deno") {
9379
+ deno++;
9380
+ const svcName = import_node_path44.default.basename(svc.dir);
9381
+ console.log(
9382
+ `neat: ${svc.dir} detected as Deno
9383
+ The installer doesn't cover this runtime deterministically.
9384
+ Configure your OTel binding to send spans to:
9385
+ http://localhost:4318/projects/${project}/v1/traces
9386
+ Set OTEL_SERVICE_NAME=${svcName}
9387
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9388
+ );
9389
+ } else if (outcome.outcome === "cloudflare-workers") {
9390
+ cloudflareWorkers++;
9391
+ const svcName = import_node_path44.default.basename(svc.dir);
9392
+ console.log(
9393
+ `neat: ${svc.dir} detected as Cloudflare Workers
9394
+ The installer doesn't cover this runtime deterministically.
9395
+ Configure your OTel binding to send spans to:
9396
+ http://localhost:4318/projects/${project}/v1/traces
9397
+ Set OTEL_SERVICE_NAME=${svcName}
9398
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9399
+ );
9400
+ } else if (outcome.outcome === "electron") {
9401
+ electron++;
9402
+ const svcName = import_node_path44.default.basename(svc.dir);
9403
+ console.log(
9404
+ `neat: ${svc.dir} detected as Electron
9405
+ The installer doesn't cover this runtime deterministically.
9406
+ Configure your OTel binding to send spans to:
9407
+ http://localhost:4318/projects/${project}/v1/traces
9408
+ Set OTEL_SERVICE_NAME=${svcName}
9409
+ See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
9410
+ );
8578
9411
  }
8579
9412
  }
8580
9413
  const packageManagerInstalls = [];
@@ -8599,6 +9432,10 @@ async function applyInstallersOver(services, project, options = {}) {
8599
9432
  libOnly,
8600
9433
  browserBundle,
8601
9434
  reactNative,
9435
+ bun,
9436
+ deno,
9437
+ cloudflareWorkers,
9438
+ electron,
8602
9439
  packageManagerInstalls
8603
9440
  };
8604
9441
  }
@@ -8736,10 +9573,10 @@ function formatPortCollisionMessage(port) {
8736
9573
  ];
8737
9574
  }
8738
9575
  function spawnDaemonDetached() {
8739
- const here = import_node_path43.default.dirname(new URL(importMetaUrl).pathname);
9576
+ const here = import_node_path44.default.dirname(new URL(importMetaUrl).pathname);
8740
9577
  const candidates = [
8741
- import_node_path43.default.join(here, "neatd.cjs"),
8742
- import_node_path43.default.join(here, "neatd.js")
9578
+ import_node_path44.default.join(here, "neatd.cjs"),
9579
+ import_node_path44.default.join(here, "neatd.js")
8743
9580
  ];
8744
9581
  let entry2 = null;
8745
9582
  const fsSync = require("fs");
@@ -8797,7 +9634,7 @@ async function runOrchestrator(opts) {
8797
9634
  browser: "skipped"
8798
9635
  }
8799
9636
  };
8800
- const stat = await import_node_fs28.promises.stat(opts.scanPath).catch(() => null);
9637
+ const stat = await import_node_fs29.promises.stat(opts.scanPath).catch(() => null);
8801
9638
  if (!stat || !stat.isDirectory()) {
8802
9639
  console.error(`neat: ${opts.scanPath} is not a directory`);
8803
9640
  result.exitCode = 2;
@@ -8940,7 +9777,7 @@ function printSummary(result, graph, dashboardUrl) {
8940
9777
 
8941
9778
  // src/cli-verbs.ts
8942
9779
  init_cjs_shims();
8943
- var import_node_path44 = __toESM(require("path"), 1);
9780
+ var import_node_path45 = __toESM(require("path"), 1);
8944
9781
 
8945
9782
  // src/cli-client.ts
8946
9783
  init_cjs_shims();
@@ -8969,10 +9806,10 @@ function createHttpClient(baseUrl, bearerToken) {
8969
9806
  const root = baseUrl.replace(/\/$/, "");
8970
9807
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
8971
9808
  return {
8972
- async get(path46) {
9809
+ async get(path47) {
8973
9810
  let res;
8974
9811
  try {
8975
- res = await fetch(`${root}${path46}`, {
9812
+ res = await fetch(`${root}${path47}`, {
8976
9813
  headers: { ...authHeader }
8977
9814
  });
8978
9815
  } catch (err) {
@@ -8984,16 +9821,16 @@ function createHttpClient(baseUrl, bearerToken) {
8984
9821
  const body = await res.text().catch(() => "");
8985
9822
  throw new HttpError(
8986
9823
  res.status,
8987
- `${res.status} ${res.statusText} on GET ${path46}: ${body}`,
9824
+ `${res.status} ${res.statusText} on GET ${path47}: ${body}`,
8988
9825
  body
8989
9826
  );
8990
9827
  }
8991
9828
  return await res.json();
8992
9829
  },
8993
- async post(path46, body) {
9830
+ async post(path47, body) {
8994
9831
  let res;
8995
9832
  try {
8996
- res = await fetch(`${root}${path46}`, {
9833
+ res = await fetch(`${root}${path47}`, {
8997
9834
  method: "POST",
8998
9835
  headers: { "content-type": "application/json", ...authHeader },
8999
9836
  body: JSON.stringify(body)
@@ -9007,7 +9844,7 @@ function createHttpClient(baseUrl, bearerToken) {
9007
9844
  const text = await res.text().catch(() => "");
9008
9845
  throw new HttpError(
9009
9846
  res.status,
9010
- `${res.status} ${res.statusText} on POST ${path46}: ${text}`,
9847
+ `${res.status} ${res.statusText} on POST ${path47}: ${text}`,
9011
9848
  text
9012
9849
  );
9013
9850
  }
@@ -9021,12 +9858,12 @@ function projectPath(project, suffix) {
9021
9858
  }
9022
9859
  async function runRootCause(client, input) {
9023
9860
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
9024
- const path46 = projectPath(
9861
+ const path47 = projectPath(
9025
9862
  input.project,
9026
9863
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
9027
9864
  );
9028
9865
  try {
9029
- const result = await client.get(path46);
9866
+ const result = await client.get(path47);
9030
9867
  const arrowPath = result.traversalPath.join(" \u2190 ");
9031
9868
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
9032
9869
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -9052,12 +9889,12 @@ async function runRootCause(client, input) {
9052
9889
  }
9053
9890
  async function runBlastRadius(client, input) {
9054
9891
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
9055
- const path46 = projectPath(
9892
+ const path47 = projectPath(
9056
9893
  input.project,
9057
9894
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
9058
9895
  );
9059
9896
  try {
9060
- const result = await client.get(path46);
9897
+ const result = await client.get(path47);
9061
9898
  if (result.totalAffected === 0) {
9062
9899
  return {
9063
9900
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -9091,12 +9928,12 @@ function formatBlastEntry(n) {
9091
9928
  }
9092
9929
  async function runDependencies(client, input) {
9093
9930
  const depth = input.depth ?? 3;
9094
- const path46 = projectPath(
9931
+ const path47 = projectPath(
9095
9932
  input.project,
9096
9933
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
9097
9934
  );
9098
9935
  try {
9099
- const result = await client.get(path46);
9936
+ const result = await client.get(path47);
9100
9937
  if (result.total === 0) {
9101
9938
  return {
9102
9939
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -9177,9 +10014,9 @@ function formatDuration(ms) {
9177
10014
  return `${Math.round(h / 24)}d`;
9178
10015
  }
9179
10016
  async function runIncidents(client, input) {
9180
- const path46 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
10017
+ const path47 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
9181
10018
  try {
9182
- const body = await client.get(path46);
10019
+ const body = await client.get(path47);
9183
10020
  const events = body.events;
9184
10021
  if (events.length === 0) {
9185
10022
  return {
@@ -9469,7 +10306,7 @@ async function resolveProjectEntry(opts) {
9469
10306
  const cwd = opts.cwd ?? process.cwd();
9470
10307
  const resolvedCwd = await normalizeProjectPath(cwd);
9471
10308
  for (const entry2 of entries) {
9472
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path44.default.sep}`)) {
10309
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path45.default.sep}`)) {
9473
10310
  return entry2;
9474
10311
  }
9475
10312
  }
@@ -9829,14 +10666,14 @@ function assignFlag(out, field, value) {
9829
10666
  out[field] = value;
9830
10667
  }
9831
10668
  function readPackageVersion() {
9832
- const here = typeof __dirname !== "undefined" ? __dirname : import_node_path45.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
10669
+ const here = typeof __dirname !== "undefined" ? __dirname : import_node_path46.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
9833
10670
  const candidates = [
9834
- import_node_path45.default.resolve(here, "../package.json"),
9835
- import_node_path45.default.resolve(here, "../../package.json")
10671
+ import_node_path46.default.resolve(here, "../package.json"),
10672
+ import_node_path46.default.resolve(here, "../../package.json")
9836
10673
  ];
9837
10674
  for (const candidate of candidates) {
9838
10675
  try {
9839
- const raw = (0, import_node_fs29.readFileSync)(candidate, "utf8");
10676
+ const raw = (0, import_node_fs30.readFileSync)(candidate, "utf8");
9840
10677
  const parsed = JSON.parse(raw);
9841
10678
  if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
9842
10679
  return parsed.version;
@@ -9900,7 +10737,7 @@ async function buildPatchSections(services, project) {
9900
10737
  }
9901
10738
  async function runInit(opts) {
9902
10739
  const written = [];
9903
- const stat = await import_node_fs29.promises.stat(opts.scanPath).catch(() => null);
10740
+ const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
9904
10741
  if (!stat || !stat.isDirectory()) {
9905
10742
  console.error(`neat init: ${opts.scanPath} is not a directory`);
9906
10743
  return { exitCode: 2, writtenFiles: written };
@@ -9909,13 +10746,13 @@ async function runInit(opts) {
9909
10746
  printDiscoveryReport(opts, services);
9910
10747
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
9911
10748
  const patch = renderPatch(sections);
9912
- const patchPath = import_node_path45.default.join(opts.scanPath, "neat.patch");
10749
+ const patchPath = import_node_path46.default.join(opts.scanPath, "neat.patch");
9913
10750
  if (opts.dryRun) {
9914
- await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
10751
+ await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
9915
10752
  written.push(patchPath);
9916
10753
  console.log(`dry-run: patch written to ${patchPath}`);
9917
- const gitignorePath = import_node_path45.default.join(opts.scanPath, ".gitignore");
9918
- const gitignoreExists = await import_node_fs29.promises.stat(gitignorePath).then(() => true).catch(() => false);
10754
+ const gitignorePath = import_node_path46.default.join(opts.scanPath, ".gitignore");
10755
+ const gitignoreExists = await import_node_fs30.promises.stat(gitignorePath).then(() => true).catch(() => false);
9919
10756
  const verb = gitignoreExists ? "append" : "create";
9920
10757
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
9921
10758
  console.log("rerun without --dry-run to register and snapshot.");
@@ -9926,9 +10763,9 @@ async function runInit(opts) {
9926
10763
  const graph = getGraph(graphKey);
9927
10764
  const projectPaths = pathsForProject(
9928
10765
  graphKey,
9929
- import_node_path45.default.join(opts.scanPath, "neat-out")
10766
+ import_node_path46.default.join(opts.scanPath, "neat-out")
9930
10767
  );
9931
- const errorsPath = import_node_path45.default.join(import_node_path45.default.dirname(opts.outPath), import_node_path45.default.basename(projectPaths.errorsPath));
10768
+ const errorsPath = import_node_path46.default.join(import_node_path46.default.dirname(opts.outPath), import_node_path46.default.basename(projectPaths.errorsPath));
9932
10769
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
9933
10770
  await saveGraphToDisk(graph, opts.outPath);
9934
10771
  written.push(opts.outPath);
@@ -10007,7 +10844,7 @@ async function runInit(opts) {
10007
10844
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
10008
10845
  }
10009
10846
  } else {
10010
- await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
10847
+ await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
10011
10848
  written.push(patchPath);
10012
10849
  }
10013
10850
  }
@@ -10047,9 +10884,9 @@ var CLAUDE_SKILL_CONFIG = {
10047
10884
  };
10048
10885
  function claudeConfigPath() {
10049
10886
  const override = process.env.NEAT_CLAUDE_CONFIG;
10050
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
10887
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
10051
10888
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10052
- return import_node_path45.default.join(home, ".claude.json");
10889
+ return import_node_path46.default.join(home, ".claude.json");
10053
10890
  }
10054
10891
  async function runSkill(opts) {
10055
10892
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -10061,7 +10898,7 @@ async function runSkill(opts) {
10061
10898
  const target = claudeConfigPath();
10062
10899
  let existing = {};
10063
10900
  try {
10064
- existing = JSON.parse(await import_node_fs29.promises.readFile(target, "utf8"));
10901
+ existing = JSON.parse(await import_node_fs30.promises.readFile(target, "utf8"));
10065
10902
  } catch (err) {
10066
10903
  if (err.code !== "ENOENT") {
10067
10904
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -10073,8 +10910,8 @@ async function runSkill(opts) {
10073
10910
  ...existing,
10074
10911
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
10075
10912
  };
10076
- await import_node_fs29.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
10077
- await import_node_fs29.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
10913
+ await import_node_fs30.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
10914
+ await import_node_fs30.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
10078
10915
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
10079
10916
  console.log("restart Claude Code to pick up the new MCP server.");
10080
10917
  return { exitCode: 0 };
@@ -10112,12 +10949,12 @@ async function main() {
10112
10949
  console.error("neat init: --apply and --dry-run are mutually exclusive");
10113
10950
  process.exit(2);
10114
10951
  }
10115
- const scanPath = import_node_path45.default.resolve(target);
10952
+ const scanPath = import_node_path46.default.resolve(target);
10116
10953
  const projectExplicit = parsed.project !== null;
10117
- const projectName = projectExplicit ? project : import_node_path45.default.basename(scanPath);
10954
+ const projectName = projectExplicit ? project : import_node_path46.default.basename(scanPath);
10118
10955
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
10119
- const fallback = pathsForProject(projectKey, import_node_path45.default.join(scanPath, "neat-out")).snapshotPath;
10120
- const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
10956
+ const fallback = pathsForProject(projectKey, import_node_path46.default.join(scanPath, "neat-out")).snapshotPath;
10957
+ const outPath = import_node_path46.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
10121
10958
  const result = await runInit({
10122
10959
  scanPath,
10123
10960
  outPath,
@@ -10138,21 +10975,21 @@ async function main() {
10138
10975
  usage();
10139
10976
  process.exit(2);
10140
10977
  }
10141
- const scanPath = import_node_path45.default.resolve(target);
10142
- const stat = await import_node_fs29.promises.stat(scanPath).catch(() => null);
10978
+ const scanPath = import_node_path46.default.resolve(target);
10979
+ const stat = await import_node_fs30.promises.stat(scanPath).catch(() => null);
10143
10980
  if (!stat || !stat.isDirectory()) {
10144
10981
  console.error(`neat watch: ${scanPath} is not a directory`);
10145
10982
  process.exit(2);
10146
10983
  }
10147
- const projectPaths = pathsForProject(project, import_node_path45.default.join(scanPath, "neat-out"));
10148
- const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
10149
- const errorsPath = import_node_path45.default.resolve(
10150
- process.env.NEAT_ERRORS_PATH ?? import_node_path45.default.join(import_node_path45.default.dirname(outPath), import_node_path45.default.basename(projectPaths.errorsPath))
10984
+ const projectPaths = pathsForProject(project, import_node_path46.default.join(scanPath, "neat-out"));
10985
+ const outPath = import_node_path46.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
10986
+ const errorsPath = import_node_path46.default.resolve(
10987
+ process.env.NEAT_ERRORS_PATH ?? import_node_path46.default.join(import_node_path46.default.dirname(outPath), import_node_path46.default.basename(projectPaths.errorsPath))
10151
10988
  );
10152
- const staleEventsPath = import_node_path45.default.resolve(
10153
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path45.default.join(import_node_path45.default.dirname(outPath), import_node_path45.default.basename(projectPaths.staleEventsPath))
10989
+ const staleEventsPath = import_node_path46.default.resolve(
10990
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path46.default.join(import_node_path46.default.dirname(outPath), import_node_path46.default.basename(projectPaths.staleEventsPath))
10154
10991
  );
10155
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path45.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
10992
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path46.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
10156
10993
  const handle = await startWatch(getGraph(project), {
10157
10994
  scanPath,
10158
10995
  outPath,
@@ -10298,11 +11135,11 @@ async function main() {
10298
11135
  process.exit(1);
10299
11136
  }
10300
11137
  async function tryOrchestrator(cmd, parsed) {
10301
- const scanPath = import_node_path45.default.resolve(cmd);
10302
- const stat = await import_node_fs29.promises.stat(scanPath).catch(() => null);
11138
+ const scanPath = import_node_path46.default.resolve(cmd);
11139
+ const stat = await import_node_fs30.promises.stat(scanPath).catch(() => null);
10303
11140
  if (!stat || !stat.isDirectory()) return null;
10304
11141
  const projectExplicit = parsed.project !== null;
10305
- const projectName = projectExplicit ? parsed.project : import_node_path45.default.basename(scanPath);
11142
+ const projectName = projectExplicit ? parsed.project : import_node_path46.default.basename(scanPath);
10306
11143
  const result = await runOrchestrator({
10307
11144
  scanPath,
10308
11145
  project: projectName,