@neat.is/core 0.5.1 → 0.5.3
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-BI3XKGVG.js → chunk-GJHEZC5K.js} +56 -21
- package/dist/chunk-GJHEZC5K.js.map +1 -0
- package/dist/{chunk-DPEPI2N6.js → chunk-X2AMX3QZ.js} +32 -14
- package/dist/chunk-X2AMX3QZ.js.map +1 -0
- package/dist/cli.cjs +383 -121
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +267 -59
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +85 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +85 -32
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +55 -20
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BI3XKGVG.js.map +0 -1
- package/dist/chunk-DPEPI2N6.js.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
|
|
|
61
61
|
]);
|
|
62
62
|
const publicRead = opts.publicRead === true;
|
|
63
63
|
app.addHook("preHandler", (req, reply, done) => {
|
|
64
|
-
const
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
64
|
+
const path62 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path62) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path62)) {
|
|
66
66
|
done();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -362,8 +362,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
362
362
|
const v = attrs[key];
|
|
363
363
|
if (typeof v === "string" && v.length > 0) {
|
|
364
364
|
const q = v.indexOf("?");
|
|
365
|
-
const
|
|
366
|
-
if (
|
|
365
|
+
const path62 = q === -1 ? v : v.slice(0, q);
|
|
366
|
+
if (path62.length > 0) return path62;
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
return void 0;
|
|
@@ -686,12 +686,12 @@ __export(cli_exports, {
|
|
|
686
686
|
runInit: () => runInit,
|
|
687
687
|
runQueryVerb: () => runQueryVerb,
|
|
688
688
|
runSkill: () => runSkill,
|
|
689
|
-
usage: () =>
|
|
689
|
+
usage: () => usage2
|
|
690
690
|
});
|
|
691
691
|
module.exports = __toCommonJS(cli_exports);
|
|
692
692
|
init_cjs_shims();
|
|
693
|
-
var
|
|
694
|
-
var
|
|
693
|
+
var import_node_path61 = __toESM(require("path"), 1);
|
|
694
|
+
var import_node_fs41 = require("fs");
|
|
695
695
|
|
|
696
696
|
// src/banner.ts
|
|
697
697
|
init_cjs_shims();
|
|
@@ -1256,19 +1256,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1256
1256
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1257
1257
|
let best = { path: [start], edges: [] };
|
|
1258
1258
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1259
|
-
function step(node,
|
|
1260
|
-
if (
|
|
1261
|
-
best = { path: [...
|
|
1259
|
+
function step(node, path62, edges) {
|
|
1260
|
+
if (path62.length > best.path.length) {
|
|
1261
|
+
best = { path: [...path62], edges: [...edges] };
|
|
1262
1262
|
}
|
|
1263
|
-
if (
|
|
1263
|
+
if (path62.length - 1 >= maxDepth) return;
|
|
1264
1264
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1265
1265
|
for (const [srcId, edge] of incoming) {
|
|
1266
1266
|
if (visited.has(srcId)) continue;
|
|
1267
1267
|
visited.add(srcId);
|
|
1268
|
-
|
|
1268
|
+
path62.push(srcId);
|
|
1269
1269
|
edges.push(edge);
|
|
1270
|
-
step(srcId,
|
|
1271
|
-
|
|
1270
|
+
step(srcId, path62, edges);
|
|
1271
|
+
path62.pop();
|
|
1272
1272
|
edges.pop();
|
|
1273
1273
|
visited.delete(srcId);
|
|
1274
1274
|
}
|
|
@@ -1462,26 +1462,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
|
|
|
1462
1462
|
return best;
|
|
1463
1463
|
}
|
|
1464
1464
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1465
|
-
const
|
|
1465
|
+
const path62 = [originServiceId];
|
|
1466
1466
|
const edges = [];
|
|
1467
1467
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1468
1468
|
let current = originServiceId;
|
|
1469
1469
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1470
1470
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1471
1471
|
if (!hop) break;
|
|
1472
|
-
|
|
1472
|
+
path62.push(hop.nextService);
|
|
1473
1473
|
edges.push(hop.edge);
|
|
1474
1474
|
visited.add(hop.nextService);
|
|
1475
1475
|
current = hop.nextService;
|
|
1476
1476
|
}
|
|
1477
1477
|
if (edges.length === 0) return null;
|
|
1478
|
-
return { path:
|
|
1478
|
+
return { path: path62, edges, culprit: current };
|
|
1479
1479
|
}
|
|
1480
1480
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1481
1481
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1482
1482
|
if (!chain) return null;
|
|
1483
1483
|
const culprit = chain.culprit;
|
|
1484
|
-
const
|
|
1484
|
+
const path62 = [...chain.path];
|
|
1485
1485
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1486
1486
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1487
1487
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1489,14 +1489,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1489
1489
|
if (loc) {
|
|
1490
1490
|
let rootCauseNode = culprit;
|
|
1491
1491
|
if (loc.fileNode) {
|
|
1492
|
-
|
|
1492
|
+
path62.push(loc.fileNode);
|
|
1493
1493
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1494
1494
|
rootCauseNode = loc.fileNode;
|
|
1495
1495
|
}
|
|
1496
1496
|
return import_types.RootCauseResultSchema.parse({
|
|
1497
1497
|
rootCauseNode,
|
|
1498
1498
|
rootCauseReason: loc.rootCauseReason,
|
|
1499
|
-
traversalPath:
|
|
1499
|
+
traversalPath: path62,
|
|
1500
1500
|
edgeProvenances,
|
|
1501
1501
|
confidence,
|
|
1502
1502
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1508,7 +1508,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1508
1508
|
return import_types.RootCauseResultSchema.parse({
|
|
1509
1509
|
rootCauseNode: culprit,
|
|
1510
1510
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1511
|
-
traversalPath:
|
|
1511
|
+
traversalPath: path62,
|
|
1512
1512
|
edgeProvenances,
|
|
1513
1513
|
confidence,
|
|
1514
1514
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -4678,14 +4678,21 @@ function engineFromImage(image) {
|
|
|
4678
4678
|
// src/extract/databases/dotenv.ts
|
|
4679
4679
|
var CONNECTION_KEYS = /* @__PURE__ */ new Set([
|
|
4680
4680
|
"DATABASE_URL",
|
|
4681
|
+
"DATABASE_URI",
|
|
4681
4682
|
"DB_URL",
|
|
4683
|
+
"DB_URI",
|
|
4682
4684
|
"POSTGRES_URL",
|
|
4685
|
+
"POSTGRES_URI",
|
|
4683
4686
|
"POSTGRESQL_URL",
|
|
4687
|
+
"POSTGRESQL_URI",
|
|
4684
4688
|
"MYSQL_URL",
|
|
4689
|
+
"MYSQL_URI",
|
|
4690
|
+
"MONGODB_URL",
|
|
4685
4691
|
"MONGODB_URI",
|
|
4686
4692
|
"MONGO_URL",
|
|
4687
4693
|
"MONGO_URI",
|
|
4688
|
-
"REDIS_URL"
|
|
4694
|
+
"REDIS_URL",
|
|
4695
|
+
"REDIS_URI"
|
|
4689
4696
|
]);
|
|
4690
4697
|
function parseDotenvLine(line) {
|
|
4691
4698
|
const trimmed = line.trim();
|
|
@@ -8198,11 +8205,42 @@ async function readPackageJson(scanPath) {
|
|
|
8198
8205
|
const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
|
|
8199
8206
|
return JSON.parse(raw);
|
|
8200
8207
|
}
|
|
8208
|
+
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
8209
|
+
"node_modules",
|
|
8210
|
+
"dist",
|
|
8211
|
+
"build",
|
|
8212
|
+
"out",
|
|
8213
|
+
"coverage",
|
|
8214
|
+
"neat-out"
|
|
8215
|
+
]);
|
|
8201
8216
|
async function findHookFiles(scanPath) {
|
|
8202
|
-
const
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8217
|
+
const found = [];
|
|
8218
|
+
const walk3 = async (dir) => {
|
|
8219
|
+
const entries = await import_node_fs28.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
8220
|
+
for (const entry2 of entries) {
|
|
8221
|
+
if (entry2.isDirectory()) {
|
|
8222
|
+
if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
|
|
8223
|
+
await walk3(import_node_path45.default.join(dir, entry2.name));
|
|
8224
|
+
} else if (entry2.isFile()) {
|
|
8225
|
+
if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
|
|
8226
|
+
const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry2.name));
|
|
8227
|
+
found.push(rel.split(import_node_path45.default.sep).join("/"));
|
|
8228
|
+
}
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
};
|
|
8232
|
+
await walk3(scanPath);
|
|
8233
|
+
return found.sort();
|
|
8234
|
+
}
|
|
8235
|
+
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
8236
|
+
let fallback = null;
|
|
8237
|
+
for (const file of hookFiles) {
|
|
8238
|
+
const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
|
|
8239
|
+
const patched = splicedContent(content, snippet2);
|
|
8240
|
+
if (patched !== null) return { file, content, patched };
|
|
8241
|
+
if (fallback === null) fallback = { file, content };
|
|
8242
|
+
}
|
|
8243
|
+
return { file: fallback.file, content: fallback.content, patched: null };
|
|
8206
8244
|
}
|
|
8207
8245
|
function extendLogPath() {
|
|
8208
8246
|
return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
@@ -8295,7 +8333,13 @@ async function applyExtension(ctx, args, options) {
|
|
|
8295
8333
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
8296
8334
|
}
|
|
8297
8335
|
}
|
|
8298
|
-
const
|
|
8336
|
+
const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet);
|
|
8337
|
+
if (primary.patched === null) {
|
|
8338
|
+
throw new Error(
|
|
8339
|
+
`Could not find instrumentation insertion point in ${hookFiles.join(", ")}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
8340
|
+
);
|
|
8341
|
+
}
|
|
8342
|
+
const primaryFile = primary.file;
|
|
8299
8343
|
const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
|
|
8300
8344
|
const filesTouched = [];
|
|
8301
8345
|
const depsAdded = [];
|
|
@@ -8307,14 +8351,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
8307
8351
|
filesTouched.push("package.json");
|
|
8308
8352
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
8309
8353
|
}
|
|
8310
|
-
|
|
8311
|
-
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
8312
|
-
if (!patched) {
|
|
8313
|
-
throw new Error(
|
|
8314
|
-
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
8315
|
-
);
|
|
8316
|
-
}
|
|
8317
|
-
await import_node_fs28.promises.writeFile(primaryPath, patched, "utf8");
|
|
8354
|
+
await import_node_fs28.promises.writeFile(primaryPath, primary.patched, "utf8");
|
|
8318
8355
|
filesTouched.push(primaryFile);
|
|
8319
8356
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
8320
8357
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -8356,7 +8393,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
8356
8393
|
};
|
|
8357
8394
|
}
|
|
8358
8395
|
}
|
|
8359
|
-
const
|
|
8396
|
+
const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet);
|
|
8360
8397
|
const filesTouched = [];
|
|
8361
8398
|
const depsToAdd = [];
|
|
8362
8399
|
let packageJsonPatch = {};
|
|
@@ -8367,10 +8404,8 @@ async function dryRunExtension(ctx, args) {
|
|
|
8367
8404
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
8368
8405
|
filesTouched.push("package.json");
|
|
8369
8406
|
}
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
if (patched) {
|
|
8373
|
-
filesTouched.push(primaryFile);
|
|
8407
|
+
if (primary.patched !== null) {
|
|
8408
|
+
filesTouched.push(primary.file);
|
|
8374
8409
|
templatePatch = `+ ${args.registration_snippet}`;
|
|
8375
8410
|
} else {
|
|
8376
8411
|
templatePatch = "Could not find insertion point in hook file.";
|
|
@@ -9943,7 +9978,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9943
9978
|
});
|
|
9944
9979
|
}
|
|
9945
9980
|
async function buildApi(opts) {
|
|
9946
|
-
const app = (0, import_fastify.default)({ logger: false });
|
|
9981
|
+
const app = (0, import_fastify.default)({ logger: false, routerOptions: { maxParamLength: 1024 } });
|
|
9947
9982
|
await app.register(import_cors.default, { origin: true });
|
|
9948
9983
|
const env = readAuthEnv();
|
|
9949
9984
|
const authToken = opts.authToken ?? env.authToken;
|
|
@@ -10451,17 +10486,20 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
10451
10486
|
// src/connectors/supabase/map.ts
|
|
10452
10487
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
10453
10488
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
10454
|
-
function targetFromRestPath(
|
|
10455
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10489
|
+
function targetFromRestPath(path62) {
|
|
10490
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path62);
|
|
10456
10491
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
10457
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10492
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path62);
|
|
10458
10493
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
10459
10494
|
return null;
|
|
10460
10495
|
}
|
|
10461
10496
|
var ERROR_STATUS_THRESHOLD = 500;
|
|
10462
10497
|
function mapEdgeLogRowsToSignals(rows) {
|
|
10463
10498
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
10499
|
+
if (!Array.isArray(rows)) return [];
|
|
10464
10500
|
for (const row of rows) {
|
|
10501
|
+
if (!row || typeof row !== "object") continue;
|
|
10502
|
+
if (typeof row.path !== "string" || typeof row.timestamp !== "string") continue;
|
|
10465
10503
|
const target = targetFromRestPath(row.path);
|
|
10466
10504
|
if (!target) continue;
|
|
10467
10505
|
const key = `${target.targetKind}:${target.name}`;
|
|
@@ -10502,9 +10540,12 @@ function tableNameFromQueryText(query) {
|
|
|
10502
10540
|
function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
|
|
10503
10541
|
const signals = [];
|
|
10504
10542
|
const seen = /* @__PURE__ */ new Set();
|
|
10543
|
+
if (!Array.isArray(rows)) return signals;
|
|
10505
10544
|
for (const row of rows) {
|
|
10506
|
-
|
|
10545
|
+
if (!row || typeof row !== "object" || typeof row.queryid !== "string") continue;
|
|
10507
10546
|
const calls = Number(row.calls);
|
|
10547
|
+
if (!Number.isFinite(calls)) continue;
|
|
10548
|
+
seen.add(row.queryid);
|
|
10508
10549
|
const prior = previous.get(row.queryid);
|
|
10509
10550
|
previous.set(row.queryid, { calls });
|
|
10510
10551
|
if (!prior || calls < prior.calls) continue;
|
|
@@ -10828,7 +10869,12 @@ function upsertBucket(buckets2, key, isError, timestamp, build) {
|
|
|
10828
10869
|
}
|
|
10829
10870
|
function mapRailwayHttpLogsToSignals(entries, routeIndex) {
|
|
10830
10871
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
10872
|
+
if (!Array.isArray(entries)) return [];
|
|
10831
10873
|
for (const entry2 of entries) {
|
|
10874
|
+
if (!entry2 || typeof entry2 !== "object") continue;
|
|
10875
|
+
if (typeof entry2.method !== "string" || typeof entry2.path !== "string" || typeof entry2.timestamp !== "string") {
|
|
10876
|
+
continue;
|
|
10877
|
+
}
|
|
10832
10878
|
const method = entry2.method.toUpperCase();
|
|
10833
10879
|
const normalizedPath = normalizePathTemplate(entry2.path);
|
|
10834
10880
|
const match = findRailwayRoute(routeIndex, method, normalizedPath);
|
|
@@ -10867,8 +10913,11 @@ function mapRailwayHttpLogsToSignals(entries, routeIndex) {
|
|
|
10867
10913
|
}
|
|
10868
10914
|
function mapRailwayNetworkFlowLogsToSignals(entries) {
|
|
10869
10915
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
10916
|
+
if (!Array.isArray(entries)) return [];
|
|
10870
10917
|
for (const entry2 of entries) {
|
|
10871
|
-
if (!entry2
|
|
10918
|
+
if (!entry2 || typeof entry2 !== "object") continue;
|
|
10919
|
+
if (typeof entry2.peerServiceId !== "string" || entry2.peerServiceId.length === 0) continue;
|
|
10920
|
+
if (typeof entry2.timestamp !== "string") continue;
|
|
10872
10921
|
const isError = entry2.dropCause !== null && entry2.dropCause !== "";
|
|
10873
10922
|
upsertBucket(buckets2, entry2.peerServiceId, isError, entry2.timestamp, () => ({
|
|
10874
10923
|
targetKind: PEER_SERVICE_TARGET_KIND,
|
|
@@ -10996,7 +11045,7 @@ async function fetchHttpRequestLogEntries(creds, sinceIso) {
|
|
|
10996
11045
|
throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
|
|
10997
11046
|
}
|
|
10998
11047
|
const json = await res.json();
|
|
10999
|
-
out.push(...json.entries
|
|
11048
|
+
if (Array.isArray(json.entries)) out.push(...json.entries);
|
|
11000
11049
|
if (!json.nextPageToken) break;
|
|
11001
11050
|
pageToken = json.nextPageToken;
|
|
11002
11051
|
}
|
|
@@ -11017,9 +11066,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
11017
11066
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
11018
11067
|
if (secondSep === -1) return null;
|
|
11019
11068
|
const method = rest.slice(0, secondSep);
|
|
11020
|
-
const
|
|
11021
|
-
if (!resourceName || !method || !
|
|
11022
|
-
return { resourceName, method, path:
|
|
11069
|
+
const path62 = rest.slice(secondSep + 1);
|
|
11070
|
+
if (!resourceName || !method || !path62) return null;
|
|
11071
|
+
return { resourceName, method, path: path62 };
|
|
11023
11072
|
}
|
|
11024
11073
|
function resourceNameFor(type, labels) {
|
|
11025
11074
|
if (!labels) return null;
|
|
@@ -11033,7 +11082,7 @@ function resourceNameFor(type, labels) {
|
|
|
11033
11082
|
}
|
|
11034
11083
|
}
|
|
11035
11084
|
function pathFromRequestUrl(requestUrl) {
|
|
11036
|
-
if (
|
|
11085
|
+
if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
|
|
11037
11086
|
if (requestUrl.startsWith("/")) {
|
|
11038
11087
|
const withoutQuery = requestUrl.split("?")[0];
|
|
11039
11088
|
return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
|
|
@@ -11048,22 +11097,23 @@ function pathFromRequestUrl(requestUrl) {
|
|
|
11048
11097
|
}
|
|
11049
11098
|
var ERROR_STATUS_THRESHOLD2 = 500;
|
|
11050
11099
|
function mapLogEntryToSignal(entry2) {
|
|
11100
|
+
if (!entry2 || typeof entry2 !== "object") return null;
|
|
11051
11101
|
const resourceType = entry2.resource?.type;
|
|
11052
11102
|
if (!resourceType || !isFirebaseResourceType(resourceType)) return null;
|
|
11053
11103
|
const resourceName = resourceNameFor(resourceType, entry2.resource?.labels);
|
|
11054
11104
|
if (!resourceName) return null;
|
|
11055
11105
|
const req = entry2.httpRequest;
|
|
11056
11106
|
if (!req) return null;
|
|
11057
|
-
if (
|
|
11107
|
+
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
11058
11108
|
const method = req.requestMethod.toUpperCase();
|
|
11059
|
-
const
|
|
11060
|
-
if (
|
|
11109
|
+
const path62 = pathFromRequestUrl(req.requestUrl);
|
|
11110
|
+
if (path62 === null) return null;
|
|
11061
11111
|
const timestamp = entry2.timestamp;
|
|
11062
|
-
if (
|
|
11112
|
+
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
11063
11113
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
11064
11114
|
return {
|
|
11065
11115
|
targetKind: resourceType,
|
|
11066
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
11116
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path62 }),
|
|
11067
11117
|
callCount: 1,
|
|
11068
11118
|
errorCount: isError ? 1 : 0,
|
|
11069
11119
|
lastObservedIso: timestamp
|
|
@@ -11211,7 +11261,7 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
|
|
|
11211
11261
|
throw new Error(`cloudflare connector: telemetry query returned an error (${message})`);
|
|
11212
11262
|
}
|
|
11213
11263
|
const events = payload.result?.events?.events;
|
|
11214
|
-
if (events
|
|
11264
|
+
if (!Array.isArray(events)) {
|
|
11215
11265
|
console.warn(
|
|
11216
11266
|
"[neat connector] cloudflare: telemetry query returned success:true but no result.events.events array \u2014 the response shape may have changed; treating as zero events this tick"
|
|
11217
11267
|
);
|
|
@@ -11241,7 +11291,7 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
|
11241
11291
|
]);
|
|
11242
11292
|
var LEADING_TOKEN_RE = /^(\S+)\s+\S/;
|
|
11243
11293
|
function parseHttpMethodFromTrigger(trigger) {
|
|
11244
|
-
if (
|
|
11294
|
+
if (typeof trigger !== "string") return null;
|
|
11245
11295
|
const match = LEADING_TOKEN_RE.exec(trigger.trim());
|
|
11246
11296
|
const token = match?.[1];
|
|
11247
11297
|
if (!token) return null;
|
|
@@ -11257,25 +11307,28 @@ function parsePathFromTrigger(trigger) {
|
|
|
11257
11307
|
}
|
|
11258
11308
|
var ERROR_STATUS_THRESHOLD3 = 500;
|
|
11259
11309
|
function mapEventToSignal(event) {
|
|
11310
|
+
if (!event || typeof event !== "object") return null;
|
|
11260
11311
|
const metadata = event.$metadata;
|
|
11261
11312
|
const workers = event.$workers;
|
|
11262
11313
|
const method = parseHttpMethodFromTrigger(metadata?.trigger);
|
|
11263
11314
|
if (!method) return null;
|
|
11264
11315
|
const scriptName = workers?.scriptName ?? metadata?.service;
|
|
11265
|
-
if (
|
|
11316
|
+
if (typeof scriptName !== "string" || scriptName.length === 0) return null;
|
|
11266
11317
|
const timestampMs = event.timestamp ?? metadata?.startTime;
|
|
11267
11318
|
if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
|
|
11319
|
+
const observedAt = new Date(timestampMs);
|
|
11320
|
+
if (Number.isNaN(observedAt.getTime())) return null;
|
|
11268
11321
|
const statusCode = metadata?.statusCode;
|
|
11269
11322
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
11270
|
-
const
|
|
11323
|
+
const path62 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
11271
11324
|
return {
|
|
11272
11325
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
11273
11326
|
targetName: scriptName,
|
|
11274
11327
|
callCount: 1,
|
|
11275
11328
|
errorCount: isError ? 1 : 0,
|
|
11276
|
-
lastObservedIso:
|
|
11329
|
+
lastObservedIso: observedAt.toISOString(),
|
|
11277
11330
|
method,
|
|
11278
|
-
...
|
|
11331
|
+
...path62 ? { path: path62 } : {},
|
|
11279
11332
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
11280
11333
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
11281
11334
|
};
|
|
@@ -11321,8 +11374,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
11321
11374
|
});
|
|
11322
11375
|
return found;
|
|
11323
11376
|
}
|
|
11324
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
11325
|
-
const normalizedPath = normalizePathTemplate(
|
|
11377
|
+
function findMatchingRouteNode(graph, serviceName, method, path62) {
|
|
11378
|
+
const normalizedPath = normalizePathTemplate(path62);
|
|
11326
11379
|
let found = null;
|
|
11327
11380
|
graph.forEachNode((id, attrs) => {
|
|
11328
11381
|
if (found) return;
|
|
@@ -11339,10 +11392,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11339
11392
|
return (signal) => {
|
|
11340
11393
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
11341
11394
|
const scriptName = signal.targetName;
|
|
11342
|
-
const { method, path:
|
|
11395
|
+
const { method, path: path62 } = signal;
|
|
11343
11396
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
11344
|
-
if (!method || !
|
|
11345
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
11397
|
+
if (!method || !path62) return wholeFileId;
|
|
11398
|
+
return findMatchingRouteNode(graph, serviceName, method, path62) ?? wholeFileId;
|
|
11346
11399
|
};
|
|
11347
11400
|
const mapping = config.workers?.[scriptName];
|
|
11348
11401
|
if (mapping) {
|
|
@@ -12539,7 +12592,7 @@ var import_semver2 = __toESM(require("semver"), 1);
|
|
|
12539
12592
|
// src/installers/templates.ts
|
|
12540
12593
|
init_cjs_shims();
|
|
12541
12594
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
12542
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
12595
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 7 \u2014 OTel init degrades to no-OBSERVED instead of crashing the host app when @opentelemetry deps are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
|
|
12543
12596
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
12544
12597
|
var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
|
|
12545
12598
|
var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
@@ -12875,6 +12928,14 @@ ${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
|
12875
12928
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
12876
12929
|
${OTEL_OTLP_HEADERS_JS}
|
|
12877
12930
|
|
|
12931
|
+
// Instrumentation is ambient \u2014 it must never break the host app. If the
|
|
12932
|
+
// @opentelemetry packages aren't installed (a package-manager install that
|
|
12933
|
+
// failed or hasn't run yet), degrade to running WITHOUT OBSERVED rather than
|
|
12934
|
+
// crashing the process on a missing module (#820). The require-based flavor can
|
|
12935
|
+
// guard this at runtime; do so around the whole SDK setup, not just the
|
|
12936
|
+
// requires, so a broken instrumentation registration can't take the app down
|
|
12937
|
+
// either.
|
|
12938
|
+
try {
|
|
12878
12939
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
12879
12940
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
12880
12941
|
const { trace, context } = require('@opentelemetry/api')
|
|
@@ -12886,6 +12947,14 @@ __INSTRUMENTATION_BLOCK__
|
|
|
12886
12947
|
const sdk = new NodeSDK({ instrumentations })
|
|
12887
12948
|
sdk.start()
|
|
12888
12949
|
${neatWireCaptureSource(false)}
|
|
12950
|
+
} catch (__neatOtelErr) {
|
|
12951
|
+
const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
|
|
12952
|
+
if (/Cannot find module|MODULE_NOT_FOUND/.test(__neatMsg)) {
|
|
12953
|
+
console.warn('[neat] OpenTelemetry is not active: its packages are not installed, so this app is running without OBSERVED tracing. Run your package manager install and restart to enable it.')
|
|
12954
|
+
} else {
|
|
12955
|
+
console.warn('[neat] OpenTelemetry failed to start; the app is running without OBSERVED tracing: ' + __neatMsg)
|
|
12956
|
+
}
|
|
12957
|
+
}
|
|
12889
12958
|
`;
|
|
12890
12959
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
12891
12960
|
${OTEL_INIT_STAMP}
|
|
@@ -15569,9 +15638,183 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
15569
15638
|
}
|
|
15570
15639
|
}
|
|
15571
15640
|
|
|
15572
|
-
// src/cli
|
|
15641
|
+
// src/hooks-cli.ts
|
|
15573
15642
|
init_cjs_shims();
|
|
15574
15643
|
var import_node_path59 = __toESM(require("path"), 1);
|
|
15644
|
+
var import_node_os5 = __toESM(require("os"), 1);
|
|
15645
|
+
var import_node_fs40 = require("fs");
|
|
15646
|
+
var import_node_url5 = require("url");
|
|
15647
|
+
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
15648
|
+
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
15649
|
+
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
15650
|
+
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
15651
|
+
function moduleDir() {
|
|
15652
|
+
return typeof __dirname !== "undefined" ? __dirname : import_node_path59.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
|
|
15653
|
+
}
|
|
15654
|
+
async function readSkillAsset(rel) {
|
|
15655
|
+
const here = moduleDir();
|
|
15656
|
+
const candidates = [
|
|
15657
|
+
import_node_path59.default.resolve(here, "../../claude-skill", rel),
|
|
15658
|
+
import_node_path59.default.resolve(here, "../../../claude-skill", rel),
|
|
15659
|
+
import_node_path59.default.resolve(here, "../claude-skill", rel)
|
|
15660
|
+
];
|
|
15661
|
+
for (const candidate of candidates) {
|
|
15662
|
+
try {
|
|
15663
|
+
return await import_node_fs40.promises.readFile(candidate, "utf8");
|
|
15664
|
+
} catch {
|
|
15665
|
+
}
|
|
15666
|
+
}
|
|
15667
|
+
throw new Error(
|
|
15668
|
+
`neat hooks: could not find @neat.is/claude-skill/${rel} \u2014 is the package installed?`
|
|
15669
|
+
);
|
|
15670
|
+
}
|
|
15671
|
+
function neatHome3() {
|
|
15672
|
+
const override = process.env.NEAT_HOME;
|
|
15673
|
+
if (override && override.length > 0) return import_node_path59.default.resolve(override);
|
|
15674
|
+
return import_node_path59.default.join(import_node_os5.default.homedir(), ".neat");
|
|
15675
|
+
}
|
|
15676
|
+
function claudeSettingsPath() {
|
|
15677
|
+
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
15678
|
+
if (override && override.length > 0) return import_node_path59.default.resolve(override);
|
|
15679
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os5.default.homedir();
|
|
15680
|
+
return import_node_path59.default.join(home, ".claude", "settings.json");
|
|
15681
|
+
}
|
|
15682
|
+
function installedHookPath() {
|
|
15683
|
+
return import_node_path59.default.join(neatHome3(), "hooks", HOOK_FILENAME);
|
|
15684
|
+
}
|
|
15685
|
+
function isNeatSearchEntry(entry2) {
|
|
15686
|
+
return (entry2.hooks ?? []).some(
|
|
15687
|
+
(h) => typeof h.command === "string" && h.command.includes(HOOK_FILENAME)
|
|
15688
|
+
);
|
|
15689
|
+
}
|
|
15690
|
+
function neatHookEntry(command) {
|
|
15691
|
+
return { matcher: HOOK_MATCHER, hooks: [{ type: "command", command }] };
|
|
15692
|
+
}
|
|
15693
|
+
function hookCommand(scriptPath) {
|
|
15694
|
+
return `node "${scriptPath}"`;
|
|
15695
|
+
}
|
|
15696
|
+
async function runHooks(opts) {
|
|
15697
|
+
if (opts.printHook) {
|
|
15698
|
+
process.stdout.write(await readSkillAsset(`hooks/${HOOK_FILENAME}`));
|
|
15699
|
+
return { exitCode: 0 };
|
|
15700
|
+
}
|
|
15701
|
+
if (opts.printGuide) {
|
|
15702
|
+
process.stdout.write(await readSkillAsset(GUIDE_FILENAME));
|
|
15703
|
+
return { exitCode: 0 };
|
|
15704
|
+
}
|
|
15705
|
+
if (opts.printSettings) {
|
|
15706
|
+
const block = {
|
|
15707
|
+
hooks: { PreToolUse: [neatHookEntry(hookCommand(installedHookPath()))] }
|
|
15708
|
+
};
|
|
15709
|
+
process.stdout.write(JSON.stringify(block, null, 2) + "\n");
|
|
15710
|
+
return { exitCode: 0 };
|
|
15711
|
+
}
|
|
15712
|
+
if (opts.apply) {
|
|
15713
|
+
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
15714
|
+
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
15715
|
+
const scriptPath = installedHookPath();
|
|
15716
|
+
await import_node_fs40.promises.mkdir(import_node_path59.default.dirname(scriptPath), { recursive: true });
|
|
15717
|
+
await import_node_fs40.promises.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
15718
|
+
const guidePath = import_node_path59.default.join(neatHome3(), GUIDE_INSTALL_NAME);
|
|
15719
|
+
await import_node_fs40.promises.writeFile(guidePath, guide, "utf8");
|
|
15720
|
+
const settingsFile = claudeSettingsPath();
|
|
15721
|
+
let settings = {};
|
|
15722
|
+
try {
|
|
15723
|
+
settings = JSON.parse(await import_node_fs40.promises.readFile(settingsFile, "utf8"));
|
|
15724
|
+
} catch (err) {
|
|
15725
|
+
if (err.code !== "ENOENT") {
|
|
15726
|
+
console.error(
|
|
15727
|
+
`neat hooks: failed to read ${settingsFile} \u2014 ${err.message}`
|
|
15728
|
+
);
|
|
15729
|
+
return { exitCode: 1 };
|
|
15730
|
+
}
|
|
15731
|
+
}
|
|
15732
|
+
const hooks = settings.hooks ?? {};
|
|
15733
|
+
const preToolUse = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
|
|
15734
|
+
const command = hookCommand(scriptPath);
|
|
15735
|
+
const existingIdx = preToolUse.findIndex(isNeatSearchEntry);
|
|
15736
|
+
if (existingIdx >= 0) {
|
|
15737
|
+
preToolUse[existingIdx] = neatHookEntry(command);
|
|
15738
|
+
} else {
|
|
15739
|
+
preToolUse.push(neatHookEntry(command));
|
|
15740
|
+
}
|
|
15741
|
+
const merged = {
|
|
15742
|
+
...settings,
|
|
15743
|
+
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
15744
|
+
};
|
|
15745
|
+
await import_node_fs40.promises.mkdir(import_node_path59.default.dirname(settingsFile), { recursive: true });
|
|
15746
|
+
await import_node_fs40.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
15747
|
+
console.log(`neat hooks: installed the search-nudge hook`);
|
|
15748
|
+
console.log(` script: ${scriptPath}`);
|
|
15749
|
+
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
15750
|
+
console.log(` guidance: ${guidePath}`);
|
|
15751
|
+
console.log("");
|
|
15752
|
+
console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
|
|
15753
|
+
console.log("your agent will now be nudged to query NEAT first.");
|
|
15754
|
+
console.log("");
|
|
15755
|
+
console.log("The hook is Claude-Code-specific. For agents on other harnesses, paste");
|
|
15756
|
+
console.log(`the guidance above into your project instructions (CLAUDE.md / AGENTS.md).`);
|
|
15757
|
+
return { exitCode: 0 };
|
|
15758
|
+
}
|
|
15759
|
+
usage();
|
|
15760
|
+
return { exitCode: 0 };
|
|
15761
|
+
}
|
|
15762
|
+
function usage() {
|
|
15763
|
+
console.log("neat hooks \u2014 wire NEAT into your agent so it queries the graph before grepping");
|
|
15764
|
+
console.log("");
|
|
15765
|
+
console.log(" --apply install the Claude Code search-nudge hook and write the");
|
|
15766
|
+
console.log(" graph-first guidance to ~/.neat/, merging into");
|
|
15767
|
+
console.log(" ~/.claude/settings.json without touching your other hooks");
|
|
15768
|
+
console.log(" --print-hook print the hook script to stdout");
|
|
15769
|
+
console.log(" --print-guide print the agent-agnostic graph-first guidance to stdout");
|
|
15770
|
+
console.log(" --print-settings print the settings.json PreToolUse block --apply would add");
|
|
15771
|
+
console.log("");
|
|
15772
|
+
console.log("The hook is a gentle, non-blocking nudge \u2014 searches still run. It is");
|
|
15773
|
+
console.log("Claude-Code-specific; other harnesses get the same steer from the guidance.");
|
|
15774
|
+
}
|
|
15775
|
+
async function runHooksCommand(args) {
|
|
15776
|
+
const opts = {
|
|
15777
|
+
apply: false,
|
|
15778
|
+
printHook: false,
|
|
15779
|
+
printGuide: false,
|
|
15780
|
+
printSettings: false
|
|
15781
|
+
};
|
|
15782
|
+
for (const arg of args) {
|
|
15783
|
+
switch (arg) {
|
|
15784
|
+
case "--apply":
|
|
15785
|
+
opts.apply = true;
|
|
15786
|
+
break;
|
|
15787
|
+
case "--print-hook":
|
|
15788
|
+
opts.printHook = true;
|
|
15789
|
+
break;
|
|
15790
|
+
case "--print-guide":
|
|
15791
|
+
opts.printGuide = true;
|
|
15792
|
+
break;
|
|
15793
|
+
case "--print-settings":
|
|
15794
|
+
opts.printSettings = true;
|
|
15795
|
+
break;
|
|
15796
|
+
case "-h":
|
|
15797
|
+
case "--help":
|
|
15798
|
+
usage();
|
|
15799
|
+
return 0;
|
|
15800
|
+
default:
|
|
15801
|
+
console.error(`neat hooks: unknown flag "${arg}"`);
|
|
15802
|
+
usage();
|
|
15803
|
+
return 2;
|
|
15804
|
+
}
|
|
15805
|
+
}
|
|
15806
|
+
try {
|
|
15807
|
+
const { exitCode } = await runHooks(opts);
|
|
15808
|
+
return exitCode;
|
|
15809
|
+
} catch (err) {
|
|
15810
|
+
console.error(err.message);
|
|
15811
|
+
return 1;
|
|
15812
|
+
}
|
|
15813
|
+
}
|
|
15814
|
+
|
|
15815
|
+
// src/cli-verbs.ts
|
|
15816
|
+
init_cjs_shims();
|
|
15817
|
+
var import_node_path60 = __toESM(require("path"), 1);
|
|
15575
15818
|
|
|
15576
15819
|
// src/cli-client.ts
|
|
15577
15820
|
init_cjs_shims();
|
|
@@ -15600,10 +15843,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15600
15843
|
const root = baseUrl.replace(/\/$/, "");
|
|
15601
15844
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
15602
15845
|
return {
|
|
15603
|
-
async get(
|
|
15846
|
+
async get(path62) {
|
|
15604
15847
|
let res;
|
|
15605
15848
|
try {
|
|
15606
|
-
res = await fetch(`${root}${
|
|
15849
|
+
res = await fetch(`${root}${path62}`, {
|
|
15607
15850
|
headers: { ...authHeader }
|
|
15608
15851
|
});
|
|
15609
15852
|
} catch (err) {
|
|
@@ -15615,16 +15858,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15615
15858
|
const body = await res.text().catch(() => "");
|
|
15616
15859
|
throw new HttpError(
|
|
15617
15860
|
res.status,
|
|
15618
|
-
`${res.status} ${res.statusText} on GET ${
|
|
15861
|
+
`${res.status} ${res.statusText} on GET ${path62}: ${body}`,
|
|
15619
15862
|
body
|
|
15620
15863
|
);
|
|
15621
15864
|
}
|
|
15622
15865
|
return await res.json();
|
|
15623
15866
|
},
|
|
15624
|
-
async post(
|
|
15867
|
+
async post(path62, body) {
|
|
15625
15868
|
let res;
|
|
15626
15869
|
try {
|
|
15627
|
-
res = await fetch(`${root}${
|
|
15870
|
+
res = await fetch(`${root}${path62}`, {
|
|
15628
15871
|
method: "POST",
|
|
15629
15872
|
headers: { "content-type": "application/json", ...authHeader },
|
|
15630
15873
|
body: JSON.stringify(body)
|
|
@@ -15638,7 +15881,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15638
15881
|
const text = await res.text().catch(() => "");
|
|
15639
15882
|
throw new HttpError(
|
|
15640
15883
|
res.status,
|
|
15641
|
-
`${res.status} ${res.statusText} on POST ${
|
|
15884
|
+
`${res.status} ${res.statusText} on POST ${path62}: ${text}`,
|
|
15642
15885
|
text
|
|
15643
15886
|
);
|
|
15644
15887
|
}
|
|
@@ -15652,12 +15895,12 @@ function projectPath(project, suffix) {
|
|
|
15652
15895
|
}
|
|
15653
15896
|
async function runRootCause(client, input) {
|
|
15654
15897
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
15655
|
-
const
|
|
15898
|
+
const path62 = projectPath(
|
|
15656
15899
|
input.project,
|
|
15657
15900
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
15658
15901
|
);
|
|
15659
15902
|
try {
|
|
15660
|
-
const result = await client.get(
|
|
15903
|
+
const result = await client.get(path62);
|
|
15661
15904
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
15662
15905
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
15663
15906
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -15683,12 +15926,12 @@ async function runRootCause(client, input) {
|
|
|
15683
15926
|
}
|
|
15684
15927
|
async function runBlastRadius(client, input) {
|
|
15685
15928
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
15686
|
-
const
|
|
15929
|
+
const path62 = projectPath(
|
|
15687
15930
|
input.project,
|
|
15688
15931
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
15689
15932
|
);
|
|
15690
15933
|
try {
|
|
15691
|
-
const result = await client.get(
|
|
15934
|
+
const result = await client.get(path62);
|
|
15692
15935
|
if (result.totalAffected === 0) {
|
|
15693
15936
|
return {
|
|
15694
15937
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -15722,12 +15965,12 @@ function formatBlastEntry(n) {
|
|
|
15722
15965
|
}
|
|
15723
15966
|
async function runDependencies(client, input) {
|
|
15724
15967
|
const depth = input.depth ?? 3;
|
|
15725
|
-
const
|
|
15968
|
+
const path62 = projectPath(
|
|
15726
15969
|
input.project,
|
|
15727
15970
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
15728
15971
|
);
|
|
15729
15972
|
try {
|
|
15730
|
-
const result = await client.get(
|
|
15973
|
+
const result = await client.get(path62);
|
|
15731
15974
|
if (result.total === 0) {
|
|
15732
15975
|
return {
|
|
15733
15976
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -15819,9 +16062,9 @@ function formatDuration(ms) {
|
|
|
15819
16062
|
return `${Math.round(h / 24)}d`;
|
|
15820
16063
|
}
|
|
15821
16064
|
async function runIncidents(client, input) {
|
|
15822
|
-
const
|
|
16065
|
+
const path62 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
15823
16066
|
try {
|
|
15824
|
-
const body = await client.get(
|
|
16067
|
+
const body = await client.get(path62);
|
|
15825
16068
|
const events = body.events;
|
|
15826
16069
|
if (events.length === 0) {
|
|
15827
16070
|
return {
|
|
@@ -16111,7 +16354,7 @@ async function resolveProjectEntry(opts) {
|
|
|
16111
16354
|
const cwd = opts.cwd ?? process.cwd();
|
|
16112
16355
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
16113
16356
|
for (const entry2 of entries) {
|
|
16114
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
16357
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path60.default.sep}`)) {
|
|
16115
16358
|
return entry2;
|
|
16116
16359
|
}
|
|
16117
16360
|
}
|
|
@@ -16276,7 +16519,7 @@ function isNpxInvocation() {
|
|
|
16276
16519
|
function commandPrefix() {
|
|
16277
16520
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
16278
16521
|
}
|
|
16279
|
-
function
|
|
16522
|
+
function usage2() {
|
|
16280
16523
|
const neat = commandPrefix();
|
|
16281
16524
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
16282
16525
|
console.log("");
|
|
@@ -16313,6 +16556,14 @@ function usage() {
|
|
|
16313
16556
|
console.log(" Flags:");
|
|
16314
16557
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
16315
16558
|
console.log(" --apply merge mcpServers.neat into ~/.claude.json");
|
|
16559
|
+
console.log(" hooks Wire NEAT into your agent so it queries the graph before");
|
|
16560
|
+
console.log(" grepping. Installs a gentle Claude Code search-nudge hook and");
|
|
16561
|
+
console.log(" writes agent-agnostic graph-first guidance for other harnesses.");
|
|
16562
|
+
console.log(" Flags:");
|
|
16563
|
+
console.log(" --apply install the hook + guidance");
|
|
16564
|
+
console.log(" --print-hook print the hook script");
|
|
16565
|
+
console.log(" --print-guide print the graph-first guidance");
|
|
16566
|
+
console.log(" --print-settings print the settings.json block --apply adds");
|
|
16316
16567
|
console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
|
|
16317
16568
|
console.log(" emit a docker-compose / systemd / docker run artifact, and");
|
|
16318
16569
|
console.log(" print the OTel env-vars block to paste into your platform.");
|
|
@@ -16557,7 +16808,7 @@ async function buildPatchSections(services, project) {
|
|
|
16557
16808
|
}
|
|
16558
16809
|
async function runInit(opts) {
|
|
16559
16810
|
const written = [];
|
|
16560
|
-
const stat = await
|
|
16811
|
+
const stat = await import_node_fs41.promises.stat(opts.scanPath).catch(() => null);
|
|
16561
16812
|
if (!stat || !stat.isDirectory()) {
|
|
16562
16813
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
16563
16814
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -16566,13 +16817,13 @@ async function runInit(opts) {
|
|
|
16566
16817
|
printDiscoveryReport(opts, services);
|
|
16567
16818
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
16568
16819
|
const patch = renderPatch(sections);
|
|
16569
|
-
const patchPath =
|
|
16820
|
+
const patchPath = import_node_path61.default.join(opts.scanPath, "neat.patch");
|
|
16570
16821
|
if (opts.dryRun) {
|
|
16571
|
-
await
|
|
16822
|
+
await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
|
|
16572
16823
|
written.push(patchPath);
|
|
16573
16824
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
16574
|
-
const gitignorePath =
|
|
16575
|
-
const gitignoreExists = await
|
|
16825
|
+
const gitignorePath = import_node_path61.default.join(opts.scanPath, ".gitignore");
|
|
16826
|
+
const gitignoreExists = await import_node_fs41.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
16576
16827
|
const verb = gitignoreExists ? "append" : "create";
|
|
16577
16828
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
16578
16829
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -16583,9 +16834,9 @@ async function runInit(opts) {
|
|
|
16583
16834
|
const graph = getGraph(graphKey);
|
|
16584
16835
|
const projectPaths = pathsForProject(
|
|
16585
16836
|
graphKey,
|
|
16586
|
-
|
|
16837
|
+
import_node_path61.default.join(opts.scanPath, "neat-out")
|
|
16587
16838
|
);
|
|
16588
|
-
const errorsPath =
|
|
16839
|
+
const errorsPath = import_node_path61.default.join(import_node_path61.default.dirname(opts.outPath), import_node_path61.default.basename(projectPaths.errorsPath));
|
|
16589
16840
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
16590
16841
|
await saveGraphToDisk(graph, opts.outPath);
|
|
16591
16842
|
written.push(opts.outPath);
|
|
@@ -16664,7 +16915,7 @@ async function runInit(opts) {
|
|
|
16664
16915
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
16665
16916
|
}
|
|
16666
16917
|
} else {
|
|
16667
|
-
await
|
|
16918
|
+
await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
|
|
16668
16919
|
written.push(patchPath);
|
|
16669
16920
|
}
|
|
16670
16921
|
}
|
|
@@ -16704,9 +16955,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
16704
16955
|
};
|
|
16705
16956
|
function claudeConfigPath() {
|
|
16706
16957
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
16707
|
-
if (override && override.length > 0) return
|
|
16958
|
+
if (override && override.length > 0) return import_node_path61.default.resolve(override);
|
|
16708
16959
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
16709
|
-
return
|
|
16960
|
+
return import_node_path61.default.join(home, ".claude.json");
|
|
16710
16961
|
}
|
|
16711
16962
|
async function runSkill(opts) {
|
|
16712
16963
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -16718,7 +16969,7 @@ async function runSkill(opts) {
|
|
|
16718
16969
|
const target = claudeConfigPath();
|
|
16719
16970
|
let existing = {};
|
|
16720
16971
|
try {
|
|
16721
|
-
existing = JSON.parse(await
|
|
16972
|
+
existing = JSON.parse(await import_node_fs41.promises.readFile(target, "utf8"));
|
|
16722
16973
|
} catch (err) {
|
|
16723
16974
|
if (err.code !== "ENOENT") {
|
|
16724
16975
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -16730,10 +16981,13 @@ async function runSkill(opts) {
|
|
|
16730
16981
|
...existing,
|
|
16731
16982
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
16732
16983
|
};
|
|
16733
|
-
await
|
|
16734
|
-
await
|
|
16984
|
+
await import_node_fs41.promises.mkdir(import_node_path61.default.dirname(target), { recursive: true });
|
|
16985
|
+
await import_node_fs41.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
16735
16986
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
16736
16987
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
16988
|
+
console.log("");
|
|
16989
|
+
console.log("Tip: run `neat hooks --apply` to also install the search-nudge hook, so");
|
|
16990
|
+
console.log("your agent reaches for the graph before it grep-scans the repo.");
|
|
16737
16991
|
return { exitCode: 0 };
|
|
16738
16992
|
}
|
|
16739
16993
|
console.log("neat skill \u2014 Claude Code MCP drop-in for NEAT");
|
|
@@ -16746,13 +17000,16 @@ async function runSkill(opts) {
|
|
|
16746
17000
|
console.log("");
|
|
16747
17001
|
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
|
|
16748
17002
|
console.log("non-default daemon by editing that value in the generated config.");
|
|
17003
|
+
console.log("");
|
|
17004
|
+
console.log("See also `neat hooks --apply` \u2014 the search-nudge hook + graph-first guidance");
|
|
17005
|
+
console.log("that steer an agent to query the graph before falling back to text search.");
|
|
16749
17006
|
return { exitCode: 0 };
|
|
16750
17007
|
}
|
|
16751
17008
|
async function main() {
|
|
16752
17009
|
const argv = process.argv.slice(2);
|
|
16753
17010
|
const cmd0 = argv[0];
|
|
16754
17011
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
16755
|
-
|
|
17012
|
+
usage2();
|
|
16756
17013
|
process.exit(0);
|
|
16757
17014
|
}
|
|
16758
17015
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -16764,6 +17021,11 @@ async function main() {
|
|
|
16764
17021
|
if (code !== 0) process.exit(code);
|
|
16765
17022
|
return;
|
|
16766
17023
|
}
|
|
17024
|
+
if (cmd0 === "hooks") {
|
|
17025
|
+
const code = await runHooksCommand(argv.slice(1));
|
|
17026
|
+
if (code !== 0) process.exit(code);
|
|
17027
|
+
return;
|
|
17028
|
+
}
|
|
16767
17029
|
const argvParsed = parseArgs(argv);
|
|
16768
17030
|
if (argvParsed.positional.length === 0) {
|
|
16769
17031
|
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
@@ -16778,19 +17040,19 @@ async function main() {
|
|
|
16778
17040
|
const target = positional[0];
|
|
16779
17041
|
if (!target) {
|
|
16780
17042
|
console.error("neat init: missing <path>");
|
|
16781
|
-
|
|
17043
|
+
usage2();
|
|
16782
17044
|
process.exit(2);
|
|
16783
17045
|
}
|
|
16784
17046
|
if (apply3 && dryRun) {
|
|
16785
17047
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
16786
17048
|
process.exit(2);
|
|
16787
17049
|
}
|
|
16788
|
-
const scanPath =
|
|
17050
|
+
const scanPath = import_node_path61.default.resolve(target);
|
|
16789
17051
|
const projectExplicit = parsed.project !== null;
|
|
16790
|
-
const projectName = projectExplicit ? project :
|
|
17052
|
+
const projectName = projectExplicit ? project : import_node_path61.default.basename(scanPath);
|
|
16791
17053
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
16792
|
-
const fallback = pathsForProject(projectKey,
|
|
16793
|
-
const outPath =
|
|
17054
|
+
const fallback = pathsForProject(projectKey, import_node_path61.default.join(scanPath, "neat-out")).snapshotPath;
|
|
17055
|
+
const outPath = import_node_path61.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
16794
17056
|
const result = await runInit({
|
|
16795
17057
|
scanPath,
|
|
16796
17058
|
outPath,
|
|
@@ -16808,24 +17070,24 @@ async function main() {
|
|
|
16808
17070
|
const target = positional[0];
|
|
16809
17071
|
if (!target) {
|
|
16810
17072
|
console.error("neat watch: missing <path>");
|
|
16811
|
-
|
|
17073
|
+
usage2();
|
|
16812
17074
|
process.exit(2);
|
|
16813
17075
|
}
|
|
16814
|
-
const scanPath =
|
|
16815
|
-
const stat = await
|
|
17076
|
+
const scanPath = import_node_path61.default.resolve(target);
|
|
17077
|
+
const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
|
|
16816
17078
|
if (!stat || !stat.isDirectory()) {
|
|
16817
17079
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
16818
17080
|
process.exit(2);
|
|
16819
17081
|
}
|
|
16820
|
-
const projectPaths = pathsForProject(project,
|
|
16821
|
-
const outPath =
|
|
16822
|
-
const errorsPath =
|
|
16823
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
17082
|
+
const projectPaths = pathsForProject(project, import_node_path61.default.join(scanPath, "neat-out"));
|
|
17083
|
+
const outPath = import_node_path61.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
17084
|
+
const errorsPath = import_node_path61.default.resolve(
|
|
17085
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path61.default.join(import_node_path61.default.dirname(outPath), import_node_path61.default.basename(projectPaths.errorsPath))
|
|
16824
17086
|
);
|
|
16825
|
-
const staleEventsPath =
|
|
16826
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
17087
|
+
const staleEventsPath = import_node_path61.default.resolve(
|
|
17088
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path61.default.join(import_node_path61.default.dirname(outPath), import_node_path61.default.basename(projectPaths.staleEventsPath))
|
|
16827
17089
|
);
|
|
16828
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
17090
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path61.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
16829
17091
|
const handle = await startWatch(getGraph(project), {
|
|
16830
17092
|
scanPath,
|
|
16831
17093
|
outPath,
|
|
@@ -16867,7 +17129,7 @@ async function main() {
|
|
|
16867
17129
|
const name = positional[0];
|
|
16868
17130
|
if (!name) {
|
|
16869
17131
|
console.error("neat pause: missing <name>");
|
|
16870
|
-
|
|
17132
|
+
usage2();
|
|
16871
17133
|
process.exit(2);
|
|
16872
17134
|
}
|
|
16873
17135
|
const daemon = await findDaemonByProject(name);
|
|
@@ -16892,7 +17154,7 @@ async function main() {
|
|
|
16892
17154
|
const name = positional[0];
|
|
16893
17155
|
if (!name) {
|
|
16894
17156
|
console.error("neat resume: missing <name>");
|
|
16895
|
-
|
|
17157
|
+
usage2();
|
|
16896
17158
|
process.exit(2);
|
|
16897
17159
|
}
|
|
16898
17160
|
const daemon = await findDaemonByProject(name);
|
|
@@ -16922,7 +17184,7 @@ async function main() {
|
|
|
16922
17184
|
const name = positional[0];
|
|
16923
17185
|
if (!name) {
|
|
16924
17186
|
console.error("neat uninstall: missing <name>");
|
|
16925
|
-
|
|
17187
|
+
usage2();
|
|
16926
17188
|
process.exit(2);
|
|
16927
17189
|
}
|
|
16928
17190
|
const daemon = await findDaemonByProject(name);
|
|
@@ -17004,15 +17266,15 @@ async function main() {
|
|
|
17004
17266
|
return;
|
|
17005
17267
|
}
|
|
17006
17268
|
console.error(`neat: unknown command "${cmd}"`);
|
|
17007
|
-
|
|
17269
|
+
usage2();
|
|
17008
17270
|
process.exit(1);
|
|
17009
17271
|
}
|
|
17010
17272
|
async function tryOrchestrator(cmd, parsed) {
|
|
17011
|
-
const scanPath =
|
|
17012
|
-
const stat = await
|
|
17273
|
+
const scanPath = import_node_path61.default.resolve(cmd);
|
|
17274
|
+
const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
|
|
17013
17275
|
if (!stat || !stat.isDirectory()) return null;
|
|
17014
17276
|
const projectExplicit = parsed.project !== null;
|
|
17015
|
-
const projectName = projectExplicit ? parsed.project :
|
|
17277
|
+
const projectName = projectExplicit ? parsed.project : import_node_path61.default.basename(scanPath);
|
|
17016
17278
|
const result = await runOrchestrator({
|
|
17017
17279
|
scanPath,
|
|
17018
17280
|
project: projectName,
|