@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/{chunk-IRPH6KL4.js → chunk-B7UUGIXB.js} +3 -1
- package/dist/chunk-B7UUGIXB.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-BVF7MVR5.js → chunk-I5KODOJV.js} +2 -2
- package/dist/{chunk-5KX7EI4F.js → chunk-NVCEZXL7.js} +331 -20
- package/dist/chunk-NVCEZXL7.js.map +1 -0
- package/dist/{chunk-I5IMCXRO.js → chunk-QYUB3FQL.js} +2 -2
- package/dist/cli.cjs +443 -42
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +111 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +323 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -12
- package/dist/index.d.ts +4 -12
- package/dist/index.js +5 -4
- package/dist/neatd.cjs +106 -5
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +112 -6
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-B4XBSI4W.js → otel-grpc-USFL3OI3.js} +3 -2
- package/dist/otel-grpc-USFL3OI3.js.map +1 -0
- package/dist/server.cjs +431 -125
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +4 -3
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-5KX7EI4F.js.map +0 -1
- package/dist/chunk-IRPH6KL4.js.map +0 -1
- /package/dist/{otel-grpc-B4XBSI4W.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-BVF7MVR5.js.map → chunk-I5KODOJV.js.map} +0 -0
- /package/dist/{chunk-I5IMCXRO.js.map → chunk-QYUB3FQL.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -3908,7 +3908,244 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
3908
3908
|
init_cjs_shims();
|
|
3909
3909
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
3910
3910
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
3911
|
-
var
|
|
3911
|
+
var import_types20 = require("@neat.is/types");
|
|
3912
|
+
|
|
3913
|
+
// src/divergences.ts
|
|
3914
|
+
init_cjs_shims();
|
|
3915
|
+
var import_types18 = require("@neat.is/types");
|
|
3916
|
+
function bucketKey(source, target, type) {
|
|
3917
|
+
return `${type}|${source}|${target}`;
|
|
3918
|
+
}
|
|
3919
|
+
function bucketEdges(graph) {
|
|
3920
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
3921
|
+
graph.forEachEdge((id, attrs) => {
|
|
3922
|
+
const e = attrs;
|
|
3923
|
+
const parsed = (0, import_types18.parseEdgeId)(id);
|
|
3924
|
+
const provenance = parsed?.provenance ?? e.provenance;
|
|
3925
|
+
const key = bucketKey(e.source, e.target, e.type);
|
|
3926
|
+
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
3927
|
+
switch (provenance) {
|
|
3928
|
+
case import_types18.Provenance.EXTRACTED:
|
|
3929
|
+
cur.extracted = e;
|
|
3930
|
+
break;
|
|
3931
|
+
case import_types18.Provenance.OBSERVED:
|
|
3932
|
+
cur.observed = e;
|
|
3933
|
+
break;
|
|
3934
|
+
case import_types18.Provenance.INFERRED:
|
|
3935
|
+
cur.inferred = e;
|
|
3936
|
+
break;
|
|
3937
|
+
case import_types18.Provenance.FRONTIER:
|
|
3938
|
+
cur.frontier = e;
|
|
3939
|
+
break;
|
|
3940
|
+
default:
|
|
3941
|
+
if (e.provenance === import_types18.Provenance.STALE) cur.stale = e;
|
|
3942
|
+
}
|
|
3943
|
+
buckets.set(key, cur);
|
|
3944
|
+
});
|
|
3945
|
+
return buckets;
|
|
3946
|
+
}
|
|
3947
|
+
function nodeIsFrontier(graph, nodeId) {
|
|
3948
|
+
if (!graph.hasNode(nodeId)) return false;
|
|
3949
|
+
const attrs = graph.getNodeAttributes(nodeId);
|
|
3950
|
+
return attrs.type === import_types18.NodeType.FrontierNode;
|
|
3951
|
+
}
|
|
3952
|
+
function hasAnyObservedFromSource(graph, sourceId) {
|
|
3953
|
+
if (!graph.hasNode(sourceId)) return false;
|
|
3954
|
+
for (const edgeId of graph.outboundEdges(sourceId)) {
|
|
3955
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
3956
|
+
if (e.provenance === import_types18.Provenance.OBSERVED) return true;
|
|
3957
|
+
}
|
|
3958
|
+
return false;
|
|
3959
|
+
}
|
|
3960
|
+
function clampConfidence(n) {
|
|
3961
|
+
if (!Number.isFinite(n)) return 0;
|
|
3962
|
+
return Math.max(0, Math.min(1, n));
|
|
3963
|
+
}
|
|
3964
|
+
function reasonForMissingObserved(source, target, type) {
|
|
3965
|
+
return `Code declares ${source} \u2192 ${target} (${type}) but no production traffic has been observed for this edge.`;
|
|
3966
|
+
}
|
|
3967
|
+
function reasonForMissingExtracted(source, target, type) {
|
|
3968
|
+
return `Production observed ${source} \u2192 ${target} (${type}) but static analysis did not surface this edge.`;
|
|
3969
|
+
}
|
|
3970
|
+
var RECOMMENDATION_MISSING_OBSERVED = "Verify the code path is exercised in production; check feature flags or conditional branches that might gate the call.";
|
|
3971
|
+
var RECOMMENDATION_MISSING_EXTRACTED = "Likely dynamic dispatch, reflection, or a coverage gap in tree-sitter extraction. Consider an `aliases` entry on the source service or file an extractor issue.";
|
|
3972
|
+
var RECOMMENDATION_HOST_MISMATCH = "Check environment-specific config overrides \u2014 the runtime host differs from what static configuration declares.";
|
|
3973
|
+
function detectMissingDivergences(graph, bucket) {
|
|
3974
|
+
const out = [];
|
|
3975
|
+
if (bucket.extracted && !bucket.observed) {
|
|
3976
|
+
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
3977
|
+
const sourceHasTraffic = hasAnyObservedFromSource(graph, bucket.source);
|
|
3978
|
+
out.push({
|
|
3979
|
+
type: "missing-observed",
|
|
3980
|
+
source: bucket.source,
|
|
3981
|
+
target: bucket.target,
|
|
3982
|
+
edgeType: bucket.type,
|
|
3983
|
+
extracted: bucket.extracted,
|
|
3984
|
+
confidence: sourceHasTraffic ? 1 : 0.5,
|
|
3985
|
+
reason: reasonForMissingObserved(bucket.source, bucket.target, bucket.type),
|
|
3986
|
+
recommendation: RECOMMENDATION_MISSING_OBSERVED
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
if (bucket.observed && !bucket.extracted) {
|
|
3991
|
+
const cascaded = clampConfidence(confidenceForEdge(bucket.observed));
|
|
3992
|
+
out.push({
|
|
3993
|
+
type: "missing-extracted",
|
|
3994
|
+
source: bucket.source,
|
|
3995
|
+
target: bucket.target,
|
|
3996
|
+
edgeType: bucket.type,
|
|
3997
|
+
observed: bucket.observed,
|
|
3998
|
+
confidence: cascaded,
|
|
3999
|
+
reason: reasonForMissingExtracted(bucket.source, bucket.target, bucket.type),
|
|
4000
|
+
recommendation: RECOMMENDATION_MISSING_EXTRACTED
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
return out;
|
|
4004
|
+
}
|
|
4005
|
+
function declaredHostFor(svc) {
|
|
4006
|
+
const raw = svc.dbConnectionTarget?.trim();
|
|
4007
|
+
if (!raw) return null;
|
|
4008
|
+
const colon = raw.lastIndexOf(":");
|
|
4009
|
+
if (colon === -1) return raw;
|
|
4010
|
+
const port = raw.slice(colon + 1);
|
|
4011
|
+
if (/^\d+$/.test(port)) return raw.slice(0, colon);
|
|
4012
|
+
return raw;
|
|
4013
|
+
}
|
|
4014
|
+
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4015
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4016
|
+
const e = graph.getEdgeAttributes(edgeId);
|
|
4017
|
+
if (e.type === import_types18.EdgeType.CONFIGURED_BY && e.provenance === import_types18.Provenance.EXTRACTED) {
|
|
4018
|
+
return true;
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
return false;
|
|
4022
|
+
}
|
|
4023
|
+
function detectHostMismatch(graph, svcId, svc) {
|
|
4024
|
+
const declaredHost = declaredHostFor(svc);
|
|
4025
|
+
if (!declaredHost) return [];
|
|
4026
|
+
if (!hasExtractedConfiguredBy(graph, svcId)) return [];
|
|
4027
|
+
const out = [];
|
|
4028
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4029
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
4030
|
+
if (edge.type !== import_types18.EdgeType.CONNECTS_TO) continue;
|
|
4031
|
+
if (edge.provenance !== import_types18.Provenance.OBSERVED) continue;
|
|
4032
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
4033
|
+
if (target.type !== import_types18.NodeType.DatabaseNode) continue;
|
|
4034
|
+
const observedHost = target.host?.trim();
|
|
4035
|
+
if (!observedHost) continue;
|
|
4036
|
+
if (observedHost === declaredHost) continue;
|
|
4037
|
+
out.push({
|
|
4038
|
+
type: "host-mismatch",
|
|
4039
|
+
source: svcId,
|
|
4040
|
+
target: edge.target,
|
|
4041
|
+
extractedHost: declaredHost,
|
|
4042
|
+
observedHost,
|
|
4043
|
+
confidence: clampConfidence(confidenceForEdge(edge)),
|
|
4044
|
+
reason: `Config declares ${svcId} connects to ${declaredHost}; production connects to ${observedHost}.`,
|
|
4045
|
+
recommendation: RECOMMENDATION_HOST_MISMATCH
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
return out;
|
|
4049
|
+
}
|
|
4050
|
+
function detectCompatDivergences(graph, svcId, svc) {
|
|
4051
|
+
const out = [];
|
|
4052
|
+
const deps = svc.dependencies ?? {};
|
|
4053
|
+
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4054
|
+
const edge = graph.getEdgeAttributes(edgeId);
|
|
4055
|
+
if (edge.type !== import_types18.EdgeType.CONNECTS_TO) continue;
|
|
4056
|
+
if (edge.provenance !== import_types18.Provenance.OBSERVED) continue;
|
|
4057
|
+
const target = graph.getNodeAttributes(edge.target);
|
|
4058
|
+
if (target.type !== import_types18.NodeType.DatabaseNode) continue;
|
|
4059
|
+
for (const pair of compatPairs()) {
|
|
4060
|
+
if (pair.engine !== target.engine) continue;
|
|
4061
|
+
const declared = deps[pair.driver];
|
|
4062
|
+
if (!declared) continue;
|
|
4063
|
+
const result = checkCompatibility(
|
|
4064
|
+
pair.driver,
|
|
4065
|
+
declared,
|
|
4066
|
+
target.engine,
|
|
4067
|
+
target.engineVersion
|
|
4068
|
+
);
|
|
4069
|
+
if (!result.compatible && result.reason) {
|
|
4070
|
+
out.push({
|
|
4071
|
+
type: "version-mismatch",
|
|
4072
|
+
source: svcId,
|
|
4073
|
+
target: edge.target,
|
|
4074
|
+
extractedVersion: declared,
|
|
4075
|
+
observedVersion: target.engineVersion,
|
|
4076
|
+
compatibility: "incompatible",
|
|
4077
|
+
confidence: 1,
|
|
4078
|
+
reason: result.reason,
|
|
4079
|
+
recommendation: result.minDriverVersion ? `Upgrade ${pair.driver} to >= ${result.minDriverVersion}.` : `Update the ${pair.driver} driver to a version compatible with ${target.engine} ${target.engineVersion}.`
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
for (const rule of deprecatedApis()) {
|
|
4084
|
+
const declared = deps[rule.package];
|
|
4085
|
+
if (!declared) continue;
|
|
4086
|
+
const result = checkDeprecatedApi(rule, declared);
|
|
4087
|
+
if (!result.compatible && result.reason) {
|
|
4088
|
+
const ruleRef = {
|
|
4089
|
+
kind: rule.kind ?? "deprecated-api",
|
|
4090
|
+
reason: result.reason,
|
|
4091
|
+
package: rule.package
|
|
4092
|
+
};
|
|
4093
|
+
out.push({
|
|
4094
|
+
type: "compat-violation",
|
|
4095
|
+
source: svcId,
|
|
4096
|
+
target: edge.target,
|
|
4097
|
+
rule: ruleRef,
|
|
4098
|
+
observed: edge,
|
|
4099
|
+
confidence: 1,
|
|
4100
|
+
reason: result.reason,
|
|
4101
|
+
recommendation: `Replace deprecated ${rule.package}@${declared} with a supported version.`
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4106
|
+
return out;
|
|
4107
|
+
}
|
|
4108
|
+
function involvesNode(d, nodeId) {
|
|
4109
|
+
return d.source === nodeId || d.target === nodeId;
|
|
4110
|
+
}
|
|
4111
|
+
function computeDivergences(graph, opts = {}) {
|
|
4112
|
+
const all = [];
|
|
4113
|
+
const buckets = bucketEdges(graph);
|
|
4114
|
+
for (const bucket of buckets.values()) {
|
|
4115
|
+
for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
|
|
4116
|
+
}
|
|
4117
|
+
graph.forEachNode((nodeId, attrs) => {
|
|
4118
|
+
const n = attrs;
|
|
4119
|
+
if (n.type !== import_types18.NodeType.ServiceNode) return;
|
|
4120
|
+
const svc = n;
|
|
4121
|
+
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4122
|
+
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
4123
|
+
});
|
|
4124
|
+
let filtered = all;
|
|
4125
|
+
if (opts.type) {
|
|
4126
|
+
const allowed = opts.type;
|
|
4127
|
+
filtered = filtered.filter((d) => allowed.has(d.type));
|
|
4128
|
+
}
|
|
4129
|
+
if (opts.minConfidence !== void 0) {
|
|
4130
|
+
const threshold = opts.minConfidence;
|
|
4131
|
+
filtered = filtered.filter((d) => d.confidence >= threshold);
|
|
4132
|
+
}
|
|
4133
|
+
if (opts.node) {
|
|
4134
|
+
const target = opts.node;
|
|
4135
|
+
filtered = filtered.filter((d) => involvesNode(d, target));
|
|
4136
|
+
}
|
|
4137
|
+
filtered.sort((a, b) => {
|
|
4138
|
+
if (b.confidence !== a.confidence) return b.confidence - a.confidence;
|
|
4139
|
+
if (a.type !== b.type) return a.type.localeCompare(b.type);
|
|
4140
|
+
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4141
|
+
return a.target.localeCompare(b.target);
|
|
4142
|
+
});
|
|
4143
|
+
return import_types18.DivergenceResultSchema.parse({
|
|
4144
|
+
divergences: filtered,
|
|
4145
|
+
totalAffected: filtered.length,
|
|
4146
|
+
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
3912
4149
|
|
|
3913
4150
|
// src/diff.ts
|
|
3914
4151
|
init_cjs_shims();
|
|
@@ -4044,7 +4281,7 @@ init_cjs_shims();
|
|
|
4044
4281
|
var import_node_fs18 = require("fs");
|
|
4045
4282
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4046
4283
|
var import_node_path30 = __toESM(require("path"), 1);
|
|
4047
|
-
var
|
|
4284
|
+
var import_types19 = require("@neat.is/types");
|
|
4048
4285
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4049
4286
|
var LOCK_RETRY_MS = 50;
|
|
4050
4287
|
function neatHome() {
|
|
@@ -4123,10 +4360,10 @@ async function readRegistry() {
|
|
|
4123
4360
|
throw err;
|
|
4124
4361
|
}
|
|
4125
4362
|
const parsed = JSON.parse(raw);
|
|
4126
|
-
return
|
|
4363
|
+
return import_types19.RegistryFileSchema.parse(parsed);
|
|
4127
4364
|
}
|
|
4128
4365
|
async function writeRegistry(reg) {
|
|
4129
|
-
const validated =
|
|
4366
|
+
const validated = import_types19.RegistryFileSchema.parse(reg);
|
|
4130
4367
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4131
4368
|
}
|
|
4132
4369
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -4169,6 +4406,10 @@ async function addProject(opts) {
|
|
|
4169
4406
|
return entry2;
|
|
4170
4407
|
});
|
|
4171
4408
|
}
|
|
4409
|
+
async function getProject(name) {
|
|
4410
|
+
const reg = await readRegistry();
|
|
4411
|
+
return reg.projects.find((p) => p.name === name);
|
|
4412
|
+
}
|
|
4172
4413
|
async function listProjects() {
|
|
4173
4414
|
const reg = await readRegistry();
|
|
4174
4415
|
return reg.projects;
|
|
@@ -4305,9 +4546,15 @@ function registerRoutes(scope, ctx) {
|
|
|
4305
4546
|
scope.get("/health", async (req, reply) => {
|
|
4306
4547
|
const proj = resolveProject(registry, req, reply);
|
|
4307
4548
|
if (!proj) return;
|
|
4549
|
+
const uptimeMs = Date.now() - startedAt;
|
|
4308
4550
|
return {
|
|
4309
|
-
|
|
4551
|
+
ok: true,
|
|
4310
4552
|
project: proj.name,
|
|
4553
|
+
uptimeMs,
|
|
4554
|
+
// Legacy fields kept additively. The web shell's StatusBar reads
|
|
4555
|
+
// nodeCount / edgeCount; ADR-061's HealthResponseSchema validates
|
|
4556
|
+
// the canonical triple and lets the extras pass through.
|
|
4557
|
+
uptime: Math.floor(uptimeMs / 1e3),
|
|
4311
4558
|
nodeCount: proj.graph.order,
|
|
4312
4559
|
edgeCount: proj.graph.size,
|
|
4313
4560
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4327,7 +4574,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4327
4574
|
if (!proj.graph.hasNode(id)) {
|
|
4328
4575
|
return reply.code(404).send({ error: "node not found", id });
|
|
4329
4576
|
}
|
|
4330
|
-
return proj.graph.getNodeAttributes(id);
|
|
4577
|
+
return { node: proj.graph.getNodeAttributes(id) };
|
|
4331
4578
|
}
|
|
4332
4579
|
);
|
|
4333
4580
|
scope.get(
|
|
@@ -4344,12 +4591,12 @@ function registerRoutes(scope, ctx) {
|
|
|
4344
4591
|
return { inbound, outbound };
|
|
4345
4592
|
}
|
|
4346
4593
|
);
|
|
4347
|
-
scope.get("/graph/
|
|
4594
|
+
scope.get("/graph/dependencies/:nodeId", async (req, reply) => {
|
|
4348
4595
|
const proj = resolveProject(registry, req, reply);
|
|
4349
4596
|
if (!proj) return;
|
|
4350
|
-
const {
|
|
4351
|
-
if (!proj.graph.hasNode(
|
|
4352
|
-
return reply.code(404).send({ error: "node not found", id });
|
|
4597
|
+
const { nodeId } = req.params;
|
|
4598
|
+
if (!proj.graph.hasNode(nodeId)) {
|
|
4599
|
+
return reply.code(404).send({ error: "node not found", id: nodeId });
|
|
4353
4600
|
}
|
|
4354
4601
|
const depth = req.query.depth ? Number(req.query.depth) : TRANSITIVE_DEPENDENCIES_DEFAULT_DEPTH;
|
|
4355
4602
|
if (!Number.isFinite(depth) || depth < 1 || depth > TRANSITIVE_DEPENDENCIES_MAX_DEPTH) {
|
|
@@ -4357,25 +4604,67 @@ function registerRoutes(scope, ctx) {
|
|
|
4357
4604
|
error: `depth must be an integer in [1, ${TRANSITIVE_DEPENDENCIES_MAX_DEPTH}]`
|
|
4358
4605
|
});
|
|
4359
4606
|
}
|
|
4360
|
-
return getTransitiveDependencies(proj.graph,
|
|
4607
|
+
return getTransitiveDependencies(proj.graph, nodeId, depth);
|
|
4608
|
+
});
|
|
4609
|
+
scope.get("/graph/divergences", async (req, reply) => {
|
|
4610
|
+
const proj = resolveProject(registry, req, reply);
|
|
4611
|
+
if (!proj) return;
|
|
4612
|
+
let typeFilter;
|
|
4613
|
+
if (req.query.type) {
|
|
4614
|
+
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
4615
|
+
const parsed = [];
|
|
4616
|
+
for (const c of candidates) {
|
|
4617
|
+
const r = import_types20.DivergenceTypeSchema.safeParse(c);
|
|
4618
|
+
if (!r.success) {
|
|
4619
|
+
return reply.code(400).send({
|
|
4620
|
+
error: `unknown divergence type "${c}"`,
|
|
4621
|
+
allowed: import_types20.DivergenceTypeSchema.options
|
|
4622
|
+
});
|
|
4623
|
+
}
|
|
4624
|
+
parsed.push(r.data);
|
|
4625
|
+
}
|
|
4626
|
+
typeFilter = new Set(parsed);
|
|
4627
|
+
}
|
|
4628
|
+
let minConfidence;
|
|
4629
|
+
if (req.query.minConfidence !== void 0) {
|
|
4630
|
+
const n = Number(req.query.minConfidence);
|
|
4631
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
4632
|
+
return reply.code(400).send({
|
|
4633
|
+
error: "minConfidence must be a number in [0, 1]"
|
|
4634
|
+
});
|
|
4635
|
+
}
|
|
4636
|
+
minConfidence = n;
|
|
4637
|
+
}
|
|
4638
|
+
return computeDivergences(proj.graph, {
|
|
4639
|
+
...typeFilter ? { type: typeFilter } : {},
|
|
4640
|
+
...minConfidence !== void 0 ? { minConfidence } : {},
|
|
4641
|
+
...req.query.node ? { node: req.query.node } : {}
|
|
4642
|
+
});
|
|
4361
4643
|
});
|
|
4362
4644
|
scope.get("/incidents", async (req, reply) => {
|
|
4363
4645
|
const proj = resolveProject(registry, req, reply);
|
|
4364
4646
|
if (!proj) return;
|
|
4365
4647
|
const epath = errorsPathFor(proj);
|
|
4366
|
-
if (!epath) return [];
|
|
4367
|
-
|
|
4648
|
+
if (!epath) return { count: 0, total: 0, events: [] };
|
|
4649
|
+
const events = await readErrorEvents(epath);
|
|
4650
|
+
const total = events.length;
|
|
4651
|
+
const limit = req.query.limit ? Number(req.query.limit) : 50;
|
|
4652
|
+
const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 200) : 50;
|
|
4653
|
+
const sliced = events.slice(0, safeLimit);
|
|
4654
|
+
return { count: sliced.length, total, events: sliced };
|
|
4368
4655
|
});
|
|
4369
|
-
scope.get("/
|
|
4656
|
+
scope.get("/stale-events", async (req, reply) => {
|
|
4370
4657
|
const proj = resolveProject(registry, req, reply);
|
|
4371
4658
|
if (!proj) return;
|
|
4372
4659
|
const spath = staleEventsPathFor(proj);
|
|
4373
|
-
if (!spath) return [];
|
|
4660
|
+
if (!spath) return { count: 0, total: 0, events: [] };
|
|
4374
4661
|
const events = await readStaleEvents(spath);
|
|
4375
4662
|
const filtered = req.query.edgeType ? events.filter((e) => e.edgeType === req.query.edgeType) : events;
|
|
4376
4663
|
const ordered = [...filtered].reverse();
|
|
4664
|
+
const total = ordered.length;
|
|
4377
4665
|
const limit = req.query.limit ? Number(req.query.limit) : 50;
|
|
4378
|
-
|
|
4666
|
+
const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
|
|
4667
|
+
return { count: sliced.length, total, events: sliced };
|
|
4379
4668
|
});
|
|
4380
4669
|
scope.get(
|
|
4381
4670
|
"/incidents/:nodeId",
|
|
@@ -4387,14 +4676,15 @@ function registerRoutes(scope, ctx) {
|
|
|
4387
4676
|
return reply.code(404).send({ error: "node not found", id: nodeId });
|
|
4388
4677
|
}
|
|
4389
4678
|
const epath = errorsPathFor(proj);
|
|
4390
|
-
if (!epath) return [];
|
|
4679
|
+
if (!epath) return { count: 0, total: 0, events: [] };
|
|
4391
4680
|
const events = await readErrorEvents(epath);
|
|
4392
|
-
|
|
4681
|
+
const filtered = events.filter(
|
|
4393
4682
|
(e) => e.affectedNode === nodeId || e.service === nodeId.replace(/^service:/, "")
|
|
4394
4683
|
);
|
|
4684
|
+
return { count: filtered.length, total: filtered.length, events: filtered };
|
|
4395
4685
|
}
|
|
4396
4686
|
);
|
|
4397
|
-
scope.get("/
|
|
4687
|
+
scope.get("/graph/root-cause/:nodeId", async (req, reply) => {
|
|
4398
4688
|
const proj = resolveProject(registry, req, reply);
|
|
4399
4689
|
if (!proj) return;
|
|
4400
4690
|
const { nodeId } = req.params;
|
|
@@ -4414,7 +4704,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4414
4704
|
if (!result) return reply.code(404).send({ error: "no root cause found", id: nodeId });
|
|
4415
4705
|
return result;
|
|
4416
4706
|
});
|
|
4417
|
-
scope.get("/
|
|
4707
|
+
scope.get("/graph/blast-radius/:nodeId", async (req, reply) => {
|
|
4418
4708
|
const proj = resolveProject(registry, req, reply);
|
|
4419
4709
|
if (!proj) return;
|
|
4420
4710
|
const { nodeId } = req.params;
|
|
@@ -4512,7 +4802,7 @@ function registerRoutes(scope, ctx) {
|
|
|
4512
4802
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
4513
4803
|
let violations = await log.readAll();
|
|
4514
4804
|
if (req.query.severity) {
|
|
4515
|
-
const sev =
|
|
4805
|
+
const sev = import_types20.PolicySeveritySchema.safeParse(req.query.severity);
|
|
4516
4806
|
if (!sev.success) {
|
|
4517
4807
|
return reply.code(400).send({
|
|
4518
4808
|
error: "invalid severity",
|
|
@@ -4524,12 +4814,12 @@ function registerRoutes(scope, ctx) {
|
|
|
4524
4814
|
if (req.query.policyId) {
|
|
4525
4815
|
violations = violations.filter((v) => v.policyId === req.query.policyId);
|
|
4526
4816
|
}
|
|
4527
|
-
return violations;
|
|
4817
|
+
return { violations };
|
|
4528
4818
|
});
|
|
4529
4819
|
scope.post("/policies/check", async (req, reply) => {
|
|
4530
4820
|
const proj = resolveProject(registry, req, reply);
|
|
4531
4821
|
if (!proj) return;
|
|
4532
|
-
const parsed =
|
|
4822
|
+
const parsed = import_types20.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
4533
4823
|
if (!parsed.success) {
|
|
4534
4824
|
return reply.code(400).send({
|
|
4535
4825
|
error: "invalid /policies/check body",
|
|
@@ -4603,6 +4893,20 @@ async function buildApi(opts) {
|
|
|
4603
4893
|
});
|
|
4604
4894
|
}
|
|
4605
4895
|
});
|
|
4896
|
+
app.get("/projects/:project", async (req, reply) => {
|
|
4897
|
+
try {
|
|
4898
|
+
const entry2 = await getProject(req.params.project);
|
|
4899
|
+
if (!entry2) {
|
|
4900
|
+
return reply.code(404).send({ error: "project not found", project: req.params.project });
|
|
4901
|
+
}
|
|
4902
|
+
return { project: entry2 };
|
|
4903
|
+
} catch (err) {
|
|
4904
|
+
return reply.code(500).send({
|
|
4905
|
+
error: "failed to read project registry",
|
|
4906
|
+
details: err.message
|
|
4907
|
+
});
|
|
4908
|
+
}
|
|
4909
|
+
});
|
|
4606
4910
|
registerRoutes(app, routeCtx);
|
|
4607
4911
|
await app.register(
|
|
4608
4912
|
async (scope) => {
|
|
@@ -4615,13 +4919,13 @@ async function buildApi(opts) {
|
|
|
4615
4919
|
|
|
4616
4920
|
// src/extract/retire.ts
|
|
4617
4921
|
init_cjs_shims();
|
|
4618
|
-
var
|
|
4922
|
+
var import_types21 = require("@neat.is/types");
|
|
4619
4923
|
function retireEdgesByFile(graph, file) {
|
|
4620
4924
|
const normalized = file.split("\\").join("/");
|
|
4621
4925
|
const toDrop = [];
|
|
4622
4926
|
graph.forEachEdge((id, attrs) => {
|
|
4623
4927
|
const edge = attrs;
|
|
4624
|
-
if (edge.provenance !==
|
|
4928
|
+
if (edge.provenance !== import_types21.Provenance.EXTRACTED) return;
|
|
4625
4929
|
if (!edge.evidence?.file) return;
|
|
4626
4930
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
4627
4931
|
});
|
|
@@ -5613,9 +5917,12 @@ function renderPatch(sections) {
|
|
|
5613
5917
|
return lines.join("\n");
|
|
5614
5918
|
}
|
|
5615
5919
|
|
|
5920
|
+
// src/cli.ts
|
|
5921
|
+
var import_types23 = require("@neat.is/types");
|
|
5922
|
+
|
|
5616
5923
|
// src/cli-client.ts
|
|
5617
5924
|
init_cjs_shims();
|
|
5618
|
-
var
|
|
5925
|
+
var import_types22 = require("@neat.is/types");
|
|
5619
5926
|
var HttpError = class extends Error {
|
|
5620
5927
|
constructor(status2, message, responseBody = "") {
|
|
5621
5928
|
super(message);
|
|
@@ -5687,7 +5994,7 @@ async function runRootCause(client, input) {
|
|
|
5687
5994
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
5688
5995
|
const path38 = projectPath(
|
|
5689
5996
|
input.project,
|
|
5690
|
-
`/
|
|
5997
|
+
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
5691
5998
|
);
|
|
5692
5999
|
try {
|
|
5693
6000
|
const result = await client.get(path38);
|
|
@@ -5718,7 +6025,7 @@ async function runBlastRadius(client, input) {
|
|
|
5718
6025
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
5719
6026
|
const path38 = projectPath(
|
|
5720
6027
|
input.project,
|
|
5721
|
-
`/
|
|
6028
|
+
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
5722
6029
|
);
|
|
5723
6030
|
try {
|
|
5724
6031
|
const result = await client.get(path38);
|
|
@@ -5750,14 +6057,14 @@ async function runBlastRadius(client, input) {
|
|
|
5750
6057
|
}
|
|
5751
6058
|
}
|
|
5752
6059
|
function formatBlastEntry(n) {
|
|
5753
|
-
const tag = n.edgeProvenance ===
|
|
6060
|
+
const tag = n.edgeProvenance === import_types22.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
5754
6061
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
5755
6062
|
}
|
|
5756
6063
|
async function runDependencies(client, input) {
|
|
5757
6064
|
const depth = input.depth ?? 3;
|
|
5758
6065
|
const path38 = projectPath(
|
|
5759
6066
|
input.project,
|
|
5760
|
-
`/graph/
|
|
6067
|
+
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
5761
6068
|
);
|
|
5762
6069
|
try {
|
|
5763
6070
|
const result = await client.get(path38);
|
|
@@ -5796,9 +6103,9 @@ async function runObservedDependencies(client, input) {
|
|
|
5796
6103
|
const edges = await client.get(
|
|
5797
6104
|
projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
|
|
5798
6105
|
);
|
|
5799
|
-
const observed = edges.outbound.filter((e) => e.provenance ===
|
|
6106
|
+
const observed = edges.outbound.filter((e) => e.provenance === import_types22.Provenance.OBSERVED);
|
|
5800
6107
|
if (observed.length === 0) {
|
|
5801
|
-
const hasExtracted = edges.outbound.some((e) => e.provenance ===
|
|
6108
|
+
const hasExtracted = edges.outbound.some((e) => e.provenance === import_types22.Provenance.EXTRACTED);
|
|
5802
6109
|
const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
5803
6110
|
return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
|
|
5804
6111
|
}
|
|
@@ -5806,7 +6113,7 @@ async function runObservedDependencies(client, input) {
|
|
|
5806
6113
|
return {
|
|
5807
6114
|
summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
5808
6115
|
block: blockLines.join("\n"),
|
|
5809
|
-
provenance:
|
|
6116
|
+
provenance: import_types22.Provenance.OBSERVED
|
|
5810
6117
|
};
|
|
5811
6118
|
} catch (err) {
|
|
5812
6119
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -5843,7 +6150,8 @@ function formatDuration(ms) {
|
|
|
5843
6150
|
async function runIncidents(client, input) {
|
|
5844
6151
|
const path38 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
5845
6152
|
try {
|
|
5846
|
-
const
|
|
6153
|
+
const body = await client.get(path38);
|
|
6154
|
+
const events = body.events;
|
|
5847
6155
|
if (events.length === 0) {
|
|
5848
6156
|
return {
|
|
5849
6157
|
summary: input.nodeId ? `No incidents recorded against ${input.nodeId}.` : "No incidents recorded."
|
|
@@ -5857,9 +6165,9 @@ async function runIncidents(client, input) {
|
|
|
5857
6165
|
}
|
|
5858
6166
|
const target = input.nodeId ?? "the project";
|
|
5859
6167
|
return {
|
|
5860
|
-
summary: `${target} has ${
|
|
6168
|
+
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
5861
6169
|
block: blockLines.join("\n"),
|
|
5862
|
-
provenance:
|
|
6170
|
+
provenance: import_types22.Provenance.OBSERVED
|
|
5863
6171
|
};
|
|
5864
6172
|
} catch (err) {
|
|
5865
6173
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -5953,9 +6261,10 @@ async function runStaleEdges(client, input) {
|
|
|
5953
6261
|
if (input.limit !== void 0) params.set("limit", String(input.limit));
|
|
5954
6262
|
if (input.edgeType) params.set("edgeType", input.edgeType);
|
|
5955
6263
|
const qs = params.size > 0 ? `?${params.toString()}` : "";
|
|
5956
|
-
const
|
|
5957
|
-
projectPath(input.project, `/
|
|
6264
|
+
const body = await client.get(
|
|
6265
|
+
projectPath(input.project, `/stale-events${qs}`)
|
|
5958
6266
|
);
|
|
6267
|
+
const events = body.events;
|
|
5959
6268
|
if (events.length === 0) {
|
|
5960
6269
|
return {
|
|
5961
6270
|
summary: input.edgeType ? `No stale ${input.edgeType} edges recorded.` : "No stale-edge transitions recorded yet."
|
|
@@ -5967,7 +6276,7 @@ async function runStaleEdges(client, input) {
|
|
|
5967
6276
|
return {
|
|
5968
6277
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
5969
6278
|
block: blockLines.join("\n"),
|
|
5970
|
-
provenance:
|
|
6279
|
+
provenance: import_types22.Provenance.STALE
|
|
5971
6280
|
};
|
|
5972
6281
|
}
|
|
5973
6282
|
async function runPolicies(client, input) {
|
|
@@ -5989,9 +6298,10 @@ async function runPolicies(client, input) {
|
|
|
5989
6298
|
const params = new URLSearchParams();
|
|
5990
6299
|
if (input.policyId) params.set("policyId", input.policyId);
|
|
5991
6300
|
const qs = params.size > 0 ? `?${params.toString()}` : "";
|
|
5992
|
-
|
|
6301
|
+
const body = await client.get(
|
|
5993
6302
|
projectPath(input.project, `/policies/violations${qs}`)
|
|
5994
6303
|
);
|
|
6304
|
+
violations = body.violations;
|
|
5995
6305
|
allowed = violations.every((v) => v.onViolation !== "block");
|
|
5996
6306
|
}
|
|
5997
6307
|
if (input.nodeId) {
|
|
@@ -6030,6 +6340,54 @@ async function runPolicies(client, input) {
|
|
|
6030
6340
|
provenance: severities.join(" ")
|
|
6031
6341
|
};
|
|
6032
6342
|
}
|
|
6343
|
+
function formatDivergenceLine(d) {
|
|
6344
|
+
switch (d.type) {
|
|
6345
|
+
case "missing-observed":
|
|
6346
|
+
case "missing-extracted":
|
|
6347
|
+
return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} (${d.edgeType}) \u2014 confidence ${d.confidence.toFixed(2)}`;
|
|
6348
|
+
case "version-mismatch":
|
|
6349
|
+
return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 declared ${d.extractedVersion}, observed engine ${d.observedVersion} (${d.compatibility})`;
|
|
6350
|
+
case "host-mismatch":
|
|
6351
|
+
return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 declared host ${d.extractedHost}, observed host ${d.observedHost}`;
|
|
6352
|
+
case "compat-violation":
|
|
6353
|
+
return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.rule.kind}${d.rule.package ? ` (${d.rule.package})` : ""}`;
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
async function runDivergences(client, input) {
|
|
6357
|
+
const params = new URLSearchParams();
|
|
6358
|
+
if (input.type && input.type.length > 0) params.set("type", input.type.join(","));
|
|
6359
|
+
if (input.minConfidence !== void 0) {
|
|
6360
|
+
params.set("minConfidence", String(input.minConfidence));
|
|
6361
|
+
}
|
|
6362
|
+
if (input.node) params.set("node", input.node);
|
|
6363
|
+
const qs = params.size > 0 ? `?${params.toString()}` : "";
|
|
6364
|
+
const result = await client.get(
|
|
6365
|
+
projectPath(input.project, `/graph/divergences${qs}`)
|
|
6366
|
+
);
|
|
6367
|
+
if (result.totalAffected === 0) {
|
|
6368
|
+
return {
|
|
6369
|
+
summary: "No divergences found between the declared (EXTRACTED) and observed (OBSERVED) views of the graph."
|
|
6370
|
+
};
|
|
6371
|
+
}
|
|
6372
|
+
const headline = result.divergences[0];
|
|
6373
|
+
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}`;
|
|
6374
|
+
const blockLines = [];
|
|
6375
|
+
for (const d of result.divergences) {
|
|
6376
|
+
blockLines.push(formatDivergenceLine(d));
|
|
6377
|
+
blockLines.push(` reason: ${d.reason}`);
|
|
6378
|
+
blockLines.push(` recommendation: ${d.recommendation}`);
|
|
6379
|
+
}
|
|
6380
|
+
const maxConfidence = result.divergences.reduce(
|
|
6381
|
+
(m, d) => Math.max(m, d.confidence),
|
|
6382
|
+
0
|
|
6383
|
+
);
|
|
6384
|
+
return {
|
|
6385
|
+
summary,
|
|
6386
|
+
block: blockLines.join("\n"),
|
|
6387
|
+
confidence: maxConfidence,
|
|
6388
|
+
provenance: "composite (EXTRACTED + OBSERVED)"
|
|
6389
|
+
};
|
|
6390
|
+
}
|
|
6033
6391
|
function formatFooter(confidence, provenance) {
|
|
6034
6392
|
const c = confidence === void 0 ? "n/a" : confidence.toFixed(2);
|
|
6035
6393
|
const p = provenance === void 0 ? "n/a" : Array.isArray(provenance) ? [...new Set(provenance)].join(", ") : provenance;
|
|
@@ -6108,6 +6466,9 @@ function usage() {
|
|
|
6108
6466
|
console.log(" policies Current policy violations.");
|
|
6109
6467
|
console.log(" Flags: --node <id>, --hypothetical-action <json>");
|
|
6110
6468
|
console.log(" example: neat policies --node service:<name> --json");
|
|
6469
|
+
console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
|
|
6470
|
+
console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
|
|
6471
|
+
console.log(" example: neat divergences --min-confidence 0.7");
|
|
6111
6472
|
console.log("");
|
|
6112
6473
|
console.log("flags:");
|
|
6113
6474
|
console.log(' --project <name> Name the project this command targets. Default: "default".');
|
|
@@ -6132,7 +6493,9 @@ var STRING_FLAGS = [
|
|
|
6132
6493
|
["--since", "since"],
|
|
6133
6494
|
["--against", "against"],
|
|
6134
6495
|
["--error-id", "errorId"],
|
|
6135
|
-
["--hypothetical-action", "hypotheticalAction"]
|
|
6496
|
+
["--hypothetical-action", "hypotheticalAction"],
|
|
6497
|
+
["--type", "type"],
|
|
6498
|
+
["--min-confidence", "minConfidence"]
|
|
6136
6499
|
];
|
|
6137
6500
|
function parseArgs(rest) {
|
|
6138
6501
|
const positional = [];
|
|
@@ -6151,6 +6514,8 @@ function parseArgs(rest) {
|
|
|
6151
6514
|
against: null,
|
|
6152
6515
|
errorId: null,
|
|
6153
6516
|
hypotheticalAction: null,
|
|
6517
|
+
type: null,
|
|
6518
|
+
minConfidence: null,
|
|
6154
6519
|
positional: []
|
|
6155
6520
|
};
|
|
6156
6521
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -6210,6 +6575,15 @@ function assignFlag(out, field, value) {
|
|
|
6210
6575
|
out[field] = n;
|
|
6211
6576
|
return;
|
|
6212
6577
|
}
|
|
6578
|
+
if (field === "minConfidence") {
|
|
6579
|
+
const n = Number(value);
|
|
6580
|
+
if (!Number.isFinite(n) || n < 0 || n > 1) {
|
|
6581
|
+
console.error("neat: --min-confidence must be a number in [0, 1]");
|
|
6582
|
+
process.exit(2);
|
|
6583
|
+
}
|
|
6584
|
+
out.minConfidence = n;
|
|
6585
|
+
return;
|
|
6586
|
+
}
|
|
6213
6587
|
;
|
|
6214
6588
|
out[field] = value;
|
|
6215
6589
|
}
|
|
@@ -6593,7 +6967,9 @@ var QUERY_VERBS = /* @__PURE__ */ new Set([
|
|
|
6593
6967
|
"search",
|
|
6594
6968
|
"diff",
|
|
6595
6969
|
"stale-edges",
|
|
6596
|
-
"policies"
|
|
6970
|
+
"policies",
|
|
6971
|
+
// Tenth verb (ADR-060) — amends ADR-050's locked allowlist of nine.
|
|
6972
|
+
"divergences"
|
|
6597
6973
|
]);
|
|
6598
6974
|
function resolveProjectFlag(parsed) {
|
|
6599
6975
|
if (parsed.project) return parsed.project;
|
|
@@ -6715,6 +7091,31 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
6715
7091
|
});
|
|
6716
7092
|
break;
|
|
6717
7093
|
}
|
|
7094
|
+
case "divergences": {
|
|
7095
|
+
let typeFilter;
|
|
7096
|
+
if (parsed.type) {
|
|
7097
|
+
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7098
|
+
const out = [];
|
|
7099
|
+
for (const p of parts) {
|
|
7100
|
+
const r = import_types23.DivergenceTypeSchema.safeParse(p);
|
|
7101
|
+
if (!r.success) {
|
|
7102
|
+
console.error(
|
|
7103
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types23.DivergenceTypeSchema.options.join(", ")}`
|
|
7104
|
+
);
|
|
7105
|
+
return 2;
|
|
7106
|
+
}
|
|
7107
|
+
out.push(r.data);
|
|
7108
|
+
}
|
|
7109
|
+
typeFilter = out;
|
|
7110
|
+
}
|
|
7111
|
+
work = runDivergences(client, {
|
|
7112
|
+
...typeFilter ? { type: typeFilter } : {},
|
|
7113
|
+
...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
|
|
7114
|
+
...parsed.node ? { node: parsed.node } : {},
|
|
7115
|
+
...project ? { project } : {}
|
|
7116
|
+
});
|
|
7117
|
+
break;
|
|
7118
|
+
}
|
|
6718
7119
|
default:
|
|
6719
7120
|
console.error(`neat: unknown query verb "${cmd}"`);
|
|
6720
7121
|
return 2;
|