@knolo/cli 5.5.1 → 6.0.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/bin/kar-benchmark.mjs +323 -0
- package/bin/kar-graph.mjs +469 -0
- package/bin/kar-limits.mjs +36 -0
- package/bin/kar-produce.mjs +299 -0
- package/bin/kar.mjs +528 -0
- package/bin/knolo.mjs +23 -0
- package/package.json +4 -4
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/Cargo.toml +1 -1
- package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.lock +2 -2
- package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.toml +2 -2
package/bin/kar.mjs
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { KAR_CLI_MAX_IMAGE_BYTES, readBounded, readBoundedJson, readBoundedText } from './kar-limits.mjs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
const USAGE = `Usage: knolo kar <evaluate|verify|explain|inspect|graph|package|produce|domain|dossier|coverage|benchmark|demo|conformance> [options]
|
|
10
|
+
|
|
11
|
+
Knolo Adversarial Retrieval over a Committed Evidence Graph. Semantics are kar-1-research-1.
|
|
12
|
+
This command does not change V4 query, BM25, or MMR.
|
|
13
|
+
CEG authoring compiles source into the frozen sidecar. It does not change retrieval.
|
|
14
|
+
Producers propose CEG Source. They do not write committed graph bytes.
|
|
15
|
+
Domain Pack relation rules are review-required until a human gold gate says otherwise.
|
|
16
|
+
|
|
17
|
+
knolo kar evaluate --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..."
|
|
18
|
+
knolo kar verify --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --result result.json
|
|
19
|
+
knolo kar explain --result result.json [--image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..."]
|
|
20
|
+
knolo kar inspect --image knowledge.knolo --graph knowledge.kar.json [--plan plan.json] [--query "..."]
|
|
21
|
+
knolo kar graph build --image knowledge.knolo --source knowledge.ceg.yaml --out knowledge.kar.json
|
|
22
|
+
knolo kar graph lint|review|inspect|diff|validate|check|rebuild
|
|
23
|
+
knolo kar graph import claim-graph|json
|
|
24
|
+
knolo kar package --image knowledge.knolo --graph knowledge.kar.json --out dist/my-knowledge-kar/
|
|
25
|
+
knolo kar package verify dist/my-knowledge-kar/
|
|
26
|
+
knolo kar produce rules --image knowledge.knolo --domain domains/contracts --out proposals.json
|
|
27
|
+
knolo kar produce review proposals.json
|
|
28
|
+
knolo kar produce apply --proposals proposals.json --out generated.ceg.yaml
|
|
29
|
+
knolo kar domain validate domains/contracts
|
|
30
|
+
knolo kar domain inspect domains/contracts
|
|
31
|
+
knolo kar domain test domains/contracts
|
|
32
|
+
knolo kar dossier build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out dossier/
|
|
33
|
+
knolo kar dossier verify dossier/
|
|
34
|
+
knolo kar coverage build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out coverage/
|
|
35
|
+
knolo kar coverage build --image knowledge.knolo --graph knowledge.kar.json --domain domains/contracts-v4 --query "..." --out coverage/
|
|
36
|
+
knolo kar coverage verify coverage/
|
|
37
|
+
knolo kar benchmark annotate --packet packet.json --decisions decisions.json --out labels.json --role human --reviewer-id <id>
|
|
38
|
+
knolo kar benchmark adjudicate --packet packet.json --a pass-a.json --b pass-b.json --decisions decisions.json --out gold.json --role human-adjudicator --reviewer-id <id>
|
|
39
|
+
knolo kar benchmark agreement --a pass-a.json --b pass-b.json
|
|
40
|
+
knolo kar demo
|
|
41
|
+
knolo kar conformance
|
|
42
|
+
|
|
43
|
+
--json Print the machine-readable evaluation, build, or inspection
|
|
44
|
+
--auto Write accepted deterministic producer output as CEG Source
|
|
45
|
+
|
|
46
|
+
Exit status is 0 for a completed command, including a KAR abstention.
|
|
47
|
+
coverage build exits 0 when the report is written, including gap and blocked rows.
|
|
48
|
+
verify, dossier verify, coverage verify, and conformance return non-zero when a check fails.
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
export async function runKarCli(argv) {
|
|
52
|
+
const { command, positionals, flags } = parseArgs(argv);
|
|
53
|
+
if (!command || command === 'help' || flags.help) {
|
|
54
|
+
console.log(USAGE.trim());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (command === 'graph' || command === 'package') {
|
|
58
|
+
const { runKarAuthoring } = await import('./kar-graph.mjs');
|
|
59
|
+
return runKarAuthoring(command, positionals.slice(1), flags);
|
|
60
|
+
}
|
|
61
|
+
if (command === 'produce' || command === 'domain') {
|
|
62
|
+
const { runKarProduce } = await import('./kar-produce.mjs');
|
|
63
|
+
return runKarProduce(command, positionals.slice(1), flags);
|
|
64
|
+
}
|
|
65
|
+
if (command === 'benchmark') {
|
|
66
|
+
const { runKarBenchmark } = await import('./kar-benchmark.mjs');
|
|
67
|
+
return runKarBenchmark(positionals.slice(1), flags);
|
|
68
|
+
}
|
|
69
|
+
if (command === 'demo') return runRepoScript(path.join('examples', 'kar-v6', 'run.mjs'));
|
|
70
|
+
if (command === 'conformance') return runRepoScript(path.join('conformance', 'kar', 'v1', 'run.mjs'));
|
|
71
|
+
const kar = await loadKar();
|
|
72
|
+
if (command === 'dossier') return dossierCommand(kar, positionals.slice(1), flags);
|
|
73
|
+
if (command === 'coverage') return coverageCommand(kar, positionals.slice(1), flags);
|
|
74
|
+
if (command === 'evaluate') return evaluateCommand(kar, flags);
|
|
75
|
+
if (command === 'verify') return verifyCommand(kar, flags);
|
|
76
|
+
if (command === 'explain') return explainCommand(kar, flags);
|
|
77
|
+
if (command === 'inspect') return inspectCommand(kar, flags);
|
|
78
|
+
throw new Error(`Unknown kar command "${command}".\n${USAGE}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function evaluateCommand(kar, flags) {
|
|
82
|
+
const { session, plan, query } = openSession(kar, flags, true);
|
|
83
|
+
const result = kar.evaluateKar(session, { proposition: query, plan });
|
|
84
|
+
if (flags.json) {
|
|
85
|
+
console.log(JSON.stringify(result, null, 2));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log(kar.renderKarEvaluation(result));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function verifyCommand(kar, flags) {
|
|
92
|
+
const image = readBytes(required(flags, 'image'));
|
|
93
|
+
const graph = readJson(required(flags, 'graph'));
|
|
94
|
+
const plan = readJson(required(flags, 'plan'));
|
|
95
|
+
const query = required(flags, 'query');
|
|
96
|
+
const result = readJson(required(flags, 'result'));
|
|
97
|
+
const verdict = kar.verifyKar({ image, graph, proposition: query, plan, result });
|
|
98
|
+
if (!verdict.ok) {
|
|
99
|
+
console.log(verdict.code);
|
|
100
|
+
console.log(verdict.reason);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log('VERIFIED');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function explainCommand(kar, flags) {
|
|
108
|
+
const result = readJson(required(flags, 'result'));
|
|
109
|
+
const wantsResolution = flags.image || flags.graph || flags.plan || flags.query;
|
|
110
|
+
if (wantsResolution && !(flags.image && flags.graph && flags.plan && flags.query)) {
|
|
111
|
+
throw new Error('Resolving evidence text requires --image, --graph, --plan, and --query together. Certificate-only explanation uses --result alone.');
|
|
112
|
+
}
|
|
113
|
+
const explanation = wantsResolution
|
|
114
|
+
? kar.explainKarResult(openSession(kar, flags, true).session, result)
|
|
115
|
+
: kar.explainKarCertificate(result);
|
|
116
|
+
if (flags.json) {
|
|
117
|
+
console.log(JSON.stringify(explanation, null, 2));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(kar.renderKarExplanation(explanation));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function inspectCommand(kar, flags) {
|
|
124
|
+
const { session, plan } = openSession(kar, flags, false);
|
|
125
|
+
const graph = session.graph;
|
|
126
|
+
const symbols = [...new Set(graph.relations.map((relation) => relation.relation))].sort();
|
|
127
|
+
const complexity = plan ? kar.inspectKarComplexity(session, plan, flags.query ?? '') : null;
|
|
128
|
+
const report = {
|
|
129
|
+
experimental: true,
|
|
130
|
+
semantics: 'kar-1-research-1',
|
|
131
|
+
stateRoot: session.image.stateRoot,
|
|
132
|
+
knowledgeRoot: session.image.knowledgeRoot,
|
|
133
|
+
objectRoot: session.image.objectRoot,
|
|
134
|
+
commitDigest: session.image.commitDigest,
|
|
135
|
+
semanticRoot: session.roots.semanticRoot,
|
|
136
|
+
nodes: graph.nodes.length,
|
|
137
|
+
relations: graph.relations.length,
|
|
138
|
+
evidenceBindings: graph.bindings.length,
|
|
139
|
+
relationTypes: symbols,
|
|
140
|
+
cover: complexity,
|
|
141
|
+
bounds: plan ? plan.bounds : null,
|
|
142
|
+
};
|
|
143
|
+
if (flags.json) {
|
|
144
|
+
console.log(JSON.stringify(report, null, 2));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const lines = [
|
|
148
|
+
'KAR INSPECTION',
|
|
149
|
+
`V5 state root ${report.stateRoot ?? '(none)'}`,
|
|
150
|
+
`KAR knowledge root ${report.knowledgeRoot}`,
|
|
151
|
+
`CEG semantic root ${report.semanticRoot}`,
|
|
152
|
+
`object root ${report.objectRoot ?? '(none)'}`,
|
|
153
|
+
`commit digest ${report.commitDigest ?? '(none)'}`,
|
|
154
|
+
`nodes ${report.nodes}`,
|
|
155
|
+
`relations ${report.relations}`,
|
|
156
|
+
`evidence bindings ${report.evidenceBindings}`,
|
|
157
|
+
`relation types ${symbols.join(', ') || '(none)'}`,
|
|
158
|
+
];
|
|
159
|
+
if (!complexity) {
|
|
160
|
+
lines.push('cover complexity plan not supplied');
|
|
161
|
+
} else {
|
|
162
|
+
lines.push(`cover risk ${complexity.risk}`);
|
|
163
|
+
lines.push(`candidates ${complexity.candidates}`);
|
|
164
|
+
lines.push(`distinct masks ${complexity.distinctRequirementMasks}`);
|
|
165
|
+
lines.push(`cardinality bound ${complexity.cardinalityBound}`);
|
|
166
|
+
lines.push(`max cover visits ${complexity.maxCoverVisits}`);
|
|
167
|
+
lines.push(`combination bound ${complexity.estimatedCombinationUpperBound}`);
|
|
168
|
+
}
|
|
169
|
+
if (plan) {
|
|
170
|
+
lines.push(`resource bounds closureNodes ${plan.bounds.maxClosureNodes}, closureEdges ${plan.bounds.maxClosureEdges}, coverVisits ${plan.bounds.maxCoverVisits}`);
|
|
171
|
+
}
|
|
172
|
+
console.log(lines.join('\n'));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function dossierCommand(kar, args, flags) {
|
|
176
|
+
if (!kar.createKarDossier || !kar.renderKarDossier || !kar.verifyKarDossier) {
|
|
177
|
+
throw new Error('Could not load the KAR dossier API. Build packages/core first.');
|
|
178
|
+
}
|
|
179
|
+
const sub = args[0];
|
|
180
|
+
if (sub === 'build') return buildDossier(kar, flags);
|
|
181
|
+
if (sub === 'verify') return verifyDossier(kar, args[1]);
|
|
182
|
+
throw new Error(`Unknown dossier command "${sub ?? ''}".\n${USAGE}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildDossier(kar, flags) {
|
|
186
|
+
const out = path.resolve(process.cwd(), required(flags, 'out'));
|
|
187
|
+
if (existsSync(path.join(out, 'manifest.json'))) {
|
|
188
|
+
throw new Error(`Refusing to overwrite ${out}`);
|
|
189
|
+
}
|
|
190
|
+
const imagePath = path.resolve(process.cwd(), required(flags, 'image'));
|
|
191
|
+
const graphPath = path.resolve(process.cwd(), required(flags, 'graph'));
|
|
192
|
+
const planPath = path.resolve(process.cwd(), required(flags, 'plan'));
|
|
193
|
+
const proposition = required(flags, 'query');
|
|
194
|
+
const image = readBytes(imagePath);
|
|
195
|
+
const graph = readJson(graphPath);
|
|
196
|
+
const plan = readJson(planPath);
|
|
197
|
+
const dossier = kar.createKarDossier({ image, graph, plan, proposition });
|
|
198
|
+
mkdirSync(out, { recursive: true });
|
|
199
|
+
copyFileSync(imagePath, path.join(out, 'knowledge.knolo'));
|
|
200
|
+
copyFileSync(graphPath, path.join(out, 'knowledge.kar.json'));
|
|
201
|
+
copyFileSync(planPath, path.join(out, 'plan.json'));
|
|
202
|
+
writeFileSync(path.join(out, 'manifest.json'), `${JSON.stringify(dossier.manifest, null, 2)}\n`);
|
|
203
|
+
writeFileSync(path.join(out, 'certificate.json'), `${JSON.stringify(dossier.certificate, null, 2)}\n`);
|
|
204
|
+
writeFileSync(path.join(out, 'result.json'), `${JSON.stringify(dossier.evaluation, null, 2)}\n`);
|
|
205
|
+
writeFileSync(path.join(out, 'evidence.md'), kar.renderKarDossier(dossier));
|
|
206
|
+
if (flags.json) console.log(JSON.stringify(dossier.manifest, null, 2));
|
|
207
|
+
else console.log(`${dossier.statusStatement}\n${out}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function verifyDossier(kar, directory) {
|
|
211
|
+
if (!directory) throw new Error('Missing dossier directory.\nknolo kar dossier verify dossier/');
|
|
212
|
+
const dir = path.resolve(process.cwd(), directory);
|
|
213
|
+
const image = readBytes(path.join(dir, 'knowledge.knolo'));
|
|
214
|
+
const graph = readJson(path.join(dir, 'knowledge.kar.json'));
|
|
215
|
+
const plan = readJson(path.join(dir, 'plan.json'));
|
|
216
|
+
const manifest = readJson(path.join(dir, 'manifest.json'));
|
|
217
|
+
const certificate = readJson(path.join(dir, 'certificate.json'));
|
|
218
|
+
const evaluation = readJson(path.join(dir, 'result.json'));
|
|
219
|
+
const evidenceMarkdown = readBoundedText(path.join(dir, 'evidence.md'));
|
|
220
|
+
const proposition = evaluation && typeof evaluation.proposition === 'string' ? evaluation.proposition : '';
|
|
221
|
+
if (!proposition) {
|
|
222
|
+
console.log('KAR_RESULT_INVALID');
|
|
223
|
+
console.log('The dossier result has no proposition.');
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const verdict = kar.verifyKarDossier({
|
|
228
|
+
image,
|
|
229
|
+
graph,
|
|
230
|
+
plan,
|
|
231
|
+
proposition,
|
|
232
|
+
manifest,
|
|
233
|
+
certificate,
|
|
234
|
+
evaluation,
|
|
235
|
+
evidenceMarkdown,
|
|
236
|
+
});
|
|
237
|
+
if (!verdict.ok) {
|
|
238
|
+
console.log(verdict.code);
|
|
239
|
+
console.log(verdict.reason);
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
console.log('VERIFIED');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function coverageCommand(kar, args, flags) {
|
|
247
|
+
if (!kar.createKarCoverage || !kar.verifyKarCoverage || !kar.renderKarCoverage) {
|
|
248
|
+
throw new Error('Could not load the KAR coverage API. Build packages/core first.');
|
|
249
|
+
}
|
|
250
|
+
const sub = args[0];
|
|
251
|
+
if (sub === 'build') return buildCoverage(kar, flags);
|
|
252
|
+
if (sub === 'verify') return verifyCoverage(kar, args[1], flags);
|
|
253
|
+
throw new Error(`Unknown coverage command "${sub ?? ''}".\n${USAGE}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function buildCoverage(kar, flags) {
|
|
257
|
+
const out = path.resolve(process.cwd(), required(flags, 'out'));
|
|
258
|
+
if (existsSync(path.join(out, 'manifest.json'))) {
|
|
259
|
+
throw new Error(`Refusing to overwrite ${out}`);
|
|
260
|
+
}
|
|
261
|
+
const imagePath = path.resolve(process.cwd(), required(flags, 'image'));
|
|
262
|
+
const graphPath = path.resolve(process.cwd(), required(flags, 'graph'));
|
|
263
|
+
const image = readBytes(imagePath);
|
|
264
|
+
const graph = readJson(graphPath);
|
|
265
|
+
const prepared = await coverageInputs(flags);
|
|
266
|
+
if (!prepared.ok) {
|
|
267
|
+
console.log(prepared.code);
|
|
268
|
+
console.log(prepared.message);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
let report;
|
|
273
|
+
try {
|
|
274
|
+
report = kar.createKarCoverage({
|
|
275
|
+
image,
|
|
276
|
+
graph,
|
|
277
|
+
cases: prepared.cases,
|
|
278
|
+
domainPack: prepared.domainPack,
|
|
279
|
+
});
|
|
280
|
+
} catch (error) {
|
|
281
|
+
if (error && typeof error.code === 'string') {
|
|
282
|
+
console.log(error.code);
|
|
283
|
+
console.log(error.message);
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
const ordered = [...prepared.cases].sort((left, right) => (left.id < right.id ? -1 : left.id > right.id ? 1 : 0));
|
|
290
|
+
mkdirSync(out, { recursive: true });
|
|
291
|
+
copyFileSync(imagePath, path.join(out, 'knowledge.knolo'));
|
|
292
|
+
copyFileSync(graphPath, path.join(out, 'knowledge.kar.json'));
|
|
293
|
+
const casesFile = { format: 'kar-coverage-cases-1', domainPack: report.domainPack, cases: ordered };
|
|
294
|
+
writeFileSync(path.join(out, 'cases.json'), `${JSON.stringify(casesFile, null, 2)}\n`);
|
|
295
|
+
writeFileSync(path.join(out, 'report.json'), `${JSON.stringify(report, null, 2)}\n`);
|
|
296
|
+
writeFileSync(path.join(out, 'coverage.md'), kar.renderKarCoverage(report));
|
|
297
|
+
writeFileSync(path.join(out, 'manifest.json'), `${JSON.stringify(report.manifest, null, 2)}\n`);
|
|
298
|
+
if (flags.json) console.log(JSON.stringify(report.manifest, null, 2));
|
|
299
|
+
else {
|
|
300
|
+
for (const row of report.cases) console.log(`${row.id} ${row.disposition} ${row.statement}`);
|
|
301
|
+
console.log(out);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function verifyCoverage(kar, directory, flags) {
|
|
306
|
+
if (!directory) throw new Error('Missing coverage directory.\nknolo kar coverage verify coverage/');
|
|
307
|
+
const dir = path.resolve(process.cwd(), directory);
|
|
308
|
+
const image = readBytes(path.join(dir, 'knowledge.knolo'));
|
|
309
|
+
const graph = readJson(path.join(dir, 'knowledge.kar.json'));
|
|
310
|
+
const stored = readJson(path.join(dir, 'cases.json'));
|
|
311
|
+
const manifest = readJson(path.join(dir, 'manifest.json'));
|
|
312
|
+
const report = readJson(path.join(dir, 'report.json'));
|
|
313
|
+
const rendering = readBoundedText(path.join(dir, 'coverage.md'));
|
|
314
|
+
if (!stored || stored.format !== 'kar-coverage-cases-1' || !Array.isArray(stored.cases)) {
|
|
315
|
+
console.log('KAR_COVERAGE_INVALID');
|
|
316
|
+
console.log('cases.json is not a coverage case file.');
|
|
317
|
+
process.exitCode = 1;
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (flags.domain) {
|
|
321
|
+
const authoring = await loadAuthoring();
|
|
322
|
+
const { loadDomainDirectory } = await import('./kar-produce.mjs');
|
|
323
|
+
const loaded = loadDomainDirectory(authoring, flags.domain);
|
|
324
|
+
if (!loaded.ok) {
|
|
325
|
+
console.log(loaded.diagnostics[0].code);
|
|
326
|
+
console.log(loaded.diagnostics[0].message);
|
|
327
|
+
process.exitCode = 1;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const root = authoring.domainPackRoot(loaded.pack);
|
|
331
|
+
const recorded = stored.domainPack;
|
|
332
|
+
if (!recorded || recorded.root !== root || recorded.id !== loaded.pack.id || String(recorded.version) !== String(loaded.pack.version)) {
|
|
333
|
+
console.log('KAR_RESULT_MISMATCH');
|
|
334
|
+
console.log('domain pack root mismatch');
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const verdict = kar.verifyKarCoverage({
|
|
340
|
+
image,
|
|
341
|
+
graph,
|
|
342
|
+
cases: stored.cases,
|
|
343
|
+
domainPack: stored.domainPack ?? null,
|
|
344
|
+
manifest,
|
|
345
|
+
report,
|
|
346
|
+
rendering,
|
|
347
|
+
});
|
|
348
|
+
if (!verdict.ok) {
|
|
349
|
+
console.log(verdict.code);
|
|
350
|
+
console.log(verdict.reason);
|
|
351
|
+
process.exitCode = 1;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
console.log('VERIFIED');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function coverageInputs(flags) {
|
|
358
|
+
const plans = flags.planList ?? [];
|
|
359
|
+
const queries = flags.queryList ?? [];
|
|
360
|
+
const templates = flags.templateList ?? [];
|
|
361
|
+
if (plans.length > 0 && flags.domain) {
|
|
362
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Use either repeated --plan or --domain, not both.' };
|
|
363
|
+
}
|
|
364
|
+
if (templates.length > 0 && !flags.domain) {
|
|
365
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: '--template requires --domain.' };
|
|
366
|
+
}
|
|
367
|
+
if (flags.queries && !flags.domain) {
|
|
368
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: '--queries requires --domain.' };
|
|
369
|
+
}
|
|
370
|
+
if (plans.length > 0) {
|
|
371
|
+
if (flags.queries) {
|
|
372
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Pair each --plan with --query. --queries is for --domain.' };
|
|
373
|
+
}
|
|
374
|
+
if (queries.length !== plans.length) {
|
|
375
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Each --plan needs one --query in the same order.' };
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
ok: true,
|
|
379
|
+
domainPack: null,
|
|
380
|
+
cases: plans.map((file, index) => ({
|
|
381
|
+
id: path.basename(file, path.extname(file)),
|
|
382
|
+
proposition: queries[index],
|
|
383
|
+
plan: readJson(path.resolve(process.cwd(), file)),
|
|
384
|
+
})),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (!flags.domain) {
|
|
388
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Coverage requires repeated --plan and --query, or --domain.' };
|
|
389
|
+
}
|
|
390
|
+
if (flags.queries && queries.length > 0) {
|
|
391
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Use either --query or --queries.' };
|
|
392
|
+
}
|
|
393
|
+
if (!flags.queries && queries.length !== 1) {
|
|
394
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: 'Domain coverage needs one --query, or a --queries file.' };
|
|
395
|
+
}
|
|
396
|
+
const authoring = await loadAuthoring();
|
|
397
|
+
const { loadDomainDirectory } = await import('./kar-produce.mjs');
|
|
398
|
+
const loaded = loadDomainDirectory(authoring, flags.domain);
|
|
399
|
+
if (!loaded.ok) {
|
|
400
|
+
const diagnostic = loaded.diagnostics[0];
|
|
401
|
+
return { ok: false, code: diagnostic?.code ?? 'CEG_DOMAIN_INVALID', message: diagnostic?.message ?? 'Domain pack did not load.' };
|
|
402
|
+
}
|
|
403
|
+
let queryMap;
|
|
404
|
+
if (flags.queries) {
|
|
405
|
+
const parsed = readJson(path.resolve(process.cwd(), flags.queries));
|
|
406
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
407
|
+
return { ok: false, code: 'KAR_COVERAGE_INVALID', message: '--queries must be a JSON object.' };
|
|
408
|
+
}
|
|
409
|
+
queryMap = parsed;
|
|
410
|
+
}
|
|
411
|
+
const expanded = authoring.coverageCasesFromDomainPack({
|
|
412
|
+
pack: loaded.pack,
|
|
413
|
+
proposition: queryMap ? undefined : queries[0],
|
|
414
|
+
templates: templates.length > 0 ? templates : undefined,
|
|
415
|
+
queries: queryMap,
|
|
416
|
+
});
|
|
417
|
+
if (!expanded.ok) {
|
|
418
|
+
const diagnostic = expanded.diagnostics[0];
|
|
419
|
+
return { ok: false, code: diagnostic.code, message: diagnostic.message };
|
|
420
|
+
}
|
|
421
|
+
return { ok: true, cases: expanded.cases, domainPack: expanded.domainPack };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function loadAuthoring() {
|
|
425
|
+
const candidates = [
|
|
426
|
+
'@knolo/core/kar/authoring',
|
|
427
|
+
path.resolve(__dirname, '../../core/dist/kar/authoring/index.js'),
|
|
428
|
+
path.resolve(__dirname, '../../core/dist/experimental/kar/authoring/index.js'),
|
|
429
|
+
'@knolo/core/experimental/kar/authoring',
|
|
430
|
+
];
|
|
431
|
+
for (const candidate of candidates) {
|
|
432
|
+
try {
|
|
433
|
+
const href = candidate.startsWith('@') ? candidate : pathToFileURL(candidate).href;
|
|
434
|
+
const loaded = await import(href);
|
|
435
|
+
if (loaded.coverageCasesFromDomainPack && loaded.domainPackRoot && loaded.parseAuthoringDocument) return loaded;
|
|
436
|
+
} catch {
|
|
437
|
+
// Try the next candidate.
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
throw new Error('Could not load @knolo/core/kar/authoring. Build packages/core first.');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function openSession(kar, flags, requirePlan) {
|
|
444
|
+
const image = readBytes(required(flags, 'image'));
|
|
445
|
+
const graph = readJson(required(flags, 'graph'));
|
|
446
|
+
const session = kar.createKarSession({ image, graph });
|
|
447
|
+
if (!requirePlan && !flags.plan) return { session, plan: null, query: flags.query ?? '' };
|
|
448
|
+
const plan = readJson(required(flags, 'plan'));
|
|
449
|
+
const query = requirePlan ? required(flags, 'query') : flags.query ?? '';
|
|
450
|
+
return { session, plan, query };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function runRepoScript(relative) {
|
|
454
|
+
const root = findRepoRoot(process.cwd()) ?? findRepoRoot(path.resolve(__dirname, '../../..'));
|
|
455
|
+
const script = root ? path.join(root, relative) : '';
|
|
456
|
+
if (!root || !existsSync(script)) {
|
|
457
|
+
throw new Error(`Could not find ${relative}. Run this command from a knolo-core checkout.`);
|
|
458
|
+
}
|
|
459
|
+
const result = spawnSync(process.execPath, [script], { stdio: 'inherit' });
|
|
460
|
+
process.exitCode = result.status === null ? 1 : result.status;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function findRepoRoot(start) {
|
|
464
|
+
let dir = start;
|
|
465
|
+
for (let hop = 0; hop < 8; hop += 1) {
|
|
466
|
+
if (existsSync(path.join(dir, 'packages', 'core', 'package.json')) && existsSync(path.join(dir, 'conformance', 'kar', 'v1', 'run.mjs'))) {
|
|
467
|
+
return dir;
|
|
468
|
+
}
|
|
469
|
+
const parent = path.dirname(dir);
|
|
470
|
+
if (parent === dir) return null;
|
|
471
|
+
dir = parent;
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function loadKar() {
|
|
477
|
+
const candidates = [
|
|
478
|
+
'@knolo/core/kar',
|
|
479
|
+
path.resolve(__dirname, '../../core/dist/kar/index.js'),
|
|
480
|
+
path.resolve(__dirname, '../../core/dist/experimental/kar/index.js'),
|
|
481
|
+
'@knolo/core/experimental/kar',
|
|
482
|
+
];
|
|
483
|
+
for (const candidate of candidates) {
|
|
484
|
+
try {
|
|
485
|
+
const href = candidate.startsWith('@') ? candidate : pathToFileURL(candidate).href;
|
|
486
|
+
const loaded = await import(href);
|
|
487
|
+
if (loaded.createKarSession && loaded.evaluateKar && loaded.verifyKar) return loaded;
|
|
488
|
+
} catch {
|
|
489
|
+
// Try the next candidate.
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
throw new Error('Could not load @knolo/core/kar. Build packages/core first.');
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function parseArgs(argv) {
|
|
496
|
+
const flags = { planList: [], queryList: [], templateList: [] };
|
|
497
|
+
const positionals = [];
|
|
498
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
499
|
+
const arg = argv[index];
|
|
500
|
+
if (arg === '--json') flags.json = true;
|
|
501
|
+
else if (arg === '--auto') flags.auto = true;
|
|
502
|
+
else if (arg === '--help' || arg === '-h') flags.help = true;
|
|
503
|
+
else if (arg.startsWith('--')) {
|
|
504
|
+
const key = arg.slice(2);
|
|
505
|
+
const value = argv[index + 1];
|
|
506
|
+
if (value === undefined || value.startsWith('--')) throw new Error(`Missing value for --${key}`);
|
|
507
|
+
if (key === 'plan') flags.planList.push(value);
|
|
508
|
+
else if (key === 'query') flags.queryList.push(value);
|
|
509
|
+
else if (key === 'template') flags.templateList.push(value);
|
|
510
|
+
flags[key] = value;
|
|
511
|
+
index += 1;
|
|
512
|
+
} else positionals.push(arg);
|
|
513
|
+
}
|
|
514
|
+
return { command: positionals[0] ?? '', positionals, flags };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function required(flags, name) {
|
|
518
|
+
if (!flags[name]) throw new Error(`Missing --${name}`);
|
|
519
|
+
return flags[name];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function readBytes(file) {
|
|
523
|
+
return readBounded(file, KAR_CLI_MAX_IMAGE_BYTES);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function readJson(file) {
|
|
527
|
+
return readBoundedJson(file);
|
|
528
|
+
}
|
package/bin/knolo.mjs
CHANGED
|
@@ -72,6 +72,7 @@ const SUBCOMMANDS = new Set([
|
|
|
72
72
|
'semantic:index',
|
|
73
73
|
'semantic:inspect',
|
|
74
74
|
'semantic:validate',
|
|
75
|
+
'kar',
|
|
75
76
|
]);
|
|
76
77
|
const ICP_SUBCOMMANDS = new Set([
|
|
77
78
|
'init',
|
|
@@ -128,6 +129,18 @@ async function loadCore() {
|
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
function printVersion() {
|
|
133
|
+
const cli = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
134
|
+
let coreVersion = 'unavailable';
|
|
135
|
+
try {
|
|
136
|
+
coreVersion = JSON.parse(readFileSync(require.resolve('@knolo/core/package.json'), 'utf8')).version;
|
|
137
|
+
} catch {
|
|
138
|
+
coreVersion = 'unavailable';
|
|
139
|
+
}
|
|
140
|
+
console.log(`@knolo/cli ${cli.version}`);
|
|
141
|
+
console.log(`@knolo/core ${coreVersion}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
131
144
|
function printRootHelp() {
|
|
132
145
|
console.log(`KnoLo CLI
|
|
133
146
|
|
|
@@ -154,11 +167,13 @@ Commands:
|
|
|
154
167
|
diff <pack-a> <pack-b> Compare pack identities and source/chunk counts
|
|
155
168
|
dev Watch config/sources and rebuild on change
|
|
156
169
|
v5 Inspect, compress, decompress, and health-check a V5 Knowledge Image
|
|
170
|
+
kar KAR evidence-set retrieval over a Committed Evidence Graph
|
|
157
171
|
icp Scaffold and operate an ICP-native knowledge canister
|
|
158
172
|
|
|
159
173
|
Global options:
|
|
160
174
|
--debug Print stack traces for errors
|
|
161
175
|
-h, --help Show help
|
|
176
|
+
-v, --version Print the CLI and @knolo/core versions
|
|
162
177
|
|
|
163
178
|
Run "knolo <command> --help" for command details.`);
|
|
164
179
|
}
|
|
@@ -188,6 +203,7 @@ function printCommandHelp(command) {
|
|
|
188
203
|
diff: 'Usage: knolo diff <pack-a.knolo> <pack-b.knolo>',
|
|
189
204
|
dev: 'Usage: knolo dev',
|
|
190
205
|
v5: 'Usage: knolo v5 <info|health|studio|query|compress|decompress> <image.v5> [--out <file>] [--mode fast|balanced|max] [--attach-index]\n\nFor query: knolo v5 query <image.v5> <EQL> [--json] [--receipt <file>].',
|
|
206
|
+
kar: 'Usage: knolo kar <evaluate|verify|explain|inspect|graph|package|produce|domain|dossier|coverage|benchmark|demo|conformance> [options]\n\nKnolo Adversarial Retrieval. Semantics are frozen at kar-1-research-1.\n\n knolo kar evaluate --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..."\n knolo kar verify --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --result result.json\n knolo kar explain --result result.json\n knolo kar inspect --image knowledge.knolo --graph knowledge.kar.json [--plan plan.json]\n knolo kar graph build --image knowledge.knolo --source knowledge.ceg.yaml --out knowledge.kar.json\n knolo kar package --image knowledge.knolo --graph knowledge.kar.json --out dist/my-knowledge-kar/\n knolo kar produce rules --image knowledge.knolo --domain domains/contracts-v4 --out proposals.json\n knolo kar domain validate domains/contracts-v4\n knolo kar dossier build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out dossier/\n knolo kar dossier verify dossier/\n knolo kar coverage build --image knowledge.knolo --graph knowledge.kar.json --plan plan.json --query "..." --out coverage/\n knolo kar coverage verify coverage/\n knolo kar benchmark annotate --packet packet.json --decisions decisions.json --out labels.json --role human --reviewer-id <id>\n knolo kar demo\n knolo kar conformance\n\n--json prints the machine-readable result. verify exits non-zero when the certificate does not match. Abstention is a result and exits 0. graph and package author a Committed Evidence Graph without changing retrieval. produce proposes CEG Source and does not write committed bytes. dossier packages one evaluation and fails closed when committed fields change. coverage reports met requirements, gaps, and plans that did not decide. A gap is not a false claim. benchmark labels gold without showing producer output. demo and conformance run from a knolo-core checkout.',
|
|
191
207
|
icp: 'Usage: knolo icp <command> [options]',
|
|
192
208
|
'semantic:index':
|
|
193
209
|
'Usage: knolo semantic:index --pack <path> [--out <path>] [--model <id>] [--endpoint <url>]',
|
|
@@ -288,6 +304,8 @@ function parseArgv(argv) {
|
|
|
288
304
|
}
|
|
289
305
|
if (args[0] === '--help' || args[0] === '-h')
|
|
290
306
|
return { global, command: 'help', commandArgs: [] };
|
|
307
|
+
if (args[0] === '--version' || args[0] === '-v')
|
|
308
|
+
return { global, command: 'version', commandArgs: [] };
|
|
291
309
|
return { global, command: args[0] || 'help', commandArgs: args.slice(1) };
|
|
292
310
|
}
|
|
293
311
|
|
|
@@ -1816,6 +1834,7 @@ async function main() {
|
|
|
1816
1834
|
const { global, command, commandArgs } = parseArgv(process.argv.slice(2));
|
|
1817
1835
|
try {
|
|
1818
1836
|
if (command === 'help') return printRootHelp();
|
|
1837
|
+
if (command === 'version') return printVersion();
|
|
1819
1838
|
if (command === 'icp') {
|
|
1820
1839
|
if (
|
|
1821
1840
|
commandArgs.length === 0 ||
|
|
@@ -1830,6 +1849,10 @@ async function main() {
|
|
|
1830
1849
|
if (SUBCOMMANDS.has(command)) {
|
|
1831
1850
|
if (commandArgs.includes('--help') || commandArgs.includes('-h'))
|
|
1832
1851
|
return printCommandHelp(command);
|
|
1852
|
+
if (command === 'kar') {
|
|
1853
|
+
const { runKarCli } = await import('./kar.mjs');
|
|
1854
|
+
return runKarCli(commandArgs);
|
|
1855
|
+
}
|
|
1833
1856
|
if (command === 'init') return await cmdInit();
|
|
1834
1857
|
if (command === 'add') return await cmdAdd(commandArgs);
|
|
1835
1858
|
if (command === 'search') return await runHubSearch(commandArgs);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knolo/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"knolo": "bin/knolo.mjs"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"bin/",
|
|
10
10
|
"templates/"
|
|
11
11
|
],
|
|
12
|
-
"description": "CLI for
|
|
12
|
+
"description": "CLI for Knolo V4 packs, V5 Knowledge Images, and KAR evidence-set retrieval.",
|
|
13
13
|
"license": "Apache-2.0",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "node -e \"console.log('cli is executable via bin/knolo.mjs')\"",
|
|
31
|
-
"test": "npm run build --prefix ../core && node --test test/cli.test.mjs test/hub.integration.test.mjs",
|
|
31
|
+
"test": "npm run build --prefix ../core && node --test test/cli.test.mjs test/kar.test.mjs test/kar-graph.test.mjs test/kar-produce.test.mjs test/hub.integration.test.mjs",
|
|
32
32
|
"test:hub": "node --test test/hub.integration.test.mjs"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@knolo/core": "^
|
|
35
|
+
"@knolo/core": "^6.0.0"
|
|
36
36
|
},
|
|
37
37
|
"private": false
|
|
38
38
|
}
|
|
@@ -245,11 +245,11 @@ dependencies = [
|
|
|
245
245
|
|
|
246
246
|
[[package]]
|
|
247
247
|
name = "knolo-core-rust"
|
|
248
|
-
version = "5.5.
|
|
248
|
+
version = "5.5.1"
|
|
249
249
|
|
|
250
250
|
[[package]]
|
|
251
251
|
name = "knolo-icp-canister"
|
|
252
|
-
version = "5.5.
|
|
252
|
+
version = "5.5.1"
|
|
253
253
|
dependencies = [
|
|
254
254
|
"candid",
|
|
255
255
|
"ic-cdk",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "knolo-icp-canister"
|
|
3
|
-
version = "5.5.
|
|
3
|
+
version = "5.5.1"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
description = "ICP canister adapter for Knolo retrieval over Candid"
|
|
@@ -16,5 +16,5 @@ path = "src/lib.rs"
|
|
|
16
16
|
candid = "0.10"
|
|
17
17
|
ic-cdk = "0.17"
|
|
18
18
|
ic-cdk-macros = "0.17"
|
|
19
|
-
knolo-core-rust = { path = "../knolo-core-rust", version = "5.5.
|
|
19
|
+
knolo-core-rust = { path = "../knolo-core-rust", version = "5.5.1" }
|
|
20
20
|
serde = { version = "1", features = ["derive"] }
|