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

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-VL7ANDPI.js → chunk-ECBRGTB2.js} +10 -8
  2. package/dist/{chunk-VL7ANDPI.js.map → chunk-ECBRGTB2.js.map} +1 -1
  3. package/dist/{chunk-KTJK2PAH.js → chunk-H2BAPN33.js} +311 -611
  4. package/dist/chunk-H2BAPN33.js.map +1 -0
  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 +409 -77
  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 +251 -77
  19. package/dist/cli.js.map +1 -1
  20. package/dist/index.cjs +165 -17
  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 +165 -17
  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 +185 -23
  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) {
@@ -18314,10 +18365,11 @@ function locusOf(graph, ev) {
18314
18365
  const rawLine = ev.attributes?.[CODE_LINENO_ATTR2];
18315
18366
  const line = typeof rawLine === "number" ? rawLine : Number(rawLine);
18316
18367
  const node = graph.hasNode(ev.affectedNode) ? graph.getNodeAttributes(ev.affectedNode) : void 0;
18368
+ const symbol = node?.type === "SymbolNode" ? node.qualname : void 0;
18317
18369
  return {
18318
18370
  file,
18319
18371
  ...Number.isFinite(line) ? { lineStart: line, lineEnd: line } : {},
18320
- ...node?.name ? { symbol: node.name } : {},
18372
+ ...symbol ? { symbol } : {},
18321
18373
  service: node?.service ?? ev.service,
18322
18374
  provenance: import_types63.Provenance.OBSERVED
18323
18375
  };
@@ -18413,7 +18465,8 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
18413
18465
  const policyCards = applicable.map((p) => ({
18414
18466
  policyName: p.policyName,
18415
18467
  severity: p.severity,
18416
- message: p.reason
18468
+ message: p.reason,
18469
+ onViolation: p.onViolation
18417
18470
  }));
18418
18471
  const divergences = inGraph ? computeDivergences(graph, { node: affected, incidents }).divergences.map((d) => ({
18419
18472
  type: d.type,
@@ -18440,8 +18493,111 @@ function buildIncidentCard(graph, errorEvent, incidents, policies) {
18440
18493
  ...divergences.length > 0 ? { divergence: divergences } : {},
18441
18494
  headline: renderHeadline(graph, errorEvent, locus, rootCause?.node ?? null)
18442
18495
  };
18496
+ card.grade = gradeIncidentCard(card);
18443
18497
  return import_types63.IncidentCardSchema.parse(card);
18444
18498
  }
18499
+ var GRADE_PRIORS_VERSION = "sniper-architecture-priors-2026-10-08";
18500
+ var GRADE_WEIGHTS = {
18501
+ evidence: 3,
18502
+ locus: 2,
18503
+ tests: 2,
18504
+ reach: 1.5,
18505
+ kind: 1.5,
18506
+ chain: 1,
18507
+ recur: 1,
18508
+ div: 1
18509
+ };
18510
+ var PROVENANCE_WEIGHT = {
18511
+ OBSERVED: 1,
18512
+ EXTRACTED: 0.8,
18513
+ INFERRED: 0.4,
18514
+ FRONTIER: 0.3,
18515
+ STALE: 0.2
18516
+ };
18517
+ var REACH_NODES_MAX = 50;
18518
+ var TIMEOUT = /timeout|timed out|deadline/i;
18519
+ var round2 = (n) => Math.round(n * 1e4) / 1e4;
18520
+ function factor(name, value, evidence, reason) {
18521
+ return {
18522
+ value: value === null ? null : round2(value),
18523
+ weight: GRADE_WEIGHTS[name],
18524
+ evidence,
18525
+ ...reason ? { reason } : {}
18526
+ };
18527
+ }
18528
+ function gradeIncidentCard(card) {
18529
+ const rc = card.rootCause;
18530
+ const chain = rc?.chain ?? [];
18531
+ const gates = {
18532
+ policyNotBlock: {
18533
+ passed: !(card.policies ?? []).some((p) => p.onViolation === "block"),
18534
+ evidence: ["policies[].onViolation"]
18535
+ },
18536
+ notSymptomOnly: {
18537
+ passed: rc?.classification !== "symptom-only",
18538
+ evidence: ["rootCause.classification"]
18539
+ },
18540
+ locusResolves: { passed: card.locus !== null, evidence: ["locus"] }
18541
+ };
18542
+ const gamma = Object.values(gates).every((g) => g.passed) ? 1 : 0;
18543
+ const carrying = chain.some((h) => h.carriesSignal === true) ? chain.filter((h) => h.carriesSignal === true) : chain;
18544
+ const hopWeights = carrying.map((h) => PROVENANCE_WEIGHT[h.provenance]);
18545
+ const evidence = hopWeights.length === 0 ? factor("evidence", null, ["rootCause.chain[].provenance"], "no root-cause chain on the card") : hopWeights.some((w) => w === void 0) ? factor("evidence", null, ["rootCause.chain[].provenance"], "a chain hop has a provenance with no weight") : factor("evidence", Math.min(...hopWeights), ["rootCause.chain[].provenance"]);
18546
+ const locus = card.locus ? factor("locus", card.locus.symbol ? 1 : 0.6, ["locus.symbol", "locus.file"]) : factor("locus", null, ["locus"], "no locus on the card (the locus gate fails)");
18547
+ const tests = factor(
18548
+ "tests",
18549
+ null,
18550
+ [],
18551
+ "the card carries no test-file classification or test-to-symbol edges"
18552
+ );
18553
+ const reach = card.blastRadius ? factor(
18554
+ "reach",
18555
+ Math.max(0, 1 - card.blastRadius.totalAffected / REACH_NODES_MAX),
18556
+ ["blastRadius.totalAffected"],
18557
+ `nodes in the blast radius against a prior bound of ${REACH_NODES_MAX}`
18558
+ ) : factor("reach", null, ["blastRadius"], "no blast radius on the card");
18559
+ const timeout = TIMEOUT.test(`${card.exceptionType ?? ""} ${card.message}`);
18560
+ const kindValue = timeout ? 0.3 : card.incidentKind === "exception" ? 1 : card.incidentKind === "5xx" || card.incidentKind === "status-error" ? 0.7 : card.incidentKind === "connector" ? 0.6 : 0.4;
18561
+ const kind = factor("kind", kindValue, ["incidentKind", "exceptionType", "message"]);
18562
+ const chainFactor = rc ? factor(
18563
+ "chain",
18564
+ 1 / (1 + 0.25 * (Math.max(1, chain.length - 1) - 1)),
18565
+ ["rootCause.chain"]
18566
+ ) : factor("chain", null, ["rootCause"], "no root cause on the card");
18567
+ const occurrences = card.count ?? 1;
18568
+ const recurValue = Math.min(1, Math.log(1 + occurrences) / Math.log(11));
18569
+ const recur = factor(
18570
+ "recur",
18571
+ recurValue,
18572
+ ["count"],
18573
+ card.count === void 0 ? "no coalesced count; one recorded occurrence" : void 0
18574
+ );
18575
+ const div = factor("div", (card.divergence ?? []).length > 0 ? 1 : 0.7, ["divergence"]);
18576
+ const factors = { evidence, locus, tests, reach, kind, chain: chainFactor, recur, div };
18577
+ let weighted = 0;
18578
+ let total = 0;
18579
+ for (const f of Object.values(factors)) {
18580
+ if (f.value === null) continue;
18581
+ weighted += f.weight * f.value;
18582
+ total += f.weight;
18583
+ }
18584
+ const C = round2(weighted / total);
18585
+ const G = round2(gamma * C);
18586
+ const band = gamma === 0 || G < 0.5 ? "out" : G >= 0.75 ? "full" : "diagnose-only";
18587
+ return {
18588
+ G,
18589
+ gamma,
18590
+ gates,
18591
+ C,
18592
+ factors,
18593
+ band,
18594
+ urgency: {
18595
+ value: null,
18596
+ reason: "f_fresh needs the time of the last OBSERVED signal at the node, which the card does not carry"
18597
+ },
18598
+ priorsVersion: GRADE_PRIORS_VERSION
18599
+ };
18600
+ }
18445
18601
 
18446
18602
  // src/ask.ts
18447
18603
  init_cjs_shims();
@@ -20327,7 +20483,7 @@ async function computePrVerdict(req, ctx, deps, deadline) {
20327
20483
  const found = observedBreakFrom(node, getObservedDependencies(ctx.liveGraph, node.id));
20328
20484
  if (found) observedBreaks.push(found);
20329
20485
  }
20330
- const { marker, body } = renderVerdict({
20486
+ const { marker, body: rendered } = renderVerdict({
20331
20487
  graph: head,
20332
20488
  delta,
20333
20489
  changedFiles,
@@ -20335,6 +20491,7 @@ async function computePrVerdict(req, ctx, deps, deadline) {
20335
20491
  observedBreaks,
20336
20492
  tone: req.tone ?? "loud"
20337
20493
  });
20494
+ const body = rendered.startsWith(marker) ? rendered.slice(marker.length).replace(/^(?:\r?\n)+/, "") : rendered;
20338
20495
  return {
20339
20496
  marker,
20340
20497
  body,
@@ -26147,13 +26304,25 @@ async function makeTransformersEmbedder() {
26147
26304
  }
26148
26305
  };
26149
26306
  }
26150
- async function pickEmbedder() {
26151
- const host = ollamaHost();
26152
- if (host && await ollamaReachable(host)) {
26153
- return makeOllamaEmbedder(host);
26307
+ async function pickEmbedder(provider) {
26308
+ if (provider !== "transformers") {
26309
+ const host = ollamaHost();
26310
+ if (host && await ollamaReachable(host)) {
26311
+ return makeOllamaEmbedder(host);
26312
+ }
26154
26313
  }
26314
+ if (provider === "ollama") return null;
26155
26315
  return makeTransformersEmbedder();
26156
26316
  }
26317
+ function searchProviderFromEnv(env = process.env, warn2 = console.warn) {
26318
+ const value = env.NEAT_SEARCH_PROVIDER;
26319
+ if (!value) return void 0;
26320
+ if (value === "substring" || value === "ollama" || value === "transformers") return value;
26321
+ warn2(
26322
+ `semantic_search: NEAT_SEARCH_PROVIDER="${value}" is not substring, ollama, or transformers; using automatic selection`
26323
+ );
26324
+ return void 0;
26325
+ }
26157
26326
  async function readCache(cachePath) {
26158
26327
  try {
26159
26328
  const raw = await import_node_fs49.promises.readFile(cachePath, "utf8");
@@ -26317,7 +26486,8 @@ async function buildSearchIndex(graph, options = {}) {
26317
26486
  if (options.embedder) {
26318
26487
  embedder = options.embedder;
26319
26488
  } else if (options.forceProvider !== "substring") {
26320
- const factory = options.embedderFactory ?? pickEmbedder;
26489
+ const forcedProvider = options.forceProvider;
26490
+ const factory = options.embedderFactory ?? (() => pickEmbedder(forcedProvider));
26321
26491
  embedder = await resolveEmbedderBounded(
26322
26492
  factory,
26323
26493
  options.initTimeoutMs ?? searchInitTimeoutMs()
@@ -26596,9 +26766,10 @@ async function startWatch(graph, opts) {
26596
26766
  const port = opts.port ?? 8080;
26597
26767
  const otelPort = opts.otelPort ?? 4318;
26598
26768
  const cachePath = opts.embeddingsCachePath ?? import_node_path86.default.join(import_node_path86.default.dirname(opts.outPath), "embeddings.json");
26769
+ const forceProvider = searchProviderFromEnv();
26599
26770
  let searchIndex;
26600
26771
  try {
26601
- searchIndex = await buildSearchIndex(graph, { cachePath });
26772
+ searchIndex = await buildSearchIndex(graph, { cachePath, forceProvider });
26602
26773
  console.log(`semantic_search: ${searchIndex.provider} provider`);
26603
26774
  } catch (err) {
26604
26775
  console.warn(
@@ -26882,7 +27053,7 @@ function emitDockerRunSnippet() {
26882
27053
  function renderOtelEnvBlock2(token, host = "<host>") {
26883
27054
  return [
26884
27055
  `OTEL_EXPORTER_OTLP_ENDPOINT=https://${host}:4318`,
26885
- `OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${token}`,
27056
+ `OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeader(token)}`,
26886
27057
  "OTEL_SERVICE_NAME=<service>"
26887
27058
  ].join("\n");
26888
27059
  }
@@ -26947,7 +27118,7 @@ var import_semver2 = __toESM(require("semver"), 1);
26947
27118
  init_cjs_shims();
26948
27119
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
26949
27120
  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";
27121
+ 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
27122
  var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
26952
27123
  var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
26953
27124
  try {
@@ -27392,7 +27563,31 @@ ${registrations.join("\n")}
27392
27563
  `;
27393
27564
  return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
27394
27565
  }
27395
- function renderEnvNeat(serviceName, projectName) {
27566
+ function renderEnvNeat(serviceName, projectName, nodeOptions, extraInstrumentations = []) {
27567
+ if (nodeOptions) {
27568
+ return [
27569
+ "# Generated by `neat init --apply` / `neat <path>` (ADR-069, ADR-232).",
27570
+ "# NEAT attaches its OpenTelemetry bootstrap at process start; no source is edited.",
27571
+ "# NODE_OPTIONS is read once when the process starts, so load this file before",
27572
+ "# your start command:",
27573
+ "# set -a; . ./.env.neat; set +a; <your start command>",
27574
+ "# or copy these settings into your process manager / platform environment.",
27575
+ `OTEL_SERVICE_NAME=${serviceName}`,
27576
+ "# The preload sends spans to this project's daemon, read from",
27577
+ "# <project>/neat-out/daemon.json; NEAT_PROJECT names the route when no record",
27578
+ "# is found. Set OTEL_EXPORTER_OTLP_ENDPOINT only to send spans elsewhere.",
27579
+ `NEAT_PROJECT=${projectName}`,
27580
+ "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
27581
+ "# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
27582
+ "# NEAT_OTEL_TOKEN=",
27583
+ ...extraInstrumentations.length > 0 ? [
27584
+ "# Instrumentations outside the auto bundle, loaded by the preload (<package>#<export>).",
27585
+ `NEAT_OTEL_INSTRUMENTATIONS=${extraInstrumentations.join(",")}`
27586
+ ] : [],
27587
+ `NODE_OPTIONS="${nodeOptions}"`,
27588
+ ""
27589
+ ].join("\n");
27590
+ }
27396
27591
  return [
27397
27592
  "# Generated by `neat init --apply` (ADR-069).",
27398
27593
  `OTEL_SERVICE_NAME=${serviceName}`,
@@ -27588,14 +27783,16 @@ function detectNonBundledInstrumentations(pkg) {
27588
27783
  out.push({
27589
27784
  pkg: "@prisma/instrumentation",
27590
27785
  version: prismaInstrVersion,
27591
- registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
27786
+ registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())",
27787
+ exportName: "PrismaInstrumentation"
27592
27788
  });
27593
27789
  }
27594
27790
  if (getMajor(deps["@nestjs/core"]) >= 11) {
27595
27791
  out.push({
27596
27792
  pkg: "@opentelemetry/instrumentation-nestjs-core",
27597
27793
  version: "^0.67.0",
27598
- registration: "instrumentations.push(new (require('@opentelemetry/instrumentation-nestjs-core').NestInstrumentation)())"
27794
+ registration: "instrumentations.push(new (require('@opentelemetry/instrumentation-nestjs-core').NestInstrumentation)())",
27795
+ exportName: "NestInstrumentation"
27599
27796
  });
27600
27797
  }
27601
27798
  return out;
@@ -28406,6 +28603,49 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
28406
28603
  }
28407
28604
  return null;
28408
28605
  }
28606
+ var ATTACH_PACKAGE_NAME = "@neat.is/otel-node";
28607
+ function attachPackageRange(coreVersion = readPackageVersion()) {
28608
+ return import_semver2.default.valid(coreVersion) ? `^${coreVersion}` : "latest";
28609
+ }
28610
+ async function planAttachment(serviceDir, pkg, manifestPath, project) {
28611
+ const svcName = serviceNodeName2(pkg, serviceDir);
28612
+ const projectName = projectToken(pkg, serviceDir, project);
28613
+ const envNeatFile = import_node_path88.default.join(serviceDir, ".env.neat");
28614
+ const flag = pkg.type === "module" ? "--import" : "--require";
28615
+ const nodeOptions = `${flag} @neat.is/otel-node/register`;
28616
+ const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
28617
+ const dependencyEdits = [];
28618
+ if (!(ATTACH_PACKAGE_NAME in existingDeps)) {
28619
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: ATTACH_PACKAGE_NAME, version: attachPackageRange() });
28620
+ }
28621
+ const nonBundled = detectNonBundledInstrumentations(pkg);
28622
+ for (const inst of nonBundled) {
28623
+ if (inst.pkg in existingDeps) {
28624
+ if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
28625
+ dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
28626
+ }
28627
+ continue;
28628
+ }
28629
+ dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
28630
+ }
28631
+ const extraInstrumentations = nonBundled.map((i) => `${i.pkg}#${i.exportName}`);
28632
+ const generatedFiles = [];
28633
+ if (!await exists3(envNeatFile)) {
28634
+ generatedFiles.push({
28635
+ file: envNeatFile,
28636
+ contents: renderEnvNeat(svcName, projectName, nodeOptions, extraInstrumentations),
28637
+ skipIfExists: true
28638
+ });
28639
+ }
28640
+ return {
28641
+ language: "javascript",
28642
+ serviceDir,
28643
+ dependencyEdits,
28644
+ entrypointEdits: [],
28645
+ envEdits: [OTEL_ENV],
28646
+ generatedFiles
28647
+ };
28648
+ }
28409
28649
  async function plan(serviceDir, opts) {
28410
28650
  const pkg = await readPackageJson2(serviceDir);
28411
28651
  const manifestPath = import_node_path88.default.join(serviceDir, "package.json");
@@ -28431,14 +28671,17 @@ async function plan(serviceDir, opts) {
28431
28671
  if (!entryFile) {
28432
28672
  return { ...empty, libOnly: true };
28433
28673
  }
28674
+ const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
28675
+ if (runtimeKind !== "node") {
28676
+ return { ...empty, runtimeKind };
28677
+ }
28678
+ }
28679
+ if (!opts?.sourceEdit) {
28680
+ return planAttachment(serviceDir, pkg, manifestPath, project);
28434
28681
  }
28435
28682
  if (frameworkDispatch) {
28436
28683
  return frameworkDispatch();
28437
28684
  }
28438
- const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
28439
- if (runtimeKind !== "node") {
28440
- return { ...empty, runtimeKind };
28441
- }
28442
28685
  if (!entryFile) {
28443
28686
  return { ...empty, libOnly: true };
28444
28687
  }
@@ -28756,6 +28999,7 @@ var SDK_PACKAGES2 = [
28756
28999
  { name: "opentelemetry-distro", version: ">=0.49b0" },
28757
29000
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
28758
29001
  ];
29002
+ var NEAT_OTEL_PACKAGE = { name: "neat-otel", version: ">=0.1.0" };
28759
29003
  var OTEL_ENV2 = {
28760
29004
  file: null,
28761
29005
  key: "OTEL_EXPORTER_OTLP_ENDPOINT",
@@ -28907,34 +29151,37 @@ function reqPackageName(line) {
28907
29151
  const head = stripped.split(/[\s;]/)[0] ?? "";
28908
29152
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
28909
29153
  }
28910
- async function planRequirementsTxtEdits(serviceDir) {
29154
+ async function planRequirementsTxtEdits(serviceDir, packages = SDK_PACKAGES2) {
28911
29155
  const file = import_node_path89.default.join(serviceDir, "requirements.txt");
28912
29156
  if (!await exists4(file)) return null;
28913
29157
  const raw = await import_node_fs53.promises.readFile(file, "utf8");
28914
29158
  const presentNames = new Set(
28915
29159
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
28916
29160
  );
28917
- const missing = SDK_PACKAGES2.filter((p) => !presentNames.has(p.name.toLowerCase()));
29161
+ const missing = packages.filter((p) => !presentNames.has(p.name.toLowerCase()));
28918
29162
  return { manifest: file, missing: [...missing] };
28919
29163
  }
28920
- async function planProcfileEdits(serviceDir) {
29164
+ var PY_LAUNCHER_PYTHON = /^python3?\b/;
29165
+ var PY_LAUNCHERS_BROAD = /^(python3?|uvicorn|gunicorn|hypercorn|daphne|granian|celery|flask|fastapi)\b/;
29166
+ async function planProcfileEdits(serviceDir, broad = false) {
28921
29167
  const procfile = import_node_path89.default.join(serviceDir, "Procfile");
28922
29168
  if (!await exists4(procfile)) return [];
28923
29169
  const raw = await import_node_fs53.promises.readFile(procfile, "utf8");
29170
+ const launcher = broad ? PY_LAUNCHERS_BROAD : PY_LAUNCHER_PYTHON;
28924
29171
  const edits = [];
28925
29172
  for (const line of raw.split(/\r?\n/)) {
28926
29173
  if (line.length === 0) continue;
28927
29174
  const m = line.match(/^([a-zA-Z0-9_-]+):\s*(.+)$/);
28928
29175
  if (!m) continue;
28929
29176
  const cmd = m[2];
28930
- if (!/^python\b/.test(cmd)) continue;
29177
+ if (!launcher.test(cmd)) continue;
28931
29178
  if (cmd.startsWith("opentelemetry-instrument ")) continue;
28932
29179
  const after = `${m[1]}: opentelemetry-instrument ${cmd}`;
28933
29180
  edits.push({ file: procfile, before: line, after });
28934
29181
  }
28935
29182
  return edits;
28936
29183
  }
28937
- async function plan2(serviceDir) {
29184
+ async function plan2(serviceDir, opts) {
28938
29185
  const empty = {
28939
29186
  language: "python",
28940
29187
  serviceDir,
@@ -28942,6 +29189,25 @@ async function plan2(serviceDir) {
28942
29189
  entrypointEdits: [],
28943
29190
  envEdits: []
28944
29191
  };
29192
+ if (!opts?.sourceEdit) {
29193
+ const entrypointEdits2 = await planProcfileEdits(serviceDir, true);
29194
+ const reqs2 = await planRequirementsTxtEdits(serviceDir, [NEAT_OTEL_PACKAGE, ...SDK_PACKAGES2]);
29195
+ const dependencyEdits2 = reqs2 ? reqs2.missing.map((sdk) => ({
29196
+ file: reqs2.manifest,
29197
+ kind: "add",
29198
+ name: sdk.name,
29199
+ version: sdk.version
29200
+ })) : [];
29201
+ if (dependencyEdits2.length === 0 && entrypointEdits2.length === 0) return empty;
29202
+ return {
29203
+ language: "python",
29204
+ serviceDir,
29205
+ dependencyEdits: dependencyEdits2,
29206
+ entrypointEdits: entrypointEdits2,
29207
+ envEdits: [OTEL_ENV2]
29208
+ };
29209
+ }
29210
+ const entrypointEdits = await planProcfileEdits(serviceDir);
28945
29211
  const dependencyEdits = [];
28946
29212
  const reqs = await planRequirementsTxtEdits(serviceDir);
28947
29213
  if (reqs) {
@@ -28954,7 +29220,6 @@ async function plan2(serviceDir) {
28954
29220
  });
28955
29221
  }
28956
29222
  }
28957
- const entrypointEdits = await planProcfileEdits(serviceDir);
28958
29223
  const entryFile = await resolvePyEntrypoint(serviceDir);
28959
29224
  const generatedFiles = entryFile ? [{ file: import_node_path89.default.join(serviceDir, NEAT_OTEL_FILENAME), contents: neatOtelPy() }] : [];
28960
29225
  if (dependencyEdits.length === 0 && entrypointEdits.length === 0 && !entryFile) {
@@ -29910,7 +30175,7 @@ async function applyInstallersOver(services, project, options = {}) {
29910
30175
  for (const svc of services) {
29911
30176
  const installer = await pickInstaller(svc.dir);
29912
30177
  if (!installer) continue;
29913
- const plan6 = await installer.plan(svc.dir, { project });
30178
+ const plan6 = await installer.plan(svc.dir, { project, sourceEdit: options.sourceEdit });
29914
30179
  if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) {
29915
30180
  already++;
29916
30181
  continue;
@@ -30484,8 +30749,12 @@ async function runOrchestrator(opts) {
30484
30749
  result.steps.apply.skipped = true;
30485
30750
  console.log("skipped instrumentation (--no-instrument)");
30486
30751
  } else {
30487
- const tally = await applyInstallersOver(services, opts.project);
30488
- result.steps.apply = { ...tally, skipped: false };
30752
+ const tally = await applyInstallersOver(services, opts.project, { sourceEdit: opts.sourceEdit });
30753
+ result.steps.apply = {
30754
+ ...tally,
30755
+ skipped: false,
30756
+ ...!opts.sourceEdit && tally.instrumented > 0 ? { attached: true } : {}
30757
+ };
30489
30758
  console.log(
30490
30759
  `instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
30491
30760
  );
@@ -30598,6 +30867,12 @@ function printSummary(result, graph, daemonLog) {
30598
30867
  for (const i of failedInstalls) {
30599
30868
  console.log(` run \`${i.pm} install\` in ${i.cwd}`);
30600
30869
  }
30870
+ } else if (daemonLog !== null && result.steps.apply.attached === true) {
30871
+ console.log("next: start your app with NEAT attached \u2014 OBSERVED edges fill in as it runs,");
30872
+ console.log(" and divergences surface where code and runtime disagree.");
30873
+ console.log(" Node: in the service directory, `set -a; . ./.env.neat; set +a`, then your");
30874
+ console.log(" usual start command (or put its NODE_OPTIONS in your process manager).");
30875
+ console.log(" Python: run your start command under `opentelemetry-instrument`.");
30601
30876
  } else if (daemonLog !== null) {
30602
30877
  console.log(
30603
30878
  "next: run your app or your test suite \u2014 OBSERVED edges fill in as it executes,"
@@ -31772,7 +32047,7 @@ async function runSsoLogin(opts, deps) {
31772
32047
  out("");
31773
32048
  out("To fill the OBSERVED layer, instrument your app to send OpenTelemetry to the hosted daemon:");
31774
32049
  out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
31775
- out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
32050
+ out(` OTEL_EXPORTER_OTLP_HEADERS=${otlpBearerHeader(cred.otelToken)}`);
31776
32051
  }
31777
32052
  await runPostLoginConnect({
31778
32053
  cpUrl: opts.cpUrl,
@@ -33013,6 +33288,12 @@ init_cjs_shims();
33013
33288
  var import_node_fs61 = require("fs");
33014
33289
  var import_node_path98 = __toESM(require("path"), 1);
33015
33290
  var import_promises5 = __toESM(require("readline/promises"), 1);
33291
+
33292
+ // src/agent-directive.generated.ts
33293
+ init_cjs_shims();
33294
+ 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';
33295
+
33296
+ // src/welcome.ts
33016
33297
  var WORDMARK = [
33017
33298
  "\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
33299
  "\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 +33302,27 @@ var WORDMARK = [
33021
33302
  "\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
33303
  "\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
33304
  ];
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:
33305
+ function renderAgentSetupPrompt(project, restPort) {
33306
+ 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}.`;
33307
+ return `NEAT is set up for project "${project}". ${endpoint2}
33308
+
33309
+ NEAT models this software system as one graph: code, data, infrastructure,
33310
+ runtime traffic, incidents, and supported provider telemetry fused where evidence
33311
+ permits. Use it to see what exists, what actually ran, where a failure began, and
33312
+ what a change could affect. Missing observations do not prove a path never runs.
33313
+
33314
+ 1. Wire NEAT's MCP server into this agent session:
33034
33315
  npx neat.is skill --apply
33035
- (other agents: npx neat.is codex|cursor|gemini \u2026 --apply)
33316
+ For Codex: npx neat.is codex --apply
33317
+ For Cursor or Gemini, use the corresponding cursor or gemini subcommand.
33318
+ 2. Verify the MCP \`ask\` tool answers:
33319
+ ask "what services are in this project and what do they talk to?"
33320
+ If the daemon is unreachable, run \`npx neat.is up\`.
33036
33321
 
33037
- Then ask NEAT instead of reading files, e.g.
33038
- npx neat.is ask "why is checkout failing?"`;
33322
+ ${AGENT_DIRECTIVE}
33323
+ As the app or tests run, traces fill the OBSERVED layer. Daemon log:
33324
+ neat-out/daemon.log. Reopen this door with \`npx neat.is welcome\`.`;
33325
+ }
33039
33326
  function printHeader(out) {
33040
33327
  for (const line of WORDMARK) out(line);
33041
33328
  out("");
@@ -33072,15 +33359,38 @@ async function runFrontDoor(deps, out) {
33072
33359
  const cwd = deps.cwd ?? process.cwd();
33073
33360
  const flagGiven = deps.instrumentFlagGiven ?? false;
33074
33361
  const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
33362
+ const readDaemon = deps.readDaemon ?? (async (project) => {
33363
+ const daemon = await findDaemonByProject(project);
33364
+ return daemon?.live ? { projectPath: daemon.record.projectPath, restPort: daemon.record.ports.rest } : void 0;
33365
+ });
33075
33366
  printHeader(out);
33076
33367
  out("Welcome to NEAT. Let's get you a graph of this system.");
33077
33368
  out("");
33078
33369
  const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
33079
33370
  } } : createKeyReader();
33080
33371
  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);
33372
+ if (picked === void 0)
33373
+ return runSelfHosted(
33374
+ out,
33375
+ readLine,
33376
+ orchestrator,
33377
+ cwd,
33378
+ readRegistry2,
33379
+ readDaemon,
33380
+ flagGiven,
33381
+ deps.dryRun === true
33382
+ );
33082
33383
  if (picked === 0) return login(["--browser"]);
33083
- return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
33384
+ return runSelfHosted(
33385
+ out,
33386
+ readLine,
33387
+ orchestrator,
33388
+ cwd,
33389
+ readRegistry2,
33390
+ readDaemon,
33391
+ flagGiven,
33392
+ deps.dryRun === true
33393
+ );
33084
33394
  }
33085
33395
  var MENU_OPTIONS = [
33086
33396
  "Log me into Hosted Neat",
@@ -33183,17 +33493,7 @@ function decodeKey(buf) {
33183
33493
  function defaultMoveCursorUp(rows) {
33184
33494
  if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
33185
33495
  }
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
- }
33496
+ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, readDaemon, instrumentFlagGiven, dryRun) {
33197
33497
  const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
33198
33498
  const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
33199
33499
  out("Building your local graph now\u2026");
@@ -33203,7 +33503,22 @@ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, in
33203
33503
  ...project !== void 0 ? { project } : {},
33204
33504
  ...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
33205
33505
  };
33206
- return orchestrator(cwd, overrides);
33506
+ const code = await orchestrator(cwd, overrides);
33507
+ if (code !== 0 || dryRun) return code;
33508
+ const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
33509
+ if (wantsPrompt !== "n" && wantsPrompt !== "no") {
33510
+ const projectName = project ?? import_node_path98.default.basename(import_node_path98.default.resolve(cwd));
33511
+ const daemon = await readDaemon(projectName).catch(() => void 0);
33512
+ const port = daemon && import_node_path98.default.resolve(daemon.projectPath) === import_node_path98.default.resolve(cwd) ? daemon.restPort : void 0;
33513
+ out("");
33514
+ out("\u2500\u2500\u2500 copy the directive below into your coding agent \u2500\u2500\u2500");
33515
+ out("");
33516
+ out(renderAgentSetupPrompt(projectName, port));
33517
+ out("");
33518
+ 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");
33519
+ out("");
33520
+ }
33521
+ return code;
33207
33522
  }
33208
33523
  async function askToInstrument(out, readLine) {
33209
33524
  out("Instrument the services for OpenTelemetry now?");
@@ -33213,7 +33528,7 @@ async function askToInstrument(out, readLine) {
33213
33528
  if (declined) {
33214
33529
  out("");
33215
33530
  out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
33216
- out("Run `neat init . --apply` when you want the runtime half.");
33531
+ out("Run `npx neat.is init . --apply` when you want the runtime half.");
33217
33532
  }
33218
33533
  out("");
33219
33534
  return declined;
@@ -34377,7 +34692,8 @@ function formatIncidentLine(card) {
34377
34692
  const cls = rc.classification ? `${rc.classification} ` : "";
34378
34693
  tag = ` \xB7 ${cls}${rc.confidence.toFixed(2)}${provs ? ` [${provs}]` : ""}`;
34379
34694
  }
34380
- return `\u2716 incident [${card.incidentKind}] ${card.headline}${tag}`;
34695
+ const grade = card.grade ? ` \xB7 grade ${card.grade.G.toFixed(2)} ${card.grade.band}` : "";
34696
+ return `\u2716 incident [${card.incidentKind}] ${card.headline}${tag}${grade}`;
34381
34697
  }
34382
34698
  function incidentJson(card) {
34383
34699
  return JSON.stringify(card);
@@ -34781,7 +35097,7 @@ async function runSync(opts) {
34781
35097
  snapshotPath = target;
34782
35098
  }
34783
35099
  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);
35100
+ const applyTally = skipApply ? { instrumented: 0, alreadyInstrumented: 0, libOnly: 0, browserBundle: 0, reactNative: 0 } : await applyInstallersOver(persisted.services, entry2.name, { sourceEdit: opts.sourceEdit });
34785
35101
  const warnings = [];
34786
35102
  let daemonState = "skipped";
34787
35103
  let exitCode = 0;
@@ -34894,6 +35210,8 @@ function usage5() {
34894
35210
  console.log(" --apply run the SDK install patch in place");
34895
35211
  console.log(" --dry-run write only neat.patch; do not register or snapshot");
34896
35212
  console.log(" --no-install skip SDK install planning entirely");
35213
+ console.log(" --source-edit inject the OTel init into entry points instead of");
35214
+ console.log(" attaching it at process start (ADR-232)");
34897
35215
  console.log(" watch <path> Start neat-core, watch <path>, re-extract on changes.");
34898
35216
  console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
34899
35217
  console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
@@ -35001,6 +35319,7 @@ function usage5() {
35001
35319
  console.log(" --token <token> bearer token for --to (or $NEAT_REMOTE_TOKEN)");
35002
35320
  console.log(" --dry-run run extraction in-memory; do not write");
35003
35321
  console.log(" --no-instrument skip the SDK install apply step");
35322
+ console.log(" --source-edit inject into entry points instead of attaching");
35004
35323
  console.log(" --json emit the delta summary as JSON");
35005
35324
  console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
35006
35325
  console.log(" firebase, cloudflare, neon, cloud-run) and push (vercel,");
@@ -35099,6 +35418,7 @@ function parseArgs(rest) {
35099
35418
  dryRun: false,
35100
35419
  noInstall: false,
35101
35420
  noInstrument: false,
35421
+ sourceEdit: false,
35102
35422
  open: false,
35103
35423
  noOpen: false,
35104
35424
  yes: false,
@@ -35137,6 +35457,10 @@ function parseArgs(rest) {
35137
35457
  out.noInstrument = true;
35138
35458
  continue;
35139
35459
  }
35460
+ if (arg === "--source-edit") {
35461
+ out.sourceEdit = true;
35462
+ continue;
35463
+ }
35140
35464
  if (arg === "--open") {
35141
35465
  out.open = true;
35142
35466
  continue;
@@ -35262,12 +35586,12 @@ function printDiscoveryReport(opts, services) {
35262
35586
  }
35263
35587
  console.log("");
35264
35588
  }
35265
- async function buildPatchSections(services, project) {
35589
+ async function buildPatchSections(services, project, sourceEdit) {
35266
35590
  const sections = [];
35267
35591
  for (const svc of services) {
35268
35592
  const installer = await pickInstaller(svc.dir);
35269
35593
  if (!installer) continue;
35270
- const plan6 = await installer.plan(svc.dir, { project });
35594
+ const plan6 = await installer.plan(svc.dir, { project, sourceEdit });
35271
35595
  if (isEmptyPlan(plan6) && !plan6.libOnly && plan6.runtimeKind === void 0) continue;
35272
35596
  sections.push({ installer: installer.name, plan: plan6 });
35273
35597
  }
@@ -35282,7 +35606,7 @@ async function runInit(opts) {
35282
35606
  }
35283
35607
  const services = await discoverServices(opts.scanPath);
35284
35608
  printDiscoveryReport(opts, services);
35285
- const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
35609
+ const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project, opts.sourceEdit);
35286
35610
  const patch = renderPatch(sections);
35287
35611
  const patchPath = import_node_path104.default.join(opts.scanPath, "neat.patch");
35288
35612
  if (opts.dryRun) {
@@ -35586,6 +35910,7 @@ async function main() {
35586
35910
  apply: apply6,
35587
35911
  dryRun,
35588
35912
  noInstall,
35913
+ sourceEdit: parsed.sourceEdit,
35589
35914
  verbose: parsed.verbose
35590
35915
  });
35591
35916
  if (result.exitCode !== 0) process.exit(result.exitCode);
@@ -35778,6 +36103,7 @@ async function main() {
35778
36103
  ...parsed.token ? { token: parsed.token } : {},
35779
36104
  dryRun: parsed.dryRun,
35780
36105
  noInstrument: parsed.noInstrument,
36106
+ sourceEdit: parsed.sourceEdit,
35781
36107
  json: parsed.json
35782
36108
  });
35783
36109
  if (result.exitCode !== 0) process.exit(result.exitCode);
@@ -35813,6 +36139,7 @@ async function tryOrchestrator(cmd, parsed, overrides = {}) {
35813
36139
  project: projectName,
35814
36140
  projectExplicit,
35815
36141
  noInstrument: parsed.noInstrument,
36142
+ sourceEdit: parsed.sourceEdit,
35816
36143
  open: parsed.open,
35817
36144
  noOpen: parsed.noOpen,
35818
36145
  yes: parsed.yes,
@@ -35825,6 +36152,10 @@ async function runWelcomeFlow(parsed) {
35825
36152
  cwd,
35826
36153
  {
35827
36154
  ...parsed,
36155
+ // ADR-232 — the front-door / local-menu run never source-edits, even if
36156
+ // `--source-edit` was on the command line. Source-edit stays an explicit
36157
+ // choice on a deliberately-typed `neat <path> --source-edit`.
36158
+ sourceEdit: false,
35828
36159
  ...opts?.project !== void 0 ? { project: opts.project } : {},
35829
36160
  ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
35830
36161
  ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
@@ -35840,7 +36171,8 @@ async function runWelcomeFlow(parsed) {
35840
36171
  // door's wordmark already opened this run, so the orchestrator's banner is skipped.
35841
36172
  orchestrator: runLocal,
35842
36173
  // An explicit flag is an answer; don't ask again.
35843
- instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
36174
+ instrumentFlagGiven: parsed.noInstrument || parsed.dryRun,
36175
+ dryRun: parsed.dryRun
35844
36176
  });
35845
36177
  }
35846
36178
  var QUERY_VERBS = /* @__PURE__ */ new Set([