@neat.is/core 0.4.11 → 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
@@ -1131,19 +1131,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1131
1131
  function longestIncomingWalk(graph, start, maxDepth) {
1132
1132
  let best = { path: [start], edges: [] };
1133
1133
  const visited = /* @__PURE__ */ new Set([start]);
1134
- function step(node, path46, edges) {
1135
- if (path46.length > best.path.length) {
1136
- best = { path: [...path46], edges: [...edges] };
1134
+ function step(node, path47, edges) {
1135
+ if (path47.length > best.path.length) {
1136
+ best = { path: [...path47], edges: [...edges] };
1137
1137
  }
1138
- if (path46.length - 1 >= maxDepth) return;
1138
+ if (path47.length - 1 >= maxDepth) return;
1139
1139
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1140
1140
  for (const [srcId, edge] of incoming) {
1141
1141
  if (visited.has(srcId)) continue;
1142
1142
  visited.add(srcId);
1143
- path46.push(srcId);
1143
+ path47.push(srcId);
1144
1144
  edges.push(edge);
1145
- step(srcId, path46, edges);
1146
- path46.pop();
1145
+ step(srcId, path47, edges);
1146
+ path47.pop();
1147
1147
  edges.pop();
1148
1148
  visited.delete(srcId);
1149
1149
  }
@@ -1975,7 +1975,7 @@ function ensureFrontierNode(graph, host, ts) {
1975
1975
  graph.addNode(id, node);
1976
1976
  return id;
1977
1977
  }
1978
- function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
1978
+ function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
1979
1979
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
1980
1980
  const id = makeObservedEdgeId(type, source, target);
1981
1981
  if (graph.hasEdge(id)) {
@@ -2012,7 +2012,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
2012
2012
  confidence: (0, import_types3.confidenceForObservedSignal)(signal),
2013
2013
  lastObserved: ts,
2014
2014
  callCount: 1,
2015
- 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 } : {}
2016
2019
  };
2017
2020
  graph.addEdgeWithKey(id, source, target, edge);
2018
2021
  return { edge, created: true };
@@ -2106,6 +2109,7 @@ async function handleSpan(ctx, span) {
2106
2109
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
2107
2110
  const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
2108
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;
2109
2113
  let affectedNode = sourceId;
2110
2114
  const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
2111
2115
  if (span.dbSystem) {
@@ -2119,7 +2123,8 @@ async function handleSpan(ctx, span) {
2119
2123
  observedSource(),
2120
2124
  targetId,
2121
2125
  ts,
2122
- isError
2126
+ isError,
2127
+ callSiteEvidence
2123
2128
  );
2124
2129
  if (result) affectedNode = targetId;
2125
2130
  }
@@ -2135,7 +2140,8 @@ async function handleSpan(ctx, span) {
2135
2140
  observedSource(),
2136
2141
  targetId,
2137
2142
  ts,
2138
- isError
2143
+ isError,
2144
+ callSiteEvidence
2139
2145
  );
2140
2146
  affectedNode = targetId;
2141
2147
  resolvedViaAddress = true;
@@ -2147,7 +2153,8 @@ async function handleSpan(ctx, span) {
2147
2153
  observedSource(),
2148
2154
  frontierNodeId,
2149
2155
  ts,
2150
- isError
2156
+ isError,
2157
+ callSiteEvidence
2151
2158
  );
2152
2159
  affectedNode = frontierNodeId;
2153
2160
  resolvedViaAddress = true;
@@ -3110,14 +3117,111 @@ async function addServiceAliases(graph, scanPath, services) {
3110
3117
 
3111
3118
  // src/extract/databases/index.ts
3112
3119
  init_cjs_shims();
3113
- 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);
3114
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
+ }
3115
3219
 
3116
3220
  // src/extract/databases/db-config-yaml.ts
3117
3221
  init_cjs_shims();
3118
- var import_node_path10 = __toESM(require("path"), 1);
3222
+ var import_node_path11 = __toESM(require("path"), 1);
3119
3223
  async function parse(serviceDir) {
3120
- const yamlPath = import_node_path10.default.join(serviceDir, "db-config.yaml");
3224
+ const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
3121
3225
  if (!await exists(yamlPath)) return [];
3122
3226
  const raw = await readYaml(yamlPath);
3123
3227
  return [
@@ -3135,13 +3239,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
3135
3239
 
3136
3240
  // src/extract/databases/dotenv.ts
3137
3241
  init_cjs_shims();
3138
- var import_node_fs11 = require("fs");
3139
- var import_node_path12 = __toESM(require("path"), 1);
3242
+ var import_node_fs12 = require("fs");
3243
+ var import_node_path13 = __toESM(require("path"), 1);
3140
3244
 
3141
3245
  // src/extract/databases/shared.ts
3142
3246
  init_cjs_shims();
3143
- var import_node_fs10 = require("fs");
3144
- var import_node_path11 = __toESM(require("path"), 1);
3247
+ var import_node_fs11 = require("fs");
3248
+ var import_node_path12 = __toESM(require("path"), 1);
3145
3249
  function schemeToEngine(scheme) {
3146
3250
  const s = scheme.toLowerCase().split("+")[0];
3147
3251
  switch (s) {
@@ -3180,14 +3284,14 @@ function parseConnectionString(url) {
3180
3284
  }
3181
3285
  async function readIfExists(filePath) {
3182
3286
  try {
3183
- return await import_node_fs10.promises.readFile(filePath, "utf8");
3287
+ return await import_node_fs11.promises.readFile(filePath, "utf8");
3184
3288
  } catch {
3185
3289
  return null;
3186
3290
  }
3187
3291
  }
3188
3292
  async function findFirst(serviceDir, candidates) {
3189
3293
  for (const rel of candidates) {
3190
- const abs = import_node_path11.default.join(serviceDir, rel);
3294
+ const abs = import_node_path12.default.join(serviceDir, rel);
3191
3295
  const content = await readIfExists(abs);
3192
3296
  if (content !== null) return abs;
3193
3297
  }
@@ -3238,15 +3342,15 @@ function parseDotenvLine(line) {
3238
3342
  return { key, value };
3239
3343
  }
3240
3344
  async function parse2(serviceDir) {
3241
- 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(() => []);
3242
3346
  const configs = [];
3243
3347
  const seen = /* @__PURE__ */ new Set();
3244
3348
  for (const entry2 of entries) {
3245
3349
  if (!entry2.isFile()) continue;
3246
3350
  const match = isConfigFile(entry2.name);
3247
3351
  if (!match.match || match.fileType !== "env") continue;
3248
- const filePath = import_node_path12.default.join(serviceDir, entry2.name);
3249
- 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");
3250
3354
  for (const line of content.split("\n")) {
3251
3355
  const parsed = parseDotenvLine(line);
3252
3356
  if (!parsed) continue;
@@ -3265,9 +3369,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
3265
3369
 
3266
3370
  // src/extract/databases/prisma.ts
3267
3371
  init_cjs_shims();
3268
- var import_node_path13 = __toESM(require("path"), 1);
3372
+ var import_node_path14 = __toESM(require("path"), 1);
3269
3373
  async function parse3(serviceDir) {
3270
- const schemaPath = import_node_path13.default.join(serviceDir, "prisma", "schema.prisma");
3374
+ const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
3271
3375
  const content = await readIfExists(schemaPath);
3272
3376
  if (!content) return [];
3273
3377
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -3399,10 +3503,10 @@ var knexParser = { name: "knex", parse: parse5 };
3399
3503
 
3400
3504
  // src/extract/databases/ormconfig.ts
3401
3505
  init_cjs_shims();
3402
- var import_node_path14 = __toESM(require("path"), 1);
3506
+ var import_node_path15 = __toESM(require("path"), 1);
3403
3507
  async function parse6(serviceDir) {
3404
3508
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
3405
- const abs = import_node_path14.default.join(serviceDir, candidate);
3509
+ const abs = import_node_path15.default.join(serviceDir, candidate);
3406
3510
  if (!await exists(abs)) continue;
3407
3511
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
3408
3512
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -3462,9 +3566,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
3462
3566
 
3463
3567
  // src/extract/databases/sequelize.ts
3464
3568
  init_cjs_shims();
3465
- var import_node_path15 = __toESM(require("path"), 1);
3569
+ var import_node_path16 = __toESM(require("path"), 1);
3466
3570
  async function parse8(serviceDir) {
3467
- const configPath = import_node_path15.default.join(serviceDir, "config", "config.json");
3571
+ const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
3468
3572
  if (!await exists(configPath)) return [];
3469
3573
  const raw = await readJson(configPath);
3470
3574
  const out = [];
@@ -3491,7 +3595,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
3491
3595
 
3492
3596
  // src/extract/databases/docker-compose.ts
3493
3597
  init_cjs_shims();
3494
- var import_node_path16 = __toESM(require("path"), 1);
3598
+ var import_node_path17 = __toESM(require("path"), 1);
3495
3599
  function portFromService(svc) {
3496
3600
  for (const raw of svc.ports ?? []) {
3497
3601
  const str = String(raw);
@@ -3518,7 +3622,7 @@ function databaseFromEnv(svc) {
3518
3622
  }
3519
3623
  async function parse9(serviceDir) {
3520
3624
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3521
- const abs = import_node_path16.default.join(serviceDir, name);
3625
+ const abs = import_node_path17.default.join(serviceDir, name);
3522
3626
  if (!await exists(abs)) continue;
3523
3627
  const raw = await readYaml(abs);
3524
3628
  if (!raw?.services) return [];
@@ -3559,8 +3663,8 @@ function compatibleDriversFor(engine) {
3559
3663
  }
3560
3664
  function toDatabaseNode(config) {
3561
3665
  return {
3562
- id: (0, import_types7.databaseId)(config.host),
3563
- type: import_types7.NodeType.DatabaseNode,
3666
+ id: (0, import_types8.databaseId)(config.host),
3667
+ type: import_types8.NodeType.DatabaseNode,
3564
3668
  name: config.database || config.host,
3565
3669
  engine: config.engine,
3566
3670
  engineVersion: config.engineVersion,
@@ -3690,19 +3794,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3690
3794
  discoveredVia: mergedDiscoveredVia
3691
3795
  });
3692
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));
3693
3807
  const edge = {
3694
- id: (0, import_types4.extractedEdgeId)(service.node.id, dbNode.id, import_types7.EdgeType.CONNECTS_TO),
3695
- source: service.node.id,
3808
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
3809
+ source: fileNodeId,
3696
3810
  target: dbNode.id,
3697
- type: import_types7.EdgeType.CONNECTS_TO,
3698
- provenance: import_types7.Provenance.EXTRACTED,
3699
- confidence: (0, import_types7.confidenceForExtracted)("structural"),
3700
- // ADR-032 / #140 — every EXTRACTED edge carries evidence.file.
3701
- // Ghost-edge cleanup keys retirement on this; the conditional
3702
- // sourceFile spread that used to live here was a v0.1.x leftover.
3703
- evidence: {
3704
- file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
3705
- }
3811
+ type: import_types8.EdgeType.CONNECTS_TO,
3812
+ provenance: import_types8.Provenance.EXTRACTED,
3813
+ confidence: (0, import_types8.confidenceForExtracted)("structural"),
3814
+ evidence: { file: evidenceFile }
3706
3815
  };
3707
3816
  if (!graph.hasEdge(edge.id)) {
3708
3817
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3728,15 +3837,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
3728
3837
 
3729
3838
  // src/extract/configs.ts
3730
3839
  init_cjs_shims();
3731
- var import_node_fs12 = require("fs");
3732
- var import_node_path18 = __toESM(require("path"), 1);
3733
- 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");
3734
3843
  async function walkConfigFiles(dir) {
3735
3844
  const out = [];
3736
3845
  async function walk(current) {
3737
- const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true });
3846
+ const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
3738
3847
  for (const entry2 of entries) {
3739
- const full = import_node_path18.default.join(current, entry2.name);
3848
+ const full = import_node_path19.default.join(current, entry2.name);
3740
3849
  if (entry2.isDirectory()) {
3741
3850
  if (IGNORED_DIRS.has(entry2.name)) continue;
3742
3851
  if (await isPythonVenvDir(full)) continue;
@@ -3755,26 +3864,35 @@ async function addConfigNodes(graph, services, scanPath) {
3755
3864
  for (const service of services) {
3756
3865
  const configFiles = await walkConfigFiles(service.dir);
3757
3866
  for (const file of configFiles) {
3758
- const relPath = import_node_path18.default.relative(scanPath, file);
3867
+ const relPath = import_node_path19.default.relative(scanPath, file);
3759
3868
  const node = {
3760
- id: (0, import_types8.configId)(relPath),
3761
- type: import_types8.NodeType.ConfigNode,
3762
- 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),
3763
3872
  path: relPath,
3764
- fileType: isConfigFile(import_node_path18.default.basename(file)).fileType
3873
+ fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
3765
3874
  };
3766
3875
  if (!graph.hasNode(node.id)) {
3767
3876
  graph.addNode(node.id, node);
3768
3877
  nodesAdded++;
3769
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;
3770
3888
  const edge = {
3771
- id: (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types8.EdgeType.CONFIGURED_BY),
3772
- source: service.node.id,
3889
+ id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
3890
+ source: fileNodeId,
3773
3891
  target: node.id,
3774
- type: import_types8.EdgeType.CONFIGURED_BY,
3775
- provenance: import_types8.Provenance.EXTRACTED,
3776
- confidence: (0, import_types8.confidenceForExtracted)("structural"),
3777
- 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("/") }
3778
3896
  };
3779
3897
  if (!graph.hasEdge(edge.id)) {
3780
3898
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3796,105 +3914,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3796
3914
  var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3797
3915
  var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
3798
3916
  var import_types10 = require("@neat.is/types");
3799
-
3800
- // src/extract/calls/shared.ts
3801
- init_cjs_shims();
3802
- var import_node_fs13 = require("fs");
3803
- var import_node_path19 = __toESM(require("path"), 1);
3804
- var import_types9 = require("@neat.is/types");
3805
- async function walkSourceFiles(dir) {
3806
- const out = [];
3807
- async function walk(current) {
3808
- const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3809
- for (const entry2 of entries) {
3810
- const full = import_node_path19.default.join(current, entry2.name);
3811
- if (entry2.isDirectory()) {
3812
- if (IGNORED_DIRS.has(entry2.name)) continue;
3813
- if (await isPythonVenvDir(full)) continue;
3814
- await walk(full);
3815
- } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
3816
- out.push(full);
3817
- }
3818
- }
3819
- }
3820
- await walk(dir);
3821
- return out;
3822
- }
3823
- async function loadSourceFiles(dir) {
3824
- const paths = await walkSourceFiles(dir);
3825
- const out = [];
3826
- for (const p of paths) {
3827
- try {
3828
- const content = await import_node_fs13.promises.readFile(p, "utf8");
3829
- out.push({ path: p, content });
3830
- } catch {
3831
- }
3832
- }
3833
- return out;
3834
- }
3835
- function lineOf(text, needle) {
3836
- const idx = text.indexOf(needle);
3837
- if (idx < 0) return 1;
3838
- return text.slice(0, idx).split("\n").length;
3839
- }
3840
- function snippet(text, line) {
3841
- const lines = text.split("\n");
3842
- return (lines[line - 1] ?? "").trim();
3843
- }
3844
- function toPosix2(p) {
3845
- return p.split("\\").join("/");
3846
- }
3847
- function languageForPath(relPath) {
3848
- switch (import_node_path19.default.extname(relPath).toLowerCase()) {
3849
- case ".py":
3850
- return "python";
3851
- case ".ts":
3852
- case ".tsx":
3853
- return "typescript";
3854
- case ".js":
3855
- case ".jsx":
3856
- case ".mjs":
3857
- case ".cjs":
3858
- return "javascript";
3859
- default:
3860
- return void 0;
3861
- }
3862
- }
3863
- function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
3864
- let nodesAdded = 0;
3865
- let edgesAdded = 0;
3866
- const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
3867
- if (!graph.hasNode(fileNodeId)) {
3868
- const language = languageForPath(relPath);
3869
- const node = {
3870
- id: fileNodeId,
3871
- type: import_types9.NodeType.FileNode,
3872
- service: serviceName,
3873
- path: relPath,
3874
- ...language ? { language } : {},
3875
- discoveredVia: "static"
3876
- };
3877
- graph.addNode(fileNodeId, node);
3878
- nodesAdded++;
3879
- }
3880
- const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
3881
- if (!graph.hasEdge(containsId)) {
3882
- const edge = {
3883
- id: containsId,
3884
- source: serviceNodeId,
3885
- target: fileNodeId,
3886
- type: import_types9.EdgeType.CONTAINS,
3887
- provenance: import_types9.Provenance.EXTRACTED,
3888
- confidence: (0, import_types9.confidenceForExtracted)("structural"),
3889
- evidence: { file: relPath }
3890
- };
3891
- graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
3892
- edgesAdded++;
3893
- }
3894
- return { fileNodeId, nodesAdded, edgesAdded };
3895
- }
3896
-
3897
- // src/extract/calls/http.ts
3898
3917
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
3899
3918
  var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
3900
3919
  function isInsideJsxExternalLink(node) {
@@ -4178,6 +4197,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
4178
4197
  var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
4179
4198
  function isLikelyAddress(value) {
4180
4199
  if (!value) return false;
4200
+ if (/\s/.test(value) || value.startsWith("{")) return false;
4181
4201
  return /:\d{2,5}$/.test(value) || value.includes(".");
4182
4202
  }
4183
4203
  function normaliseForMatch(s) {
@@ -4489,17 +4509,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
4489
4509
  graph.addNode(node.id, node);
4490
4510
  nodesAdded++;
4491
4511
  }
4492
- 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);
4493
4522
  if (!graph.hasEdge(edgeId)) {
4494
4523
  const edge = {
4495
4524
  id: edgeId,
4496
- source: service.node.id,
4525
+ source: fileNodeId,
4497
4526
  target: node.id,
4498
4527
  type: import_types18.EdgeType.RUNS_ON,
4499
4528
  provenance: import_types18.Provenance.EXTRACTED,
4500
4529
  confidence: (0, import_types18.confidenceForExtracted)("structural"),
4501
4530
  evidence: {
4502
- 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))
4503
4532
  }
4504
4533
  };
4505
4534
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -5198,38 +5227,353 @@ function renderValueForwardSummary(input) {
5198
5227
  for (const [t, c] of [...byEdge.entries()].sort()) lines.push(` ${t}: ${c}`);
5199
5228
  lines.push("");
5200
5229
  }
5201
- return lines.join("\n");
5230
+ return lines.join("\n");
5231
+ }
5232
+ function formatIncompat(inc) {
5233
+ if (inc.kind === "node-engine") {
5234
+ const range = inc.declaredNodeEngine ? ` (engines.node="${inc.declaredNodeEngine}")` : "";
5235
+ return `${inc.package}@${inc.packageVersion ?? "?"} requires Node ${inc.requiredNodeVersion}${range} \u2014 ${inc.reason}`;
5236
+ }
5237
+ if (inc.kind === "package-conflict") {
5238
+ const found = inc.foundVersion ? `@${inc.foundVersion}` : " (missing)";
5239
+ return `${inc.package}@${inc.packageVersion ?? "?"} requires ${inc.requires.name}>=${inc.requires.minVersion}; found ${inc.requires.name}${found} \u2014 ${inc.reason}`;
5240
+ }
5241
+ if (inc.kind === "deprecated-api") {
5242
+ return `${inc.package}@${inc.packageVersion ?? "?"} is deprecated \u2014 ${inc.reason}`;
5243
+ }
5244
+ return `${inc.driver}@${inc.driverVersion} vs ${inc.engine} ${inc.engineVersion} \u2014 ${inc.reason}`;
5245
+ }
5246
+
5247
+ // src/watch.ts
5248
+ init_cjs_shims();
5249
+ var import_node_fs25 = __toESM(require("fs"), 1);
5250
+ var import_node_path40 = __toESM(require("path"), 1);
5251
+ var import_chokidar = __toESM(require("chokidar"), 1);
5252
+
5253
+ // src/api.ts
5254
+ init_cjs_shims();
5255
+ var import_fastify = __toESM(require("fastify"), 1);
5256
+ var import_cors = __toESM(require("@fastify/cors"), 1);
5257
+ var import_types24 = require("@neat.is/types");
5258
+
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
5267
+ init_cjs_shims();
5268
+ var import_node_fs20 = require("fs");
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 };
5202
5493
  }
5203
- function formatIncompat(inc) {
5204
- if (inc.kind === "node-engine") {
5205
- const range = inc.declaredNodeEngine ? ` (engines.node="${inc.declaredNodeEngine}")` : "";
5206
- return `${inc.package}@${inc.packageVersion ?? "?"} requires Node ${inc.requiredNodeVersion}${range} \u2014 ${inc.reason}`;
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
+ };
5207
5504
  }
5208
- if (inc.kind === "package-conflict") {
5209
- const found = inc.foundVersion ? `@${inc.foundVersion}` : " (missing)";
5210
- return `${inc.package}@${inc.packageVersion ?? "?"} requires ${inc.requires.name}>=${inc.requires.minVersion}; found ${inc.requires.name}${found} \u2014 ${inc.reason}`;
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
+ }
5211
5516
  }
5212
- if (inc.kind === "deprecated-api") {
5213
- return `${inc.package}@${inc.packageVersion ?? "?"} is deprecated \u2014 ${inc.reason}`;
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
+ }
5214
5567
  }
5215
- return `${inc.driver}@${inc.driverVersion} vs ${inc.engine} ${inc.engineVersion} \u2014 ${inc.reason}`;
5568
+ return {
5569
+ undone: true,
5570
+ message: `rolled back ${match.library} (${match.instrumentation_package})`
5571
+ };
5216
5572
  }
5217
5573
 
5218
- // src/watch.ts
5219
- init_cjs_shims();
5220
- var import_node_fs23 = __toESM(require("fs"), 1);
5221
- var import_node_path38 = __toESM(require("path"), 1);
5222
- var import_chokidar = __toESM(require("chokidar"), 1);
5223
-
5224
- // src/api.ts
5225
- init_cjs_shims();
5226
- var import_fastify = __toESM(require("fastify"), 1);
5227
- var import_cors = __toESM(require("@fastify/cors"), 1);
5228
- var import_types24 = require("@neat.is/types");
5229
-
5230
5574
  // src/diff.ts
5231
5575
  init_cjs_shims();
5232
- var import_node_fs20 = require("fs");
5576
+ var import_node_fs22 = require("fs");
5233
5577
  async function loadSnapshotForDiff(target) {
5234
5578
  if (/^https?:\/\//i.test(target)) {
5235
5579
  const res = await fetch(target);
@@ -5238,7 +5582,7 @@ async function loadSnapshotForDiff(target) {
5238
5582
  }
5239
5583
  return await res.json();
5240
5584
  }
5241
- const raw = await import_node_fs20.promises.readFile(target, "utf8");
5585
+ const raw = await import_node_fs22.promises.readFile(target, "utf8");
5242
5586
  return JSON.parse(raw);
5243
5587
  }
5244
5588
  function indexEntries(entries) {
@@ -5306,23 +5650,23 @@ function canonicalJson(value) {
5306
5650
 
5307
5651
  // src/projects.ts
5308
5652
  init_cjs_shims();
5309
- var import_node_path33 = __toESM(require("path"), 1);
5653
+ var import_node_path35 = __toESM(require("path"), 1);
5310
5654
  function pathsForProject(project, baseDir) {
5311
5655
  if (project === DEFAULT_PROJECT) {
5312
5656
  return {
5313
- snapshotPath: import_node_path33.default.join(baseDir, "graph.json"),
5314
- errorsPath: import_node_path33.default.join(baseDir, "errors.ndjson"),
5315
- staleEventsPath: import_node_path33.default.join(baseDir, "stale-events.ndjson"),
5316
- embeddingsCachePath: import_node_path33.default.join(baseDir, "embeddings.json"),
5317
- 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")
5318
5662
  };
5319
5663
  }
5320
5664
  return {
5321
- snapshotPath: import_node_path33.default.join(baseDir, `${project}.json`),
5322
- errorsPath: import_node_path33.default.join(baseDir, `errors.${project}.ndjson`),
5323
- staleEventsPath: import_node_path33.default.join(baseDir, `stale-events.${project}.ndjson`),
5324
- embeddingsCachePath: import_node_path33.default.join(baseDir, `embeddings.${project}.json`),
5325
- 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`)
5326
5670
  };
5327
5671
  }
5328
5672
  var Projects = class {
@@ -5358,25 +5702,25 @@ var Projects = class {
5358
5702
 
5359
5703
  // src/registry.ts
5360
5704
  init_cjs_shims();
5361
- var import_node_fs21 = require("fs");
5362
- var import_node_os2 = __toESM(require("os"), 1);
5363
- 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);
5364
5708
  var import_types23 = require("@neat.is/types");
5365
5709
  var LOCK_TIMEOUT_MS = 5e3;
5366
5710
  var LOCK_RETRY_MS = 50;
5367
5711
  function neatHome() {
5368
5712
  const override = process.env.NEAT_HOME;
5369
- if (override && override.length > 0) return import_node_path34.default.resolve(override);
5370
- 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");
5371
5715
  }
5372
5716
  function registryPath() {
5373
- return import_node_path34.default.join(neatHome(), "projects.json");
5717
+ return import_node_path36.default.join(neatHome(), "projects.json");
5374
5718
  }
5375
5719
  function registryLockPath() {
5376
- return import_node_path34.default.join(neatHome(), "projects.json.lock");
5720
+ return import_node_path36.default.join(neatHome(), "projects.json.lock");
5377
5721
  }
5378
5722
  function daemonPidPath() {
5379
- return import_node_path34.default.join(neatHome(), "neatd.pid");
5723
+ return import_node_path36.default.join(neatHome(), "neatd.pid");
5380
5724
  }
5381
5725
  function isPidAliveDefault(pid) {
5382
5726
  try {
@@ -5388,7 +5732,7 @@ function isPidAliveDefault(pid) {
5388
5732
  }
5389
5733
  async function readPidFile(file) {
5390
5734
  try {
5391
- const raw = await import_node_fs21.promises.readFile(file, "utf8");
5735
+ const raw = await import_node_fs23.promises.readFile(file, "utf8");
5392
5736
  const pid = Number.parseInt(raw.trim(), 10);
5393
5737
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
5394
5738
  } catch {
@@ -5436,32 +5780,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
5436
5780
  }
5437
5781
  }
5438
5782
  async function normalizeProjectPath(input) {
5439
- const resolved = import_node_path34.default.resolve(input);
5783
+ const resolved = import_node_path36.default.resolve(input);
5440
5784
  try {
5441
- return await import_node_fs21.promises.realpath(resolved);
5785
+ return await import_node_fs23.promises.realpath(resolved);
5442
5786
  } catch {
5443
5787
  return resolved;
5444
5788
  }
5445
5789
  }
5446
5790
  async function writeAtomically(target, contents) {
5447
- 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 });
5448
5792
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
5449
- const fd = await import_node_fs21.promises.open(tmp, "w");
5793
+ const fd = await import_node_fs23.promises.open(tmp, "w");
5450
5794
  try {
5451
5795
  await fd.writeFile(contents, "utf8");
5452
5796
  await fd.sync();
5453
5797
  } finally {
5454
5798
  await fd.close();
5455
5799
  }
5456
- await import_node_fs21.promises.rename(tmp, target);
5800
+ await import_node_fs23.promises.rename(tmp, target);
5457
5801
  }
5458
5802
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
5459
5803
  const deadline = Date.now() + timeoutMs;
5460
- 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 });
5461
5805
  let probedHolder = false;
5462
5806
  while (true) {
5463
5807
  try {
5464
- const fd = await import_node_fs21.promises.open(lockPath, "wx");
5808
+ const fd = await import_node_fs23.promises.open(lockPath, "wx");
5465
5809
  try {
5466
5810
  await fd.writeFile(`${process.pid}
5467
5811
  `, "utf8");
@@ -5486,7 +5830,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
5486
5830
  }
5487
5831
  }
5488
5832
  async function releaseLock(lockPath) {
5489
- await import_node_fs21.promises.unlink(lockPath).catch(() => {
5833
+ await import_node_fs23.promises.unlink(lockPath).catch(() => {
5490
5834
  });
5491
5835
  }
5492
5836
  async function withLock(fn) {
@@ -5502,7 +5846,7 @@ async function readRegistry() {
5502
5846
  const file = registryPath();
5503
5847
  let raw;
5504
5848
  try {
5505
- raw = await import_node_fs21.promises.readFile(file, "utf8");
5849
+ raw = await import_node_fs23.promises.readFile(file, "utf8");
5506
5850
  } catch (err) {
5507
5851
  if (err.code === "ENOENT") {
5508
5852
  return { version: 1, projects: [] };
@@ -6043,6 +6387,105 @@ function registerRoutes(scope, ctx) {
6043
6387
  violations
6044
6388
  };
6045
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
+ });
6046
6489
  }
6047
6490
  async function buildApi(opts) {
6048
6491
  const app = (0, import_fastify.default)({ logger: false });
@@ -6154,8 +6597,8 @@ init_otel_grpc();
6154
6597
 
6155
6598
  // src/search.ts
6156
6599
  init_cjs_shims();
6157
- var import_node_fs22 = require("fs");
6158
- var import_node_path37 = __toESM(require("path"), 1);
6600
+ var import_node_fs24 = require("fs");
6601
+ var import_node_path39 = __toESM(require("path"), 1);
6159
6602
  var import_node_crypto3 = require("crypto");
6160
6603
  var DEFAULT_LIMIT = 10;
6161
6604
  var NOMIC_DIM = 768;
@@ -6285,7 +6728,7 @@ async function pickEmbedder() {
6285
6728
  }
6286
6729
  async function readCache(cachePath) {
6287
6730
  try {
6288
- const raw = await import_node_fs22.promises.readFile(cachePath, "utf8");
6731
+ const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
6289
6732
  const parsed = JSON.parse(raw);
6290
6733
  if (parsed.version !== 1) return null;
6291
6734
  return parsed;
@@ -6294,8 +6737,8 @@ async function readCache(cachePath) {
6294
6737
  }
6295
6738
  }
6296
6739
  async function writeCache(cachePath, cache) {
6297
- await import_node_fs22.promises.mkdir(import_node_path37.default.dirname(cachePath), { recursive: true });
6298
- 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));
6299
6742
  }
6300
6743
  var VectorIndex = class {
6301
6744
  constructor(embedder, cachePath) {
@@ -6450,8 +6893,8 @@ var ALL_PHASES = [
6450
6893
  ];
6451
6894
  function classifyChange(relPath) {
6452
6895
  const phases = /* @__PURE__ */ new Set();
6453
- const base = import_node_path38.default.basename(relPath).toLowerCase();
6454
- 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());
6455
6898
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
6456
6899
  phases.add("services");
6457
6900
  phases.add("aliases");
@@ -6559,16 +7002,16 @@ function countWatchableDirs(scanPath, limit) {
6559
7002
  if (count >= limit) return;
6560
7003
  let entries;
6561
7004
  try {
6562
- entries = import_node_fs23.default.readdirSync(dir, { withFileTypes: true });
7005
+ entries = import_node_fs25.default.readdirSync(dir, { withFileTypes: true });
6563
7006
  } catch {
6564
7007
  return;
6565
7008
  }
6566
7009
  for (const e of entries) {
6567
7010
  if (count >= limit) return;
6568
7011
  if (!e.isDirectory()) continue;
6569
- 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;
6570
7013
  count++;
6571
- 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);
6572
7015
  }
6573
7016
  };
6574
7017
  visit(scanPath, 0);
@@ -6586,8 +7029,8 @@ async function startWatch(graph, opts) {
6586
7029
  const projectName = opts.project ?? DEFAULT_PROJECT;
6587
7030
  await loadGraphFromDisk(graph, opts.outPath);
6588
7031
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
6589
- const policyFilePath = import_node_path38.default.join(opts.scanPath, "policy.json");
6590
- 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");
6591
7034
  let policies = [];
6592
7035
  try {
6593
7036
  policies = await loadPolicyFile(policyFilePath);
@@ -6638,7 +7081,7 @@ async function startWatch(graph, opts) {
6638
7081
  assertBindAuthority(host, auth.authToken);
6639
7082
  const port = opts.port ?? 8080;
6640
7083
  const otelPort = opts.otelPort ?? 4318;
6641
- 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");
6642
7085
  let searchIndex;
6643
7086
  try {
6644
7087
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -6656,7 +7099,7 @@ async function startWatch(graph, opts) {
6656
7099
  // Paths are derived from the explicit options the watch caller passes
6657
7100
  // — pathsForProject is only used to fill in the embeddings/snapshot
6658
7101
  // fields so the registry shape is complete.
6659
- ...pathsForProject(projectName, import_node_path38.default.dirname(opts.outPath)),
7102
+ ...pathsForProject(projectName, import_node_path40.default.dirname(opts.outPath)),
6660
7103
  snapshotPath: opts.outPath,
6661
7104
  errorsPath: opts.errorsPath,
6662
7105
  staleEventsPath: opts.staleEventsPath
@@ -6743,9 +7186,9 @@ async function startWatch(graph, opts) {
6743
7186
  };
6744
7187
  const onPath = (absPath) => {
6745
7188
  if (shouldIgnore(absPath)) return;
6746
- const rel = import_node_path38.default.relative(opts.scanPath, absPath);
7189
+ const rel = import_node_path40.default.relative(opts.scanPath, absPath);
6747
7190
  if (!rel || rel.startsWith("..")) return;
6748
- pendingPaths.add(rel.split(import_node_path38.default.sep).join("/"));
7191
+ pendingPaths.add(rel.split(import_node_path40.default.sep).join("/"));
6749
7192
  const phases = classifyChange(rel);
6750
7193
  if (phases.size === 0) {
6751
7194
  for (const p of ALL_PHASES) pending.add(p);
@@ -6799,16 +7242,16 @@ async function startWatch(graph, opts) {
6799
7242
 
6800
7243
  // src/deploy/detect.ts
6801
7244
  init_cjs_shims();
6802
- var import_node_fs24 = require("fs");
6803
- var import_node_path39 = __toESM(require("path"), 1);
6804
- 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");
6805
7248
  var import_node_crypto4 = require("crypto");
6806
7249
  function generateToken() {
6807
7250
  return (0, import_node_crypto4.randomBytes)(32).toString("base64url");
6808
7251
  }
6809
7252
  async function probeBinary(binary, arg) {
6810
7253
  return new Promise((resolve) => {
6811
- 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" });
6812
7255
  const timer = setTimeout(() => {
6813
7256
  child.kill("SIGKILL");
6814
7257
  resolve(false);
@@ -6900,21 +7343,21 @@ async function runDeploy(opts = {}) {
6900
7343
  const token = generateToken();
6901
7344
  switch (substrate) {
6902
7345
  case "docker-compose": {
6903
- 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");
6904
7347
  const contents = emitDockerCompose(cwd);
6905
- await import_node_fs24.promises.writeFile(artifactPath, contents, "utf8");
7348
+ await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
6906
7349
  return {
6907
7350
  substrate,
6908
7351
  artifactPath,
6909
7352
  token,
6910
7353
  contents,
6911
- 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`
6912
7355
  };
6913
7356
  }
6914
7357
  case "systemd": {
6915
- const artifactPath = import_node_path39.default.join(cwd, "neat.service");
7358
+ const artifactPath = import_node_path41.default.join(cwd, "neat.service");
6916
7359
  const contents = emitSystemdUnit(cwd);
6917
- await import_node_fs24.promises.writeFile(artifactPath, contents, "utf8");
7360
+ await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
6918
7361
  return {
6919
7362
  substrate,
6920
7363
  artifactPath,
@@ -6947,8 +7390,9 @@ init_cjs_shims();
6947
7390
 
6948
7391
  // src/installers/javascript.ts
6949
7392
  init_cjs_shims();
6950
- var import_node_fs25 = require("fs");
6951
- 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);
6952
7396
 
6953
7397
  // src/installers/templates.ts
6954
7398
  init_cjs_shims();
@@ -7485,15 +7929,15 @@ var OTEL_ENV = {
7485
7929
  value: "http://localhost:4318/projects/<project>/v1/traces"
7486
7930
  };
7487
7931
  function serviceNodeName(pkg, serviceDir) {
7488
- return pkg.name ?? import_node_path40.default.basename(serviceDir);
7932
+ return pkg.name ?? import_node_path42.default.basename(serviceDir);
7489
7933
  }
7490
7934
  function projectToken(pkg, serviceDir, project) {
7491
7935
  if (project && project.length > 0) return project;
7492
- return pkg.name ?? import_node_path40.default.basename(serviceDir);
7936
+ return pkg.name ?? import_node_path42.default.basename(serviceDir);
7493
7937
  }
7494
7938
  async function readJsonFile(p) {
7495
7939
  try {
7496
- const raw = await import_node_fs25.promises.readFile(p, "utf8");
7940
+ const raw = await import_node_fs27.promises.readFile(p, "utf8");
7497
7941
  return JSON.parse(raw);
7498
7942
  } catch {
7499
7943
  return null;
@@ -7502,26 +7946,33 @@ async function readJsonFile(p) {
7502
7946
  async function detectRuntimeKind(pkgRoot, pkg) {
7503
7947
  const deps = allDeps(pkg);
7504
7948
  if ("react-native" in deps || "expo" in deps) return "react-native";
7505
- 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"));
7506
7950
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
7507
7951
  return "react-native";
7508
7952
  }
7509
- 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) {
7510
7954
  return "browser-bundle";
7511
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";
7512
7963
  return "node";
7513
7964
  }
7514
- async function readPackageJson(serviceDir) {
7965
+ async function readPackageJson2(serviceDir) {
7515
7966
  try {
7516
- 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");
7517
7968
  return JSON.parse(raw);
7518
7969
  } catch {
7519
7970
  return null;
7520
7971
  }
7521
7972
  }
7522
- async function exists2(p) {
7973
+ async function exists3(p) {
7523
7974
  try {
7524
- await import_node_fs25.promises.stat(p);
7975
+ await import_node_fs27.promises.stat(p);
7525
7976
  return true;
7526
7977
  } catch {
7527
7978
  return false;
@@ -7529,7 +7980,7 @@ async function exists2(p) {
7529
7980
  }
7530
7981
  async function readFileMaybe(p) {
7531
7982
  try {
7532
- return await import_node_fs25.promises.readFile(p, "utf8");
7983
+ return await import_node_fs27.promises.readFile(p, "utf8");
7533
7984
  } catch {
7534
7985
  return null;
7535
7986
  }
@@ -7545,14 +7996,20 @@ async function planOtelInitGeneration(file, contents) {
7545
7996
  return null;
7546
7997
  }
7547
7998
  async function detect(serviceDir) {
7548
- const pkg = await readPackageJson(serviceDir);
7999
+ const pkg = await readPackageJson2(serviceDir);
7549
8000
  return pkg !== null && typeof pkg.name === "string";
7550
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
+ }
7551
8008
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
7552
8009
  async function findNextConfig(serviceDir) {
7553
8010
  for (const name of NEXT_CONFIG_CANDIDATES) {
7554
- const candidate = import_node_path40.default.join(serviceDir, name);
7555
- if (await exists2(candidate)) return candidate;
8011
+ const candidate = import_node_path42.default.join(serviceDir, name);
8012
+ if (await exists3(candidate)) return candidate;
7556
8013
  }
7557
8014
  return null;
7558
8015
  }
@@ -7578,8 +8035,8 @@ function hasRemixDependency(pkg) {
7578
8035
  }
7579
8036
  async function findRemixEntry(serviceDir) {
7580
8037
  for (const rel of REMIX_ENTRY_CANDIDATES) {
7581
- const candidate = import_node_path40.default.join(serviceDir, rel);
7582
- if (await exists2(candidate)) return candidate;
8038
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8039
+ if (await exists3(candidate)) return candidate;
7583
8040
  }
7584
8041
  return null;
7585
8042
  }
@@ -7590,15 +8047,15 @@ function hasSvelteKitDependency(pkg) {
7590
8047
  }
7591
8048
  async function findSvelteKitHooks(serviceDir) {
7592
8049
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
7593
- const candidate = import_node_path40.default.join(serviceDir, rel);
7594
- if (await exists2(candidate)) return candidate;
8050
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8051
+ if (await exists3(candidate)) return candidate;
7595
8052
  }
7596
8053
  return null;
7597
8054
  }
7598
8055
  async function findSvelteKitConfig(serviceDir) {
7599
8056
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
7600
- const candidate = import_node_path40.default.join(serviceDir, rel);
7601
- if (await exists2(candidate)) return candidate;
8057
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8058
+ if (await exists3(candidate)) return candidate;
7602
8059
  }
7603
8060
  return null;
7604
8061
  }
@@ -7608,8 +8065,8 @@ function hasNuxtDependency(pkg) {
7608
8065
  }
7609
8066
  async function findNuxtConfig(serviceDir) {
7610
8067
  for (const name of NUXT_CONFIG_CANDIDATES) {
7611
- const candidate = import_node_path40.default.join(serviceDir, name);
7612
- if (await exists2(candidate)) return candidate;
8068
+ const candidate = import_node_path42.default.join(serviceDir, name);
8069
+ if (await exists3(candidate)) return candidate;
7613
8070
  }
7614
8071
  return null;
7615
8072
  }
@@ -7619,8 +8076,8 @@ function hasAstroDependency(pkg) {
7619
8076
  }
7620
8077
  async function findAstroConfig(serviceDir) {
7621
8078
  for (const name of ASTRO_CONFIG_CANDIDATES) {
7622
- const candidate = import_node_path40.default.join(serviceDir, name);
7623
- if (await exists2(candidate)) return candidate;
8079
+ const candidate = import_node_path42.default.join(serviceDir, name);
8080
+ if (await exists3(candidate)) return candidate;
7624
8081
  }
7625
8082
  return null;
7626
8083
  }
@@ -7633,7 +8090,7 @@ function parseNextMajor(range) {
7633
8090
  return Number.isFinite(n) ? n : null;
7634
8091
  }
7635
8092
  async function isTypeScriptProject(serviceDir) {
7636
- return exists2(import_node_path40.default.join(serviceDir, "tsconfig.json"));
8093
+ return exists3(import_node_path42.default.join(serviceDir, "tsconfig.json"));
7637
8094
  }
7638
8095
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
7639
8096
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -7679,8 +8136,8 @@ function entryFromScript(script) {
7679
8136
  }
7680
8137
  async function resolveEntry(serviceDir, pkg) {
7681
8138
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
7682
- const candidate = import_node_path40.default.resolve(serviceDir, pkg.main);
7683
- if (await exists2(candidate)) return candidate;
8139
+ const candidate = import_node_path42.default.resolve(serviceDir, pkg.main);
8140
+ if (await exists3(candidate)) return candidate;
7684
8141
  }
7685
8142
  if (pkg.bin) {
7686
8143
  let binEntry;
@@ -7693,36 +8150,36 @@ async function resolveEntry(serviceDir, pkg) {
7693
8150
  if (typeof first === "string") binEntry = first;
7694
8151
  }
7695
8152
  if (binEntry) {
7696
- const candidate = import_node_path40.default.resolve(serviceDir, binEntry);
7697
- if (await exists2(candidate)) return candidate;
8153
+ const candidate = import_node_path42.default.resolve(serviceDir, binEntry);
8154
+ if (await exists3(candidate)) return candidate;
7698
8155
  }
7699
8156
  }
7700
8157
  const startEntry = entryFromScript(pkg.scripts?.start);
7701
8158
  if (startEntry) {
7702
- const candidate = import_node_path40.default.resolve(serviceDir, startEntry);
7703
- if (await exists2(candidate)) return candidate;
8159
+ const candidate = import_node_path42.default.resolve(serviceDir, startEntry);
8160
+ if (await exists3(candidate)) return candidate;
7704
8161
  }
7705
8162
  const devEntry = entryFromScript(pkg.scripts?.dev);
7706
8163
  if (devEntry) {
7707
- const candidate = import_node_path40.default.resolve(serviceDir, devEntry);
7708
- if (await exists2(candidate)) return candidate;
8164
+ const candidate = import_node_path42.default.resolve(serviceDir, devEntry);
8165
+ if (await exists3(candidate)) return candidate;
7709
8166
  }
7710
8167
  for (const rel of SRC_INDEX_CANDIDATES) {
7711
- const candidate = import_node_path40.default.join(serviceDir, rel);
7712
- if (await exists2(candidate)) return candidate;
8168
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8169
+ if (await exists3(candidate)) return candidate;
7713
8170
  }
7714
8171
  for (const rel of SRC_NAMED_CANDIDATES) {
7715
- const candidate = import_node_path40.default.join(serviceDir, rel);
7716
- if (await exists2(candidate)) return candidate;
8172
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8173
+ if (await exists3(candidate)) return candidate;
7717
8174
  }
7718
8175
  for (const name of INDEX_CANDIDATES) {
7719
- const candidate = import_node_path40.default.join(serviceDir, name);
7720
- if (await exists2(candidate)) return candidate;
8176
+ const candidate = import_node_path42.default.join(serviceDir, name);
8177
+ if (await exists3(candidate)) return candidate;
7721
8178
  }
7722
8179
  return null;
7723
8180
  }
7724
8181
  function dispatchEntry(entryFile, pkg) {
7725
- const ext = import_node_path40.default.extname(entryFile).toLowerCase();
8182
+ const ext = import_node_path42.default.extname(entryFile).toLowerCase();
7726
8183
  if (ext === ".ts" || ext === ".tsx") return "ts";
7727
8184
  if (ext === ".mjs") return "esm";
7728
8185
  if (ext === ".cjs") return "cjs";
@@ -7739,9 +8196,9 @@ function otelInitContents(flavor) {
7739
8196
  return OTEL_INIT_CJS;
7740
8197
  }
7741
8198
  function injectionLine(flavor, entryFile, otelInitFile) {
7742
- 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);
7743
8200
  if (!rel.startsWith(".")) rel = `./${rel}`;
7744
- rel = rel.split(import_node_path40.default.sep).join("/");
8201
+ rel = rel.split(import_node_path42.default.sep).join("/");
7745
8202
  if (flavor === "cjs") return `require('${rel}')`;
7746
8203
  if (flavor === "esm") return `import '${rel}'`;
7747
8204
  const tsRel = rel.replace(/\.ts$/, "");
@@ -7754,56 +8211,56 @@ function lineIsOtelInjection(line) {
7754
8211
  }
7755
8212
  async function detectsSrcLayout(serviceDir) {
7756
8213
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
7757
- exists2(import_node_path40.default.join(serviceDir, "src", "app")),
7758
- exists2(import_node_path40.default.join(serviceDir, "src", "pages")),
7759
- exists2(import_node_path40.default.join(serviceDir, "app")),
7760
- 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"))
7761
8218
  ]);
7762
8219
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
7763
8220
  }
7764
8221
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
7765
8222
  const useTs = await isTypeScriptProject(serviceDir);
7766
8223
  const srcLayout = await detectsSrcLayout(serviceDir);
7767
- const baseDir = srcLayout ? import_node_path40.default.join(serviceDir, "src") : serviceDir;
7768
- const instrumentationFile = import_node_path40.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
7769
- 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(
7770
8227
  baseDir,
7771
8228
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
7772
8229
  );
7773
- const envNeatFile = import_node_path40.default.join(baseDir, ".env.neat");
8230
+ const envNeatFile = import_node_path42.default.join(baseDir, ".env.neat");
7774
8231
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
7775
8232
  const dependencyEdits = [];
7776
8233
  for (const sdk of SDK_PACKAGES) {
7777
- if (sdk.name in existingDeps) continue;
7778
- dependencyEdits.push({
7779
- file: manifestPath,
7780
- kind: "add",
7781
- name: sdk.name,
7782
- version: sdk.version
7783
- });
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 });
7784
8241
  }
7785
8242
  const nonBundled = detectNonBundledInstrumentations(pkg);
7786
8243
  for (const inst of nonBundled) {
7787
- if (inst.pkg in existingDeps) continue;
7788
- dependencyEdits.push({
7789
- file: manifestPath,
7790
- kind: "add",
7791
- name: inst.pkg,
7792
- version: inst.version
7793
- });
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 });
7794
8251
  }
7795
8252
  const svcName = serviceNodeName(pkg, serviceDir);
7796
8253
  const projectName = projectToken(pkg, serviceDir, project);
7797
8254
  const registrations = nonBundled.map((i) => i.registration);
7798
8255
  const generatedFiles = [];
7799
- if (!await exists2(instrumentationFile)) {
8256
+ if (!await exists3(instrumentationFile)) {
7800
8257
  generatedFiles.push({
7801
8258
  file: instrumentationFile,
7802
8259
  contents: useTs ? NEXT_INSTRUMENTATION_TS : NEXT_INSTRUMENTATION_JS,
7803
8260
  skipIfExists: true
7804
8261
  });
7805
8262
  }
7806
- if (!await exists2(instrumentationNodeFile)) {
8263
+ if (!await exists3(instrumentationNodeFile)) {
7807
8264
  generatedFiles.push({
7808
8265
  file: instrumentationNodeFile,
7809
8266
  contents: renderNextInstrumentationNode(
@@ -7815,7 +8272,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7815
8272
  skipIfExists: true
7816
8273
  });
7817
8274
  }
7818
- if (!await exists2(envNeatFile)) {
8275
+ if (!await exists3(envNeatFile)) {
7819
8276
  generatedFiles.push({
7820
8277
  file: envNeatFile,
7821
8278
  contents: renderEnvNeat(svcName, projectName),
@@ -7827,7 +8284,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7827
8284
  const nextMajor = parseNextMajor(nextRange);
7828
8285
  if (nextMajor !== null && nextMajor < 15) {
7829
8286
  try {
7830
- const raw = await import_node_fs25.promises.readFile(nextConfigPath, "utf8");
8287
+ const raw = await import_node_fs27.promises.readFile(nextConfigPath, "utf8");
7831
8288
  if (!raw.includes("instrumentationHook")) {
7832
8289
  nextConfigEdit = {
7833
8290
  file: nextConfigPath,
@@ -7875,8 +8332,8 @@ function buildDependencyEdits(pkg, manifestPath) {
7875
8332
  return edits;
7876
8333
  }
7877
8334
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
7878
- const envNeatFile = import_node_path40.default.join(serviceDir, ".env.neat");
7879
- if (!await exists2(envNeatFile)) {
8335
+ const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
8336
+ if (!await exists3(envNeatFile)) {
7880
8337
  generatedFiles.push({
7881
8338
  file: envNeatFile,
7882
8339
  contents: renderEnvNeat(
@@ -7910,13 +8367,13 @@ function fileImportsOtelHook(raw, specifiers) {
7910
8367
  }
7911
8368
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7912
8369
  const useTs = await isTypeScriptProject(serviceDir);
7913
- const otelServerFile = import_node_path40.default.join(
8370
+ const otelServerFile = import_node_path42.default.join(
7914
8371
  serviceDir,
7915
8372
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
7916
8373
  );
7917
8374
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7918
8375
  const generatedFiles = [];
7919
- if (!await exists2(otelServerFile)) {
8376
+ if (!await exists3(otelServerFile)) {
7920
8377
  generatedFiles.push({
7921
8378
  file: otelServerFile,
7922
8379
  contents: renderFrameworkOtelInitForPkg(
@@ -7931,7 +8388,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7931
8388
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
7932
8389
  const entrypointEdits = [];
7933
8390
  try {
7934
- const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
8391
+ const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
7935
8392
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
7936
8393
  const lines = raw.split(/\r?\n/);
7937
8394
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -7967,15 +8424,15 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
7967
8424
  }
7968
8425
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
7969
8426
  const useTs = await isTypeScriptProject(serviceDir);
7970
- const otelInitFile = import_node_path40.default.join(
8427
+ const otelInitFile = import_node_path42.default.join(
7971
8428
  serviceDir,
7972
8429
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
7973
8430
  );
7974
- 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");
7975
8432
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
7976
8433
  const generatedFiles = [];
7977
8434
  const entrypointEdits = [];
7978
- if (!await exists2(otelInitFile)) {
8435
+ if (!await exists3(otelInitFile)) {
7979
8436
  generatedFiles.push({
7980
8437
  file: otelInitFile,
7981
8438
  contents: renderFrameworkOtelInitForPkg(
@@ -7996,7 +8453,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
7996
8453
  });
7997
8454
  } else {
7998
8455
  try {
7999
- const raw = await import_node_fs25.promises.readFile(hooksFile, "utf8");
8456
+ const raw = await import_node_fs27.promises.readFile(hooksFile, "utf8");
8000
8457
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8001
8458
  const lines = raw.split(/\r?\n/);
8002
8459
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8033,17 +8490,17 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
8033
8490
  }
8034
8491
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
8035
8492
  const useTs = await isTypeScriptProject(serviceDir);
8036
- const otelPluginFile = import_node_path40.default.join(
8493
+ const otelPluginFile = import_node_path42.default.join(
8037
8494
  serviceDir,
8038
8495
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
8039
8496
  );
8040
- const otelInitFile = import_node_path40.default.join(
8497
+ const otelInitFile = import_node_path42.default.join(
8041
8498
  serviceDir,
8042
8499
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
8043
8500
  );
8044
8501
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8045
8502
  const generatedFiles = [];
8046
- if (!await exists2(otelInitFile)) {
8503
+ if (!await exists3(otelInitFile)) {
8047
8504
  generatedFiles.push({
8048
8505
  file: otelInitFile,
8049
8506
  contents: renderFrameworkOtelInitForPkg(
@@ -8055,7 +8512,7 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8055
8512
  skipIfExists: true
8056
8513
  });
8057
8514
  }
8058
- if (!await exists2(otelPluginFile)) {
8515
+ if (!await exists3(otelPluginFile)) {
8059
8516
  generatedFiles.push({
8060
8517
  file: otelPluginFile,
8061
8518
  contents: useTs ? NUXT_OTEL_PLUGIN_TS : NUXT_OTEL_PLUGIN_JS,
@@ -8088,23 +8545,23 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
8088
8545
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
8089
8546
  async function findAstroMiddleware(serviceDir) {
8090
8547
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
8091
- const candidate = import_node_path40.default.join(serviceDir, rel);
8092
- if (await exists2(candidate)) return candidate;
8548
+ const candidate = import_node_path42.default.join(serviceDir, rel);
8549
+ if (await exists3(candidate)) return candidate;
8093
8550
  }
8094
8551
  return null;
8095
8552
  }
8096
8553
  async function planAstro(serviceDir, pkg, manifestPath, project) {
8097
8554
  const useTs = await isTypeScriptProject(serviceDir);
8098
- const otelInitFile = import_node_path40.default.join(
8555
+ const otelInitFile = import_node_path42.default.join(
8099
8556
  serviceDir,
8100
8557
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
8101
8558
  );
8102
8559
  const existingMiddleware = await findAstroMiddleware(serviceDir);
8103
- 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");
8104
8561
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
8105
8562
  const generatedFiles = [];
8106
8563
  const entrypointEdits = [];
8107
- if (!await exists2(otelInitFile)) {
8564
+ if (!await exists3(otelInitFile)) {
8108
8565
  generatedFiles.push({
8109
8566
  file: otelInitFile,
8110
8567
  contents: renderFrameworkOtelInitForPkg(
@@ -8125,7 +8582,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
8125
8582
  });
8126
8583
  } else {
8127
8584
  try {
8128
- const raw = await import_node_fs25.promises.readFile(existingMiddleware, "utf8");
8585
+ const raw = await import_node_fs27.promises.readFile(existingMiddleware, "utf8");
8129
8586
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
8130
8587
  const lines = raw.split(/\r?\n/);
8131
8588
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -8195,8 +8652,8 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
8195
8652
  return null;
8196
8653
  }
8197
8654
  async function plan(serviceDir, opts) {
8198
- const pkg = await readPackageJson(serviceDir);
8199
- 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");
8200
8657
  const project = opts?.project;
8201
8658
  const empty = {
8202
8659
  language: "javascript",
@@ -8231,32 +8688,32 @@ async function plan(serviceDir, opts) {
8231
8688
  return { ...empty, libOnly: true };
8232
8689
  }
8233
8690
  const flavor = dispatchEntry(entryFile, pkg);
8234
- const otelInitFile = import_node_path40.default.join(import_node_path40.default.dirname(entryFile), otelInitFilename(flavor));
8235
- 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");
8236
8693
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
8237
8694
  const dependencyEdits = [];
8238
8695
  for (const sdk of SDK_PACKAGES) {
8239
- if (sdk.name in existingDeps) continue;
8240
- dependencyEdits.push({
8241
- file: manifestPath,
8242
- kind: "add",
8243
- name: sdk.name,
8244
- version: sdk.version
8245
- });
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 });
8246
8703
  }
8247
8704
  const nonBundled = detectNonBundledInstrumentations(pkg);
8248
8705
  for (const inst of nonBundled) {
8249
- if (inst.pkg in existingDeps) continue;
8250
- dependencyEdits.push({
8251
- file: manifestPath,
8252
- kind: "add",
8253
- name: inst.pkg,
8254
- version: inst.version
8255
- });
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 });
8256
8713
  }
8257
8714
  const entrypointEdits = [];
8258
8715
  try {
8259
- const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
8716
+ const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
8260
8717
  const lines = raw.split(/\r?\n/);
8261
8718
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
8262
8719
  if (!lineIsOtelInjection(firstReal)) {
@@ -8279,7 +8736,7 @@ async function plan(serviceDir, opts) {
8279
8736
  renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
8280
8737
  );
8281
8738
  if (otelInitGen) generatedFiles.push(otelInitGen);
8282
- if (!await exists2(envNeatFile)) {
8739
+ if (!await exists3(envNeatFile)) {
8283
8740
  generatedFiles.push({
8284
8741
  file: envNeatFile,
8285
8742
  contents: renderEnvNeat(svcName, projectName),
@@ -8301,13 +8758,13 @@ async function plan(serviceDir, opts) {
8301
8758
  };
8302
8759
  }
8303
8760
  function isAllowedWritePath(serviceDir, target) {
8304
- const rel = import_node_path40.default.relative(serviceDir, target);
8761
+ const rel = import_node_path42.default.relative(serviceDir, target);
8305
8762
  if (rel.startsWith("..")) return false;
8306
- const base = import_node_path40.default.basename(target);
8763
+ const base = import_node_path42.default.basename(target);
8307
8764
  if (base === "package.json") return true;
8308
8765
  if (base === ".env.neat") return true;
8309
8766
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
8310
- const relPosix = rel.split(import_node_path40.default.sep).join("/");
8767
+ const relPosix = rel.split(import_node_path42.default.sep).join("/");
8311
8768
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
8312
8769
  if (relPosix === base) return true;
8313
8770
  if (relPosix === `src/${base}`) return true;
@@ -8324,10 +8781,10 @@ function isAllowedWritePath(serviceDir, target) {
8324
8781
  return false;
8325
8782
  }
8326
8783
  async function writeAtomic(file, contents) {
8327
- 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 });
8328
8785
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
8329
- await import_node_fs25.promises.writeFile(tmp, contents, "utf8");
8330
- 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);
8331
8788
  }
8332
8789
  async function apply(installPlan) {
8333
8790
  const { serviceDir } = installPlan;
@@ -8355,6 +8812,28 @@ async function apply(installPlan) {
8355
8812
  writtenFiles: []
8356
8813
  };
8357
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
+ }
8358
8837
  if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
8359
8838
  return {
8360
8839
  serviceDir,
@@ -8379,9 +8858,9 @@ async function apply(installPlan) {
8379
8858
  const originals = /* @__PURE__ */ new Map();
8380
8859
  const createdFiles = [];
8381
8860
  for (const target of allTargets) {
8382
- if (await exists2(target)) {
8861
+ if (await exists3(target)) {
8383
8862
  try {
8384
- originals.set(target, await import_node_fs25.promises.readFile(target, "utf8"));
8863
+ originals.set(target, await import_node_fs27.promises.readFile(target, "utf8"));
8385
8864
  } catch {
8386
8865
  }
8387
8866
  }
@@ -8405,6 +8884,8 @@ async function apply(installPlan) {
8405
8884
  if (!(dep.name in (pkg.dependencies ?? {}))) {
8406
8885
  pkg.dependencies[dep.name] = dep.version;
8407
8886
  }
8887
+ } else if (dep.kind === "upgrade") {
8888
+ pkg.dependencies[dep.name] = dep.version;
8408
8889
  } else {
8409
8890
  delete pkg.dependencies[dep.name];
8410
8891
  }
@@ -8414,7 +8895,7 @@ async function apply(installPlan) {
8414
8895
  writtenFiles.push(file);
8415
8896
  }
8416
8897
  for (const gen of installPlan.generatedFiles ?? []) {
8417
- if (gen.skipIfExists && await exists2(gen.file)) {
8898
+ if (gen.skipIfExists && await exists3(gen.file)) {
8418
8899
  continue;
8419
8900
  }
8420
8901
  await writeAtomic(gen.file, gen.contents);
@@ -8462,14 +8943,14 @@ async function rollback(installPlan, originals, createdFiles) {
8462
8943
  const removed = [];
8463
8944
  for (const [file, raw] of originals.entries()) {
8464
8945
  try {
8465
- await import_node_fs25.promises.writeFile(file, raw, "utf8");
8946
+ await import_node_fs27.promises.writeFile(file, raw, "utf8");
8466
8947
  restored.push(file);
8467
8948
  } catch {
8468
8949
  }
8469
8950
  }
8470
8951
  for (const file of createdFiles) {
8471
8952
  try {
8472
- await import_node_fs25.promises.unlink(file);
8953
+ await import_node_fs27.promises.unlink(file);
8473
8954
  removed.push(file);
8474
8955
  } catch {
8475
8956
  }
@@ -8484,8 +8965,8 @@ async function rollback(installPlan, originals, createdFiles) {
8484
8965
  ...removed.map((f) => `removed: ${f}`),
8485
8966
  ""
8486
8967
  ];
8487
- const rollbackPath = import_node_path40.default.join(installPlan.serviceDir, "neat-rollback.patch");
8488
- 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");
8489
8970
  }
8490
8971
  function injectInstrumentationHook(raw) {
8491
8972
  if (raw.includes("instrumentationHook")) return raw;
@@ -8514,8 +8995,8 @@ var javascriptInstaller = {
8514
8995
 
8515
8996
  // src/installers/python.ts
8516
8997
  init_cjs_shims();
8517
- var import_node_fs26 = require("fs");
8518
- var import_node_path41 = __toESM(require("path"), 1);
8998
+ var import_node_fs28 = require("fs");
8999
+ var import_node_path43 = __toESM(require("path"), 1);
8519
9000
  var SDK_PACKAGES2 = [
8520
9001
  { name: "opentelemetry-distro", version: ">=0.49b0" },
8521
9002
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -8525,9 +9006,9 @@ var OTEL_ENV2 = {
8525
9006
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
8526
9007
  value: "http://localhost:4318"
8527
9008
  };
8528
- async function exists3(p) {
9009
+ async function exists4(p) {
8529
9010
  try {
8530
- await import_node_fs26.promises.stat(p);
9011
+ await import_node_fs28.promises.stat(p);
8531
9012
  return true;
8532
9013
  } catch {
8533
9014
  return false;
@@ -8536,7 +9017,7 @@ async function exists3(p) {
8536
9017
  async function detect2(serviceDir) {
8537
9018
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
8538
9019
  for (const m of markers) {
8539
- 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;
8540
9021
  }
8541
9022
  return false;
8542
9023
  }
@@ -8546,9 +9027,9 @@ function reqPackageName(line) {
8546
9027
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
8547
9028
  }
8548
9029
  async function planRequirementsTxtEdits(serviceDir) {
8549
- const file = import_node_path41.default.join(serviceDir, "requirements.txt");
8550
- if (!await exists3(file)) return null;
8551
- 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");
8552
9033
  const presentNames = new Set(
8553
9034
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
8554
9035
  );
@@ -8556,9 +9037,9 @@ async function planRequirementsTxtEdits(serviceDir) {
8556
9037
  return { manifest: file, missing: [...missing] };
8557
9038
  }
8558
9039
  async function planProcfileEdits(serviceDir) {
8559
- const procfile = import_node_path41.default.join(serviceDir, "Procfile");
8560
- if (!await exists3(procfile)) return [];
8561
- 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");
8562
9043
  const edits = [];
8563
9044
  for (const line of raw.split(/\r?\n/)) {
8564
9045
  if (line.length === 0) continue;
@@ -8610,8 +9091,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
8610
9091
  const next = `${original}${trailing}${newlines.join("\n")}
8611
9092
  `;
8612
9093
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
8613
- await import_node_fs26.promises.writeFile(tmp, next, "utf8");
8614
- 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);
8615
9096
  }
8616
9097
  async function applyProcfile(procfile, edits, original) {
8617
9098
  let next = original;
@@ -8620,8 +9101,8 @@ async function applyProcfile(procfile, edits, original) {
8620
9101
  next = next.replace(e.before, e.after);
8621
9102
  }
8622
9103
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
8623
- await import_node_fs26.promises.writeFile(tmp, next, "utf8");
8624
- 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);
8625
9106
  }
8626
9107
  async function apply2(installPlan) {
8627
9108
  const { serviceDir } = installPlan;
@@ -8634,7 +9115,7 @@ async function apply2(installPlan) {
8634
9115
  const originals = /* @__PURE__ */ new Map();
8635
9116
  for (const file of touched) {
8636
9117
  try {
8637
- originals.set(file, await import_node_fs26.promises.readFile(file, "utf8"));
9118
+ originals.set(file, await import_node_fs28.promises.readFile(file, "utf8"));
8638
9119
  } catch {
8639
9120
  }
8640
9121
  }
@@ -8645,7 +9126,7 @@ async function apply2(installPlan) {
8645
9126
  if (raw === void 0) {
8646
9127
  throw new Error(`python installer: cannot read ${file} during apply`);
8647
9128
  }
8648
- const base = import_node_path41.default.basename(file);
9129
+ const base = import_node_path43.default.basename(file);
8649
9130
  if (base === "requirements.txt") {
8650
9131
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
8651
9132
  if (edits.length > 0) {
@@ -8670,7 +9151,7 @@ async function rollback2(installPlan, originals) {
8670
9151
  const restored = [];
8671
9152
  for (const [file, raw] of originals.entries()) {
8672
9153
  try {
8673
- await import_node_fs26.promises.writeFile(file, raw, "utf8");
9154
+ await import_node_fs28.promises.writeFile(file, raw, "utf8");
8674
9155
  restored.push(file);
8675
9156
  } catch {
8676
9157
  }
@@ -8684,8 +9165,8 @@ async function rollback2(installPlan, originals) {
8684
9165
  ...restored.map((f) => `restored: ${f}`),
8685
9166
  ""
8686
9167
  ];
8687
- const rollbackPath = import_node_path41.default.join(installPlan.serviceDir, "neat-rollback.patch");
8688
- 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");
8689
9170
  }
8690
9171
  var pythonInstaller = {
8691
9172
  name: "python",
@@ -8808,100 +9289,19 @@ function renderPatch(sections) {
8808
9289
 
8809
9290
  // src/orchestrator.ts
8810
9291
  init_cjs_shims();
8811
- var import_node_fs28 = require("fs");
9292
+ var import_node_fs29 = require("fs");
8812
9293
  var import_node_http = __toESM(require("http"), 1);
8813
9294
  var import_node_net = __toESM(require("net"), 1);
8814
- var import_node_path43 = __toESM(require("path"), 1);
9295
+ var import_node_path44 = __toESM(require("path"), 1);
8815
9296
  var import_node_child_process3 = require("child_process");
8816
9297
  var import_node_readline = __toESM(require("readline"), 1);
8817
-
8818
- // src/installers/package-manager.ts
8819
- init_cjs_shims();
8820
- var import_node_fs27 = require("fs");
8821
- var import_node_path42 = __toESM(require("path"), 1);
8822
- var import_node_child_process2 = require("child_process");
8823
- var LOCKFILE_PRIORITY = [
8824
- { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
8825
- { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
8826
- { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
8827
- {
8828
- lockfile: "package-lock.json",
8829
- pm: "npm",
8830
- args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
8831
- }
8832
- ];
8833
- var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
8834
- async function exists4(p) {
8835
- try {
8836
- await import_node_fs27.promises.access(p);
8837
- return true;
8838
- } catch {
8839
- return false;
8840
- }
8841
- }
8842
- async function detectPackageManager(serviceDir) {
8843
- let dir = import_node_path42.default.resolve(serviceDir);
8844
- const stops = /* @__PURE__ */ new Set();
8845
- for (let i = 0; i < 64; i++) {
8846
- if (stops.has(dir)) break;
8847
- stops.add(dir);
8848
- for (const candidate of LOCKFILE_PRIORITY) {
8849
- const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
8850
- if (await exists4(lockPath)) {
8851
- return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8852
- }
8853
- }
8854
- const parent = import_node_path42.default.dirname(dir);
8855
- if (parent === dir) break;
8856
- dir = parent;
8857
- }
8858
- return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8859
- }
8860
- async function runPackageManagerInstall(cmd) {
8861
- return new Promise((resolve) => {
8862
- const child = (0, import_node_child_process2.spawn)(cmd.pm, cmd.args, {
8863
- cwd: cmd.cwd,
8864
- // Inherit PATH + HOME so the user's installed managers resolve.
8865
- env: process.env,
8866
- // `false` keeps the parent in control of cleanup if the orchestrator
8867
- // exits before install finishes. Cross-platform-safe.
8868
- shell: false,
8869
- stdio: ["ignore", "ignore", "pipe"]
8870
- });
8871
- let stderr = "";
8872
- child.stderr?.on("data", (chunk) => {
8873
- stderr += chunk.toString("utf8");
8874
- });
8875
- child.on("error", (err) => {
8876
- resolve({
8877
- pm: cmd.pm,
8878
- cwd: cmd.cwd,
8879
- args: cmd.args,
8880
- exitCode: 127,
8881
- stderr: stderr + `
8882
- ${err.message}`
8883
- });
8884
- });
8885
- child.on("close", (code) => {
8886
- resolve({
8887
- pm: cmd.pm,
8888
- cwd: cmd.cwd,
8889
- args: cmd.args,
8890
- exitCode: code ?? 1,
8891
- stderr: stderr.trim()
8892
- });
8893
- });
8894
- });
8895
- }
8896
-
8897
- // src/orchestrator.ts
8898
9298
  async function extractAndPersist(opts) {
8899
9299
  const services = await discoverServices(opts.scanPath);
8900
9300
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
8901
9301
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
8902
9302
  resetGraph(graphKey);
8903
9303
  const graph = getGraph(graphKey);
8904
- 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"));
8905
9305
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
8906
9306
  errorsPath: projectPaths.errorsPath
8907
9307
  });
@@ -8927,6 +9327,10 @@ async function applyInstallersOver(services, project, options = {}) {
8927
9327
  let libOnly = 0;
8928
9328
  let browserBundle = 0;
8929
9329
  let reactNative = 0;
9330
+ let bun = 0;
9331
+ let deno = 0;
9332
+ let cloudflareWorkers = 0;
9333
+ let electron = 0;
8930
9334
  const installPlans = /* @__PURE__ */ new Map();
8931
9335
  for (const svc of services) {
8932
9336
  const installer = await pickInstaller(svc.dir);
@@ -8951,7 +9355,59 @@ async function applyInstallersOver(services, project, options = {}) {
8951
9355
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
8952
9356
  } else if (outcome.outcome === "react-native") {
8953
9357
  reactNative++;
8954
- 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
+ );
8955
9411
  }
8956
9412
  }
8957
9413
  const packageManagerInstalls = [];
@@ -8976,6 +9432,10 @@ async function applyInstallersOver(services, project, options = {}) {
8976
9432
  libOnly,
8977
9433
  browserBundle,
8978
9434
  reactNative,
9435
+ bun,
9436
+ deno,
9437
+ cloudflareWorkers,
9438
+ electron,
8979
9439
  packageManagerInstalls
8980
9440
  };
8981
9441
  }
@@ -9113,10 +9573,10 @@ function formatPortCollisionMessage(port) {
9113
9573
  ];
9114
9574
  }
9115
9575
  function spawnDaemonDetached() {
9116
- const here = import_node_path43.default.dirname(new URL(importMetaUrl).pathname);
9576
+ const here = import_node_path44.default.dirname(new URL(importMetaUrl).pathname);
9117
9577
  const candidates = [
9118
- import_node_path43.default.join(here, "neatd.cjs"),
9119
- 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")
9120
9580
  ];
9121
9581
  let entry2 = null;
9122
9582
  const fsSync = require("fs");
@@ -9174,7 +9634,7 @@ async function runOrchestrator(opts) {
9174
9634
  browser: "skipped"
9175
9635
  }
9176
9636
  };
9177
- 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);
9178
9638
  if (!stat || !stat.isDirectory()) {
9179
9639
  console.error(`neat: ${opts.scanPath} is not a directory`);
9180
9640
  result.exitCode = 2;
@@ -9317,7 +9777,7 @@ function printSummary(result, graph, dashboardUrl) {
9317
9777
 
9318
9778
  // src/cli-verbs.ts
9319
9779
  init_cjs_shims();
9320
- var import_node_path44 = __toESM(require("path"), 1);
9780
+ var import_node_path45 = __toESM(require("path"), 1);
9321
9781
 
9322
9782
  // src/cli-client.ts
9323
9783
  init_cjs_shims();
@@ -9346,10 +9806,10 @@ function createHttpClient(baseUrl, bearerToken) {
9346
9806
  const root = baseUrl.replace(/\/$/, "");
9347
9807
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
9348
9808
  return {
9349
- async get(path46) {
9809
+ async get(path47) {
9350
9810
  let res;
9351
9811
  try {
9352
- res = await fetch(`${root}${path46}`, {
9812
+ res = await fetch(`${root}${path47}`, {
9353
9813
  headers: { ...authHeader }
9354
9814
  });
9355
9815
  } catch (err) {
@@ -9361,16 +9821,16 @@ function createHttpClient(baseUrl, bearerToken) {
9361
9821
  const body = await res.text().catch(() => "");
9362
9822
  throw new HttpError(
9363
9823
  res.status,
9364
- `${res.status} ${res.statusText} on GET ${path46}: ${body}`,
9824
+ `${res.status} ${res.statusText} on GET ${path47}: ${body}`,
9365
9825
  body
9366
9826
  );
9367
9827
  }
9368
9828
  return await res.json();
9369
9829
  },
9370
- async post(path46, body) {
9830
+ async post(path47, body) {
9371
9831
  let res;
9372
9832
  try {
9373
- res = await fetch(`${root}${path46}`, {
9833
+ res = await fetch(`${root}${path47}`, {
9374
9834
  method: "POST",
9375
9835
  headers: { "content-type": "application/json", ...authHeader },
9376
9836
  body: JSON.stringify(body)
@@ -9384,7 +9844,7 @@ function createHttpClient(baseUrl, bearerToken) {
9384
9844
  const text = await res.text().catch(() => "");
9385
9845
  throw new HttpError(
9386
9846
  res.status,
9387
- `${res.status} ${res.statusText} on POST ${path46}: ${text}`,
9847
+ `${res.status} ${res.statusText} on POST ${path47}: ${text}`,
9388
9848
  text
9389
9849
  );
9390
9850
  }
@@ -9398,12 +9858,12 @@ function projectPath(project, suffix) {
9398
9858
  }
9399
9859
  async function runRootCause(client, input) {
9400
9860
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
9401
- const path46 = projectPath(
9861
+ const path47 = projectPath(
9402
9862
  input.project,
9403
9863
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
9404
9864
  );
9405
9865
  try {
9406
- const result = await client.get(path46);
9866
+ const result = await client.get(path47);
9407
9867
  const arrowPath = result.traversalPath.join(" \u2190 ");
9408
9868
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
9409
9869
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -9429,12 +9889,12 @@ async function runRootCause(client, input) {
9429
9889
  }
9430
9890
  async function runBlastRadius(client, input) {
9431
9891
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
9432
- const path46 = projectPath(
9892
+ const path47 = projectPath(
9433
9893
  input.project,
9434
9894
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
9435
9895
  );
9436
9896
  try {
9437
- const result = await client.get(path46);
9897
+ const result = await client.get(path47);
9438
9898
  if (result.totalAffected === 0) {
9439
9899
  return {
9440
9900
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -9468,12 +9928,12 @@ function formatBlastEntry(n) {
9468
9928
  }
9469
9929
  async function runDependencies(client, input) {
9470
9930
  const depth = input.depth ?? 3;
9471
- const path46 = projectPath(
9931
+ const path47 = projectPath(
9472
9932
  input.project,
9473
9933
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
9474
9934
  );
9475
9935
  try {
9476
- const result = await client.get(path46);
9936
+ const result = await client.get(path47);
9477
9937
  if (result.total === 0) {
9478
9938
  return {
9479
9939
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -9554,9 +10014,9 @@ function formatDuration(ms) {
9554
10014
  return `${Math.round(h / 24)}d`;
9555
10015
  }
9556
10016
  async function runIncidents(client, input) {
9557
- 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");
9558
10018
  try {
9559
- const body = await client.get(path46);
10019
+ const body = await client.get(path47);
9560
10020
  const events = body.events;
9561
10021
  if (events.length === 0) {
9562
10022
  return {
@@ -9846,7 +10306,7 @@ async function resolveProjectEntry(opts) {
9846
10306
  const cwd = opts.cwd ?? process.cwd();
9847
10307
  const resolvedCwd = await normalizeProjectPath(cwd);
9848
10308
  for (const entry2 of entries) {
9849
- 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}`)) {
9850
10310
  return entry2;
9851
10311
  }
9852
10312
  }
@@ -10206,14 +10666,14 @@ function assignFlag(out, field, value) {
10206
10666
  out[field] = value;
10207
10667
  }
10208
10668
  function readPackageVersion() {
10209
- 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));
10210
10670
  const candidates = [
10211
- import_node_path45.default.resolve(here, "../package.json"),
10212
- 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")
10213
10673
  ];
10214
10674
  for (const candidate of candidates) {
10215
10675
  try {
10216
- const raw = (0, import_node_fs29.readFileSync)(candidate, "utf8");
10676
+ const raw = (0, import_node_fs30.readFileSync)(candidate, "utf8");
10217
10677
  const parsed = JSON.parse(raw);
10218
10678
  if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
10219
10679
  return parsed.version;
@@ -10277,7 +10737,7 @@ async function buildPatchSections(services, project) {
10277
10737
  }
10278
10738
  async function runInit(opts) {
10279
10739
  const written = [];
10280
- 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);
10281
10741
  if (!stat || !stat.isDirectory()) {
10282
10742
  console.error(`neat init: ${opts.scanPath} is not a directory`);
10283
10743
  return { exitCode: 2, writtenFiles: written };
@@ -10286,13 +10746,13 @@ async function runInit(opts) {
10286
10746
  printDiscoveryReport(opts, services);
10287
10747
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
10288
10748
  const patch = renderPatch(sections);
10289
- const patchPath = import_node_path45.default.join(opts.scanPath, "neat.patch");
10749
+ const patchPath = import_node_path46.default.join(opts.scanPath, "neat.patch");
10290
10750
  if (opts.dryRun) {
10291
- await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
10751
+ await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
10292
10752
  written.push(patchPath);
10293
10753
  console.log(`dry-run: patch written to ${patchPath}`);
10294
- const gitignorePath = import_node_path45.default.join(opts.scanPath, ".gitignore");
10295
- 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);
10296
10756
  const verb = gitignoreExists ? "append" : "create";
10297
10757
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
10298
10758
  console.log("rerun without --dry-run to register and snapshot.");
@@ -10303,9 +10763,9 @@ async function runInit(opts) {
10303
10763
  const graph = getGraph(graphKey);
10304
10764
  const projectPaths = pathsForProject(
10305
10765
  graphKey,
10306
- import_node_path45.default.join(opts.scanPath, "neat-out")
10766
+ import_node_path46.default.join(opts.scanPath, "neat-out")
10307
10767
  );
10308
- 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));
10309
10769
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
10310
10770
  await saveGraphToDisk(graph, opts.outPath);
10311
10771
  written.push(opts.outPath);
@@ -10384,7 +10844,7 @@ async function runInit(opts) {
10384
10844
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
10385
10845
  }
10386
10846
  } else {
10387
- await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
10847
+ await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
10388
10848
  written.push(patchPath);
10389
10849
  }
10390
10850
  }
@@ -10424,9 +10884,9 @@ var CLAUDE_SKILL_CONFIG = {
10424
10884
  };
10425
10885
  function claudeConfigPath() {
10426
10886
  const override = process.env.NEAT_CLAUDE_CONFIG;
10427
- 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);
10428
10888
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10429
- return import_node_path45.default.join(home, ".claude.json");
10889
+ return import_node_path46.default.join(home, ".claude.json");
10430
10890
  }
10431
10891
  async function runSkill(opts) {
10432
10892
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -10438,7 +10898,7 @@ async function runSkill(opts) {
10438
10898
  const target = claudeConfigPath();
10439
10899
  let existing = {};
10440
10900
  try {
10441
- existing = JSON.parse(await import_node_fs29.promises.readFile(target, "utf8"));
10901
+ existing = JSON.parse(await import_node_fs30.promises.readFile(target, "utf8"));
10442
10902
  } catch (err) {
10443
10903
  if (err.code !== "ENOENT") {
10444
10904
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -10450,8 +10910,8 @@ async function runSkill(opts) {
10450
10910
  ...existing,
10451
10911
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
10452
10912
  };
10453
- await import_node_fs29.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
10454
- 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");
10455
10915
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
10456
10916
  console.log("restart Claude Code to pick up the new MCP server.");
10457
10917
  return { exitCode: 0 };
@@ -10489,12 +10949,12 @@ async function main() {
10489
10949
  console.error("neat init: --apply and --dry-run are mutually exclusive");
10490
10950
  process.exit(2);
10491
10951
  }
10492
- const scanPath = import_node_path45.default.resolve(target);
10952
+ const scanPath = import_node_path46.default.resolve(target);
10493
10953
  const projectExplicit = parsed.project !== null;
10494
- const projectName = projectExplicit ? project : import_node_path45.default.basename(scanPath);
10954
+ const projectName = projectExplicit ? project : import_node_path46.default.basename(scanPath);
10495
10955
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
10496
- const fallback = pathsForProject(projectKey, import_node_path45.default.join(scanPath, "neat-out")).snapshotPath;
10497
- 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);
10498
10958
  const result = await runInit({
10499
10959
  scanPath,
10500
10960
  outPath,
@@ -10515,21 +10975,21 @@ async function main() {
10515
10975
  usage();
10516
10976
  process.exit(2);
10517
10977
  }
10518
- const scanPath = import_node_path45.default.resolve(target);
10519
- 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);
10520
10980
  if (!stat || !stat.isDirectory()) {
10521
10981
  console.error(`neat watch: ${scanPath} is not a directory`);
10522
10982
  process.exit(2);
10523
10983
  }
10524
- const projectPaths = pathsForProject(project, import_node_path45.default.join(scanPath, "neat-out"));
10525
- const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
10526
- const errorsPath = import_node_path45.default.resolve(
10527
- 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))
10528
10988
  );
10529
- const staleEventsPath = import_node_path45.default.resolve(
10530
- 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))
10531
10991
  );
10532
- 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;
10533
10993
  const handle = await startWatch(getGraph(project), {
10534
10994
  scanPath,
10535
10995
  outPath,
@@ -10675,11 +11135,11 @@ async function main() {
10675
11135
  process.exit(1);
10676
11136
  }
10677
11137
  async function tryOrchestrator(cmd, parsed) {
10678
- const scanPath = import_node_path45.default.resolve(cmd);
10679
- 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);
10680
11140
  if (!stat || !stat.isDirectory()) return null;
10681
11141
  const projectExplicit = parsed.project !== null;
10682
- const projectName = projectExplicit ? parsed.project : import_node_path45.default.basename(scanPath);
11142
+ const projectName = projectExplicit ? parsed.project : import_node_path46.default.basename(scanPath);
10683
11143
  const result = await runOrchestrator({
10684
11144
  scanPath,
10685
11145
  project: projectName,