@neat.is/core 0.2.9 → 0.3.0

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/cli.d.cts CHANGED
@@ -27,6 +27,8 @@ interface ParsedArgs {
27
27
  against: string | null;
28
28
  errorId: string | null;
29
29
  hypotheticalAction: string | null;
30
+ type: string | null;
31
+ minConfidence: number | null;
30
32
  positional: string[];
31
33
  }
32
34
  declare function parseArgs(rest: string[]): ParsedArgs;
package/dist/cli.d.ts CHANGED
@@ -27,6 +27,8 @@ interface ParsedArgs {
27
27
  against: string | null;
28
28
  errorId: string | null;
29
29
  hypotheticalAction: string | null;
30
+ type: string | null;
31
+ minConfidence: number | null;
30
32
  positional: string[];
31
33
  }
32
34
  declare function parseArgs(rest: string[]): ParsedArgs;
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-XOOCA5T7.js";
5
5
  import {
6
6
  buildApi
7
- } from "./chunk-5KX7EI4F.js";
7
+ } from "./chunk-NVCEZXL7.js";
8
8
  import {
9
9
  DEFAULT_PROJECT,
10
10
  PolicyViolationsLog,
@@ -37,11 +37,12 @@ import {
37
37
  setStatus,
38
38
  startPersistLoop,
39
39
  startStalenessLoop
40
- } from "./chunk-IRPH6KL4.js";
40
+ } from "./chunk-B7UUGIXB.js";
41
41
  import {
42
42
  buildOtelReceiver,
43
43
  startOtelGrpcReceiver
44
- } from "./chunk-I5IMCXRO.js";
44
+ } from "./chunk-QYUB3FQL.js";
45
+ import "./chunk-DGUM43GV.js";
45
46
 
46
47
  // src/cli.ts
47
48
  import path4 from "path";
@@ -753,6 +754,9 @@ function renderPatch(sections) {
753
754
  return lines.join("\n");
754
755
  }
755
756
 
757
+ // src/cli.ts
758
+ import { DivergenceTypeSchema } from "@neat.is/types";
759
+
756
760
  // src/cli-client.ts
757
761
  import { Provenance as Provenance2 } from "@neat.is/types";
758
762
  var HttpError = class extends Error {
@@ -826,7 +830,7 @@ async function runRootCause(client, input) {
826
830
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
827
831
  const path5 = projectPath(
828
832
  input.project,
829
- `/traverse/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
833
+ `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
830
834
  );
831
835
  try {
832
836
  const result = await client.get(path5);
@@ -857,7 +861,7 @@ async function runBlastRadius(client, input) {
857
861
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
858
862
  const path5 = projectPath(
859
863
  input.project,
860
- `/traverse/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
864
+ `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
861
865
  );
862
866
  try {
863
867
  const result = await client.get(path5);
@@ -896,7 +900,7 @@ async function runDependencies(client, input) {
896
900
  const depth = input.depth ?? 3;
897
901
  const path5 = projectPath(
898
902
  input.project,
899
- `/graph/node/${encodeURIComponent(input.nodeId)}/dependencies?depth=${depth}`
903
+ `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
900
904
  );
901
905
  try {
902
906
  const result = await client.get(path5);
@@ -982,7 +986,8 @@ function formatDuration(ms) {
982
986
  async function runIncidents(client, input) {
983
987
  const path5 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
984
988
  try {
985
- const events = await client.get(path5);
989
+ const body = await client.get(path5);
990
+ const events = body.events;
986
991
  if (events.length === 0) {
987
992
  return {
988
993
  summary: input.nodeId ? `No incidents recorded against ${input.nodeId}.` : "No incidents recorded."
@@ -996,7 +1001,7 @@ async function runIncidents(client, input) {
996
1001
  }
997
1002
  const target = input.nodeId ?? "the project";
998
1003
  return {
999
- summary: `${target} has ${events.length} recorded incident${events.length === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
1004
+ summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
1000
1005
  block: blockLines.join("\n"),
1001
1006
  provenance: Provenance2.OBSERVED
1002
1007
  };
@@ -1092,9 +1097,10 @@ async function runStaleEdges(client, input) {
1092
1097
  if (input.limit !== void 0) params.set("limit", String(input.limit));
1093
1098
  if (input.edgeType) params.set("edgeType", input.edgeType);
1094
1099
  const qs = params.size > 0 ? `?${params.toString()}` : "";
1095
- const events = await client.get(
1096
- projectPath(input.project, `/incidents/stale${qs}`)
1100
+ const body = await client.get(
1101
+ projectPath(input.project, `/stale-events${qs}`)
1097
1102
  );
1103
+ const events = body.events;
1098
1104
  if (events.length === 0) {
1099
1105
  return {
1100
1106
  summary: input.edgeType ? `No stale ${input.edgeType} edges recorded.` : "No stale-edge transitions recorded yet."
@@ -1128,9 +1134,10 @@ async function runPolicies(client, input) {
1128
1134
  const params = new URLSearchParams();
1129
1135
  if (input.policyId) params.set("policyId", input.policyId);
1130
1136
  const qs = params.size > 0 ? `?${params.toString()}` : "";
1131
- violations = await client.get(
1137
+ const body = await client.get(
1132
1138
  projectPath(input.project, `/policies/violations${qs}`)
1133
1139
  );
1140
+ violations = body.violations;
1134
1141
  allowed = violations.every((v) => v.onViolation !== "block");
1135
1142
  }
1136
1143
  if (input.nodeId) {
@@ -1169,6 +1176,54 @@ async function runPolicies(client, input) {
1169
1176
  provenance: severities.join(" ")
1170
1177
  };
1171
1178
  }
1179
+ function formatDivergenceLine(d) {
1180
+ switch (d.type) {
1181
+ case "missing-observed":
1182
+ case "missing-extracted":
1183
+ return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} (${d.edgeType}) \u2014 confidence ${d.confidence.toFixed(2)}`;
1184
+ case "version-mismatch":
1185
+ return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 declared ${d.extractedVersion}, observed engine ${d.observedVersion} (${d.compatibility})`;
1186
+ case "host-mismatch":
1187
+ return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 declared host ${d.extractedHost}, observed host ${d.observedHost}`;
1188
+ case "compat-violation":
1189
+ return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.rule.kind}${d.rule.package ? ` (${d.rule.package})` : ""}`;
1190
+ }
1191
+ }
1192
+ async function runDivergences(client, input) {
1193
+ const params = new URLSearchParams();
1194
+ if (input.type && input.type.length > 0) params.set("type", input.type.join(","));
1195
+ if (input.minConfidence !== void 0) {
1196
+ params.set("minConfidence", String(input.minConfidence));
1197
+ }
1198
+ if (input.node) params.set("node", input.node);
1199
+ const qs = params.size > 0 ? `?${params.toString()}` : "";
1200
+ const result = await client.get(
1201
+ projectPath(input.project, `/graph/divergences${qs}`)
1202
+ );
1203
+ if (result.totalAffected === 0) {
1204
+ return {
1205
+ summary: "No divergences found between the declared (EXTRACTED) and observed (OBSERVED) views of the graph."
1206
+ };
1207
+ }
1208
+ const headline = result.divergences[0];
1209
+ const summary = `Found ${result.totalAffected} divergence${result.totalAffected === 1 ? "" : "s"} between code and production. Highest-confidence: ${headline.type} on ${headline.source} \u2192 ${headline.target}. ${headline.reason}`;
1210
+ const blockLines = [];
1211
+ for (const d of result.divergences) {
1212
+ blockLines.push(formatDivergenceLine(d));
1213
+ blockLines.push(` reason: ${d.reason}`);
1214
+ blockLines.push(` recommendation: ${d.recommendation}`);
1215
+ }
1216
+ const maxConfidence = result.divergences.reduce(
1217
+ (m, d) => Math.max(m, d.confidence),
1218
+ 0
1219
+ );
1220
+ return {
1221
+ summary,
1222
+ block: blockLines.join("\n"),
1223
+ confidence: maxConfidence,
1224
+ provenance: "composite (EXTRACTED + OBSERVED)"
1225
+ };
1226
+ }
1172
1227
  function formatFooter(confidence, provenance) {
1173
1228
  const c = confidence === void 0 ? "n/a" : confidence.toFixed(2);
1174
1229
  const p = provenance === void 0 ? "n/a" : Array.isArray(provenance) ? [...new Set(provenance)].join(", ") : provenance;
@@ -1247,6 +1302,9 @@ function usage() {
1247
1302
  console.log(" policies Current policy violations.");
1248
1303
  console.log(" Flags: --node <id>, --hypothetical-action <json>");
1249
1304
  console.log(" example: neat policies --node service:<name> --json");
1305
+ console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
1306
+ console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
1307
+ console.log(" example: neat divergences --min-confidence 0.7");
1250
1308
  console.log("");
1251
1309
  console.log("flags:");
1252
1310
  console.log(' --project <name> Name the project this command targets. Default: "default".');
@@ -1271,7 +1329,9 @@ var STRING_FLAGS = [
1271
1329
  ["--since", "since"],
1272
1330
  ["--against", "against"],
1273
1331
  ["--error-id", "errorId"],
1274
- ["--hypothetical-action", "hypotheticalAction"]
1332
+ ["--hypothetical-action", "hypotheticalAction"],
1333
+ ["--type", "type"],
1334
+ ["--min-confidence", "minConfidence"]
1275
1335
  ];
1276
1336
  function parseArgs(rest) {
1277
1337
  const positional = [];
@@ -1290,6 +1350,8 @@ function parseArgs(rest) {
1290
1350
  against: null,
1291
1351
  errorId: null,
1292
1352
  hypotheticalAction: null,
1353
+ type: null,
1354
+ minConfidence: null,
1293
1355
  positional: []
1294
1356
  };
1295
1357
  for (let i = 0; i < rest.length; i++) {
@@ -1349,6 +1411,15 @@ function assignFlag(out, field, value) {
1349
1411
  out[field] = n;
1350
1412
  return;
1351
1413
  }
1414
+ if (field === "minConfidence") {
1415
+ const n = Number(value);
1416
+ if (!Number.isFinite(n) || n < 0 || n > 1) {
1417
+ console.error("neat: --min-confidence must be a number in [0, 1]");
1418
+ process.exit(2);
1419
+ }
1420
+ out.minConfidence = n;
1421
+ return;
1422
+ }
1352
1423
  ;
1353
1424
  out[field] = value;
1354
1425
  }
@@ -1732,7 +1803,9 @@ var QUERY_VERBS = /* @__PURE__ */ new Set([
1732
1803
  "search",
1733
1804
  "diff",
1734
1805
  "stale-edges",
1735
- "policies"
1806
+ "policies",
1807
+ // Tenth verb (ADR-060) — amends ADR-050's locked allowlist of nine.
1808
+ "divergences"
1736
1809
  ]);
1737
1810
  function resolveProjectFlag(parsed) {
1738
1811
  if (parsed.project) return parsed.project;
@@ -1854,6 +1927,31 @@ async function runQueryVerb(cmd, parsed) {
1854
1927
  });
1855
1928
  break;
1856
1929
  }
1930
+ case "divergences": {
1931
+ let typeFilter;
1932
+ if (parsed.type) {
1933
+ const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1934
+ const out = [];
1935
+ for (const p of parts) {
1936
+ const r = DivergenceTypeSchema.safeParse(p);
1937
+ if (!r.success) {
1938
+ console.error(
1939
+ `neat divergences: unknown --type "${p}". allowed: ${DivergenceTypeSchema.options.join(", ")}`
1940
+ );
1941
+ return 2;
1942
+ }
1943
+ out.push(r.data);
1944
+ }
1945
+ typeFilter = out;
1946
+ }
1947
+ work = runDivergences(client, {
1948
+ ...typeFilter ? { type: typeFilter } : {},
1949
+ ...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
1950
+ ...parsed.node ? { node: parsed.node } : {},
1951
+ ...project ? { project } : {}
1952
+ });
1953
+ break;
1954
+ }
1857
1955
  default:
1858
1956
  console.error(`neat: unknown query verb "${cmd}"`);
1859
1957
  return 2;