@neat.is/core 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -49,9 +49,9 @@ function mountBearerAuth(app, opts) {
49
49
  const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
50
50
  const publicRead = opts.publicRead === true;
51
51
  app.addHook("preHandler", (req, reply, done) => {
52
- const path45 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
52
+ const path46 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
53
53
  for (const suffix of suffixes) {
54
- if (path45 === suffix || path45.endsWith(suffix)) {
54
+ if (path46 === suffix || path46.endsWith(suffix)) {
55
55
  done();
56
56
  return;
57
57
  }
@@ -301,12 +301,15 @@ function parseOtlpRequest(body) {
301
301
  const out = [];
302
302
  for (const rs of body.resourceSpans ?? []) {
303
303
  const resourceAttrs = attrsToRecord(rs.resource?.attributes);
304
- const service = typeof resourceAttrs["service.name"] === "string" ? resourceAttrs["service.name"] : "unknown";
304
+ const rawServiceName = resourceAttrs["service.name"];
305
+ const resourceServiceNamePresent = typeof rawServiceName === "string" && rawServiceName.length > 0;
306
+ const service = resourceServiceNamePresent ? rawServiceName : "unidentified";
305
307
  for (const ss of rs.scopeSpans ?? []) {
306
308
  for (const span of ss.spans ?? []) {
307
309
  const attrs = attrsToRecord(span.attributes);
308
310
  const parsed = {
309
311
  service,
312
+ resourceServiceNamePresent,
310
313
  traceId: span.traceId ?? "",
311
314
  spanId: span.spanId ?? "",
312
315
  parentSpanId: span.parentSpanId || void 0,
@@ -559,8 +562,8 @@ __export(cli_exports, {
559
562
  });
560
563
  module.exports = __toCommonJS(cli_exports);
561
564
  init_cjs_shims();
562
- var import_node_path44 = __toESM(require("path"), 1);
563
- var import_node_fs28 = require("fs");
565
+ var import_node_path45 = __toESM(require("path"), 1);
566
+ var import_node_fs29 = require("fs");
564
567
  var import_node_url3 = require("url");
565
568
 
566
569
  // src/graph.ts
@@ -1071,19 +1074,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1071
1074
  function longestIncomingWalk(graph, start, maxDepth) {
1072
1075
  let best = { path: [start], edges: [] };
1073
1076
  const visited = /* @__PURE__ */ new Set([start]);
1074
- function step(node, path45, edges) {
1075
- if (path45.length > best.path.length) {
1076
- best = { path: [...path45], edges: [...edges] };
1077
+ function step(node, path46, edges) {
1078
+ if (path46.length > best.path.length) {
1079
+ best = { path: [...path46], edges: [...edges] };
1077
1080
  }
1078
- if (path45.length - 1 >= maxDepth) return;
1081
+ if (path46.length - 1 >= maxDepth) return;
1079
1082
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1080
1083
  for (const [srcId, edge] of incoming) {
1081
1084
  if (visited.has(srcId)) continue;
1082
1085
  visited.add(srcId);
1083
- path45.push(srcId);
1086
+ path46.push(srcId);
1084
1087
  edges.push(edge);
1085
- step(srcId, path45, edges);
1086
- path45.pop();
1088
+ step(srcId, path46, edges);
1089
+ path46.pop();
1087
1090
  edges.pop();
1088
1091
  visited.delete(srcId);
1089
1092
  }
@@ -1620,6 +1623,14 @@ function thresholdForEdgeType(edgeType, overrides) {
1620
1623
  function nowIso(ctx) {
1621
1624
  return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
1622
1625
  }
1626
+ var unidentifiedWarnedProjects = /* @__PURE__ */ new Set();
1627
+ function warnUnidentifiedSpan(project) {
1628
+ if (unidentifiedWarnedProjects.has(project)) return;
1629
+ unidentifiedWarnedProjects.add(project);
1630
+ console.warn(
1631
+ `[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
1632
+ );
1633
+ }
1623
1634
  function pickAttr(span, ...keys) {
1624
1635
  for (const k of keys) {
1625
1636
  const v = span.attributes[k];
@@ -1874,6 +1885,9 @@ async function handleSpan(ctx, span) {
1874
1885
  const ts = span.startTimeIso ?? nowIso(ctx);
1875
1886
  const nowMs = ctx.now ? ctx.now() : Date.now();
1876
1887
  const env = span.env ?? "unknown";
1888
+ if (span.resourceServiceNamePresent === false) {
1889
+ warnUnidentifiedSpan(ctx.project ?? DEFAULT_PROJECT);
1890
+ }
1877
1891
  const sourceId = ensureServiceNode(ctx.graph, span.service, env);
1878
1892
  const isError = span.statusCode === 2;
1879
1893
  cacheSpanService(span, nowMs);
@@ -6559,22 +6573,40 @@ var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
6559
6573
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6560
6574
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6561
6575
 
6562
- require('@opentelemetry/auto-instrumentations-node/register')
6576
+ const { NodeSDK } = require('@opentelemetry/sdk-node')
6577
+ const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
6578
+
6579
+ const instrumentations = [getNodeAutoInstrumentations()]
6580
+ __INSTRUMENTATION_BLOCK__
6581
+ new NodeSDK({ instrumentations }).start()
6563
6582
  `;
6564
6583
  var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
6565
6584
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6566
6585
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6567
6586
 
6568
- await import('@opentelemetry/auto-instrumentations-node/register')
6587
+ import { NodeSDK } from '@opentelemetry/sdk-node'
6588
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6589
+
6590
+ const instrumentations = [getNodeAutoInstrumentations()]
6591
+ __INSTRUMENTATION_BLOCK__
6592
+ new NodeSDK({ instrumentations }).start()
6569
6593
  `;
6570
6594
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
6571
6595
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
6572
6596
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
6573
6597
 
6574
- await import('@opentelemetry/auto-instrumentations-node/register')
6598
+ import { NodeSDK } from '@opentelemetry/sdk-node'
6599
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6600
+
6601
+ const instrumentations = [getNodeAutoInstrumentations()]
6602
+ __INSTRUMENTATION_BLOCK__
6603
+ new NodeSDK({ instrumentations }).start()
6575
6604
  `;
6576
- function renderNodeOtelInit(template, serviceName, projectName) {
6577
- return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
6605
+ function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
6606
+ const block = registrations.length === 0 ? "" : `
6607
+ ${registrations.join("\n")}
6608
+ `;
6609
+ return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
6578
6610
  }
6579
6611
  function renderEnvNeat(serviceName, projectName) {
6580
6612
  return [
@@ -6606,7 +6638,9 @@ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projec
6606
6638
  import { NodeSDK } from '@opentelemetry/sdk-node'
6607
6639
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
6608
6640
 
6609
- new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
6641
+ const instrumentations = [getNodeAutoInstrumentations()]
6642
+ __INSTRUMENTATION_BLOCK__
6643
+ new NodeSDK({ instrumentations }).start()
6610
6644
  `;
6611
6645
  var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
6612
6646
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
@@ -6615,10 +6649,15 @@ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projec
6615
6649
  const { NodeSDK } = require('@opentelemetry/sdk-node')
6616
6650
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
6617
6651
 
6618
- new NodeSDK({ instrumentations: [getNodeAutoInstrumentations()] }).start()
6652
+ const instrumentations = [getNodeAutoInstrumentations()]
6653
+ __INSTRUMENTATION_BLOCK__
6654
+ new NodeSDK({ instrumentations }).start()
6619
6655
  `;
6620
- function renderNextInstrumentationNode(template, serviceName, projectName) {
6621
- return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName);
6656
+ function renderNextInstrumentationNode(template, serviceName, projectName, registrations = []) {
6657
+ const block = registrations.length === 0 ? "" : `
6658
+ ${registrations.join("\n")}
6659
+ `;
6660
+ return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
6622
6661
  }
6623
6662
  var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
6624
6663
  var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
@@ -6714,6 +6753,25 @@ var SDK_PACKAGES = [
6714
6753
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
6715
6754
  { name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
6716
6755
  ];
6756
+ function getMajor(versionRange) {
6757
+ if (!versionRange) return 0;
6758
+ const match = versionRange.match(/(\d+)/);
6759
+ return match ? parseInt(match[1], 10) : 0;
6760
+ }
6761
+ function detectNonBundledInstrumentations(pkg) {
6762
+ const deps = allDeps(pkg);
6763
+ const out = [];
6764
+ if ("@prisma/client" in deps) {
6765
+ const prismaMajor = getMajor(deps["@prisma/client"]);
6766
+ const prismaInstrVersion = prismaMajor >= 6 ? "^6.0.0" : "^5.0.0";
6767
+ out.push({
6768
+ pkg: "@prisma/instrumentation",
6769
+ version: prismaInstrVersion,
6770
+ registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
6771
+ });
6772
+ }
6773
+ return out;
6774
+ }
6717
6775
  var OTEL_ENV = {
6718
6776
  // ADR-069 §4 — endpoint moves into the per-package .env.neat (written
6719
6777
  // by the apply phase). The envEdits surface stays for the dry-run
@@ -7004,8 +7062,19 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7004
7062
  version: sdk.version
7005
7063
  });
7006
7064
  }
7065
+ const nonBundled = detectNonBundledInstrumentations(pkg);
7066
+ for (const inst of nonBundled) {
7067
+ if (inst.pkg in existingDeps) continue;
7068
+ dependencyEdits.push({
7069
+ file: manifestPath,
7070
+ kind: "add",
7071
+ name: inst.pkg,
7072
+ version: inst.version
7073
+ });
7074
+ }
7007
7075
  const svcName = serviceNodeName(pkg, serviceDir);
7008
7076
  const projectName = projectToken(pkg, serviceDir, project);
7077
+ const registrations = nonBundled.map((i) => i.registration);
7009
7078
  const generatedFiles = [];
7010
7079
  if (!await exists2(instrumentationFile)) {
7011
7080
  generatedFiles.push({
@@ -7020,7 +7089,8 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
7020
7089
  contents: renderNextInstrumentationNode(
7021
7090
  useTs ? NEXT_INSTRUMENTATION_NODE_TS : NEXT_INSTRUMENTATION_NODE_JS,
7022
7091
  svcName,
7023
- projectName
7092
+ projectName,
7093
+ registrations
7024
7094
  ),
7025
7095
  skipIfExists: true
7026
7096
  });
@@ -7370,55 +7440,73 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
7370
7440
  framework: "astro"
7371
7441
  };
7372
7442
  }
7373
- async function plan(serviceDir, opts) {
7374
- const pkg = await readPackageJson(serviceDir);
7375
- const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
7376
- const project = opts?.project;
7377
- const empty = {
7378
- language: "javascript",
7379
- serviceDir,
7380
- dependencyEdits: [],
7381
- entrypointEdits: [],
7382
- envEdits: [],
7383
- generatedFiles: []
7384
- };
7385
- if (!pkg) return empty;
7443
+ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
7386
7444
  if (hasNextDependency(pkg)) {
7387
7445
  const nextConfig = await findNextConfig(serviceDir);
7388
7446
  if (nextConfig) {
7389
- return planNext(serviceDir, pkg, manifestPath, nextConfig, project);
7447
+ return () => planNext(serviceDir, pkg, manifestPath, nextConfig, project);
7390
7448
  }
7391
7449
  }
7392
7450
  if (hasRemixDependency(pkg)) {
7393
7451
  const remixEntry = await findRemixEntry(serviceDir);
7394
7452
  if (remixEntry) {
7395
- return planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
7453
+ return () => planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
7396
7454
  }
7397
7455
  }
7398
7456
  if (hasSvelteKitDependency(pkg)) {
7399
7457
  const hooks = await findSvelteKitHooks(serviceDir);
7400
7458
  const config = await findSvelteKitConfig(serviceDir);
7401
7459
  if (hooks || config) {
7402
- return planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
7460
+ return () => planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
7403
7461
  }
7404
7462
  }
7405
7463
  if (hasNuxtDependency(pkg)) {
7406
7464
  const nuxtConfig = await findNuxtConfig(serviceDir);
7407
7465
  if (nuxtConfig) {
7408
- return planNuxt(serviceDir, pkg, manifestPath, project);
7466
+ return () => planNuxt(serviceDir, pkg, manifestPath, project);
7409
7467
  }
7410
7468
  }
7411
7469
  if (hasAstroDependency(pkg)) {
7412
7470
  const astroConfig = await findAstroConfig(serviceDir);
7413
7471
  if (astroConfig) {
7414
- return planAstro(serviceDir, pkg, manifestPath, project);
7472
+ return () => planAstro(serviceDir, pkg, manifestPath, project);
7473
+ }
7474
+ }
7475
+ return null;
7476
+ }
7477
+ async function plan(serviceDir, opts) {
7478
+ const pkg = await readPackageJson(serviceDir);
7479
+ const manifestPath = import_node_path40.default.join(serviceDir, "package.json");
7480
+ const project = opts?.project;
7481
+ const empty = {
7482
+ language: "javascript",
7483
+ serviceDir,
7484
+ dependencyEdits: [],
7485
+ entrypointEdits: [],
7486
+ envEdits: [],
7487
+ generatedFiles: []
7488
+ };
7489
+ if (!pkg) return empty;
7490
+ const frameworkDispatch = await findFrameworkDispatch(
7491
+ serviceDir,
7492
+ pkg,
7493
+ manifestPath,
7494
+ project
7495
+ );
7496
+ let entryFile = null;
7497
+ if (!frameworkDispatch) {
7498
+ entryFile = await resolveEntry(serviceDir, pkg);
7499
+ if (!entryFile) {
7500
+ return { ...empty, libOnly: true };
7415
7501
  }
7416
7502
  }
7503
+ if (frameworkDispatch) {
7504
+ return frameworkDispatch();
7505
+ }
7417
7506
  const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
7418
7507
  if (runtimeKind !== "node") {
7419
7508
  return { ...empty, runtimeKind };
7420
7509
  }
7421
- const entryFile = await resolveEntry(serviceDir, pkg);
7422
7510
  if (!entryFile) {
7423
7511
  return { ...empty, libOnly: true };
7424
7512
  }
@@ -7436,6 +7524,16 @@ async function plan(serviceDir, opts) {
7436
7524
  version: sdk.version
7437
7525
  });
7438
7526
  }
7527
+ const nonBundled = detectNonBundledInstrumentations(pkg);
7528
+ for (const inst of nonBundled) {
7529
+ if (inst.pkg in existingDeps) continue;
7530
+ dependencyEdits.push({
7531
+ file: manifestPath,
7532
+ kind: "add",
7533
+ name: inst.pkg,
7534
+ version: inst.version
7535
+ });
7536
+ }
7439
7537
  const entrypointEdits = [];
7440
7538
  try {
7441
7539
  const raw = await import_node_fs25.promises.readFile(entryFile, "utf8");
@@ -7454,11 +7552,17 @@ async function plan(serviceDir, opts) {
7454
7552
  }
7455
7553
  const svcName = serviceNodeName(pkg, serviceDir);
7456
7554
  const projectName = projectToken(pkg, serviceDir, project);
7555
+ const registrations = nonBundled.map((i) => i.registration);
7457
7556
  const generatedFiles = [];
7458
7557
  if (!await exists2(otelInitFile)) {
7459
7558
  generatedFiles.push({
7460
7559
  file: otelInitFile,
7461
- contents: renderNodeOtelInit(otelInitContents(flavor), svcName, projectName),
7560
+ contents: renderNodeOtelInit(
7561
+ otelInitContents(flavor),
7562
+ svcName,
7563
+ projectName,
7564
+ registrations
7565
+ ),
7462
7566
  skipIfExists: true
7463
7567
  });
7464
7568
  }
@@ -7991,18 +8095,100 @@ function renderPatch(sections) {
7991
8095
 
7992
8096
  // src/orchestrator.ts
7993
8097
  init_cjs_shims();
7994
- var import_node_fs27 = require("fs");
8098
+ var import_node_fs28 = require("fs");
7995
8099
  var import_node_http = __toESM(require("http"), 1);
8100
+ var import_node_net = __toESM(require("net"), 1);
8101
+ var import_node_path43 = __toESM(require("path"), 1);
8102
+ var import_node_child_process3 = require("child_process");
8103
+ var import_node_readline = __toESM(require("readline"), 1);
8104
+
8105
+ // src/installers/package-manager.ts
8106
+ init_cjs_shims();
8107
+ var import_node_fs27 = require("fs");
7996
8108
  var import_node_path42 = __toESM(require("path"), 1);
7997
8109
  var import_node_child_process2 = require("child_process");
7998
- var import_node_readline = __toESM(require("readline"), 1);
8110
+ var LOCKFILE_PRIORITY = [
8111
+ { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
8112
+ { lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
8113
+ { lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
8114
+ {
8115
+ lockfile: "package-lock.json",
8116
+ pm: "npm",
8117
+ args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
8118
+ }
8119
+ ];
8120
+ var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
8121
+ async function exists4(p) {
8122
+ try {
8123
+ await import_node_fs27.promises.access(p);
8124
+ return true;
8125
+ } catch {
8126
+ return false;
8127
+ }
8128
+ }
8129
+ async function detectPackageManager(serviceDir) {
8130
+ let dir = import_node_path42.default.resolve(serviceDir);
8131
+ const stops = /* @__PURE__ */ new Set();
8132
+ for (let i = 0; i < 64; i++) {
8133
+ if (stops.has(dir)) break;
8134
+ stops.add(dir);
8135
+ for (const candidate of LOCKFILE_PRIORITY) {
8136
+ const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
8137
+ if (await exists4(lockPath)) {
8138
+ return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8139
+ }
8140
+ }
8141
+ const parent = import_node_path42.default.dirname(dir);
8142
+ if (parent === dir) break;
8143
+ dir = parent;
8144
+ }
8145
+ return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8146
+ }
8147
+ async function runPackageManagerInstall(cmd) {
8148
+ return new Promise((resolve) => {
8149
+ const child = (0, import_node_child_process2.spawn)(cmd.pm, cmd.args, {
8150
+ cwd: cmd.cwd,
8151
+ // Inherit PATH + HOME so the user's installed managers resolve.
8152
+ env: process.env,
8153
+ // `false` keeps the parent in control of cleanup if the orchestrator
8154
+ // exits before install finishes. Cross-platform-safe.
8155
+ shell: false,
8156
+ stdio: ["ignore", "ignore", "pipe"]
8157
+ });
8158
+ let stderr = "";
8159
+ child.stderr?.on("data", (chunk) => {
8160
+ stderr += chunk.toString("utf8");
8161
+ });
8162
+ child.on("error", (err) => {
8163
+ resolve({
8164
+ pm: cmd.pm,
8165
+ cwd: cmd.cwd,
8166
+ args: cmd.args,
8167
+ exitCode: 127,
8168
+ stderr: stderr + `
8169
+ ${err.message}`
8170
+ });
8171
+ });
8172
+ child.on("close", (code) => {
8173
+ resolve({
8174
+ pm: cmd.pm,
8175
+ cwd: cmd.cwd,
8176
+ args: cmd.args,
8177
+ exitCode: code ?? 1,
8178
+ stderr: stderr.trim()
8179
+ });
8180
+ });
8181
+ });
8182
+ }
8183
+
8184
+ // src/orchestrator.ts
7999
8185
  async function extractAndPersist(opts) {
8000
8186
  const services = await discoverServices(opts.scanPath);
8001
8187
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
8002
8188
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
8003
8189
  resetGraph(graphKey);
8004
8190
  const graph = getGraph(graphKey);
8005
- const projectPaths = pathsForProject(graphKey, import_node_path42.default.join(opts.scanPath, "neat-out"));
8191
+ const projectPaths = pathsForProject(graphKey, import_node_path43.default.join(opts.scanPath, "neat-out"));
8006
8192
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
8007
8193
  errorsPath: projectPaths.errorsPath
8008
8194
  });
@@ -8020,12 +8206,15 @@ async function extractAndPersist(opts) {
8020
8206
  errorsPath: projectPaths.errorsPath
8021
8207
  };
8022
8208
  }
8023
- async function applyInstallersOver(services, project) {
8209
+ async function applyInstallersOver(services, project, options = {}) {
8210
+ const resolveManager = options.resolveManager ?? detectPackageManager;
8211
+ const runInstall = options.runInstall ?? runPackageManagerInstall;
8024
8212
  let instrumented = 0;
8025
8213
  let already = 0;
8026
8214
  let libOnly = 0;
8027
8215
  let browserBundle = 0;
8028
8216
  let reactNative = 0;
8217
+ const installPlans = /* @__PURE__ */ new Map();
8029
8218
  for (const svc of services) {
8030
8219
  const installer = await pickInstaller(svc.dir);
8031
8220
  if (!installer) continue;
@@ -8035,8 +8224,14 @@ async function applyInstallersOver(services, project) {
8035
8224
  continue;
8036
8225
  }
8037
8226
  const outcome = await installer.apply(plan3);
8038
- if (outcome.outcome === "instrumented") instrumented++;
8039
- else if (outcome.outcome === "already-instrumented") already++;
8227
+ if (outcome.outcome === "instrumented") {
8228
+ instrumented++;
8229
+ if (plan3.dependencyEdits.length > 0) {
8230
+ const cmd = await resolveManager(svc.dir);
8231
+ const key = `${cmd.pm}:${cmd.cwd}`;
8232
+ if (!installPlans.has(key)) installPlans.set(key, cmd);
8233
+ }
8234
+ } else if (outcome.outcome === "already-instrumented") already++;
8040
8235
  else if (outcome.outcome === "lib-only") libOnly++;
8041
8236
  else if (outcome.outcome === "browser-bundle") {
8042
8237
  browserBundle++;
@@ -8046,7 +8241,30 @@ async function applyInstallersOver(services, project) {
8046
8241
  console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
8047
8242
  }
8048
8243
  }
8049
- return { instrumented, alreadyInstrumented: already, libOnly, browserBundle, reactNative };
8244
+ const packageManagerInstalls = [];
8245
+ for (const cmd of installPlans.values()) {
8246
+ console.log(`running \`${cmd.pm} ${cmd.args.join(" ")}\` in ${cmd.cwd}`);
8247
+ const result = await runInstall(cmd);
8248
+ packageManagerInstalls.push(result);
8249
+ if (result.exitCode !== 0) {
8250
+ console.error(
8251
+ `neat: ${cmd.pm} install failed in ${cmd.cwd} (exit ${result.exitCode}); run it manually to surface the error.`
8252
+ );
8253
+ if (result.stderr.length > 0) {
8254
+ for (const line of result.stderr.split(/\r?\n/).slice(0, 20)) {
8255
+ console.error(` ${line}`);
8256
+ }
8257
+ }
8258
+ }
8259
+ }
8260
+ return {
8261
+ instrumented,
8262
+ alreadyInstrumented: already,
8263
+ libOnly,
8264
+ browserBundle,
8265
+ reactNative,
8266
+ packageManagerInstalls
8267
+ };
8050
8268
  }
8051
8269
  async function promptYesNo(question) {
8052
8270
  const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stdout });
@@ -8159,11 +8377,33 @@ async function waitForDaemonReady(restPort, timeoutMs) {
8159
8377
  stillBootstrapping: projects.filter((p) => p.status === "bootstrapping").map((p) => p.name)
8160
8378
  };
8161
8379
  }
8380
+ var NEAT_PORTS = [8080, 4318, 6328];
8381
+ async function isPortFree(port) {
8382
+ return new Promise((resolve) => {
8383
+ const server = import_node_net.default.createServer();
8384
+ server.once("error", () => resolve(false));
8385
+ server.once("listening", () => server.close(() => resolve(true)));
8386
+ server.listen(port, "127.0.0.1");
8387
+ });
8388
+ }
8389
+ async function probePortsFree() {
8390
+ for (const port of NEAT_PORTS) {
8391
+ if (!await isPortFree(port)) return { free: false, held: port };
8392
+ }
8393
+ return { free: true };
8394
+ }
8395
+ function formatPortCollisionMessage(port) {
8396
+ return [
8397
+ `neat: port ${port} is in use; the NEAT daemon needs it.`,
8398
+ ` run \`neatd stop\` to release the previous daemon, or`,
8399
+ ` \`lsof -i :${port}\` to find the holding process.`
8400
+ ];
8401
+ }
8162
8402
  function spawnDaemonDetached() {
8163
- const here = import_node_path42.default.dirname(new URL(importMetaUrl).pathname);
8403
+ const here = import_node_path43.default.dirname(new URL(importMetaUrl).pathname);
8164
8404
  const candidates = [
8165
- import_node_path42.default.join(here, "neatd.cjs"),
8166
- import_node_path42.default.join(here, "neatd.js")
8405
+ import_node_path43.default.join(here, "neatd.cjs"),
8406
+ import_node_path43.default.join(here, "neatd.js")
8167
8407
  ];
8168
8408
  let entry2 = null;
8169
8409
  const fsSync = require("fs");
@@ -8183,7 +8423,7 @@ function spawnDaemonDetached() {
8183
8423
  if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
8184
8424
  env.HOST = "127.0.0.1";
8185
8425
  }
8186
- const child = (0, import_node_child_process2.spawn)(process.execPath, [entry2, "start"], {
8426
+ const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
8187
8427
  detached: true,
8188
8428
  // stderr inherits the orchestrator's fd so the daemon's
8189
8429
  // `BindAuthorityError` message lands in front of the operator instead
@@ -8200,7 +8440,7 @@ function openBrowser(url) {
8200
8440
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
8201
8441
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
8202
8442
  try {
8203
- const child = (0, import_node_child_process2.spawn)(cmd, args, { detached: true, stdio: "ignore" });
8443
+ const child = (0, import_node_child_process3.spawn)(cmd, args, { detached: true, stdio: "ignore" });
8204
8444
  child.on("error", () => {
8205
8445
  });
8206
8446
  child.unref();
@@ -8221,7 +8461,7 @@ async function runOrchestrator(opts) {
8221
8461
  browser: "skipped"
8222
8462
  }
8223
8463
  };
8224
- const stat = await import_node_fs27.promises.stat(opts.scanPath).catch(() => null);
8464
+ const stat = await import_node_fs28.promises.stat(opts.scanPath).catch(() => null);
8225
8465
  if (!stat || !stat.isDirectory()) {
8226
8466
  console.error(`neat: ${opts.scanPath} is not a directory`);
8227
8467
  result.exitCode = 2;
@@ -8289,12 +8529,24 @@ async function runOrchestrator(opts) {
8289
8529
  console.log(
8290
8530
  `instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
8291
8531
  );
8532
+ const failedInstalls = tally.packageManagerInstalls.filter((i) => i.exitCode !== 0);
8533
+ if (failedInstalls.length > 0) {
8534
+ result.exitCode = 1;
8535
+ }
8292
8536
  }
8293
8537
  const restPort = Number(process.env.PORT ?? 8080);
8294
8538
  const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
8295
8539
  if (await checkDaemonHealth(restPort)) {
8296
8540
  result.steps.daemon = "already-running";
8297
8541
  } else {
8542
+ const probe = await probePortsFree();
8543
+ if (!probe.free) {
8544
+ for (const line of formatPortCollisionMessage(probe.held)) {
8545
+ console.error(line);
8546
+ }
8547
+ result.exitCode = 3;
8548
+ return result;
8549
+ }
8298
8550
  try {
8299
8551
  spawnDaemonDetached();
8300
8552
  } catch (err) {
@@ -8352,7 +8604,7 @@ function printSummary(result, graph, dashboardUrl) {
8352
8604
 
8353
8605
  // src/cli-verbs.ts
8354
8606
  init_cjs_shims();
8355
- var import_node_path43 = __toESM(require("path"), 1);
8607
+ var import_node_path44 = __toESM(require("path"), 1);
8356
8608
 
8357
8609
  // src/cli-client.ts
8358
8610
  init_cjs_shims();
@@ -8377,10 +8629,10 @@ function createHttpClient(baseUrl, bearerToken) {
8377
8629
  const root = baseUrl.replace(/\/$/, "");
8378
8630
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
8379
8631
  return {
8380
- async get(path45) {
8632
+ async get(path46) {
8381
8633
  let res;
8382
8634
  try {
8383
- res = await fetch(`${root}${path45}`, {
8635
+ res = await fetch(`${root}${path46}`, {
8384
8636
  headers: { ...authHeader }
8385
8637
  });
8386
8638
  } catch (err) {
@@ -8392,16 +8644,16 @@ function createHttpClient(baseUrl, bearerToken) {
8392
8644
  const body = await res.text().catch(() => "");
8393
8645
  throw new HttpError(
8394
8646
  res.status,
8395
- `${res.status} ${res.statusText} on GET ${path45}: ${body}`,
8647
+ `${res.status} ${res.statusText} on GET ${path46}: ${body}`,
8396
8648
  body
8397
8649
  );
8398
8650
  }
8399
8651
  return await res.json();
8400
8652
  },
8401
- async post(path45, body) {
8653
+ async post(path46, body) {
8402
8654
  let res;
8403
8655
  try {
8404
- res = await fetch(`${root}${path45}`, {
8656
+ res = await fetch(`${root}${path46}`, {
8405
8657
  method: "POST",
8406
8658
  headers: { "content-type": "application/json", ...authHeader },
8407
8659
  body: JSON.stringify(body)
@@ -8415,7 +8667,7 @@ function createHttpClient(baseUrl, bearerToken) {
8415
8667
  const text = await res.text().catch(() => "");
8416
8668
  throw new HttpError(
8417
8669
  res.status,
8418
- `${res.status} ${res.statusText} on POST ${path45}: ${text}`,
8670
+ `${res.status} ${res.statusText} on POST ${path46}: ${text}`,
8419
8671
  text
8420
8672
  );
8421
8673
  }
@@ -8429,12 +8681,12 @@ function projectPath(project, suffix) {
8429
8681
  }
8430
8682
  async function runRootCause(client, input) {
8431
8683
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
8432
- const path45 = projectPath(
8684
+ const path46 = projectPath(
8433
8685
  input.project,
8434
8686
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
8435
8687
  );
8436
8688
  try {
8437
- const result = await client.get(path45);
8689
+ const result = await client.get(path46);
8438
8690
  const arrowPath = result.traversalPath.join(" \u2190 ");
8439
8691
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
8440
8692
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -8460,12 +8712,12 @@ async function runRootCause(client, input) {
8460
8712
  }
8461
8713
  async function runBlastRadius(client, input) {
8462
8714
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
8463
- const path45 = projectPath(
8715
+ const path46 = projectPath(
8464
8716
  input.project,
8465
8717
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
8466
8718
  );
8467
8719
  try {
8468
- const result = await client.get(path45);
8720
+ const result = await client.get(path46);
8469
8721
  if (result.totalAffected === 0) {
8470
8722
  return {
8471
8723
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -8499,12 +8751,12 @@ function formatBlastEntry(n) {
8499
8751
  }
8500
8752
  async function runDependencies(client, input) {
8501
8753
  const depth = input.depth ?? 3;
8502
- const path45 = projectPath(
8754
+ const path46 = projectPath(
8503
8755
  input.project,
8504
8756
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
8505
8757
  );
8506
8758
  try {
8507
- const result = await client.get(path45);
8759
+ const result = await client.get(path46);
8508
8760
  if (result.total === 0) {
8509
8761
  return {
8510
8762
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -8585,9 +8837,9 @@ function formatDuration(ms) {
8585
8837
  return `${Math.round(h / 24)}d`;
8586
8838
  }
8587
8839
  async function runIncidents(client, input) {
8588
- const path45 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
8840
+ const path46 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
8589
8841
  try {
8590
- const body = await client.get(path45);
8842
+ const body = await client.get(path46);
8591
8843
  const events = body.events;
8592
8844
  if (events.length === 0) {
8593
8845
  return {
@@ -8877,7 +9129,7 @@ async function resolveProjectEntry(opts) {
8877
9129
  const cwd = opts.cwd ?? process.cwd();
8878
9130
  const resolvedCwd = await normalizeProjectPath(cwd);
8879
9131
  for (const entry2 of entries) {
8880
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path43.default.sep}`)) {
9132
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path44.default.sep}`)) {
8881
9133
  return entry2;
8882
9134
  }
8883
9135
  }
@@ -9102,7 +9354,8 @@ function usage() {
9102
9354
  console.log(" 0 success");
9103
9355
  console.log(" 1 server error (4xx/5xx body printed to stderr)");
9104
9356
  console.log(" 2 misuse (missing args, bad flags) \u2014 handled before any network call");
9105
- console.log(" 3 daemon not reachable (connection refused / timeout)");
9357
+ console.log(" 3 environmental \u2014 daemon unreachable, or one of ports 8080 / 4318 / 6328");
9358
+ console.log(" is held by another process when `neat <path>` tries to spawn the daemon");
9106
9359
  console.log("");
9107
9360
  console.log("environment:");
9108
9361
  console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
@@ -9236,14 +9489,14 @@ function assignFlag(out, field, value) {
9236
9489
  out[field] = value;
9237
9490
  }
9238
9491
  function readPackageVersion() {
9239
- const here = typeof __dirname !== "undefined" ? __dirname : import_node_path44.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
9492
+ const here = typeof __dirname !== "undefined" ? __dirname : import_node_path45.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
9240
9493
  const candidates = [
9241
- import_node_path44.default.resolve(here, "../package.json"),
9242
- import_node_path44.default.resolve(here, "../../package.json")
9494
+ import_node_path45.default.resolve(here, "../package.json"),
9495
+ import_node_path45.default.resolve(here, "../../package.json")
9243
9496
  ];
9244
9497
  for (const candidate of candidates) {
9245
9498
  try {
9246
- const raw = (0, import_node_fs28.readFileSync)(candidate, "utf8");
9499
+ const raw = (0, import_node_fs29.readFileSync)(candidate, "utf8");
9247
9500
  const parsed = JSON.parse(raw);
9248
9501
  if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
9249
9502
  return parsed.version;
@@ -9307,7 +9560,7 @@ async function buildPatchSections(services, project) {
9307
9560
  }
9308
9561
  async function runInit(opts) {
9309
9562
  const written = [];
9310
- const stat = await import_node_fs28.promises.stat(opts.scanPath).catch(() => null);
9563
+ const stat = await import_node_fs29.promises.stat(opts.scanPath).catch(() => null);
9311
9564
  if (!stat || !stat.isDirectory()) {
9312
9565
  console.error(`neat init: ${opts.scanPath} is not a directory`);
9313
9566
  return { exitCode: 2, writtenFiles: written };
@@ -9316,13 +9569,13 @@ async function runInit(opts) {
9316
9569
  printDiscoveryReport(opts, services);
9317
9570
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
9318
9571
  const patch = renderPatch(sections);
9319
- const patchPath = import_node_path44.default.join(opts.scanPath, "neat.patch");
9572
+ const patchPath = import_node_path45.default.join(opts.scanPath, "neat.patch");
9320
9573
  if (opts.dryRun) {
9321
- await import_node_fs28.promises.writeFile(patchPath, patch, "utf8");
9574
+ await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
9322
9575
  written.push(patchPath);
9323
9576
  console.log(`dry-run: patch written to ${patchPath}`);
9324
- const gitignorePath = import_node_path44.default.join(opts.scanPath, ".gitignore");
9325
- const gitignoreExists = await import_node_fs28.promises.stat(gitignorePath).then(() => true).catch(() => false);
9577
+ const gitignorePath = import_node_path45.default.join(opts.scanPath, ".gitignore");
9578
+ const gitignoreExists = await import_node_fs29.promises.stat(gitignorePath).then(() => true).catch(() => false);
9326
9579
  const verb = gitignoreExists ? "append" : "create";
9327
9580
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
9328
9581
  console.log("rerun without --dry-run to register and snapshot.");
@@ -9333,9 +9586,9 @@ async function runInit(opts) {
9333
9586
  const graph = getGraph(graphKey);
9334
9587
  const projectPaths = pathsForProject(
9335
9588
  graphKey,
9336
- import_node_path44.default.join(opts.scanPath, "neat-out")
9589
+ import_node_path45.default.join(opts.scanPath, "neat-out")
9337
9590
  );
9338
- const errorsPath = import_node_path44.default.join(import_node_path44.default.dirname(opts.outPath), import_node_path44.default.basename(projectPaths.errorsPath));
9591
+ const errorsPath = import_node_path45.default.join(import_node_path45.default.dirname(opts.outPath), import_node_path45.default.basename(projectPaths.errorsPath));
9339
9592
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
9340
9593
  await saveGraphToDisk(graph, opts.outPath);
9341
9594
  written.push(opts.outPath);
@@ -9414,7 +9667,7 @@ async function runInit(opts) {
9414
9667
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
9415
9668
  }
9416
9669
  } else {
9417
- await import_node_fs28.promises.writeFile(patchPath, patch, "utf8");
9670
+ await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
9418
9671
  written.push(patchPath);
9419
9672
  }
9420
9673
  }
@@ -9454,9 +9707,9 @@ var CLAUDE_SKILL_CONFIG = {
9454
9707
  };
9455
9708
  function claudeConfigPath() {
9456
9709
  const override = process.env.NEAT_CLAUDE_CONFIG;
9457
- if (override && override.length > 0) return import_node_path44.default.resolve(override);
9710
+ if (override && override.length > 0) return import_node_path45.default.resolve(override);
9458
9711
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9459
- return import_node_path44.default.join(home, ".claude.json");
9712
+ return import_node_path45.default.join(home, ".claude.json");
9460
9713
  }
9461
9714
  async function runSkill(opts) {
9462
9715
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -9468,7 +9721,7 @@ async function runSkill(opts) {
9468
9721
  const target = claudeConfigPath();
9469
9722
  let existing = {};
9470
9723
  try {
9471
- existing = JSON.parse(await import_node_fs28.promises.readFile(target, "utf8"));
9724
+ existing = JSON.parse(await import_node_fs29.promises.readFile(target, "utf8"));
9472
9725
  } catch (err) {
9473
9726
  if (err.code !== "ENOENT") {
9474
9727
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -9480,8 +9733,8 @@ async function runSkill(opts) {
9480
9733
  ...existing,
9481
9734
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
9482
9735
  };
9483
- await import_node_fs28.promises.mkdir(import_node_path44.default.dirname(target), { recursive: true });
9484
- await import_node_fs28.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
9736
+ await import_node_fs29.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
9737
+ await import_node_fs29.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
9485
9738
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
9486
9739
  console.log("restart Claude Code to pick up the new MCP server.");
9487
9740
  return { exitCode: 0 };
@@ -9519,12 +9772,12 @@ async function main() {
9519
9772
  console.error("neat init: --apply and --dry-run are mutually exclusive");
9520
9773
  process.exit(2);
9521
9774
  }
9522
- const scanPath = import_node_path44.default.resolve(target);
9775
+ const scanPath = import_node_path45.default.resolve(target);
9523
9776
  const projectExplicit = parsed.project !== null;
9524
- const projectName = projectExplicit ? project : import_node_path44.default.basename(scanPath);
9777
+ const projectName = projectExplicit ? project : import_node_path45.default.basename(scanPath);
9525
9778
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
9526
- const fallback = pathsForProject(projectKey, import_node_path44.default.join(scanPath, "neat-out")).snapshotPath;
9527
- const outPath = import_node_path44.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
9779
+ const fallback = pathsForProject(projectKey, import_node_path45.default.join(scanPath, "neat-out")).snapshotPath;
9780
+ const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
9528
9781
  const result = await runInit({
9529
9782
  scanPath,
9530
9783
  outPath,
@@ -9545,21 +9798,21 @@ async function main() {
9545
9798
  usage();
9546
9799
  process.exit(2);
9547
9800
  }
9548
- const scanPath = import_node_path44.default.resolve(target);
9549
- const stat = await import_node_fs28.promises.stat(scanPath).catch(() => null);
9801
+ const scanPath = import_node_path45.default.resolve(target);
9802
+ const stat = await import_node_fs29.promises.stat(scanPath).catch(() => null);
9550
9803
  if (!stat || !stat.isDirectory()) {
9551
9804
  console.error(`neat watch: ${scanPath} is not a directory`);
9552
9805
  process.exit(2);
9553
9806
  }
9554
- const projectPaths = pathsForProject(project, import_node_path44.default.join(scanPath, "neat-out"));
9555
- const outPath = import_node_path44.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
9556
- const errorsPath = import_node_path44.default.resolve(
9557
- process.env.NEAT_ERRORS_PATH ?? import_node_path44.default.join(import_node_path44.default.dirname(outPath), import_node_path44.default.basename(projectPaths.errorsPath))
9807
+ const projectPaths = pathsForProject(project, import_node_path45.default.join(scanPath, "neat-out"));
9808
+ const outPath = import_node_path45.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
9809
+ const errorsPath = import_node_path45.default.resolve(
9810
+ process.env.NEAT_ERRORS_PATH ?? import_node_path45.default.join(import_node_path45.default.dirname(outPath), import_node_path45.default.basename(projectPaths.errorsPath))
9558
9811
  );
9559
- const staleEventsPath = import_node_path44.default.resolve(
9560
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path44.default.join(import_node_path44.default.dirname(outPath), import_node_path44.default.basename(projectPaths.staleEventsPath))
9812
+ const staleEventsPath = import_node_path45.default.resolve(
9813
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path45.default.join(import_node_path45.default.dirname(outPath), import_node_path45.default.basename(projectPaths.staleEventsPath))
9561
9814
  );
9562
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path44.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
9815
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path45.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
9563
9816
  const handle = await startWatch(getGraph(project), {
9564
9817
  scanPath,
9565
9818
  outPath,
@@ -9705,11 +9958,11 @@ async function main() {
9705
9958
  process.exit(1);
9706
9959
  }
9707
9960
  async function tryOrchestrator(cmd, parsed) {
9708
- const scanPath = import_node_path44.default.resolve(cmd);
9709
- const stat = await import_node_fs28.promises.stat(scanPath).catch(() => null);
9961
+ const scanPath = import_node_path45.default.resolve(cmd);
9962
+ const stat = await import_node_fs29.promises.stat(scanPath).catch(() => null);
9710
9963
  if (!stat || !stat.isDirectory()) return null;
9711
9964
  const projectExplicit = parsed.project !== null;
9712
- const projectName = projectExplicit ? parsed.project : import_node_path44.default.basename(scanPath);
9965
+ const projectName = projectExplicit ? parsed.project : import_node_path45.default.basename(scanPath);
9713
9966
  const result = await runOrchestrator({
9714
9967
  scanPath,
9715
9968
  project: projectName,