@neat.is/core 0.10.6-dev.20261007 → 0.10.6-dev.20261008

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.
Files changed (46) hide show
  1. package/dist/{chunk-KTJK2PAH.js → chunk-6QMWJAWR.js} +204 -609
  2. package/dist/chunk-6QMWJAWR.js.map +1 -0
  3. package/dist/{chunk-VL7ANDPI.js → chunk-EMFX3V77.js} +10 -8
  4. package/dist/{chunk-VL7ANDPI.js.map → chunk-EMFX3V77.js.map} +1 -1
  5. package/dist/chunk-MR64ICXX.js +19 -0
  6. package/dist/chunk-MR64ICXX.js.map +1 -0
  7. package/dist/{chunk-EFBWQIKG.js → chunk-QPG2QXCW.js} +11 -27
  8. package/dist/chunk-QPG2QXCW.js.map +1 -0
  9. package/dist/chunk-RGYC5U2Q.js +511 -0
  10. package/dist/chunk-RGYC5U2Q.js.map +1 -0
  11. package/dist/{chunk-LXQCL4MP.js → chunk-TZXSWSHX.js} +2 -2
  12. package/dist/{chunk-BC53SCT7.js → chunk-VFZ6CXLI.js} +20 -6
  13. package/dist/chunk-VFZ6CXLI.js.map +1 -0
  14. package/dist/cli.cjs +300 -74
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.d.cts +2 -0
  17. package/dist/cli.d.ts +2 -0
  18. package/dist/cli.js +249 -76
  19. package/dist/cli.js.map +1 -1
  20. package/dist/index.cjs +58 -15
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +3 -99
  23. package/dist/index.d.ts +3 -99
  24. package/dist/index.js +18 -15
  25. package/dist/neatd.cjs +58 -15
  26. package/dist/neatd.cjs.map +1 -1
  27. package/dist/neatd.js +7 -4
  28. package/dist/neatd.js.map +1 -1
  29. package/dist/otel-grpc-CCCI772O.js +11 -0
  30. package/dist/registry.cjs +570 -0
  31. package/dist/registry.cjs.map +1 -0
  32. package/dist/registry.d.cts +220 -0
  33. package/dist/registry.d.ts +220 -0
  34. package/dist/registry.js +62 -0
  35. package/dist/registry.js.map +1 -0
  36. package/dist/server.cjs +78 -21
  37. package/dist/server.cjs.map +1 -1
  38. package/dist/server.js +10 -6
  39. package/dist/server.js.map +1 -1
  40. package/package.json +8 -3
  41. package/dist/chunk-BC53SCT7.js.map +0 -1
  42. package/dist/chunk-EFBWQIKG.js.map +0 -1
  43. package/dist/chunk-KTJK2PAH.js.map +0 -1
  44. package/dist/otel-grpc-B7ABHLV7.js +0 -10
  45. /package/dist/{chunk-LXQCL4MP.js.map → chunk-TZXSWSHX.js.map} +0 -0
  46. /package/dist/{otel-grpc-B7ABHLV7.js.map → otel-grpc-CCCI772O.js.map} +0 -0
package/dist/cli.cjs CHANGED
@@ -6724,7 +6724,29 @@ function ensureFrontierNode(graph, host, ts) {
6724
6724
  graph.addNode(id, node);
6725
6725
  return id;
6726
6726
  }
6727
- function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence, durationMs) {
6727
+ var EDGE_ERROR_MESSAGE_MAX = 512;
6728
+ function buildEdgeErrorExemplar(detail, ts) {
6729
+ return {
6730
+ ...detail.exceptionType !== void 0 ? { exceptionType: detail.exceptionType } : {},
6731
+ ...detail.message !== void 0 ? { message: detail.message.slice(0, EDGE_ERROR_MESSAGE_MAX) } : {},
6732
+ ...detail.httpStatusCode !== void 0 ? { httpStatusCode: detail.httpStatusCode } : {},
6733
+ at: ts
6734
+ };
6735
+ }
6736
+ function edgeErrorDetailFromSpan(span) {
6737
+ const exceptionType = span.exception?.type;
6738
+ const message = span.exception?.message;
6739
+ const httpStatusCode = httpResponseStatus(span);
6740
+ if (exceptionType === void 0 && message === void 0 && httpStatusCode === void 0) {
6741
+ return void 0;
6742
+ }
6743
+ return {
6744
+ ...exceptionType !== void 0 ? { exceptionType } : {},
6745
+ ...message !== void 0 ? { message } : {},
6746
+ ...httpStatusCode !== void 0 ? { httpStatusCode } : {}
6747
+ };
6748
+ }
6749
+ function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence, durationMs, errorDetail) {
6728
6750
  if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
6729
6751
  const grain = source.startsWith("file:") || source.startsWith("symbol:") ? "file" : "service";
6730
6752
  const id = makeObservedEdgeId(type, source, target);
@@ -6734,13 +6756,15 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
6734
6756
  const newErrorCount = (existing.signal?.errorCount ?? 0) + (isError ? 1 : 0);
6735
6757
  const latencyHist2 = durationMs !== void 0 ? recordLatency({ ...existing.signal?.latencyHist ?? {} }, durationMs) : existing.signal?.latencyHist;
6736
6758
  const latencyMs2 = latencyPercentiles(latencyHist2) ?? existing.signal?.latencyMs;
6759
+ const lastError2 = isError && errorDetail ? buildEdgeErrorExemplar(errorDetail, ts) : existing.signal?.lastError;
6737
6760
  const newSignal = {
6738
6761
  spanCount: newSpanCount,
6739
6762
  errorCount: newErrorCount,
6740
6763
  lastObservedAgeMs: 0,
6741
6764
  ...latencyHist2 ? { latencyHist: latencyHist2 } : {},
6742
6765
  ...latencyMs2 ? { latencyMs: latencyMs2 } : {},
6743
- ...existing.signal?.anomalous !== void 0 ? { anomalous: existing.signal.anomalous } : {}
6766
+ ...existing.signal?.anomalous !== void 0 ? { anomalous: existing.signal.anomalous } : {},
6767
+ ...lastError2 ? { lastError: lastError2 } : {}
6744
6768
  };
6745
6769
  const updated = {
6746
6770
  ...existing,
@@ -6757,12 +6781,14 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false, ev
6757
6781
  }
6758
6782
  const latencyHist = durationMs !== void 0 ? recordLatency({}, durationMs) : void 0;
6759
6783
  const latencyMs = latencyHist ? latencyPercentiles(latencyHist) : void 0;
6784
+ const lastError = isError && errorDetail ? buildEdgeErrorExemplar(errorDetail, ts) : void 0;
6760
6785
  const signal = {
6761
6786
  spanCount: 1,
6762
6787
  errorCount: isError ? 1 : 0,
6763
6788
  lastObservedAgeMs: 0,
6764
6789
  ...latencyHist ? { latencyHist } : {},
6765
- ...latencyMs ? { latencyMs } : {}
6790
+ ...latencyMs ? { latencyMs } : {},
6791
+ ...lastError ? { lastError } : {}
6766
6792
  };
6767
6793
  const edge = {
6768
6794
  id,
@@ -7103,6 +7129,7 @@ async function handleSpan(ctx, span) {
7103
7129
  const sourceId = ensureServiceNode(ctx.graph, span.service, env);
7104
7130
  const isError = spanRecordsError(span);
7105
7131
  const durationMs = span.durationNanos > 0n && !spanIsStreaming(span) ? Number(span.durationNanos) / 1e6 : void 0;
7132
+ const errorDetail = isError ? edgeErrorDetailFromSpan(span) : void 0;
7106
7133
  const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
7107
7134
  const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
7108
7135
  cacheSpanService(span, nowMs, callSite);
@@ -7142,7 +7169,8 @@ async function handleSpan(ctx, span) {
7142
7169
  ts,
7143
7170
  isError,
7144
7171
  callSiteEvidence,
7145
- durationMs
7172
+ durationMs,
7173
+ errorDetail
7146
7174
  );
7147
7175
  if (result) affectedNode = targetId;
7148
7176
  if (span.dbSystem === "mongodb" && span.dbCollection) {
@@ -7155,7 +7183,8 @@ async function handleSpan(ctx, span) {
7155
7183
  ts,
7156
7184
  isError,
7157
7185
  callSiteEvidence,
7158
- durationMs
7186
+ durationMs,
7187
+ errorDetail
7159
7188
  );
7160
7189
  }
7161
7190
  if (span.dbTable) {
@@ -7168,7 +7197,8 @@ async function handleSpan(ctx, span) {
7168
7197
  ts,
7169
7198
  isError,
7170
7199
  callSiteEvidence,
7171
- durationMs
7200
+ durationMs,
7201
+ errorDetail
7172
7202
  );
7173
7203
  mergeObservedColumns(ctx.graph, tableId, span.dbColumns);
7174
7204
  }
@@ -7188,7 +7218,8 @@ async function handleSpan(ctx, span) {
7188
7218
  ts,
7189
7219
  isError,
7190
7220
  callSiteEvidence,
7191
- durationMs
7221
+ durationMs,
7222
+ errorDetail
7192
7223
  );
7193
7224
  if (result) affectedNode = targetId;
7194
7225
  } else if (span.graphqlOperationName && span.graphqlOperationType && spanServesGraphqlOperation(span.kind)) {
@@ -7206,7 +7237,8 @@ async function handleSpan(ctx, span) {
7206
7237
  ts,
7207
7238
  isError,
7208
7239
  callSiteEvidence,
7209
- durationMs
7240
+ durationMs,
7241
+ errorDetail
7210
7242
  );
7211
7243
  if (result) affectedNode = targetId;
7212
7244
  } else if (span.rpcSystem === "grpc" && span.rpcService && span.rpcMethod && spanServesGrpcMethod(span.kind)) {
@@ -7219,7 +7251,8 @@ async function handleSpan(ctx, span) {
7219
7251
  ts,
7220
7252
  isError,
7221
7253
  callSiteEvidence,
7222
- durationMs
7254
+ durationMs,
7255
+ errorDetail
7223
7256
  );
7224
7257
  if (result) affectedNode = targetId;
7225
7258
  } else if (span.websocketChannel && spanServesWebsocketChannel(span.kind)) {
@@ -7236,7 +7269,8 @@ async function handleSpan(ctx, span) {
7236
7269
  ts,
7237
7270
  isError,
7238
7271
  callSiteEvidence,
7239
- durationMs
7272
+ durationMs,
7273
+ errorDetail
7240
7274
  );
7241
7275
  if (result) affectedNode = targetId;
7242
7276
  } else {
@@ -7253,7 +7287,8 @@ async function handleSpan(ctx, span) {
7253
7287
  ts,
7254
7288
  isError,
7255
7289
  callSiteEvidence,
7256
- durationMs
7290
+ durationMs,
7291
+ errorDetail
7257
7292
  );
7258
7293
  affectedNode = targetId;
7259
7294
  resolvedViaAddress = true;
@@ -7267,7 +7302,8 @@ async function handleSpan(ctx, span) {
7267
7302
  ts,
7268
7303
  isError,
7269
7304
  callSiteEvidence,
7270
- durationMs
7305
+ durationMs,
7306
+ errorDetail
7271
7307
  );
7272
7308
  affectedNode = frontierNodeId;
7273
7309
  resolvedViaAddress = true;
@@ -7294,7 +7330,8 @@ async function handleSpan(ctx, span) {
7294
7330
  ts,
7295
7331
  isError,
7296
7332
  fallbackEvidence,
7297
- durationMs
7333
+ durationMs,
7334
+ errorDetail
7298
7335
  );
7299
7336
  }
7300
7337
  }
@@ -7309,7 +7346,7 @@ async function handleSpan(ctx, span) {
7309
7346
  );
7310
7347
  if (routeNodeId) {
7311
7348
  const routeSvc = ctx.graph.getNodeAttributes(routeNodeId).service;
7312
- upsertObservedEdge(ctx.graph, import_types9.EdgeType.CONTAINS, (0, import_types9.serviceId)(routeSvc), routeNodeId, ts, isError, void 0, durationMs);
7349
+ upsertObservedEdge(ctx.graph, import_types9.EdgeType.CONTAINS, (0, import_types9.serviceId)(routeSvc), routeNodeId, ts, isError, void 0, durationMs, errorDetail);
7313
7350
  }
7314
7351
  }
7315
7352
  if (span.statusCode === 2) {
@@ -17226,6 +17263,11 @@ function detectObservedFailingEdge(graph, bucket) {
17226
17263
  spanCount,
17227
17264
  errorCount,
17228
17265
  errorRate: clampConfidence(errorRate),
17266
+ // The bounded last-error exemplar rides the finding as evidence (ADR-236):
17267
+ // the error rate says HOW MUCH is failing, the exemplar says WHAT failed —
17268
+ // a timeout vs a deadlock vs a connection-refused, which `errorCount`
17269
+ // alone cannot name. Present only when the observed edge carries one.
17270
+ ...signal.lastError ? { lastError: signal.lastError } : {},
17229
17271
  confidence: OBSERVED_FAILING_CONFIDENCE,
17230
17272
  reason: `Code declares ${bucket.source} \u2192 ${bucket.target} (${bucket.type}) and production observes it, but ${errorCount}/${spanCount} observed calls fail (${pct}% error rate) \u2014 the declared dependency is predominantly failing. The declared intent (EXTRACTED) and the observed behaviour (OBSERVED) diverge.`,
17231
17273
  recommendation: "Treat this as a broken declared dependency, not a coverage gap: production runs the call the code declares and most calls error. Check the dependency\u2019s health, recent deploys, and the observed error responses on this edge."
@@ -17726,11 +17768,20 @@ ${NEAT_OUT_LINE}
17726
17768
  // src/summary.ts
17727
17769
  init_cjs_shims();
17728
17770
  var import_types62 = require("@neat.is/types");
17771
+
17772
+ // src/otlp-headers.ts
17773
+ init_cjs_shims();
17774
+ var OTLP_BEARER_PREFIX = "Authorization=Bearer%20";
17775
+ function otlpBearerHeader(token) {
17776
+ return OTLP_BEARER_PREFIX + encodeURIComponent(token);
17777
+ }
17778
+
17779
+ // src/summary.ts
17729
17780
  function renderOtelEnvBlock() {
17730
17781
  return [
17731
17782
  "for prod OTel routing, set these in your deploy platform's env:",
17732
17783
  " OTEL_EXPORTER_OTLP_ENDPOINT=https://<your-neat-host>:4318",
17733
- " OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <NEAT_AUTH_TOKEN>"
17784
+ ` OTEL_EXPORTER_OTLP_HEADERS=${OTLP_BEARER_PREFIX}<NEAT_AUTH_TOKEN>`
17734
17785
  ].join("\n");
17735
17786
  }
17736
17787
  function findIncompatServices(nodes) {
@@ -20327,7 +20378,7 @@ async function computePrVerdict(req, ctx, deps, deadline) {
20327
20378
  const found = observedBreakFrom(node, getObservedDependencies(ctx.liveGraph, node.id));
20328
20379
  if (found) observedBreaks.push(found);
20329
20380
  }
20330
- const { marker, body } = renderVerdict({
20381
+ const { marker, body: rendered } = renderVerdict({
20331
20382
  graph: head,
20332
20383
  delta,
20333
20384
  changedFiles,
@@ -20335,6 +20386,7 @@ async function computePrVerdict(req, ctx, deps, deadline) {
20335
20386
  observedBreaks,
20336
20387
  tone: req.tone ?? "loud"
20337
20388
  });
20389
+ const body = rendered.startsWith(marker) ? rendered.slice(marker.length).replace(/^(?:\r?\n)+/, "") : rendered;
20338
20390
  return {
20339
20391
  marker,
20340
20392
  body,
@@ -26147,13 +26199,25 @@ async function makeTransformersEmbedder() {
26147
26199
  }
26148
26200
  };
26149
26201
  }
26150
- async function pickEmbedder() {
26151
- const host = ollamaHost();
26152
- if (host && await ollamaReachable(host)) {
26153
- return makeOllamaEmbedder(host);
26202
+ async function pickEmbedder(provider) {
26203
+ if (provider !== "transformers") {
26204
+ const host = ollamaHost();
26205
+ if (host && await ollamaReachable(host)) {
26206
+ return makeOllamaEmbedder(host);
26207
+ }
26154
26208
  }
26209
+ if (provider === "ollama") return null;
26155
26210
  return makeTransformersEmbedder();
26156
26211
  }
26212
+ function searchProviderFromEnv(env = process.env, warn2 = console.warn) {
26213
+ const value = env.NEAT_SEARCH_PROVIDER;
26214
+ if (!value) return void 0;
26215
+ if (value === "substring" || value === "ollama" || value === "transformers") return value;
26216
+ warn2(
26217
+ `semantic_search: NEAT_SEARCH_PROVIDER="${value}" is not substring, ollama, or transformers; using automatic selection`
26218
+ );
26219
+ return void 0;
26220
+ }
26157
26221
  async function readCache(cachePath) {
26158
26222
  try {
26159
26223
  const raw = await import_node_fs49.promises.readFile(cachePath, "utf8");
@@ -26317,7 +26381,8 @@ async function buildSearchIndex(graph, options = {}) {
26317
26381
  if (options.embedder) {
26318
26382
  embedder = options.embedder;
26319
26383
  } else if (options.forceProvider !== "substring") {
26320
- const factory = options.embedderFactory ?? pickEmbedder;
26384
+ const forcedProvider = options.forceProvider;
26385
+ const factory = options.embedderFactory ?? (() => pickEmbedder(forcedProvider));
26321
26386
  embedder = await resolveEmbedderBounded(
26322
26387
  factory,
26323
26388
  options.initTimeoutMs ?? searchInitTimeoutMs()
@@ -26596,9 +26661,10 @@ async function startWatch(graph, opts) {
26596
26661
  const port = opts.port ?? 8080;
26597
26662
  const otelPort = opts.otelPort ?? 4318;
26598
26663
  const cachePath = opts.embeddingsCachePath ?? import_node_path86.default.join(import_node_path86.default.dirname(opts.outPath), "embeddings.json");
26664
+ const forceProvider = searchProviderFromEnv();
26599
26665
  let searchIndex;
26600
26666
  try {
26601
- searchIndex = await buildSearchIndex(graph, { cachePath });
26667
+ searchIndex = await buildSearchIndex(graph, { cachePath, forceProvider });
26602
26668
  console.log(`semantic_search: ${searchIndex.provider} provider`);
26603
26669
  } catch (err) {
26604
26670
  console.warn(
@@ -26882,7 +26948,7 @@ function emitDockerRunSnippet() {
26882
26948
  function renderOtelEnvBlock2(token, host = "<host>") {
26883
26949
  return [
26884
26950
  `OTEL_EXPORTER_OTLP_ENDPOINT=https://${host}:4318`,
26885
- `OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${token}`,
26951
+ `OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeader(token)}`,
26886
26952
  "OTEL_SERVICE_NAME=<service>"
26887
26953
  ].join("\n");
26888
26954
  }
@@ -26947,7 +27013,7 @@ var import_semver2 = __toESM(require("semver"), 1);
26947
27013
  init_cjs_shims();
26948
27014
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
26949
27015
  var OTEL_INIT_STAMP = "// neat-template-version: 8 \u2014 CommonJS TypeScript uses a synchronous bootstrap (#910); OTel init degrades to no-OBSERVED when dependencies are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
26950
- var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
27016
+ var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer%20' + encodeURIComponent(process.env.NEAT_OTEL_TOKEN)";
26951
27017
  var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
26952
27018
  var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
26953
27019
  try {
@@ -27392,7 +27458,31 @@ ${registrations.join("\n")}
27392
27458
  `;
27393
27459
  return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
27394
27460
  }
27395
- function renderEnvNeat(serviceName, projectName) {
27461
+ function renderEnvNeat(serviceName, projectName, nodeOptions, extraInstrumentations = []) {
27462
+ if (nodeOptions) {
27463
+ return [
27464
+ "# Generated by `neat init --apply` / `neat <path>` (ADR-069, ADR-232).",
27465
+ "# NEAT attaches its OpenTelemetry bootstrap at process start; no source is edited.",
27466
+ "# NODE_OPTIONS is read once when the process starts, so load this file before",
27467
+ "# your start command:",
27468
+ "# set -a; . ./.env.neat; set +a; <your start command>",
27469
+ "# or copy these settings into your process manager / platform environment.",
27470
+ `OTEL_SERVICE_NAME=${serviceName}`,
27471
+ "# The preload sends spans to this project's daemon, read from",
27472
+ "# <project>/neat-out/daemon.json; NEAT_PROJECT names the route when no record",
27473
+ "# is found. Set OTEL_EXPORTER_OTLP_ENDPOINT only to send spans elsewhere.",
27474
+ `NEAT_PROJECT=${projectName}`,
27475
+ "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
27476
+ "# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
27477
+ "# NEAT_OTEL_TOKEN=",
27478
+ ...extraInstrumentations.length > 0 ? [
27479
+ "# Instrumentations outside the auto bundle, loaded by the preload (<package>#<export>).",
27480
+ `NEAT_OTEL_INSTRUMENTATIONS=${extraInstrumentations.join(",")}`
27481
+ ] : [],
27482
+ `NODE_OPTIONS="${nodeOptions}"`,
27483
+ ""
27484
+ ].join("\n");
27485
+ }
27396
27486
  return [
27397
27487
  "# Generated by `neat init --apply` (ADR-069).",
27398
27488
  `OTEL_SERVICE_NAME=${serviceName}`,
@@ -27588,14 +27678,16 @@ function detectNonBundledInstrumentations(pkg) {
27588
27678
  out.push({
27589
27679
  pkg: "@prisma/instrumentation",
27590
27680
  version: prismaInstrVersion,
27591
- registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
27681
+ registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())",
27682
+ exportName: "PrismaInstrumentation"
27592
27683
  });
27593
27684
  }
27594
27685
  if (getMajor(deps["@nestjs/core"]) >= 11) {
27595
27686
  out.push({
27596
27687
  pkg: "@opentelemetry/instrumentation-nestjs-core",
27597
27688
  version: "^0.67.0",
27598
- registration: "instrumentations.push(new (require('@opentelemetry/instrumentation-nestjs-core').NestInstrumentation)())"
27689
+ registration: "instrumentations.push(new (require('@opentelemetry/instrumentation-nestjs-core').NestInstrumentation)())",
27690
+ exportName: "NestInstrumentation"
27599
27691
  });
27600
27692
  }
27601
27693
  return out;
@@ -28406,6 +28498,49 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
28406
28498
  }
28407
28499
  return null;
28408
28500
  }
28501
+ var ATTACH_PACKAGE_NAME = "@neat.is/otel-node";
28502
+ function attachPackageRange(coreVersion = readPackageVersion()) {
28503
+ return import_semver2.default.valid(coreVersion) ? `^${coreVersion}` : "latest";
28504
+ }
28505
+ async function planAttachment(serviceDir, pkg, manifestPath, project) {
28506
+ const svcName = serviceNodeName2(pkg, serviceDir);
28507
+ const projectName = projectToken(pkg, serviceDir, project);
28508
+ const envNeatFile = import_node_path88.default.join(serviceDir, ".env.neat");
28509
+ const flag = pkg.type === "module" ? "--import" : "--require";
28510
+ const nodeOptions = `${flag} @neat.is/otel-node/register`;
28511
+ const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
28512
+ const dependencyEdits = [];
28513
+ if (!(ATTACH_PACKAGE_NAME in existingDeps)) {
28514
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: ATTACH_PACKAGE_NAME, version: attachPackageRange() });
28515
+ }
28516
+ const nonBundled = detectNonBundledInstrumentations(pkg);
28517
+ for (const inst of nonBundled) {
28518
+ if (inst.pkg in existingDeps) {
28519
+ if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
28520
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
28521
+ }
28522
+ continue;
28523
+ }
28524
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
28525
+ }
28526
+ const extraInstrumentations = nonBundled.map((i) => `${i.pkg}#${i.exportName}`);
28527
+ const generatedFiles = [];
28528
+ if (!await exists3(envNeatFile)) {
28529
+ generatedFiles.push({
28530
+ file: envNeatFile,
28531
+ contents: renderEnvNeat(svcName, projectName, nodeOptions, extraInstrumentations),
28532
+ skipIfExists: true
28533
+ });
28534
+ }
28535
+ return {
28536
+ language: "javascript",
28537
+ serviceDir,
28538
+ dependencyEdits,
28539
+ entrypointEdits: [],
28540
+ envEdits: [OTEL_ENV],
28541
+ generatedFiles
28542
+ };
28543
+ }
28409
28544
  async function plan(serviceDir, opts) {
28410
28545
  const pkg = await readPackageJson2(serviceDir);
28411
28546
  const manifestPath = import_node_path88.default.join(serviceDir, "package.json");
@@ -28431,14 +28566,17 @@ async function plan(serviceDir, opts) {
28431
28566
  if (!entryFile) {
28432
28567
  return { ...empty, libOnly: true };
28433
28568
  }
28569
+ const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
28570
+ if (runtimeKind !== "node") {
28571
+ return { ...empty, runtimeKind };
28572
+ }
28573
+ }
28574
+ if (!opts?.sourceEdit) {
28575
+ return planAttachment(serviceDir, pkg, manifestPath, project);
28434
28576
  }
28435
28577
  if (frameworkDispatch) {
28436
28578
  return frameworkDispatch();
28437
28579
  }
28438
- const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
28439
- if (runtimeKind !== "node") {
28440
- return { ...empty, runtimeKind };
28441
- }
28442
28580
  if (!entryFile) {
28443
28581
  return { ...empty, libOnly: true };
28444
28582
  }
@@ -28756,6 +28894,7 @@ var SDK_PACKAGES2 = [
28756
28894
  { name: "opentelemetry-distro", version: ">=0.49b0" },
28757
28895
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
28758
28896
  ];
28897
+ var NEAT_OTEL_PACKAGE = { name: "neat-otel", version: ">=0.1.0" };
28759
28898
  var OTEL_ENV2 = {
28760
28899
  file: null,
28761
28900
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
@@ -28907,34 +29046,37 @@ function reqPackageName(line) {
28907
29046
  const head = stripped.split(/[\s;]/)[0] ?? "";
28908
29047
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
28909
29048
  }
28910
- async function planRequirementsTxtEdits(serviceDir) {
29049
+ async function planRequirementsTxtEdits(serviceDir, packages = SDK_PACKAGES2) {
28911
29050
  const file = import_node_path89.default.join(serviceDir, "requirements.txt");
28912
29051
  if (!await exists4(file)) return null;
28913
29052
  const raw = await import_node_fs53.promises.readFile(file, "utf8");
28914
29053
  const presentNames = new Set(
28915
29054
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
28916
29055
  );
28917
- const missing = SDK_PACKAGES2.filter((p) => !presentNames.has(p.name.toLowerCase()));
29056
+ const missing = packages.filter((p) => !presentNames.has(p.name.toLowerCase()));
28918
29057
  return { manifest: file, missing: [...missing] };
28919
29058
  }
28920
- async function planProcfileEdits(serviceDir) {
29059
+ var PY_LAUNCHER_PYTHON = /^python3?\b/;
29060
+ var PY_LAUNCHERS_BROAD = /^(python3?|uvicorn|gunicorn|hypercorn|daphne|granian|celery|flask|fastapi)\b/;
29061
+ async function planProcfileEdits(serviceDir, broad = false) {
28921
29062
  const procfile = import_node_path89.default.join(serviceDir, "Procfile");
28922
29063
  if (!await exists4(procfile)) return [];
28923
29064
  const raw = await import_node_fs53.promises.readFile(procfile, "utf8");
29065
+ const launcher = broad ? PY_LAUNCHERS_BROAD : PY_LAUNCHER_PYTHON;
28924
29066
  const edits = [];
28925
29067
  for (const line of raw.split(/\r?\n/)) {
28926
29068
  if (line.length === 0) continue;
28927
29069
  const m = line.match(/^([a-zA-Z0-9_-]+):\s*(.+)$/);
28928
29070
  if (!m) continue;
28929
29071
  const cmd = m[2];
28930
- if (!/^python\b/.test(cmd)) continue;
29072
+ if (!launcher.test(cmd)) continue;
28931
29073
  if (cmd.startsWith("opentelemetry-instrument ")) continue;
28932
29074
  const after = `${m[1]}: opentelemetry-instrument ${cmd}`;
28933
29075
  edits.push({ file: procfile, before: line, after });
28934
29076
  }
28935
29077
  return edits;
28936
29078
  }
28937
- async function plan2(serviceDir) {
29079
+ async function plan2(serviceDir, opts) {
28938
29080
  const empty = {
28939
29081
  language: "python",
28940
29082
  serviceDir,
@@ -28942,6 +29084,25 @@ async function plan2(serviceDir) {
28942
29084
  entrypointEdits: [],
28943
29085
  envEdits: []
28944
29086
  };
29087
+ if (!opts?.sourceEdit) {
29088
+ const entrypointEdits2 = await planProcfileEdits(serviceDir, true);
29089
+ const reqs2 = await planRequirementsTxtEdits(serviceDir, [NEAT_OTEL_PACKAGE, ...SDK_PACKAGES2]);
29090
+ const dependencyEdits2 = reqs2 ? reqs2.missing.map((sdk) => ({
29091
+ file: reqs2.manifest,
29092
+ kind: "add",
29093
+ name: sdk.name,
29094
+ version: sdk.version
29095
+ })) : [];
29096
+ if (dependencyEdits2.length === 0 && entrypointEdits2.length === 0) return empty;
29097
+ return {
29098
+ language: "python",
29099
+ serviceDir,
29100
+ dependencyEdits: dependencyEdits2,
29101
+ entrypointEdits: entrypointEdits2,
29102
+ envEdits: [OTEL_ENV2]
29103
+ };
29104
+ }
29105
+ const entrypointEdits = await planProcfileEdits(serviceDir);
28945
29106
  const dependencyEdits = [];
28946
29107
  const reqs = await planRequirementsTxtEdits(serviceDir);
28947
29108
  if (reqs) {
@@ -28954,7 +29115,6 @@ async function plan2(serviceDir) {
28954
29115
  });
28955
29116
  }
28956
29117
  }
28957
- const entrypointEdits = await planProcfileEdits(serviceDir);
28958
29118
  const entryFile = await resolvePyEntrypoint(serviceDir);
28959
29119
  const generatedFiles = entryFile ? [{ file: import_node_path89.default.join(serviceDir, NEAT_OTEL_FILENAME), contents: neatOtelPy() }] : [];
28960
29120
  if (dependencyEdits.length === 0 && entrypointEdits.length === 0 && !entryFile) {
@@ -29910,7 +30070,7 @@ async function applyInstallersOver(services, project, options = {}) {
29910
30070
  for (const svc of services) {
29911
30071
  const installer = await pickInstaller(svc.dir);
29912
30072
  if (!installer) continue;
29913
- const plan6 = await installer.plan(svc.dir, { project });
30073
+ const plan6 = await installer.plan(svc.dir, { project, sourceEdit: options.sourceEdit });
29914
30074
  if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) {
29915
30075
  already++;
29916
30076
  continue;
@@ -30484,8 +30644,12 @@ async function runOrchestrator(opts) {
30484
30644
  result.steps.apply.skipped = true;
30485
30645
  console.log("skipped instrumentation (--no-instrument)");
30486
30646
  } else {
30487
- const tally = await applyInstallersOver(services, opts.project);
30488
- result.steps.apply = { ...tally, skipped: false };
30647
+ const tally = await applyInstallersOver(services, opts.project, { sourceEdit: opts.sourceEdit });
30648
+ result.steps.apply = {
30649
+ ...tally,
30650
+ skipped: false,
30651
+ ...!opts.sourceEdit && tally.instrumented > 0 ? { attached: true } : {}
30652
+ };
30489
30653
  console.log(
30490
30654
  `instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
30491
30655
  );
@@ -30598,6 +30762,12 @@ function printSummary(result, graph, daemonLog) {
30598
30762
  for (const i of failedInstalls) {
30599
30763
  console.log(` run \`${i.pm} install\` in ${i.cwd}`);
30600
30764
  }
30765
+ } else if (daemonLog !== null && result.steps.apply.attached === true) {
30766
+ console.log("next: start your app with NEAT attached \u2014 OBSERVED edges fill in as it runs,");
30767
+ console.log(" and divergences surface where code and runtime disagree.");
30768
+ console.log(" Node: in the service directory, `set -a; . ./.env.neat; set +a`, then your");
30769
+ console.log(" usual start command (or put its NODE_OPTIONS in your process manager).");
30770
+ console.log(" Python: run your start command under `opentelemetry-instrument`.");
30601
30771
  } else if (daemonLog !== null) {
30602
30772
  console.log(
30603
30773
  "next: run your app or your test suite \u2014 OBSERVED edges fill in as it executes,"
@@ -31772,7 +31942,7 @@ async function runSsoLogin(opts, deps) {
31772
31942
  out("");
31773
31943
  out("To fill the OBSERVED layer, instrument your app to send OpenTelemetry to the hosted daemon:");
31774
31944
  out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
31775
- out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
31945
+ out(` OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeader(cred.otelToken)}`);
31776
31946
  }
31777
31947
  await runPostLoginConnect({
31778
31948
  cpUrl: opts.cpUrl,
@@ -33013,6 +33183,12 @@ init_cjs_shims();
33013
33183
  var import_node_fs61 = require("fs");
33014
33184
  var import_node_path98 = __toESM(require("path"), 1);
33015
33185
  var import_promises5 = __toESM(require("readline/promises"), 1);
33186
+
33187
+ // src/agent-directive.generated.ts
33188
+ init_cjs_shims();
33189
+ var AGENT_DIRECTIVE = '## Query NEAT before searching files\n\nThis project has a live NEAT graph: code, data and infrastructure declarations\nfused with runtime traffic, incidents and supported provider telemetry. It can\nshow what exists, what actually ran, where a failure began, and what may break\nwhen a node changes. The graph is deterministic: the agent supplies the model;\nNEAT resolves named nodes and traverses recorded evidence rather than asking an\nLLM to infer architecture from file names.\n\nEvery claim carries provenance and confidence. `EXTRACTED` comes from recognized\nsource or configuration, `OBSERVED` from spans or supported provider signals,\n`INFERRED` from a bridged relationship, and `STALE` marks an observed edge that\nwent quiet. Missing runtime evidence does not prove a path never runs.\n\n**For questions about this system\'s behavior, structure, data dependencies,\nfailures, or change impact, call the MCP `ask` tool (`mcp__neat__ask`) before\nRead/Grep/Glob/Bash.** You do not need an exact node id. `ask` resolves names\nand routes to the relevant graph traversal, returning provenance-tagged facts.\nWhen MCP is unavailable, use the same door through the CLI:\n\n```\nnpx neat.is ask "why is checkout failing?"\nnpx neat.is ask "what breaks if I change the orders table?"\nnpx neat.is ask "what does the payments service depend on at runtime?"\n```\n\nFor a failure, ask first, then call `get_incident_card` on the named service,\nfile or symbol. The card combines the incident, likely cause, blast radius,\npolicies and divergence into a work order. Use `expand` one hop at a time and\n`relate` to test whether the suspected cause and symptom share a signal. Check\nprovenance and confidence before acting.\n\nBefore a change, use `get_blast_radius` and `check_policies` to see dependents\nand advisory architectural rules. Use `get_divergences` to compare declared\nintent with observed behavior, `get_graph_diff` to compare a saved snapshot\nwith the live graph, and `get_recent_stale_edges` when traffic goes quiet.\n`get_dependencies` maps a node\'s outgoing graph; `get_observed_dependencies`\nshows only seen runtime/provider calls. `semantic_search` finds node labels,\nnot arbitrary source text.\n\nRead source when the graph does not model what you need: comments, arbitrary\nstring literals, config details, unsupported syntax, or a repository that has\nnot been extracted or connected. If a graph answer is empty, check the daemon\nwith `npx neat.is list` or restart it with `npx neat.is up`; wire MCP with\n`npx neat.is skill --apply`.\n';
33190
+
33191
+ // src/welcome.ts
33016
33192
  var WORDMARK = [
33017
33193
  "\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
33018
33194
  "\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D",
@@ -33021,21 +33197,27 @@ var WORDMARK = [
33021
33197
  "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
33022
33198
  "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D "
33023
33199
  ];
33024
- var AGENT_SETUP_PROMPT = `Set up self-hosted NEAT for this project. NEAT keeps a live graph of the code
33025
- fused with runtime OpenTelemetry data, queryable over MCP \u2014 so you can ask it
33026
- about the system instead of grepping files.
33027
-
33028
- 1. Extract the code graph and instrument the services for OpenTelemetry:
33029
- npx neat.is init . --apply
33030
- Then install the deps it added (npm install, or your language's equivalent).
33031
- 2. Start the NEAT daemon so it watches the code and ingests live traces:
33032
- npx neat.is watch .
33033
- 3. Wire NEAT's MCP server into your agent so it queries the graph first:
33200
+ function renderAgentSetupPrompt(project, restPort) {
33201
+ const endpoint2 = restPort === void 0 ? "The daemon endpoint is unavailable; run `npx neat.is up` to start it." : `The local daemon is serving it at http://127.0.0.1:${restPort}.`;
33202
+ return `NEAT is set up for project "${project}". ${endpoint2}
33203
+
33204
+ NEAT models this software system as one graph: code, data, infrastructure,
33205
+ runtime traffic, incidents, and supported provider telemetry fused where evidence
33206
+ permits. Use it to see what exists, what actually ran, where a failure began, and
33207
+ what a change could affect. Missing observations do not prove a path never runs.
33208
+
33209
+ 1. Wire NEAT's MCP server into this agent session:
33034
33210
  npx neat.is skill --apply
33035
- (other agents: npx neat.is codex|cursor|gemini \u2026 --apply)
33211
+ For Codex: npx neat.is codex --apply
33212
+ For Cursor or Gemini, use the corresponding cursor or gemini subcommand.
33213
+ 2. Verify the MCP \`ask\` tool answers:
33214
+ ask "what services are in this project and what do they talk to?"
33215
+ If the daemon is unreachable, run \`npx neat.is up\`.
33036
33216
 
33037
- Then ask NEAT instead of reading files, e.g.
33038
- npx neat.is ask "why is checkout failing?"`;
33217
+ ${AGENT_DIRECTIVE}
33218
+ As the app or tests run, traces fill the OBSERVED layer. Daemon log:
33219
+ neat-out/daemon.log. Reopen this door with \`npx neat.is welcome\`.`;
33220
+ }
33039
33221
  function printHeader(out) {
33040
33222
  for (const line of WORDMARK) out(line);
33041
33223
  out("");
@@ -33072,15 +33254,38 @@ async function runFrontDoor(deps, out) {
33072
33254
  const cwd = deps.cwd ?? process.cwd();
33073
33255
  const flagGiven = deps.instrumentFlagGiven ?? false;
33074
33256
  const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
33257
+ const readDaemon = deps.readDaemon ?? (async (project) => {
33258
+ const daemon = await findDaemonByProject(project);
33259
+ return daemon?.live ? { projectPath: daemon.record.projectPath, restPort: daemon.record.ports.rest } : void 0;
33260
+ });
33075
33261
  printHeader(out);
33076
33262
  out("Welcome to NEAT. Let's get you a graph of this system.");
33077
33263
  out("");
33078
33264
  const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
33079
33265
  } } : createKeyReader();
33080
33266
  const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
33081
- if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
33267
+ if (picked === void 0)
33268
+ return runSelfHosted(
33269
+ out,
33270
+ readLine,
33271
+ orchestrator,
33272
+ cwd,
33273
+ readRegistry2,
33274
+ readDaemon,
33275
+ flagGiven,
33276
+ deps.dryRun === true
33277
+ );
33082
33278
  if (picked === 0) return login(["--browser"]);
33083
- return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
33279
+ return runSelfHosted(
33280
+ out,
33281
+ readLine,
33282
+ orchestrator,
33283
+ cwd,
33284
+ readRegistry2,
33285
+ readDaemon,
33286
+ flagGiven,
33287
+ deps.dryRun === true
33288
+ );
33084
33289
  }
33085
33290
  var MENU_OPTIONS = [
33086
33291
  "Log me into Hosted Neat",
@@ -33183,17 +33388,7 @@ function decodeKey(buf) {
33183
33388
  function defaultMoveCursorUp(rows) {
33184
33389
  if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
33185
33390
  }
33186
- async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
33187
- const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
33188
- if (wantsPrompt !== "n" && wantsPrompt !== "no") {
33189
- out("");
33190
- out("\u2500\u2500\u2500 copy the prompt below into your coding agent \u2500\u2500\u2500");
33191
- out("");
33192
- out(AGENT_SETUP_PROMPT);
33193
- out("");
33194
- out("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
33195
- out("");
33196
- }
33391
+ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, readDaemon, instrumentFlagGiven, dryRun) {
33197
33392
  const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
33198
33393
  const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
33199
33394
  out("Building your local graph now\u2026");
@@ -33203,7 +33398,22 @@ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, in
33203
33398
  ...project !== void 0 ? { project } : {},
33204
33399
  ...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
33205
33400
  };
33206
- return orchestrator(cwd, overrides);
33401
+ const code = await orchestrator(cwd, overrides);
33402
+ if (code !== 0 || dryRun) return code;
33403
+ const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
33404
+ if (wantsPrompt !== "n" && wantsPrompt !== "no") {
33405
+ const projectName = project ?? import_node_path98.default.basename(import_node_path98.default.resolve(cwd));
33406
+ const daemon = await readDaemon(projectName).catch(() => void 0);
33407
+ const port = daemon && import_node_path98.default.resolve(daemon.projectPath) === import_node_path98.default.resolve(cwd) ? daemon.restPort : void 0;
33408
+ out("");
33409
+ out("\u2500\u2500\u2500 copy the directive below into your coding agent \u2500\u2500\u2500");
33410
+ out("");
33411
+ out(renderAgentSetupPrompt(projectName, port));
33412
+ out("");
33413
+ out("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
33414
+ out("");
33415
+ }
33416
+ return code;
33207
33417
  }
33208
33418
  async function askToInstrument(out, readLine) {
33209
33419
  out("Instrument the services for OpenTelemetry now?");
@@ -33213,7 +33423,7 @@ async function askToInstrument(out, readLine) {
33213
33423
  if (declined) {
33214
33424
  out("");
33215
33425
  out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
33216
- out("Run `neat init . --apply` when you want the runtime half.");
33426
+ out("Run `npx neat.is init . --apply` when you want the runtime half.");
33217
33427
  }
33218
33428
  out("");
33219
33429
  return declined;
@@ -34781,7 +34991,7 @@ async function runSync(opts) {
34781
34991
  snapshotPath = target;
34782
34992
  }
34783
34993
  const skipApply = opts.dryRun || opts.noInstrument;
34784
- const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0, browserBundle: 0, reactNative: 0 } : await applyInstallersOver(persisted.services, entry2.name);
34994
+ const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0, browserBundle: 0, reactNative: 0 } : await applyInstallersOver(persisted.services, entry2.name, { sourceEdit: opts.sourceEdit });
34785
34995
  const warnings = [];
34786
34996
  let daemonState = "skipped";
34787
34997
  let exitCode = 0;
@@ -34894,6 +35104,8 @@ function usage5() {
34894
35104
  console.log(" --apply run the SDK install patch in place");
34895
35105
  console.log(" --dry-run write only neat.patch; do not register or snapshot");
34896
35106
  console.log(" --no-install skip SDK install planning entirely");
35107
+ console.log(" --source-edit inject the OTel init into entry points instead of");
35108
+ console.log(" attaching it at process start (ADR-232)");
34897
35109
  console.log(" watch <path> Start neat-core, watch <path>, re-extract on changes.");
34898
35110
  console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
34899
35111
  console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
@@ -35001,6 +35213,7 @@ function usage5() {
35001
35213
  console.log(" --token <token> bearer token for --to (or $NEAT_REMOTE_TOKEN)");
35002
35214
  console.log(" --dry-run run extraction in-memory; do not write");
35003
35215
  console.log(" --no-instrument skip the SDK install apply step");
35216
+ console.log(" --source-edit inject into entry points instead of attaching");
35004
35217
  console.log(" --json emit the delta summary as JSON");
35005
35218
  console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
35006
35219
  console.log(" firebase, cloudflare, neon, cloud-run) and push (vercel,");
@@ -35099,6 +35312,7 @@ function parseArgs(rest) {
35099
35312
  dryRun: false,
35100
35313
  noInstall: false,
35101
35314
  noInstrument: false,
35315
+ sourceEdit: false,
35102
35316
  open: false,
35103
35317
  noOpen: false,
35104
35318
  yes: false,
@@ -35137,6 +35351,10 @@ function parseArgs(rest) {
35137
35351
  out.noInstrument = true;
35138
35352
  continue;
35139
35353
  }
35354
+ if (arg === "--source-edit") {
35355
+ out.sourceEdit = true;
35356
+ continue;
35357
+ }
35140
35358
  if (arg === "--open") {
35141
35359
  out.open = true;
35142
35360
  continue;
@@ -35262,12 +35480,12 @@ function printDiscoveryReport(opts, services) {
35262
35480
  }
35263
35481
  console.log("");
35264
35482
  }
35265
- async function buildPatchSections(services, project) {
35483
+ async function buildPatchSections(services, project, sourceEdit) {
35266
35484
  const sections = [];
35267
35485
  for (const svc of services) {
35268
35486
  const installer = await pickInstaller(svc.dir);
35269
35487
  if (!installer) continue;
35270
- const plan6 = await installer.plan(svc.dir, { project });
35488
+ const plan6 = await installer.plan(svc.dir, { project, sourceEdit });
35271
35489
  if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) continue;
35272
35490
  sections.push({ installer: installer.name, plan: plan6 });
35273
35491
  }
@@ -35282,7 +35500,7 @@ async function runInit(opts) {
35282
35500
  }
35283
35501
  const services = await discoverServices(opts.scanPath);
35284
35502
  printDiscoveryReport(opts, services);
35285
- const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
35503
+ const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project, opts.sourceEdit);
35286
35504
  const patch = renderPatch(sections);
35287
35505
  const patchPath = import_node_path104.default.join(opts.scanPath, "neat.patch");
35288
35506
  if (opts.dryRun) {
@@ -35586,6 +35804,7 @@ async function main() {
35586
35804
  apply: apply6,
35587
35805
  dryRun,
35588
35806
  noInstall,
35807
+ sourceEdit: parsed.sourceEdit,
35589
35808
  verbose: parsed.verbose
35590
35809
  });
35591
35810
  if (result.exitCode !== 0) process.exit(result.exitCode);
@@ -35778,6 +35997,7 @@ async function main() {
35778
35997
  ...parsed.token ? { token: parsed.token } : {},
35779
35998
  dryRun: parsed.dryRun,
35780
35999
  noInstrument: parsed.noInstrument,
36000
+ sourceEdit: parsed.sourceEdit,
35781
36001
  json: parsed.json
35782
36002
  });
35783
36003
  if (result.exitCode !== 0) process.exit(result.exitCode);
@@ -35813,6 +36033,7 @@ async function tryOrchestrator(cmd, parsed, overrides = {}) {
35813
36033
  project: projectName,
35814
36034
  projectExplicit,
35815
36035
  noInstrument: parsed.noInstrument,
36036
+ sourceEdit: parsed.sourceEdit,
35816
36037
  open: parsed.open,
35817
36038
  noOpen: parsed.noOpen,
35818
36039
  yes: parsed.yes,
@@ -35825,6 +36046,10 @@ async function runWelcomeFlow(parsed) {
35825
36046
  cwd,
35826
36047
  {
35827
36048
  ...parsed,
36049
+ // ADR-232 — the front-door / local-menu run never source-edits, even if
36050
+ // `--source-edit` was on the command line. Source-edit stays an explicit
36051
+ // choice on a deliberately-typed `neat <path> --source-edit`.
36052
+ sourceEdit: false,
35828
36053
  ...opts?.project !== void 0 ? { project: opts.project } : {},
35829
36054
  ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
35830
36055
  ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
@@ -35840,7 +36065,8 @@ async function runWelcomeFlow(parsed) {
35840
36065
  // door's wordmark already opened this run, so the orchestrator's banner is skipped.
35841
36066
  orchestrator: runLocal,
35842
36067
  // An explicit flag is an answer; don't ask again.
35843
- instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
36068
+ instrumentFlagGiven: parsed.noInstrument || parsed.dryRun,
36069
+ dryRun: parsed.dryRun
35844
36070
  });
35845
36071
  }
35846
36072
  var QUERY_VERBS = /* @__PURE__ */ new Set([