@aikdna/kdna-cli 0.9.0 → 0.12.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.
@@ -0,0 +1,471 @@
1
+ /**
2
+ * KDNA Governance commands — Phase 6: Human-Governed Self-Improvement.
3
+ *
4
+ * kdna proposal create --from-test <run.json> --domain <path>
5
+ * kdna proposal validate <proposal.json>
6
+ * kdna review accept <proposal.json> --by <identity> --reason "..."
7
+ * kdna review reject <proposal.json> --by <identity> --reason "..."
8
+ * kdna lock card <id> --by <identity> --reason "..."
9
+ * kdna evolution add-proposal <proposal.json>
10
+ * kdna evolution add-lock <lock.json>
11
+ * kdna evolution report <domain>
12
+ * kdna regression <old> <new> --evals <dir> [--json]
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { error, readJson, writeJson, EXIT } = require('./_common');
18
+
19
+ const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
20
+ const EVOLUTION_DIR = path.join(USER_KDNA_DIR, 'evolution');
21
+
22
+ // ─── Proposal ──────────────────────────────────────────────────────────
23
+
24
+ function cmdProposalCreate(args = []) {
25
+ const jsonMode = args.includes('--json');
26
+ const testIdx = args.indexOf('--from-test');
27
+ const testFile = testIdx >= 0 ? args[testIdx + 1] : null;
28
+ const domainIdx = args.indexOf('--domain');
29
+ const domainPath = domainIdx >= 0 ? args[domainIdx + 1] : '.';
30
+
31
+ if (!testFile) {
32
+ error('Usage: kdna proposal create --from-test <run-file> --domain <path>', EXIT.INPUT_ERROR);
33
+ }
34
+
35
+ const absTest = path.resolve(testFile);
36
+ if (!fs.existsSync(absTest)) error(`Test run file not found: ${absTest}`, EXIT.INPUT_ERROR);
37
+
38
+ const runData = readJson(absTest);
39
+ if (!runData || !runData.test_id) error(`Not a valid test run file: ${absTest}`, EXIT.INPUT_ERROR);
40
+
41
+ const absDomain = path.resolve(domainPath);
42
+
43
+ // Create proposal from test failure data
44
+ const proposal = {
45
+ proposal_id: `prop_${runData.test_id}_${Date.now()}`,
46
+ type: 'judgment_proposal',
47
+ source: 'test_lab',
48
+ source_run: path.basename(absTest),
49
+ domain: runData.domain || path.basename(absDomain),
50
+ domain_path: absDomain,
51
+ created: new Date().toISOString(),
52
+ author: null,
53
+ status: 'draft',
54
+ trigger: {
55
+ test_id: runData.test_id,
56
+ input: runData.input,
57
+ expected_classification: runData.expected?.classification || null,
58
+ actual_classification: runData.results?.classification || null,
59
+ },
60
+ suggested_changes: [],
61
+ reasoning: '',
62
+ review: null,
63
+ };
64
+
65
+ // Auto-detect suggested changes from test result gaps
66
+ if (runData.expected?.classification && runData.expected.classification !== runData.results?.classification) {
67
+ proposal.suggested_changes.push({
68
+ what: 'axiom',
69
+ field: 'applies_when',
70
+ reason: `Expected classification "${runData.expected.classification}" but got "${runData.results?.classification || 'none'}"`,
71
+ });
72
+ }
73
+ if (runData.results?.violations?.length) {
74
+ for (const v of runData.results.violations) {
75
+ proposal.suggested_changes.push({ what: 'boundary', field: 'violation', reason: v });
76
+ }
77
+ }
78
+
79
+ const outFile = path.join(absDomain, `${proposal.proposal_id}.json`);
80
+ writeJson(outFile, proposal);
81
+
82
+ if (jsonMode) {
83
+ console.log(JSON.stringify({ proposal_id: proposal.proposal_id, saved: outFile, suggested_changes: proposal.suggested_changes.length }, null, 2));
84
+ return;
85
+ }
86
+
87
+ console.log(`Proposal created: ${proposal.proposal_id}`);
88
+ console.log(` Source: ${runData.test_id}`);
89
+ console.log(` Domain: ${proposal.domain}`);
90
+ console.log(` Changes: ${proposal.suggested_changes.length} suggested`);
91
+ if (proposal.suggested_changes.length) {
92
+ for (const c of proposal.suggested_changes) {
93
+ console.log(` - ${c.what}.${c.field}: ${c.reason}`);
94
+ }
95
+ }
96
+ console.log(`\nNext: kdna proposal validate ${proposal.proposal_id}.json`);
97
+ }
98
+
99
+ function cmdProposalValidate(args = []) {
100
+ const jsonMode = args.includes('--json');
101
+ const target = args.filter((a) => !a.startsWith('--'))[2] || args.filter((a) => !a.startsWith('--'))[1];
102
+ if (!target) error('Usage: kdna proposal validate <proposal.json>', EXIT.INPUT_ERROR);
103
+
104
+ const abs = path.resolve(target);
105
+ if (!fs.existsSync(abs)) error(`Proposal not found: ${abs}`, EXIT.INPUT_ERROR);
106
+
107
+ const proposal = readJson(abs);
108
+ if (!proposal || !proposal.proposal_id) error(`Not a valid proposal: ${abs}`, EXIT.INPUT_ERROR);
109
+
110
+ const issues = [];
111
+
112
+ if (!proposal.type) issues.push('missing type');
113
+ if (!proposal.source) issues.push('missing source');
114
+ if (!proposal.domain) issues.push('missing domain');
115
+ if (!proposal.trigger?.test_id) issues.push('missing trigger.test_id');
116
+ if (!proposal.reasoning || proposal.reasoning.length < 10) issues.push('reasoning too short (min 10 chars)');
117
+ if (!proposal.suggested_changes || proposal.suggested_changes.length === 0) {
118
+ issues.push('no suggested changes');
119
+ } else {
120
+ for (const c of proposal.suggested_changes) {
121
+ if (!c.what) issues.push('change missing "what" field');
122
+ if (!c.reason) issues.push('change missing reason');
123
+ }
124
+ }
125
+
126
+ if (jsonMode) {
127
+ console.log(JSON.stringify({
128
+ proposal_id: proposal.proposal_id,
129
+ valid: issues.length === 0,
130
+ issues,
131
+ }, null, 2));
132
+ process.exit(issues.length ? EXIT.VALIDATION_FAILED : EXIT.OK);
133
+ }
134
+
135
+ if (issues.length) {
136
+ console.log('Issues:');
137
+ issues.forEach((i) => console.log(` ✗ ${i}`));
138
+ process.exit(EXIT.VALIDATION_FAILED);
139
+ }
140
+
141
+ console.log(`✓ Proposal valid: ${proposal.proposal_id}`);
142
+ }
143
+
144
+ // ─── Review ────────────────────────────────────────────────────────────
145
+
146
+ function cmdReview(args = []) {
147
+ const jsonMode = args.includes('--json');
148
+ const sub = args[1];
149
+ const target = args[2];
150
+ const byIdx = args.indexOf('--by');
151
+ const by = byIdx >= 0 ? args[byIdx + 1] : 'unknown';
152
+ const reasonIdx = args.indexOf('--reason');
153
+ const reason = reasonIdx >= 0 ? args[reasonIdx + 1] : '';
154
+
155
+ if (!sub || !['accept', 'reject'].includes(sub) || !target) {
156
+ error(
157
+ 'Usage:\n' +
158
+ ' kdna review accept <proposal.json> --by <name> --reason "..."\n' +
159
+ ' kdna review reject <proposal.json> --by <name> --reason "..."',
160
+ EXIT.INPUT_ERROR,
161
+ );
162
+ }
163
+
164
+ const abs = path.resolve(target);
165
+ if (!fs.existsSync(abs)) error(`Proposal not found: ${abs}`, EXIT.INPUT_ERROR);
166
+
167
+ const proposal = readJson(abs);
168
+ if (!proposal || !proposal.proposal_id) error(`Not a valid proposal: ${abs}`, EXIT.INPUT_ERROR);
169
+
170
+ proposal.review = {
171
+ decision: sub,
172
+ by,
173
+ reason,
174
+ at: new Date().toISOString(),
175
+ };
176
+
177
+ // On accept, move the accepted changes into the proposal
178
+ if (sub === 'accept') {
179
+ proposal.accepted = true;
180
+ proposal.accepted_at = new Date().toISOString();
181
+ proposal.accepted_by = by;
182
+ }
183
+
184
+ writeJson(abs, proposal);
185
+
186
+ if (jsonMode) {
187
+ console.log(JSON.stringify({
188
+ proposal_id: proposal.proposal_id,
189
+ decision: sub,
190
+ by,
191
+ reason,
192
+ }, null, 2));
193
+ process.exit(sub === 'reject' ? EXIT.POLICY_VIOLATION : EXIT.OK);
194
+ }
195
+
196
+ const mark = sub === 'accept' ? '✓' : '✗';
197
+ console.log(`${mark} Review ${sub}ed: ${proposal.proposal_id}`);
198
+ console.log(` By: ${by}`);
199
+ console.log(` Reason: ${reason}`);
200
+ }
201
+
202
+ // ─── Lock ──────────────────────────────────────────────────────────────
203
+
204
+ function cmdLockCard(args = []) {
205
+ const jsonMode = args.includes('--json');
206
+ const sub = args[1];
207
+ const cardId = args[2];
208
+ const byIdx = args.indexOf('--by');
209
+ const by = byIdx >= 0 ? args[byIdx + 1] : 'unknown';
210
+ const reasonIdx = args.indexOf('--reason');
211
+ const reason = reasonIdx >= 0 ? args[reasonIdx + 1] : '';
212
+
213
+ if (sub !== 'card' || !cardId) {
214
+ error('Usage: kdna lock card <card-id> --by <name> --reason "..."', EXIT.INPUT_ERROR);
215
+ }
216
+
217
+ // Lock card in the current studio project (finds studio.project.json)
218
+ const projectPath = path.resolve('studio.project.json');
219
+ if (!fs.existsSync(projectPath)) {
220
+ error('No studio.project.json found in current directory. Run: kdna studio scaffold', EXIT.INPUT_ERROR);
221
+ }
222
+
223
+ const project = readJson(projectPath);
224
+ const [cardType, id] = cardId.includes('.') ? cardId.split('.') : [null, cardId];
225
+
226
+ let found = false;
227
+ for (const [type, cardFile] of Object.entries(project.cards || {})) {
228
+ if (cardType && type !== cardType) continue;
229
+
230
+ const cardPath = path.resolve(projectPath, '..', cardFile);
231
+ if (!fs.existsSync(cardPath)) continue;
232
+
233
+ const cards = readJson(cardPath) || [];
234
+ const card = cards.find((c) => c.id === id);
235
+ if (!card) continue;
236
+
237
+ card.locked = true;
238
+ card.human_lock = { by, reason, at: new Date().toISOString() };
239
+ writeJson(cardPath, cards);
240
+ found = true;
241
+
242
+ if (jsonMode) {
243
+ console.log(JSON.stringify({
244
+ card: `${type}.${id}`,
245
+ locked: true,
246
+ lock: card.human_lock,
247
+ }, null, 2));
248
+ } else {
249
+ console.log(`✓ Locked: ${type}.${id}`);
250
+ console.log(` By: ${by}`);
251
+ console.log(` Reason: ${reason}`);
252
+ }
253
+ break;
254
+ }
255
+
256
+ if (!found) {
257
+ error(`Card not found: ${cardId}. Check the card ID and that a studio project exists.`, EXIT.INPUT_ERROR);
258
+ }
259
+ }
260
+
261
+ // ─── Evolution ─────────────────────────────────────────────────────────
262
+
263
+ function cmdEvolution(args = []) {
264
+ const sub = args[1];
265
+ const target = args[2];
266
+ const jsonMode = args.includes('--json');
267
+
268
+ if (!sub || !['add-proposal', 'add-lock', 'report'].includes(sub)) {
269
+ error(
270
+ 'Usage:\n' +
271
+ ' kdna evolution add-proposal <proposal.json>\n' +
272
+ ' kdna evolution add-lock <lock-record.json>\n' +
273
+ ' kdna evolution report <domain-path>',
274
+ EXIT.INPUT_ERROR,
275
+ );
276
+ }
277
+
278
+ if (sub === 'add-proposal') {
279
+ if (!target) error('Usage: kdna evolution add-proposal <proposal.json>', EXIT.INPUT_ERROR);
280
+ addEvolutionRecord('proposal', path.resolve(target));
281
+ return;
282
+ }
283
+
284
+ if (sub === 'add-lock') {
285
+ if (!target) error('Usage: kdna evolution add-lock <lock-record.json>', EXIT.INPUT_ERROR);
286
+ addEvolutionRecord('lock', path.resolve(target));
287
+ return;
288
+ }
289
+
290
+ if (sub === 'report') {
291
+ const domainPath = path.resolve(target || '.');
292
+ cmdEvolutionReport(domainPath, jsonMode);
293
+ }
294
+ }
295
+
296
+ function addEvolutionRecord(type, sourcePath) {
297
+ if (!fs.existsSync(sourcePath)) error(`${type} file not found: ${sourcePath}`, EXIT.INPUT_ERROR);
298
+
299
+ fs.mkdirSync(EVOLUTION_DIR, { recursive: true });
300
+ const destFile = path.join(EVOLUTION_DIR, `${type}_${Date.now()}.json`);
301
+ fs.copyFileSync(sourcePath, destFile);
302
+
303
+ console.log(`✓ Evolution record added: ${path.basename(destFile)}`);
304
+ console.log(` Type: ${type}`);
305
+ console.log(` Source: ${path.basename(sourcePath)}`);
306
+ }
307
+
308
+ function cmdEvolutionReport(domainPath, jsonMode) {
309
+ const abs = path.resolve(domainPath);
310
+ const evoFile = path.join(abs, 'KDNA_Evolution.json');
311
+
312
+ let evolution = readJson(evoFile);
313
+ if (!evolution) {
314
+ evolution = { stages: [], pending: [] };
315
+ console.log('No KDNA_Evolution.json found. Creating empty record.');
316
+ writeJson(evoFile, evolution);
317
+ }
318
+
319
+ const stages = evolution.stages || [];
320
+ const pending = evolution.pending || [];
321
+
322
+ if (jsonMode) {
323
+ console.log(JSON.stringify({
324
+ domain: path.basename(abs),
325
+ total_stages: stages.length,
326
+ pending_changes: pending.length,
327
+ stages: stages.map((s) => ({
328
+ stage: s.stage,
329
+ version: s.version,
330
+ date: s.date,
331
+ changes: s.changes?.length || 0,
332
+ })),
333
+ }, null, 2));
334
+ return;
335
+ }
336
+
337
+ console.log(`Evolution report: ${path.basename(abs)}`);
338
+ console.log(` Stages: ${stages.length} | Pending: ${pending.length}`);
339
+ console.log('');
340
+
341
+ if (stages.length) {
342
+ for (const s of stages) {
343
+ console.log(` ${s.stage || '?'} — v${s.version || '?'} (${s.date || '?'})`);
344
+ if (s.changes) {
345
+ s.changes.forEach((c) => console.log(` - ${c}`));
346
+ }
347
+ }
348
+ } else {
349
+ console.log(' No evolution stages recorded yet.');
350
+ console.log(' Use: kdna evolution add-proposal <proposal.json>');
351
+ }
352
+ }
353
+
354
+ // ─── Regression ────────────────────────────────────────────────────────
355
+
356
+ function cmdRegression(args = []) {
357
+ const jsonMode = args.includes('--json');
358
+ const positional = args.filter((a) => !a.startsWith('--'));
359
+ const oldPath = positional[1];
360
+ const newPath = positional[2];
361
+ const evalsIdx = args.indexOf('--evals');
362
+ const evalsDir = evalsIdx >= 0 ? args[evalsIdx + 1] : null;
363
+
364
+ if (!oldPath || !newPath) {
365
+ error('Usage: kdna regression <old-domain> <new-domain> --evals <dir> [--json]', EXIT.INPUT_ERROR);
366
+ }
367
+
368
+ const absOld = path.resolve(oldPath);
369
+ const absNew = path.resolve(newPath);
370
+
371
+ if (!fs.existsSync(absOld)) error(`Old domain not found: ${absOld}`, EXIT.INPUT_ERROR);
372
+ if (!fs.existsSync(absNew)) error(`New domain not found: ${absNew}`, EXIT.INPUT_ERROR);
373
+
374
+ const oldCore = readJson(path.join(absOld, 'KDNA_Core.json'));
375
+ const newCore = readJson(path.join(absNew, 'KDNA_Core.json'));
376
+ const oldPat = readJson(path.join(absOld, 'KDNA_Patterns.json'));
377
+ const newPat = readJson(path.join(absNew, 'KDNA_Patterns.json'));
378
+
379
+ // Compare judgment surface
380
+ const oldAxiomCount = (oldCore?.axioms || []).length;
381
+ const newAxiomCount = (newCore?.axioms || []).length;
382
+ const oldMisCount = (oldPat?.misunderstandings || []).length;
383
+ const newMisCount = (newPat?.misunderstandings || []).length;
384
+ const oldSelfCheckCount = (oldPat?.self_check || []).length;
385
+ const newSelfCheckCount = (newPat?.self_check || []).length;
386
+
387
+ // v2.1 governance coverage comparison
388
+ function governanceCoverage(core) {
389
+ const axioms = core?.axioms || [];
390
+ if (!axioms.length) return 0;
391
+ const governed = axioms.filter(
392
+ (a) => a.applies_when?.length && a.does_not_apply_when?.length && a.failure_risk,
393
+ ).length;
394
+ return Math.round((governed / axioms.length) * 100);
395
+ }
396
+
397
+ const oldGov = governanceCoverage(oldCore);
398
+ const newGov = governanceCoverage(newCore);
399
+
400
+ // Eval file comparison
401
+ let passedEvals = 0;
402
+ let failedEvals = 0;
403
+ let totalEvals = 0;
404
+
405
+ if (evalsDir && fs.existsSync(evalsDir)) {
406
+ const evalFiles = fs.readdirSync(evalsDir).filter((f) => f.endsWith('.json'));
407
+ totalEvals = evalFiles.length;
408
+ for (const f of evalFiles) {
409
+ const evalData = readJson(path.join(evalsDir, f));
410
+ if (evalData?.cases) {
411
+ for (const c of evalData.cases) {
412
+ if (c.pass === true) passedEvals++;
413
+ else if (c.pass === false) failedEvals++;
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ const degraded = newGov < oldGov || newAxiomCount < oldAxiomCount || newMisCount < oldMisCount;
420
+ const improved = newGov > oldGov || newAxiomCount > oldAxiomCount;
421
+
422
+ const result = {
423
+ domain: path.basename(absOld),
424
+ old: { axioms: oldAxiomCount, misunderstandings: oldMisCount, self_checks: oldSelfCheckCount, governance_coverage: oldGov },
425
+ new: { axioms: newAxiomCount, misunderstandings: newMisCount, self_checks: newSelfCheckCount, governance_coverage: newGov },
426
+ delta: {
427
+ axioms: newAxiomCount - oldAxiomCount,
428
+ misunderstandings: newMisCount - oldMisCount,
429
+ self_checks: newSelfCheckCount - oldSelfCheckCount,
430
+ governance_coverage: newGov - oldGov,
431
+ },
432
+ evals: { total: totalEvals, passed: passedEvals, failed: failedEvals },
433
+ degraded,
434
+ improved,
435
+ safe: !degraded,
436
+ };
437
+
438
+ if (jsonMode) {
439
+ console.log(JSON.stringify(result, null, 2));
440
+ process.exit(result.safe ? EXIT.OK : EXIT.POLICY_VIOLATION);
441
+ }
442
+
443
+ console.log(`Regression check: ${path.basename(absOld)} → ${path.basename(absNew)}`);
444
+ console.log('');
445
+ console.log(` Axioms: ${oldAxiomCount} → ${newAxiomCount} (${result.delta.axioms >= 0 ? '+' : ''}${result.delta.axioms})`);
446
+ console.log(` Misunderstandings: ${oldMisCount} → ${newMisCount} (${result.delta.misunderstandings >= 0 ? '+' : ''}${result.delta.misunderstandings})`);
447
+ console.log(` Self-checks: ${oldSelfCheckCount} → ${newSelfCheckCount} (${result.delta.self_checks >= 0 ? '+' : ''}${result.delta.self_checks})`);
448
+ console.log(` Governance coverage: ${oldGov}% → ${newGov}% (${result.delta.governance_coverage >= 0 ? '+' : ''}${result.delta.governance_coverage}%)`);
449
+ if (totalEvals) {
450
+ console.log(` Evals: ${passedEvals} passed, ${failedEvals} failed out of ${totalEvals}`);
451
+ }
452
+ console.log('');
453
+ const mark = result.safe ? '✓' : '✗';
454
+ if (result.degraded) {
455
+ console.log(`${mark} REGRESSION DETECTED — judgment surface has degraded`);
456
+ process.exit(EXIT.POLICY_VIOLATION);
457
+ } else if (result.improved) {
458
+ console.log(`${mark} No regression — judgment surface has improved`);
459
+ } else {
460
+ console.log(`${mark} No regression — judgment surface unchanged`);
461
+ }
462
+ }
463
+
464
+ module.exports = {
465
+ cmdProposalCreate,
466
+ cmdProposalValidate,
467
+ cmdReview,
468
+ cmdLockCard,
469
+ cmdEvolution,
470
+ cmdRegression,
471
+ };
@@ -1,4 +1,4 @@
1
- const { error } = require('./_common');
1
+ const { error, EXIT } = require('./_common');
2
2
 
3
3
  function cmdIdentity(args) {
4
4
  const {
@@ -11,17 +11,18 @@ function cmdIdentity(args) {
11
11
  if (sub === 'init') {
12
12
  cmdIdentityInit();
13
13
  } else if (sub === 'show') {
14
- cmdIdentityShow();
14
+ cmdIdentityShow(args.includes('--json'));
15
15
  } else if (sub === 'export') {
16
16
  const outIdx = args.indexOf('--out');
17
17
  cmdIdentityExport(outIdx >= 0 ? args[outIdx + 1] : null);
18
18
  } else if (sub === 'import') {
19
19
  const target = args[2];
20
- if (!target) error('Usage: kdna identity import <file>');
20
+ if (!target) error('Usage: kdna identity import <file>', EXIT.INPUT_ERROR);
21
21
  cmdIdentityImport(target);
22
22
  } else {
23
23
  error(
24
- `Usage: kdna identity init\n kdna identity show\n kdna identity export [--out <file>]\n kdna identity import <file>`,
24
+ `Usage: kdna identity init\n kdna identity show [--json]\n kdna identity export [--out <file>]\n kdna identity import <file>`,
25
+ EXIT.INPUT_ERROR,
25
26
  );
26
27
  }
27
28
  }
@@ -1,30 +1,37 @@
1
- const { error } = require('./_common');
1
+ const { error, EXIT } = require('./_common');
2
2
 
3
3
  function cmdCompare(args) {
4
4
  const { cmdCompare } = require('../compare');
5
+ const jsonMode = args.includes('--json');
5
6
  const target = args.filter((a) => !a.startsWith('--'))[1];
6
7
  if (!target || !args.includes('--input')) {
7
8
  error(
8
9
  'Usage:\n' +
9
- ' kdna compare <name> --input "<text>"\n' +
10
+ ' kdna compare <name> --input "<text>" [--json]\n' +
10
11
  '\n' +
11
12
  'Runs your input through the LLM twice (with/without KDNA loaded),\n' +
12
13
  'then diffs the reasoning trajectory. Requires ANTHROPIC_API_KEY or\n' +
13
14
  'OPENAI_API_KEY in the environment.',
15
+ EXIT.INPUT_ERROR,
14
16
  );
15
17
  }
16
18
  (async () => {
17
19
  try {
18
20
  await cmdCompare(target, args);
19
21
  } catch (e) {
22
+ if (jsonMode) {
23
+ console.log(JSON.stringify({ error: e.message }));
24
+ process.exit(EXIT.PROVIDER_ERROR);
25
+ }
20
26
  console.error(`Error: ${e.message}`);
21
- process.exit(1);
27
+ process.exit(EXIT.VALIDATION_FAILED);
22
28
  }
23
29
  })();
24
30
  }
25
31
 
26
32
  function cmdDiff(args) {
27
33
  const { cmdDiff } = require('../diff');
34
+ const jsonMode = args.includes('--json');
28
35
  const positional = args.filter((a) => !a.startsWith('--'));
29
36
  const a = positional[1];
30
37
  const b = positional[2];
@@ -32,26 +39,32 @@ function cmdDiff(args) {
32
39
  error(
33
40
  'Usage:\n' +
34
41
  ' kdna diff <name>@<v1> <name>@<v2> Compare two versions\n' +
35
- ' kdna diff <name> Installed vs registry-current\n' +
42
+ ' kdna diff <name> [--json] Installed vs registry-current\n' +
36
43
  '\n' +
37
44
  'Surfaces judgment-level diff: added/removed/changed axioms,\n' +
38
45
  'misunderstandings, banned terms, stances.',
46
+ EXIT.INPUT_ERROR,
39
47
  );
40
48
  }
41
49
  (async () => {
42
50
  try {
43
- await cmdDiff(a, b);
51
+ await cmdDiff(a, b, args);
44
52
  } catch (e) {
53
+ if (jsonMode) {
54
+ console.log(JSON.stringify({ error: e.message }));
55
+ process.exit(EXIT.VALIDATION_FAILED);
56
+ }
45
57
  console.error(`Error: ${e.message}`);
46
- process.exit(1);
58
+ process.exit(EXIT.VALIDATION_FAILED);
47
59
  }
48
60
  })();
49
61
  }
50
62
 
51
63
  function cmdSearch(args) {
52
64
  const { cmdSearch } = require('../search');
53
- const query = args.slice(1).join(' ').trim();
54
- cmdSearch(query);
65
+ const json = args.includes('--json');
66
+ const query = args.slice(1).filter((a) => a !== '--json').join(' ').trim();
67
+ cmdSearch(query, json);
55
68
  }
56
69
 
57
70
  function cmdAvailable(args) {
@@ -70,18 +83,30 @@ function cmdMatch(args) {
70
83
  cmdMatch(positional.join(' ').trim(), flags);
71
84
  }
72
85
 
86
+ function cmdSelect(args) {
87
+ const { cmdSelect } = require('../agent');
88
+ cmdSelect(args);
89
+ }
90
+
73
91
  function cmdLoad(args) {
74
92
  const { cmdLoad } = require('../agent');
75
93
  const target = args.filter((a) => !a.startsWith('--'))[1];
76
- if (!target) error('Usage: kdna load <name> [--as=prompt|json|raw]');
94
+ if (!target) error('Usage: kdna load <name> [--as=prompt|json|raw] [--profile=index|compact|scenario|full]');
77
95
  cmdLoad(target, args);
78
96
  }
79
97
 
98
+ function cmdPostvalidate(args) {
99
+ const { cmdPostvalidate } = require('../agent');
100
+ cmdPostvalidate(args);
101
+ }
102
+
80
103
  module.exports = {
81
104
  cmdCompare,
82
105
  cmdDiff,
83
106
  cmdSearch,
84
107
  cmdAvailable,
85
108
  cmdMatch,
109
+ cmdSelect,
86
110
  cmdLoad,
111
+ cmdPostvalidate,
87
112
  };