@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.
- package/dist/{chunk-KTJK2PAH.js → chunk-6QMWJAWR.js} +204 -609
- package/dist/chunk-6QMWJAWR.js.map +1 -0
- package/dist/{chunk-VL7ANDPI.js → chunk-EMFX3V77.js} +10 -8
- package/dist/{chunk-VL7ANDPI.js.map → chunk-EMFX3V77.js.map} +1 -1
- package/dist/chunk-MR64ICXX.js +19 -0
- package/dist/chunk-MR64ICXX.js.map +1 -0
- package/dist/{chunk-EFBWQIKG.js → chunk-QPG2QXCW.js} +11 -27
- package/dist/chunk-QPG2QXCW.js.map +1 -0
- package/dist/chunk-RGYC5U2Q.js +511 -0
- package/dist/chunk-RGYC5U2Q.js.map +1 -0
- package/dist/{chunk-LXQCL4MP.js → chunk-TZXSWSHX.js} +2 -2
- package/dist/{chunk-BC53SCT7.js → chunk-VFZ6CXLI.js} +20 -6
- package/dist/chunk-VFZ6CXLI.js.map +1 -0
- package/dist/cli.cjs +300 -74
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +249 -76
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +58 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -99
- package/dist/index.d.ts +3 -99
- package/dist/index.js +18 -15
- package/dist/neatd.cjs +58 -15
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +7 -4
- package/dist/neatd.js.map +1 -1
- package/dist/otel-grpc-CCCI772O.js +11 -0
- package/dist/registry.cjs +570 -0
- package/dist/registry.cjs.map +1 -0
- package/dist/registry.d.cts +220 -0
- package/dist/registry.d.ts +220 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/dist/server.cjs +78 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +10 -6
- package/dist/server.js.map +1 -1
- package/package.json +8 -3
- package/dist/chunk-BC53SCT7.js.map +0 -1
- package/dist/chunk-EFBWQIKG.js.map +0 -1
- package/dist/chunk-KTJK2PAH.js.map +0 -1
- package/dist/otel-grpc-B7ABHLV7.js +0 -10
- /package/dist/{chunk-LXQCL4MP.js.map → chunk-TZXSWSHX.js.map} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26152
|
-
|
|
26153
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
29056
|
+
const missing = packages.filter((p) => !presentNames.has(p.name.toLowerCase()));
|
|
28918
29057
|
return { manifest: file, missing: [...missing] };
|
|
28919
29058
|
}
|
|
28920
|
-
|
|
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 (
|
|
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 = {
|
|
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
|
|
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
|
-
|
|
33025
|
-
|
|
33026
|
-
|
|
33027
|
-
|
|
33028
|
-
|
|
33029
|
-
|
|
33030
|
-
|
|
33031
|
-
|
|
33032
|
-
|
|
33033
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33038
|
-
|
|
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)
|
|
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(
|
|
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
|
-
|
|
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([
|