@neat.is/core 0.5.2 → 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 +366 -120
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +250 -58
- 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) {
|
|
@@ -15585,9 +15638,183 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
15585
15638
|
}
|
|
15586
15639
|
}
|
|
15587
15640
|
|
|
15588
|
-
// src/cli
|
|
15641
|
+
// src/hooks-cli.ts
|
|
15589
15642
|
init_cjs_shims();
|
|
15590
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);
|
|
15591
15818
|
|
|
15592
15819
|
// src/cli-client.ts
|
|
15593
15820
|
init_cjs_shims();
|
|
@@ -15616,10 +15843,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15616
15843
|
const root = baseUrl.replace(/\/$/, "");
|
|
15617
15844
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
15618
15845
|
return {
|
|
15619
|
-
async get(
|
|
15846
|
+
async get(path62) {
|
|
15620
15847
|
let res;
|
|
15621
15848
|
try {
|
|
15622
|
-
res = await fetch(`${root}${
|
|
15849
|
+
res = await fetch(`${root}${path62}`, {
|
|
15623
15850
|
headers: { ...authHeader }
|
|
15624
15851
|
});
|
|
15625
15852
|
} catch (err) {
|
|
@@ -15631,16 +15858,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15631
15858
|
const body = await res.text().catch(() => "");
|
|
15632
15859
|
throw new HttpError(
|
|
15633
15860
|
res.status,
|
|
15634
|
-
`${res.status} ${res.statusText} on GET ${
|
|
15861
|
+
`${res.status} ${res.statusText} on GET ${path62}: ${body}`,
|
|
15635
15862
|
body
|
|
15636
15863
|
);
|
|
15637
15864
|
}
|
|
15638
15865
|
return await res.json();
|
|
15639
15866
|
},
|
|
15640
|
-
async post(
|
|
15867
|
+
async post(path62, body) {
|
|
15641
15868
|
let res;
|
|
15642
15869
|
try {
|
|
15643
|
-
res = await fetch(`${root}${
|
|
15870
|
+
res = await fetch(`${root}${path62}`, {
|
|
15644
15871
|
method: "POST",
|
|
15645
15872
|
headers: { "content-type": "application/json", ...authHeader },
|
|
15646
15873
|
body: JSON.stringify(body)
|
|
@@ -15654,7 +15881,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
15654
15881
|
const text = await res.text().catch(() => "");
|
|
15655
15882
|
throw new HttpError(
|
|
15656
15883
|
res.status,
|
|
15657
|
-
`${res.status} ${res.statusText} on POST ${
|
|
15884
|
+
`${res.status} ${res.statusText} on POST ${path62}: ${text}`,
|
|
15658
15885
|
text
|
|
15659
15886
|
);
|
|
15660
15887
|
}
|
|
@@ -15668,12 +15895,12 @@ function projectPath(project, suffix) {
|
|
|
15668
15895
|
}
|
|
15669
15896
|
async function runRootCause(client, input) {
|
|
15670
15897
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
15671
|
-
const
|
|
15898
|
+
const path62 = projectPath(
|
|
15672
15899
|
input.project,
|
|
15673
15900
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
15674
15901
|
);
|
|
15675
15902
|
try {
|
|
15676
|
-
const result = await client.get(
|
|
15903
|
+
const result = await client.get(path62);
|
|
15677
15904
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
15678
15905
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
15679
15906
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -15699,12 +15926,12 @@ async function runRootCause(client, input) {
|
|
|
15699
15926
|
}
|
|
15700
15927
|
async function runBlastRadius(client, input) {
|
|
15701
15928
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
15702
|
-
const
|
|
15929
|
+
const path62 = projectPath(
|
|
15703
15930
|
input.project,
|
|
15704
15931
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
15705
15932
|
);
|
|
15706
15933
|
try {
|
|
15707
|
-
const result = await client.get(
|
|
15934
|
+
const result = await client.get(path62);
|
|
15708
15935
|
if (result.totalAffected === 0) {
|
|
15709
15936
|
return {
|
|
15710
15937
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -15738,12 +15965,12 @@ function formatBlastEntry(n) {
|
|
|
15738
15965
|
}
|
|
15739
15966
|
async function runDependencies(client, input) {
|
|
15740
15967
|
const depth = input.depth ?? 3;
|
|
15741
|
-
const
|
|
15968
|
+
const path62 = projectPath(
|
|
15742
15969
|
input.project,
|
|
15743
15970
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
15744
15971
|
);
|
|
15745
15972
|
try {
|
|
15746
|
-
const result = await client.get(
|
|
15973
|
+
const result = await client.get(path62);
|
|
15747
15974
|
if (result.total === 0) {
|
|
15748
15975
|
return {
|
|
15749
15976
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -15835,9 +16062,9 @@ function formatDuration(ms) {
|
|
|
15835
16062
|
return `${Math.round(h / 24)}d`;
|
|
15836
16063
|
}
|
|
15837
16064
|
async function runIncidents(client, input) {
|
|
15838
|
-
const
|
|
16065
|
+
const path62 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
15839
16066
|
try {
|
|
15840
|
-
const body = await client.get(
|
|
16067
|
+
const body = await client.get(path62);
|
|
15841
16068
|
const events = body.events;
|
|
15842
16069
|
if (events.length === 0) {
|
|
15843
16070
|
return {
|
|
@@ -16127,7 +16354,7 @@ async function resolveProjectEntry(opts) {
|
|
|
16127
16354
|
const cwd = opts.cwd ?? process.cwd();
|
|
16128
16355
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
16129
16356
|
for (const entry2 of entries) {
|
|
16130
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
16357
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path60.default.sep}`)) {
|
|
16131
16358
|
return entry2;
|
|
16132
16359
|
}
|
|
16133
16360
|
}
|
|
@@ -16292,7 +16519,7 @@ function isNpxInvocation() {
|
|
|
16292
16519
|
function commandPrefix() {
|
|
16293
16520
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
16294
16521
|
}
|
|
16295
|
-
function
|
|
16522
|
+
function usage2() {
|
|
16296
16523
|
const neat = commandPrefix();
|
|
16297
16524
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
16298
16525
|
console.log("");
|
|
@@ -16329,6 +16556,14 @@ function usage() {
|
|
|
16329
16556
|
console.log(" Flags:");
|
|
16330
16557
|
console.log(" --print-config print the JSON snippet to stdout");
|
|
16331
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");
|
|
16332
16567
|
console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
|
|
16333
16568
|
console.log(" emit a docker-compose / systemd / docker run artifact, and");
|
|
16334
16569
|
console.log(" print the OTel env-vars block to paste into your platform.");
|
|
@@ -16573,7 +16808,7 @@ async function buildPatchSections(services, project) {
|
|
|
16573
16808
|
}
|
|
16574
16809
|
async function runInit(opts) {
|
|
16575
16810
|
const written = [];
|
|
16576
|
-
const stat = await
|
|
16811
|
+
const stat = await import_node_fs41.promises.stat(opts.scanPath).catch(() => null);
|
|
16577
16812
|
if (!stat || !stat.isDirectory()) {
|
|
16578
16813
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
16579
16814
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -16582,13 +16817,13 @@ async function runInit(opts) {
|
|
|
16582
16817
|
printDiscoveryReport(opts, services);
|
|
16583
16818
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
16584
16819
|
const patch = renderPatch(sections);
|
|
16585
|
-
const patchPath =
|
|
16820
|
+
const patchPath = import_node_path61.default.join(opts.scanPath, "neat.patch");
|
|
16586
16821
|
if (opts.dryRun) {
|
|
16587
|
-
await
|
|
16822
|
+
await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
|
|
16588
16823
|
written.push(patchPath);
|
|
16589
16824
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
16590
|
-
const gitignorePath =
|
|
16591
|
-
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);
|
|
16592
16827
|
const verb = gitignoreExists ? "append" : "create";
|
|
16593
16828
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
16594
16829
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -16599,9 +16834,9 @@ async function runInit(opts) {
|
|
|
16599
16834
|
const graph = getGraph(graphKey);
|
|
16600
16835
|
const projectPaths = pathsForProject(
|
|
16601
16836
|
graphKey,
|
|
16602
|
-
|
|
16837
|
+
import_node_path61.default.join(opts.scanPath, "neat-out")
|
|
16603
16838
|
);
|
|
16604
|
-
const errorsPath =
|
|
16839
|
+
const errorsPath = import_node_path61.default.join(import_node_path61.default.dirname(opts.outPath), import_node_path61.default.basename(projectPaths.errorsPath));
|
|
16605
16840
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
16606
16841
|
await saveGraphToDisk(graph, opts.outPath);
|
|
16607
16842
|
written.push(opts.outPath);
|
|
@@ -16680,7 +16915,7 @@ async function runInit(opts) {
|
|
|
16680
16915
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
16681
16916
|
}
|
|
16682
16917
|
} else {
|
|
16683
|
-
await
|
|
16918
|
+
await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
|
|
16684
16919
|
written.push(patchPath);
|
|
16685
16920
|
}
|
|
16686
16921
|
}
|
|
@@ -16720,9 +16955,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
16720
16955
|
};
|
|
16721
16956
|
function claudeConfigPath() {
|
|
16722
16957
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
16723
|
-
if (override && override.length > 0) return
|
|
16958
|
+
if (override && override.length > 0) return import_node_path61.default.resolve(override);
|
|
16724
16959
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
16725
|
-
return
|
|
16960
|
+
return import_node_path61.default.join(home, ".claude.json");
|
|
16726
16961
|
}
|
|
16727
16962
|
async function runSkill(opts) {
|
|
16728
16963
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -16734,7 +16969,7 @@ async function runSkill(opts) {
|
|
|
16734
16969
|
const target = claudeConfigPath();
|
|
16735
16970
|
let existing = {};
|
|
16736
16971
|
try {
|
|
16737
|
-
existing = JSON.parse(await
|
|
16972
|
+
existing = JSON.parse(await import_node_fs41.promises.readFile(target, "utf8"));
|
|
16738
16973
|
} catch (err) {
|
|
16739
16974
|
if (err.code !== "ENOENT") {
|
|
16740
16975
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -16746,10 +16981,13 @@ async function runSkill(opts) {
|
|
|
16746
16981
|
...existing,
|
|
16747
16982
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
16748
16983
|
};
|
|
16749
|
-
await
|
|
16750
|
-
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");
|
|
16751
16986
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
16752
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.");
|
|
16753
16991
|
return { exitCode: 0 };
|
|
16754
16992
|
}
|
|
16755
16993
|
console.log("neat skill \u2014 Claude Code MCP drop-in for NEAT");
|
|
@@ -16762,13 +17000,16 @@ async function runSkill(opts) {
|
|
|
16762
17000
|
console.log("");
|
|
16763
17001
|
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
|
|
16764
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.");
|
|
16765
17006
|
return { exitCode: 0 };
|
|
16766
17007
|
}
|
|
16767
17008
|
async function main() {
|
|
16768
17009
|
const argv = process.argv.slice(2);
|
|
16769
17010
|
const cmd0 = argv[0];
|
|
16770
17011
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
16771
|
-
|
|
17012
|
+
usage2();
|
|
16772
17013
|
process.exit(0);
|
|
16773
17014
|
}
|
|
16774
17015
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -16780,6 +17021,11 @@ async function main() {
|
|
|
16780
17021
|
if (code !== 0) process.exit(code);
|
|
16781
17022
|
return;
|
|
16782
17023
|
}
|
|
17024
|
+
if (cmd0 === "hooks") {
|
|
17025
|
+
const code = await runHooksCommand(argv.slice(1));
|
|
17026
|
+
if (code !== 0) process.exit(code);
|
|
17027
|
+
return;
|
|
17028
|
+
}
|
|
16783
17029
|
const argvParsed = parseArgs(argv);
|
|
16784
17030
|
if (argvParsed.positional.length === 0) {
|
|
16785
17031
|
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
@@ -16794,19 +17040,19 @@ async function main() {
|
|
|
16794
17040
|
const target = positional[0];
|
|
16795
17041
|
if (!target) {
|
|
16796
17042
|
console.error("neat init: missing <path>");
|
|
16797
|
-
|
|
17043
|
+
usage2();
|
|
16798
17044
|
process.exit(2);
|
|
16799
17045
|
}
|
|
16800
17046
|
if (apply3 && dryRun) {
|
|
16801
17047
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
16802
17048
|
process.exit(2);
|
|
16803
17049
|
}
|
|
16804
|
-
const scanPath =
|
|
17050
|
+
const scanPath = import_node_path61.default.resolve(target);
|
|
16805
17051
|
const projectExplicit = parsed.project !== null;
|
|
16806
|
-
const projectName = projectExplicit ? project :
|
|
17052
|
+
const projectName = projectExplicit ? project : import_node_path61.default.basename(scanPath);
|
|
16807
17053
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
16808
|
-
const fallback = pathsForProject(projectKey,
|
|
16809
|
-
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);
|
|
16810
17056
|
const result = await runInit({
|
|
16811
17057
|
scanPath,
|
|
16812
17058
|
outPath,
|
|
@@ -16824,24 +17070,24 @@ async function main() {
|
|
|
16824
17070
|
const target = positional[0];
|
|
16825
17071
|
if (!target) {
|
|
16826
17072
|
console.error("neat watch: missing <path>");
|
|
16827
|
-
|
|
17073
|
+
usage2();
|
|
16828
17074
|
process.exit(2);
|
|
16829
17075
|
}
|
|
16830
|
-
const scanPath =
|
|
16831
|
-
const stat = await
|
|
17076
|
+
const scanPath = import_node_path61.default.resolve(target);
|
|
17077
|
+
const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
|
|
16832
17078
|
if (!stat || !stat.isDirectory()) {
|
|
16833
17079
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
16834
17080
|
process.exit(2);
|
|
16835
17081
|
}
|
|
16836
|
-
const projectPaths = pathsForProject(project,
|
|
16837
|
-
const outPath =
|
|
16838
|
-
const errorsPath =
|
|
16839
|
-
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))
|
|
16840
17086
|
);
|
|
16841
|
-
const staleEventsPath =
|
|
16842
|
-
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))
|
|
16843
17089
|
);
|
|
16844
|
-
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;
|
|
16845
17091
|
const handle = await startWatch(getGraph(project), {
|
|
16846
17092
|
scanPath,
|
|
16847
17093
|
outPath,
|
|
@@ -16883,7 +17129,7 @@ async function main() {
|
|
|
16883
17129
|
const name = positional[0];
|
|
16884
17130
|
if (!name) {
|
|
16885
17131
|
console.error("neat pause: missing <name>");
|
|
16886
|
-
|
|
17132
|
+
usage2();
|
|
16887
17133
|
process.exit(2);
|
|
16888
17134
|
}
|
|
16889
17135
|
const daemon = await findDaemonByProject(name);
|
|
@@ -16908,7 +17154,7 @@ async function main() {
|
|
|
16908
17154
|
const name = positional[0];
|
|
16909
17155
|
if (!name) {
|
|
16910
17156
|
console.error("neat resume: missing <name>");
|
|
16911
|
-
|
|
17157
|
+
usage2();
|
|
16912
17158
|
process.exit(2);
|
|
16913
17159
|
}
|
|
16914
17160
|
const daemon = await findDaemonByProject(name);
|
|
@@ -16938,7 +17184,7 @@ async function main() {
|
|
|
16938
17184
|
const name = positional[0];
|
|
16939
17185
|
if (!name) {
|
|
16940
17186
|
console.error("neat uninstall: missing <name>");
|
|
16941
|
-
|
|
17187
|
+
usage2();
|
|
16942
17188
|
process.exit(2);
|
|
16943
17189
|
}
|
|
16944
17190
|
const daemon = await findDaemonByProject(name);
|
|
@@ -17020,15 +17266,15 @@ async function main() {
|
|
|
17020
17266
|
return;
|
|
17021
17267
|
}
|
|
17022
17268
|
console.error(`neat: unknown command "${cmd}"`);
|
|
17023
|
-
|
|
17269
|
+
usage2();
|
|
17024
17270
|
process.exit(1);
|
|
17025
17271
|
}
|
|
17026
17272
|
async function tryOrchestrator(cmd, parsed) {
|
|
17027
|
-
const scanPath =
|
|
17028
|
-
const stat = await
|
|
17273
|
+
const scanPath = import_node_path61.default.resolve(cmd);
|
|
17274
|
+
const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
|
|
17029
17275
|
if (!stat || !stat.isDirectory()) return null;
|
|
17030
17276
|
const projectExplicit = parsed.project !== null;
|
|
17031
|
-
const projectName = projectExplicit ? parsed.project :
|
|
17277
|
+
const projectName = projectExplicit ? parsed.project : import_node_path61.default.basename(scanPath);
|
|
17032
17278
|
const result = await runOrchestrator({
|
|
17033
17279
|
scanPath,
|
|
17034
17280
|
project: projectName,
|