@neat.is/mcp 0.9.7-dev.20260827 → 0.9.8-dev.20260828
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/index.cjs +49 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +49 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -445,7 +445,29 @@ function projectPath(project, suffix) {
|
|
|
445
445
|
if (!project) return suffix;
|
|
446
446
|
return `/projects/${encodeURIComponent(project)}${suffix}`;
|
|
447
447
|
}
|
|
448
|
-
async function
|
|
448
|
+
async function readinessReason(client2, project) {
|
|
449
|
+
let health;
|
|
450
|
+
try {
|
|
451
|
+
const named = project ?? "default";
|
|
452
|
+
health = await client2.get(projectPath(named, "/health"));
|
|
453
|
+
} catch {
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
const coverage = health.coverage;
|
|
457
|
+
if (!coverage || typeof coverage.skippedFiles !== "number") {
|
|
458
|
+
return "(index still building \u2014 the first extraction pass has not finished yet, so this may be incomplete; retry once it settles.)";
|
|
459
|
+
}
|
|
460
|
+
if (coverage.skippedFiles > 0) {
|
|
461
|
+
const n = coverage.skippedFiles;
|
|
462
|
+
return `(index partial \u2014 ${n} file${n === 1 ? "" : "s"} failed to parse in the last pass, so this may be incomplete.)`;
|
|
463
|
+
}
|
|
464
|
+
return "(graph is current \u2014 this is a real empty result, not a still-building index.)";
|
|
465
|
+
}
|
|
466
|
+
async function emptyWithReadiness(client2, project, summary) {
|
|
467
|
+
const reason = await readinessReason(client2, project);
|
|
468
|
+
return reason ? formatToolResponse({ summary: `${summary} ${reason}` }) : formatEmptyResponse(summary);
|
|
469
|
+
}
|
|
470
|
+
async function withMissingNodeFallback(client2, project, fn, notFoundMessage) {
|
|
449
471
|
try {
|
|
450
472
|
return await fn();
|
|
451
473
|
} catch (err) {
|
|
@@ -453,7 +475,7 @@ async function withMissingNodeFallback(fn, notFoundMessage) {
|
|
|
453
475
|
return formatErrorResponse(err.message);
|
|
454
476
|
}
|
|
455
477
|
if (err instanceof HttpError && err.status === 404) {
|
|
456
|
-
return
|
|
478
|
+
return emptyWithReadiness(client2, project, notFoundMessage);
|
|
457
479
|
}
|
|
458
480
|
return formatErrorResponse(`Error talking to neat-core: ${err.message}`);
|
|
459
481
|
}
|
|
@@ -464,7 +486,7 @@ async function getRootCause(client2, input) {
|
|
|
464
486
|
input.project,
|
|
465
487
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
466
488
|
);
|
|
467
|
-
return withMissingNodeFallback(async () => {
|
|
489
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
468
490
|
const result = await client2.get(path);
|
|
469
491
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
470
492
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
@@ -498,10 +520,12 @@ async function getBlastRadius(client2, input) {
|
|
|
498
520
|
input.project,
|
|
499
521
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
500
522
|
);
|
|
501
|
-
return withMissingNodeFallback(async () => {
|
|
523
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
502
524
|
const result = await client2.get(path);
|
|
503
525
|
if (result.totalAffected === 0) {
|
|
504
|
-
return
|
|
526
|
+
return emptyWithReadiness(
|
|
527
|
+
client2,
|
|
528
|
+
input.project,
|
|
505
529
|
`${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
506
530
|
);
|
|
507
531
|
}
|
|
@@ -532,10 +556,12 @@ async function getDependencies(client2, input) {
|
|
|
532
556
|
input.project,
|
|
533
557
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
534
558
|
);
|
|
535
|
-
return withMissingNodeFallback(async () => {
|
|
559
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
536
560
|
const result = await client2.get(path);
|
|
537
561
|
if (result.total === 0) {
|
|
538
|
-
return
|
|
562
|
+
return emptyWithReadiness(
|
|
563
|
+
client2,
|
|
564
|
+
input.project,
|
|
539
565
|
depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
540
566
|
);
|
|
541
567
|
}
|
|
@@ -568,7 +594,7 @@ function observedDepLine(nodeId, e) {
|
|
|
568
594
|
return ` \u2022 ${e.target} \u2014 ${e.type}${via}${edgeMeta(e)}`;
|
|
569
595
|
}
|
|
570
596
|
async function getObservedDependencies(client2, input) {
|
|
571
|
-
return withMissingNodeFallback(async () => {
|
|
597
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
572
598
|
const result = await client2.get(
|
|
573
599
|
projectPath(
|
|
574
600
|
input.project,
|
|
@@ -623,7 +649,7 @@ async function expandNode(client2, input) {
|
|
|
623
649
|
input.project,
|
|
624
650
|
`/graph/expand/${encodeURIComponent(input.nodeId)}?direction=${input.direction}`
|
|
625
651
|
);
|
|
626
|
-
return withMissingNodeFallback(async () => {
|
|
652
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
627
653
|
const result = await client2.get(path);
|
|
628
654
|
const dirWord = input.direction === "up" ? "callers/dependents (up)" : "callees/dependencies (down)";
|
|
629
655
|
const summary = `${result.node.id} is ${result.node.classification}. ${result.neighbours.length} ${dirWord}.`;
|
|
@@ -665,7 +691,7 @@ async function relate(client2, input) {
|
|
|
665
691
|
async function ask(client2, input) {
|
|
666
692
|
const question = input.question.trim();
|
|
667
693
|
if (!question) return formatEmptyResponse("ask: the question was empty.");
|
|
668
|
-
return withMissingNodeFallback(async () => {
|
|
694
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
669
695
|
const result = await client2.get(
|
|
670
696
|
projectPath(input.project, `/graph/ask?q=${encodeURIComponent(question)}`)
|
|
671
697
|
);
|
|
@@ -693,7 +719,7 @@ async function ask(client2, input) {
|
|
|
693
719
|
}, `ask: nothing in "${question}" resolved to a node in the graph.`);
|
|
694
720
|
}
|
|
695
721
|
async function getIncidentHistory(client2, input) {
|
|
696
|
-
return withMissingNodeFallback(async () => {
|
|
722
|
+
return withMissingNodeFallback(client2, input.project, async () => {
|
|
697
723
|
const body = await client2.get(
|
|
698
724
|
projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`)
|
|
699
725
|
);
|
|
@@ -968,13 +994,23 @@ function formatDivergenceLine(d) {
|
|
|
968
994
|
const member = d.symbol ? ` ${d.symbol}` : "";
|
|
969
995
|
return ` \u2022 [${d.type}] ${d.source}${member}${at} (${d.mismatchKind}) \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
970
996
|
}
|
|
997
|
+
case "observed-failing": {
|
|
998
|
+
if (d.edgeType) {
|
|
999
|
+
const rate = d.errorRate !== void 0 ? ` ${Math.round(d.errorRate * 100)}% of observed calls fail` : "";
|
|
1000
|
+
return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} (${d.edgeType}) \u2014${rate} (${d.failureKind}) \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
1001
|
+
}
|
|
1002
|
+
const at = d.location ? ` at ${d.location}` : "";
|
|
1003
|
+
return ` \u2022 [${d.type}] ${d.source}${at} (${d.failureKind}) \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
1004
|
+
}
|
|
971
1005
|
}
|
|
972
1006
|
}
|
|
973
1007
|
async function getDivergences(client2, input) {
|
|
974
1008
|
try {
|
|
975
1009
|
const result = await client2.get(buildDivergencesPath(input));
|
|
976
1010
|
if (result.totalAffected === 0) {
|
|
977
|
-
return
|
|
1011
|
+
return emptyWithReadiness(
|
|
1012
|
+
client2,
|
|
1013
|
+
input.project,
|
|
978
1014
|
"No divergences found between the declared (EXTRACTED) and observed (OBSERVED) views of the graph."
|
|
979
1015
|
);
|
|
980
1016
|
}
|
|
@@ -1297,7 +1333,7 @@ registerTool(
|
|
|
1297
1333
|
"Returns places where what the code declares (EXTRACTED) doesn't match what production observed (OBSERVED). The single most NEAT-shaped query \u2014 the one that justifies the whole graph. Use when the user asks 'is anything weird?' or 'what does production do that the code doesn't?' or 'find me a bug' on an unfamiliar codebase. Returns divergences ranked by confidence \xD7 severity. Prefer this over `get_root_cause` when no specific node is failing.",
|
|
1298
1334
|
{
|
|
1299
1335
|
type: import_zod.z.array(import_types2.DivergenceTypeSchema).optional().describe(
|
|
1300
|
-
"Filter by divergence type. One or more of: missing-observed, missing-extracted, version-mismatch, host-mismatch, compat-violation, observed-symbol-mismatch. Omit for all."
|
|
1336
|
+
"Filter by divergence type. One or more of: missing-observed, missing-extracted, version-mismatch, host-mismatch, compat-violation, observed-symbol-mismatch, observed-failing. Omit for all."
|
|
1301
1337
|
),
|
|
1302
1338
|
minConfidence: import_zod.z.number().min(0).max(1).optional().describe("Drop divergences below this confidence threshold (0.0 - 1.0)."),
|
|
1303
1339
|
node: import_zod.z.string().optional().describe("Scope to divergences involving this node id (as source or target)."),
|