@neat.is/core 0.4.5 → 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
  }
@@ -562,8 +562,8 @@ __export(cli_exports, {
562
562
  });
563
563
  module.exports = __toCommonJS(cli_exports);
564
564
  init_cjs_shims();
565
- var import_node_path44 = __toESM(require("path"), 1);
566
- var import_node_fs28 = require("fs");
565
+ var import_node_path45 = __toESM(require("path"), 1);
566
+ var import_node_fs29 = require("fs");
567
567
  var import_node_url3 = require("url");
568
568
 
569
569
  // src/graph.ts
@@ -1074,19 +1074,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1074
1074
  function longestIncomingWalk(graph, start, maxDepth) {
1075
1075
  let best = { path: [start], edges: [] };
1076
1076
  const visited = /* @__PURE__ */ new Set([start]);
1077
- function step(node, path45, edges) {
1078
- if (path45.length > best.path.length) {
1079
- best = { path: [...path45], edges: [...edges] };
1077
+ function step(node, path46, edges) {
1078
+ if (path46.length > best.path.length) {
1079
+ best = { path: [...path46], edges: [...edges] };
1080
1080
  }
1081
- if (path45.length - 1 >= maxDepth) return;
1081
+ if (path46.length - 1 >= maxDepth) return;
1082
1082
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1083
1083
  for (const [srcId, edge] of incoming) {
1084
1084
  if (visited.has(srcId)) continue;
1085
1085
  visited.add(srcId);
1086
- path45.push(srcId);
1086
+ path46.push(srcId);
1087
1087
  edges.push(edge);
1088
- step(srcId, path45, edges);
1089
- path45.pop();
1088
+ step(srcId, path46, edges);
1089
+ path46.pop();
1090
1090
  edges.pop();
1091
1091
  visited.delete(srcId);
1092
1092
  }
@@ -6753,13 +6753,20 @@ var SDK_PACKAGES = [
6753
6753
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
6754
6754
  { name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
6755
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
+ }
6756
6761
  function detectNonBundledInstrumentations(pkg) {
6757
6762
  const deps = allDeps(pkg);
6758
6763
  const out = [];
6759
6764
  if ("@prisma/client" in deps) {
6765
+ const prismaMajor = getMajor(deps["@prisma/client"]);
6766
+ const prismaInstrVersion = prismaMajor >= 6 ? "^6.0.0" : "^5.0.0";
6760
6767
  out.push({
6761
6768
  pkg: "@prisma/instrumentation",
6762
- version: "^5.0.0",
6769
+ version: prismaInstrVersion,
6763
6770
  registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
6764
6771
  });
6765
6772
  }
@@ -8088,19 +8095,100 @@ function renderPatch(sections) {
8088
8095
 
8089
8096
  // src/orchestrator.ts
8090
8097
  init_cjs_shims();
8091
- var import_node_fs27 = require("fs");
8098
+ var import_node_fs28 = require("fs");
8092
8099
  var import_node_http = __toESM(require("http"), 1);
8093
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");
8094
8108
  var import_node_path42 = __toESM(require("path"), 1);
8095
8109
  var import_node_child_process2 = require("child_process");
8096
- 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
8097
8185
  async function extractAndPersist(opts) {
8098
8186
  const services = await discoverServices(opts.scanPath);
8099
8187
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
8100
8188
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
8101
8189
  resetGraph(graphKey);
8102
8190
  const graph = getGraph(graphKey);
8103
- 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"));
8104
8192
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
8105
8193
  errorsPath: projectPaths.errorsPath
8106
8194
  });
@@ -8118,12 +8206,15 @@ async function extractAndPersist(opts) {
8118
8206
  errorsPath: projectPaths.errorsPath
8119
8207
  };
8120
8208
  }
8121
- async function applyInstallersOver(services, project) {
8209
+ async function applyInstallersOver(services, project, options = {}) {
8210
+ const resolveManager = options.resolveManager ?? detectPackageManager;
8211
+ const runInstall = options.runInstall ?? runPackageManagerInstall;
8122
8212
  let instrumented = 0;
8123
8213
  let already = 0;
8124
8214
  let libOnly = 0;
8125
8215
  let browserBundle = 0;
8126
8216
  let reactNative = 0;
8217
+ const installPlans = /* @__PURE__ */ new Map();
8127
8218
  for (const svc of services) {
8128
8219
  const installer = await pickInstaller(svc.dir);
8129
8220
  if (!installer) continue;
@@ -8133,8 +8224,14 @@ async function applyInstallersOver(services, project) {
8133
8224
  continue;
8134
8225
  }
8135
8226
  const outcome = await installer.apply(plan3);
8136
- if (outcome.outcome === "instrumented") instrumented++;
8137
- 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++;
8138
8235
  else if (outcome.outcome === "lib-only") libOnly++;
8139
8236
  else if (outcome.outcome === "browser-bundle") {
8140
8237
  browserBundle++;
@@ -8144,7 +8241,30 @@ async function applyInstallersOver(services, project) {
8144
8241
  console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
8145
8242
  }
8146
8243
  }
8147
- 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
+ };
8148
8268
  }
8149
8269
  async function promptYesNo(question) {
8150
8270
  const rl = import_node_readline.default.createInterface({ input: process.stdin, output: process.stdout });
@@ -8280,10 +8400,10 @@ function formatPortCollisionMessage(port) {
8280
8400
  ];
8281
8401
  }
8282
8402
  function spawnDaemonDetached() {
8283
- const here = import_node_path42.default.dirname(new URL(importMetaUrl).pathname);
8403
+ const here = import_node_path43.default.dirname(new URL(importMetaUrl).pathname);
8284
8404
  const candidates = [
8285
- import_node_path42.default.join(here, "neatd.cjs"),
8286
- 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")
8287
8407
  ];
8288
8408
  let entry2 = null;
8289
8409
  const fsSync = require("fs");
@@ -8303,7 +8423,7 @@ function spawnDaemonDetached() {
8303
8423
  if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
8304
8424
  env.HOST = "127.0.0.1";
8305
8425
  }
8306
- 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"], {
8307
8427
  detached: true,
8308
8428
  // stderr inherits the orchestrator's fd so the daemon's
8309
8429
  // `BindAuthorityError` message lands in front of the operator instead
@@ -8320,7 +8440,7 @@ function openBrowser(url) {
8320
8440
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
8321
8441
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
8322
8442
  try {
8323
- 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" });
8324
8444
  child.on("error", () => {
8325
8445
  });
8326
8446
  child.unref();
@@ -8341,7 +8461,7 @@ async function runOrchestrator(opts) {
8341
8461
  browser: "skipped"
8342
8462
  }
8343
8463
  };
8344
- 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);
8345
8465
  if (!stat || !stat.isDirectory()) {
8346
8466
  console.error(`neat: ${opts.scanPath} is not a directory`);
8347
8467
  result.exitCode = 2;
@@ -8409,6 +8529,10 @@ async function runOrchestrator(opts) {
8409
8529
  console.log(
8410
8530
  `instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
8411
8531
  );
8532
+ const failedInstalls = tally.packageManagerInstalls.filter((i) => i.exitCode !== 0);
8533
+ if (failedInstalls.length > 0) {
8534
+ result.exitCode = 1;
8535
+ }
8412
8536
  }
8413
8537
  const restPort = Number(process.env.PORT ?? 8080);
8414
8538
  const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
@@ -8480,7 +8604,7 @@ function printSummary(result, graph, dashboardUrl) {
8480
8604
 
8481
8605
  // src/cli-verbs.ts
8482
8606
  init_cjs_shims();
8483
- var import_node_path43 = __toESM(require("path"), 1);
8607
+ var import_node_path44 = __toESM(require("path"), 1);
8484
8608
 
8485
8609
  // src/cli-client.ts
8486
8610
  init_cjs_shims();
@@ -8505,10 +8629,10 @@ function createHttpClient(baseUrl, bearerToken) {
8505
8629
  const root = baseUrl.replace(/\/$/, "");
8506
8630
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
8507
8631
  return {
8508
- async get(path45) {
8632
+ async get(path46) {
8509
8633
  let res;
8510
8634
  try {
8511
- res = await fetch(`${root}${path45}`, {
8635
+ res = await fetch(`${root}${path46}`, {
8512
8636
  headers: { ...authHeader }
8513
8637
  });
8514
8638
  } catch (err) {
@@ -8520,16 +8644,16 @@ function createHttpClient(baseUrl, bearerToken) {
8520
8644
  const body = await res.text().catch(() => "");
8521
8645
  throw new HttpError(
8522
8646
  res.status,
8523
- `${res.status} ${res.statusText} on GET ${path45}: ${body}`,
8647
+ `${res.status} ${res.statusText} on GET ${path46}: ${body}`,
8524
8648
  body
8525
8649
  );
8526
8650
  }
8527
8651
  return await res.json();
8528
8652
  },
8529
- async post(path45, body) {
8653
+ async post(path46, body) {
8530
8654
  let res;
8531
8655
  try {
8532
- res = await fetch(`${root}${path45}`, {
8656
+ res = await fetch(`${root}${path46}`, {
8533
8657
  method: "POST",
8534
8658
  headers: { "content-type": "application/json", ...authHeader },
8535
8659
  body: JSON.stringify(body)
@@ -8543,7 +8667,7 @@ function createHttpClient(baseUrl, bearerToken) {
8543
8667
  const text = await res.text().catch(() => "");
8544
8668
  throw new HttpError(
8545
8669
  res.status,
8546
- `${res.status} ${res.statusText} on POST ${path45}: ${text}`,
8670
+ `${res.status} ${res.statusText} on POST ${path46}: ${text}`,
8547
8671
  text
8548
8672
  );
8549
8673
  }
@@ -8557,12 +8681,12 @@ function projectPath(project, suffix) {
8557
8681
  }
8558
8682
  async function runRootCause(client, input) {
8559
8683
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
8560
- const path45 = projectPath(
8684
+ const path46 = projectPath(
8561
8685
  input.project,
8562
8686
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
8563
8687
  );
8564
8688
  try {
8565
- const result = await client.get(path45);
8689
+ const result = await client.get(path46);
8566
8690
  const arrowPath = result.traversalPath.join(" \u2190 ");
8567
8691
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
8568
8692
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -8588,12 +8712,12 @@ async function runRootCause(client, input) {
8588
8712
  }
8589
8713
  async function runBlastRadius(client, input) {
8590
8714
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
8591
- const path45 = projectPath(
8715
+ const path46 = projectPath(
8592
8716
  input.project,
8593
8717
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
8594
8718
  );
8595
8719
  try {
8596
- const result = await client.get(path45);
8720
+ const result = await client.get(path46);
8597
8721
  if (result.totalAffected === 0) {
8598
8722
  return {
8599
8723
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -8627,12 +8751,12 @@ function formatBlastEntry(n) {
8627
8751
  }
8628
8752
  async function runDependencies(client, input) {
8629
8753
  const depth = input.depth ?? 3;
8630
- const path45 = projectPath(
8754
+ const path46 = projectPath(
8631
8755
  input.project,
8632
8756
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
8633
8757
  );
8634
8758
  try {
8635
- const result = await client.get(path45);
8759
+ const result = await client.get(path46);
8636
8760
  if (result.total === 0) {
8637
8761
  return {
8638
8762
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -8713,9 +8837,9 @@ function formatDuration(ms) {
8713
8837
  return `${Math.round(h / 24)}d`;
8714
8838
  }
8715
8839
  async function runIncidents(client, input) {
8716
- 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");
8717
8841
  try {
8718
- const body = await client.get(path45);
8842
+ const body = await client.get(path46);
8719
8843
  const events = body.events;
8720
8844
  if (events.length === 0) {
8721
8845
  return {
@@ -9005,7 +9129,7 @@ async function resolveProjectEntry(opts) {
9005
9129
  const cwd = opts.cwd ?? process.cwd();
9006
9130
  const resolvedCwd = await normalizeProjectPath(cwd);
9007
9131
  for (const entry2 of entries) {
9008
- 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}`)) {
9009
9133
  return entry2;
9010
9134
  }
9011
9135
  }
@@ -9365,14 +9489,14 @@ function assignFlag(out, field, value) {
9365
9489
  out[field] = value;
9366
9490
  }
9367
9491
  function readPackageVersion() {
9368
- 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));
9369
9493
  const candidates = [
9370
- import_node_path44.default.resolve(here, "../package.json"),
9371
- 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")
9372
9496
  ];
9373
9497
  for (const candidate of candidates) {
9374
9498
  try {
9375
- const raw = (0, import_node_fs28.readFileSync)(candidate, "utf8");
9499
+ const raw = (0, import_node_fs29.readFileSync)(candidate, "utf8");
9376
9500
  const parsed = JSON.parse(raw);
9377
9501
  if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
9378
9502
  return parsed.version;
@@ -9436,7 +9560,7 @@ async function buildPatchSections(services, project) {
9436
9560
  }
9437
9561
  async function runInit(opts) {
9438
9562
  const written = [];
9439
- 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);
9440
9564
  if (!stat || !stat.isDirectory()) {
9441
9565
  console.error(`neat init: ${opts.scanPath} is not a directory`);
9442
9566
  return { exitCode: 2, writtenFiles: written };
@@ -9445,13 +9569,13 @@ async function runInit(opts) {
9445
9569
  printDiscoveryReport(opts, services);
9446
9570
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
9447
9571
  const patch = renderPatch(sections);
9448
- const patchPath = import_node_path44.default.join(opts.scanPath, "neat.patch");
9572
+ const patchPath = import_node_path45.default.join(opts.scanPath, "neat.patch");
9449
9573
  if (opts.dryRun) {
9450
- await import_node_fs28.promises.writeFile(patchPath, patch, "utf8");
9574
+ await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
9451
9575
  written.push(patchPath);
9452
9576
  console.log(`dry-run: patch written to ${patchPath}`);
9453
- const gitignorePath = import_node_path44.default.join(opts.scanPath, ".gitignore");
9454
- 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);
9455
9579
  const verb = gitignoreExists ? "append" : "create";
9456
9580
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
9457
9581
  console.log("rerun without --dry-run to register and snapshot.");
@@ -9462,9 +9586,9 @@ async function runInit(opts) {
9462
9586
  const graph = getGraph(graphKey);
9463
9587
  const projectPaths = pathsForProject(
9464
9588
  graphKey,
9465
- import_node_path44.default.join(opts.scanPath, "neat-out")
9589
+ import_node_path45.default.join(opts.scanPath, "neat-out")
9466
9590
  );
9467
- 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));
9468
9592
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
9469
9593
  await saveGraphToDisk(graph, opts.outPath);
9470
9594
  written.push(opts.outPath);
@@ -9543,7 +9667,7 @@ async function runInit(opts) {
9543
9667
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
9544
9668
  }
9545
9669
  } else {
9546
- await import_node_fs28.promises.writeFile(patchPath, patch, "utf8");
9670
+ await import_node_fs29.promises.writeFile(patchPath, patch, "utf8");
9547
9671
  written.push(patchPath);
9548
9672
  }
9549
9673
  }
@@ -9583,9 +9707,9 @@ var CLAUDE_SKILL_CONFIG = {
9583
9707
  };
9584
9708
  function claudeConfigPath() {
9585
9709
  const override = process.env.NEAT_CLAUDE_CONFIG;
9586
- 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);
9587
9711
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9588
- return import_node_path44.default.join(home, ".claude.json");
9712
+ return import_node_path45.default.join(home, ".claude.json");
9589
9713
  }
9590
9714
  async function runSkill(opts) {
9591
9715
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -9597,7 +9721,7 @@ async function runSkill(opts) {
9597
9721
  const target = claudeConfigPath();
9598
9722
  let existing = {};
9599
9723
  try {
9600
- existing = JSON.parse(await import_node_fs28.promises.readFile(target, "utf8"));
9724
+ existing = JSON.parse(await import_node_fs29.promises.readFile(target, "utf8"));
9601
9725
  } catch (err) {
9602
9726
  if (err.code !== "ENOENT") {
9603
9727
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -9609,8 +9733,8 @@ async function runSkill(opts) {
9609
9733
  ...existing,
9610
9734
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
9611
9735
  };
9612
- await import_node_fs28.promises.mkdir(import_node_path44.default.dirname(target), { recursive: true });
9613
- 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");
9614
9738
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
9615
9739
  console.log("restart Claude Code to pick up the new MCP server.");
9616
9740
  return { exitCode: 0 };
@@ -9648,12 +9772,12 @@ async function main() {
9648
9772
  console.error("neat init: --apply and --dry-run are mutually exclusive");
9649
9773
  process.exit(2);
9650
9774
  }
9651
- const scanPath = import_node_path44.default.resolve(target);
9775
+ const scanPath = import_node_path45.default.resolve(target);
9652
9776
  const projectExplicit = parsed.project !== null;
9653
- const projectName = projectExplicit ? project : import_node_path44.default.basename(scanPath);
9777
+ const projectName = projectExplicit ? project : import_node_path45.default.basename(scanPath);
9654
9778
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
9655
- const fallback = pathsForProject(projectKey, import_node_path44.default.join(scanPath, "neat-out")).snapshotPath;
9656
- 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);
9657
9781
  const result = await runInit({
9658
9782
  scanPath,
9659
9783
  outPath,
@@ -9674,21 +9798,21 @@ async function main() {
9674
9798
  usage();
9675
9799
  process.exit(2);
9676
9800
  }
9677
- const scanPath = import_node_path44.default.resolve(target);
9678
- 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);
9679
9803
  if (!stat || !stat.isDirectory()) {
9680
9804
  console.error(`neat watch: ${scanPath} is not a directory`);
9681
9805
  process.exit(2);
9682
9806
  }
9683
- const projectPaths = pathsForProject(project, import_node_path44.default.join(scanPath, "neat-out"));
9684
- const outPath = import_node_path44.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
9685
- const errorsPath = import_node_path44.default.resolve(
9686
- 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))
9687
9811
  );
9688
- const staleEventsPath = import_node_path44.default.resolve(
9689
- 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))
9690
9814
  );
9691
- 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;
9692
9816
  const handle = await startWatch(getGraph(project), {
9693
9817
  scanPath,
9694
9818
  outPath,
@@ -9834,11 +9958,11 @@ async function main() {
9834
9958
  process.exit(1);
9835
9959
  }
9836
9960
  async function tryOrchestrator(cmd, parsed) {
9837
- const scanPath = import_node_path44.default.resolve(cmd);
9838
- 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);
9839
9963
  if (!stat || !stat.isDirectory()) return null;
9840
9964
  const projectExplicit = parsed.project !== null;
9841
- const projectName = projectExplicit ? parsed.project : import_node_path44.default.basename(scanPath);
9965
+ const projectName = projectExplicit ? parsed.project : import_node_path45.default.basename(scanPath);
9842
9966
  const result = await runOrchestrator({
9843
9967
  scanPath,
9844
9968
  project: projectName,