@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.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": "5.5.1",
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 building, inspecting, verifying, and operating Knolo V4 packs and V5 Knowledge Images.",
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": "^5.5.0"
35
+ "@knolo/core": "^6.0.0"
36
36
  },
37
37
  "private": false
38
38
  }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "knolo-core-rust"
3
- version = "5.5.0"
3
+ version = "5.5.1"
4
4
  edition = "2021"
5
5
  license = "Apache-2.0"
6
6
  description = "Rust runtime support for reading and querying .knolo packs"
@@ -245,11 +245,11 @@ dependencies = [
245
245
 
246
246
  [[package]]
247
247
  name = "knolo-core-rust"
248
- version = "5.5.0"
248
+ version = "5.5.1"
249
249
 
250
250
  [[package]]
251
251
  name = "knolo-icp-canister"
252
- version = "5.5.0"
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.0"
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.0" }
19
+ knolo-core-rust = { path = "../knolo-core-rust", version = "5.5.1" }
20
20
  serde = { version = "1", features = ["derive"] }