@aikdna/kdna-cli 0.17.0 → 0.19.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.
@@ -1,9 +1,8 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
1
  const { CANONICAL_REGISTRY_URL, REGISTRY_CACHE, fetchRegistry } = require('../registry');
4
- const { error, readJson, loadRegistry, INSTALL_DIR, EXIT } = require('./_common');
2
+ const { error, loadRegistry, EXIT } = require('./_common');
3
+ const { listInstalled, readContainer } = require('../package-store');
5
4
 
6
- function cmdList(showAvailable, jsonMode = false, locale = null) {
5
+ function cmdList(showAvailable, jsonMode = false, _locale = null) {
7
6
  if (showAvailable) {
8
7
  const domains = loadRegistry({ allowNetwork: true });
9
8
  if (!domains || !domains.length) {
@@ -33,9 +32,7 @@ function cmdList(showAvailable, jsonMode = false, locale = null) {
33
32
  console.log('');
34
33
  for (const d of domains) {
35
34
  const name = d.name || d.id || '?';
36
- const [scope, ident] = name.includes('/') ? name.split('/') : [null, name];
37
- const installedPath = scope ? path.join(INSTALL_DIR, scope, ident) : null;
38
- const installed = installedPath && fs.existsSync(installedPath) ? '[installed]' : '';
35
+ const installed = listInstalled().some((entry) => entry.full === name) ? '[installed]' : '';
39
36
  const yanked = d.yanked ? '[yanked] ' : '';
40
37
  const dep = d.deprecated ? '[deprecated] ' : '';
41
38
  console.log(
@@ -47,78 +44,29 @@ function cmdList(showAvailable, jsonMode = false, locale = null) {
47
44
  return;
48
45
  }
49
46
 
50
- if (!fs.existsSync(INSTALL_DIR)) {
51
- if (jsonMode) {
52
- console.log(JSON.stringify([]));
53
- process.exit(EXIT.OK);
54
- }
55
- console.log('No domains installed.');
56
- console.log(`Installation directory: ${INSTALL_DIR}`);
57
- return;
58
- }
59
-
60
- // v0.7 layout: ~/.kdna/domains/@scope/name/
61
- const scopes = fs.readdirSync(INSTALL_DIR).filter((d) => {
62
- if (!d.startsWith('@')) return false;
63
- try {
64
- return fs.statSync(path.join(INSTALL_DIR, d)).isDirectory();
65
- } catch {
66
- return false;
67
- }
68
- });
69
-
70
- const installed = [];
71
- for (const scope of scopes) {
72
- const sd = path.join(INSTALL_DIR, scope);
73
- for (const ident of fs.readdirSync(sd)) {
74
- if (ident.startsWith('.')) continue;
75
- const full = path.join(sd, ident);
76
- try {
77
- if (!fs.statSync(full).isDirectory()) continue;
78
- } catch {
79
- continue;
80
- }
81
- installed.push({ scope, ident, full });
82
- }
83
- }
84
-
85
- // Detect and warn about legacy (un-scoped) installs
86
- if (!jsonMode) {
87
- const legacy = fs.readdirSync(INSTALL_DIR).filter((d) => {
88
- if (d.startsWith('@') || d.startsWith('.')) return false;
89
- try {
90
- return fs.statSync(path.join(INSTALL_DIR, d)).isDirectory();
91
- } catch {
92
- return false;
93
- }
94
- });
95
- if (legacy.length) {
96
- console.log('⚠ Legacy (un-scoped) directories detected — please remove + re-install:');
97
- legacy.forEach((d) => console.log(` ~/.kdna/domains/${d}/`));
98
- console.log('');
99
- }
100
- }
47
+ const installed = listInstalled();
101
48
 
102
49
  if (!installed.length) {
103
50
  if (jsonMode) {
104
51
  console.log(JSON.stringify([]));
105
52
  process.exit(EXIT.OK);
106
53
  }
107
- console.log('No v0.7 domains installed.');
54
+ console.log('No KDNA assets installed.');
108
55
  console.log(`Run: kdna install <name> # e.g. kdna install writing`);
109
56
  return;
110
57
  }
111
58
 
112
59
  // Build structured data for installed domains
113
- const domains = installed.map(({ scope, ident, full }) => {
114
- const core = readJson(path.join(full, 'KDNA_Core.json'));
115
- const manifest = readJson(path.join(full, 'kdna.json'));
116
- const cluster = readJson(path.join(full, 'cluster.json'));
60
+ const domains = installed.map((entry) => {
61
+ const { core = {}, manifest = {} } = readContainer(entry.asset_path);
117
62
  return {
118
- name: `${scope}/${ident}`,
119
- version: manifest?.version || manifest?._source?.version || core?.meta?.version || '?',
120
- type: cluster ? 'cluster' : 'domain',
63
+ name: entry.full,
64
+ version: manifest?.version || entry.version || core?.meta?.version || '?',
65
+ type: 'domain',
121
66
  description: manifest?.description || core?.meta?.purpose || '',
67
+ asset: entry.asset_path,
68
+ asset_digest: entry.asset_digest || null,
69
+ content_digest: entry.content_digest || null,
122
70
  };
123
71
  });
124
72
 
@@ -135,7 +83,7 @@ function cmdList(showAvailable, jsonMode = false, locale = null) {
135
83
  if (d.description) console.log(` ${d.description}`);
136
84
  }
137
85
  console.log('');
138
- console.log(`Location: ${INSTALL_DIR}`);
86
+ console.log('Assets are stored under ~/.kdna/packages/.');
139
87
  }
140
88
 
141
89
  function cmdRegistry(subcommand) {
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { error, readJson, writeJson, EXIT } = require('./_common');
13
+ const { error, readJson, writeJson, EXIT, isYesNoSelfCheck } = require('./_common');
14
14
 
15
15
  // ─── Scaffold ─────────────────────────────────────────────────────────
16
16
 
@@ -79,9 +79,16 @@ const CARD_TEMPLATES = {
79
79
  };
80
80
 
81
81
  function cmdStudioScaffold(name, args = []) {
82
- if (!name) error('Usage: kdna studio scaffold <name> [--type=domain|cluster] [--minimal]', EXIT.INPUT_ERROR);
82
+ if (!name)
83
+ error(
84
+ 'Usage: kdna studio scaffold <name> [--type=domain|cluster] [--minimal]',
85
+ EXIT.INPUT_ERROR,
86
+ );
83
87
  if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
84
- error(`Invalid name "${name}". Use lowercase letters, numbers, hyphens, underscores. Start with letter.`, EXIT.INPUT_ERROR);
88
+ error(
89
+ `Invalid name "${name}". Use lowercase letters, numbers, hyphens, underscores. Start with letter.`,
90
+ EXIT.INPUT_ERROR,
91
+ );
85
92
  }
86
93
 
87
94
  const type = args.includes('--type=cluster') ? 'cluster' : 'domain';
@@ -154,15 +161,22 @@ function cmdCardsValidate(projectPath, args = []) {
154
161
 
155
162
  if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
156
163
  const project = readJson(abs);
157
- if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
164
+ if (!project || !project.kdna_studio)
165
+ error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
158
166
 
159
167
  const errors = [];
160
168
  const warnings = [];
161
169
  const passed = [];
162
170
 
163
- function fail(msg) { errors.push(msg); }
164
- function warn(msg) { warnings.push(msg); }
165
- function ok(msg) { passed.push(msg); }
171
+ function fail(msg) {
172
+ errors.push(msg);
173
+ }
174
+ function warn(msg) {
175
+ warnings.push(msg);
176
+ }
177
+ function ok(msg) {
178
+ passed.push(msg);
179
+ }
166
180
 
167
181
  // Validate each card set
168
182
  for (const [cardType, cardFile] of Object.entries(project.cards || {})) {
@@ -192,7 +206,11 @@ function cmdCardsValidate(projectPath, args = []) {
192
206
  } else {
193
207
  ok(`axiom ${label}: applies_when has ${ax.applies_when.length} entries`);
194
208
  }
195
- if (!ax.does_not_apply_when || !Array.isArray(ax.does_not_apply_when) || ax.does_not_apply_when.length === 0) {
209
+ if (
210
+ !ax.does_not_apply_when ||
211
+ !Array.isArray(ax.does_not_apply_when) ||
212
+ ax.does_not_apply_when.length === 0
213
+ ) {
196
214
  fail(`axiom ${label}: missing does_not_apply_when`);
197
215
  } else {
198
216
  ok(`axiom ${label}: does_not_apply_when has ${ax.does_not_apply_when.length} entries`);
@@ -208,9 +226,11 @@ function cmdCardsValidate(projectPath, args = []) {
208
226
  case 'misunderstandings':
209
227
  for (const ms of cards) {
210
228
  const label = ms.id || '?';
211
- if (!ms.wrong || ms.wrong.includes('[TODO]')) warn(`misunderstanding ${label}: wrong is placeholder`);
229
+ if (!ms.wrong || ms.wrong.includes('[TODO]'))
230
+ warn(`misunderstanding ${label}: wrong is placeholder`);
212
231
  else ok(`misunderstanding ${label}: wrong OK`);
213
- if (!ms.correct || ms.correct.includes('[TODO]')) warn(`misunderstanding ${label}: correct is placeholder`);
232
+ if (!ms.correct || ms.correct.includes('[TODO]'))
233
+ warn(`misunderstanding ${label}: correct is placeholder`);
214
234
  else ok(`misunderstanding ${label}: correct OK`);
215
235
  if (!ms.key_distinction || ms.key_distinction.length < 15) {
216
236
  fail(`misunderstanding ${label}: key_distinction missing or too short`);
@@ -223,14 +243,18 @@ function cmdCardsValidate(projectPath, args = []) {
223
243
  case 'boundaries':
224
244
  for (const bd of cards) {
225
245
  const label = bd.id || '?';
226
- if (!bd.scope || bd.scope.includes('[TODO]')) warn(`boundary ${label}: scope is placeholder`);
246
+ if (!bd.scope || bd.scope.includes('[TODO]'))
247
+ warn(`boundary ${label}: scope is placeholder`);
227
248
  else ok(`boundary ${label}: scope OK`);
228
- if (!bd.out_of_scope || bd.out_of_scope.includes('[TODO]')) warn(`boundary ${label}: out_of_scope is placeholder`);
249
+ if (!bd.out_of_scope || bd.out_of_scope.includes('[TODO]'))
250
+ warn(`boundary ${label}: out_of_scope is placeholder`);
229
251
  else ok(`boundary ${label}: out_of_scope OK`);
230
252
  if (!bd.acceptable_exceptions || !Array.isArray(bd.acceptable_exceptions)) {
231
253
  warn(`boundary ${label}: acceptable_exceptions not declared`);
232
254
  } else {
233
- ok(`boundary ${label}: acceptable_exceptions has ${bd.acceptable_exceptions.length} entries`);
255
+ ok(
256
+ `boundary ${label}: acceptable_exceptions has ${bd.acceptable_exceptions.length} entries`,
257
+ );
234
258
  }
235
259
  }
236
260
  break;
@@ -240,8 +264,8 @@ function cmdCardsValidate(projectPath, args = []) {
240
264
  const label = sc.id || '?';
241
265
  if (!sc.question || sc.question.includes('[TODO]')) {
242
266
  warn(`self_check ${label}: question is placeholder`);
243
- } else if (!sc.question.trim().endsWith('?')) {
244
- fail(`self_check ${label}: question should end with "?"`);
267
+ } else if (!isYesNoSelfCheck(sc.question)) {
268
+ fail(`self_check ${label}: question should be answerable with yes/no`);
245
269
  } else {
246
270
  ok(`self_check ${label}: question OK`);
247
271
  }
@@ -251,11 +275,14 @@ function cmdCardsValidate(projectPath, args = []) {
251
275
  case 'ontology':
252
276
  for (const ont of cards) {
253
277
  const label = ont.id || '?';
254
- if (!ont.essence || ont.essence.includes('[TODO]')) warn(`ontology ${label}: essence is placeholder`);
278
+ if (!ont.essence || ont.essence.includes('[TODO]'))
279
+ warn(`ontology ${label}: essence is placeholder`);
255
280
  else ok(`ontology ${label}: essence OK`);
256
- if (!ont.boundary || ont.boundary.includes('[TODO]')) warn(`ontology ${label}: boundary is placeholder`);
281
+ if (!ont.boundary || ont.boundary.includes('[TODO]'))
282
+ warn(`ontology ${label}: boundary is placeholder`);
257
283
  else ok(`ontology ${label}: boundary OK`);
258
- if (!ont.trigger_signal || ont.trigger_signal.includes('[TODO]')) warn(`ontology ${label}: trigger_signal is placeholder`);
284
+ if (!ont.trigger_signal || ont.trigger_signal.includes('[TODO]'))
285
+ warn(`ontology ${label}: trigger_signal is placeholder`);
259
286
  else ok(`ontology ${label}: trigger_signal OK`);
260
287
  }
261
288
  break;
@@ -263,14 +290,20 @@ function cmdCardsValidate(projectPath, args = []) {
263
290
  }
264
291
 
265
292
  if (jsonMode) {
266
- console.log(JSON.stringify({
267
- project: path.basename(abs),
268
- valid: errors.length === 0,
269
- errors,
270
- warnings,
271
- passed: passed.length,
272
- total_checks: errors.length + warnings.length + passed.length,
273
- }, null, 2));
293
+ console.log(
294
+ JSON.stringify(
295
+ {
296
+ project: path.basename(abs),
297
+ valid: errors.length === 0,
298
+ errors,
299
+ warnings,
300
+ passed: passed.length,
301
+ total_checks: errors.length + warnings.length + passed.length,
302
+ },
303
+ null,
304
+ 2,
305
+ ),
306
+ );
274
307
  process.exit(errors.length ? EXIT.VALIDATION_FAILED : EXIT.OK);
275
308
  }
276
309
 
@@ -298,7 +331,8 @@ function cmdLockVerify(projectPath, args = []) {
298
331
 
299
332
  if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
300
333
  const project = readJson(abs);
301
- if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
334
+ if (!project || !project.kdna_studio)
335
+ error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
302
336
 
303
337
  const locked = [];
304
338
  const unlocked = [];
@@ -324,7 +358,10 @@ function cmdLockVerify(projectPath, args = []) {
324
358
  locked.push(label);
325
359
  } else {
326
360
  // Check Feynman restatement for axioms and misunderstandings
327
- if ((cardType === 'axioms' || cardType === 'misunderstandings') && !card.feynman_restatement) {
361
+ if (
362
+ (cardType === 'axioms' || cardType === 'misunderstandings') &&
363
+ !card.feynman_restatement
364
+ ) {
328
365
  unlocked.push(label);
329
366
  blocking.push(`${label} missing Feynman restatement`);
330
367
  } else {
@@ -341,15 +378,21 @@ function cmdLockVerify(projectPath, args = []) {
341
378
  const publishable = blocking.length === 0 && locked.length > 0;
342
379
 
343
380
  if (jsonMode) {
344
- console.log(JSON.stringify({
345
- project: path.basename(abs),
346
- locked_cards: locked.length,
347
- unlocked_cards: unlocked.length,
348
- publishable,
349
- blocking,
350
- locked: locked.sort(),
351
- unlocked: unlocked.sort(),
352
- }, null, 2));
381
+ console.log(
382
+ JSON.stringify(
383
+ {
384
+ project: path.basename(abs),
385
+ locked_cards: locked.length,
386
+ unlocked_cards: unlocked.length,
387
+ publishable,
388
+ blocking,
389
+ locked: locked.sort(),
390
+ unlocked: unlocked.sort(),
391
+ },
392
+ null,
393
+ 2,
394
+ ),
395
+ );
353
396
  process.exit(publishable ? EXIT.OK : EXIT.HUMAN_LOCK_REQUIRED);
354
397
  }
355
398
 
@@ -381,13 +424,15 @@ function cmdStudioCompile(projectPath, args = []) {
381
424
 
382
425
  if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
383
426
  const project = readJson(abs);
384
- if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
427
+ if (!project || !project.kdna_studio)
428
+ error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
385
429
 
386
430
  // Determine output directory
387
431
  const outIdx = args.indexOf('--out');
388
- const outDir = outIdx >= 0
389
- ? path.resolve(args[outIdx + 1])
390
- : path.join(path.dirname(abs), project.exports?.dir || 'exports');
432
+ const outDir =
433
+ outIdx >= 0
434
+ ? path.resolve(args[outIdx + 1])
435
+ : path.join(path.dirname(abs), project.exports?.dir || 'exports');
391
436
 
392
437
  fs.mkdirSync(outDir, { recursive: true });
393
438
 
@@ -398,7 +443,6 @@ function cmdStudioCompile(projectPath, args = []) {
398
443
  // Compile axioms → KDNA_Core.json
399
444
  const axioms = loadCards(project, path.dirname(abs), 'axioms');
400
445
  const ontology = loadCards(project, path.dirname(abs), 'ontology');
401
- const _boundaries = loadCards(project, path.dirname(abs), 'boundaries');
402
446
 
403
447
  const core = {
404
448
  meta: {
@@ -460,12 +504,17 @@ function cmdStudioCompile(projectPath, args = []) {
460
504
 
461
505
  // Compile manifest → kdna.json
462
506
  const manifest = {
463
- kdna_spec: '1.0-rc',
507
+ format: 'kdna',
508
+ format_version: '1.0',
509
+ spec_version: '1.0-rc',
464
510
  name: `@aikdna/${project.name}`,
465
511
  version: '0.1.0',
512
+ judgment_version: '0.1.0',
466
513
  status: 'experimental',
514
+ quality_badge: 'untested',
467
515
  access: 'open',
468
- language: 'en',
516
+ languages: ['en'],
517
+ default_language: 'en',
469
518
  author: { name: '', id: '' },
470
519
  license: { type: 'CC-BY-4.0' },
471
520
  description: '',
@@ -495,7 +544,7 @@ function cmdStudioCompile(projectPath, args = []) {
495
544
 
496
545
  console.log('');
497
546
  console.log('Next:');
498
- console.log(` kdna validate ${outDir}`);
547
+ console.log(` kdna dev validate ${outDir}`);
499
548
  }
500
549
 
501
550
  function loadCards(project, projectDir, cardType) {
@@ -520,7 +569,8 @@ function cmdStudioReadiness(projectPath, args = []) {
520
569
 
521
570
  if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
522
571
  const project = readJson(abs);
523
- if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
572
+ if (!project || !project.kdna_studio)
573
+ error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
524
574
 
525
575
  const readiness = {
526
576
  axioms: loadCardStats(project, path.dirname(abs), 'axioms'),
@@ -534,7 +584,9 @@ function cmdStudioReadiness(projectPath, args = []) {
534
584
  };
535
585
 
536
586
  // Determine publishability
537
- const allTypes = Object.values(readiness).filter((v) => v && typeof v === 'object' && 'total' in v);
587
+ const allTypes = Object.values(readiness).filter(
588
+ (v) => v && typeof v === 'object' && 'total' in v,
589
+ );
538
590
  let allLocked = allTypes.every((t) => t.total > 0 && t.total === t.locked);
539
591
  if (allTypes.length === 0) allLocked = false;
540
592
  readiness.publishable = allLocked;
package/src/cmds/test.js CHANGED
@@ -12,9 +12,9 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { error, readJson, writeJson, EXIT } = require('./_common');
14
14
  const { parseName } = require('../registry');
15
+ const { getInstalled } = require('../package-store');
15
16
 
16
17
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
17
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
18
18
  const RUNS_DIR = path.join(USER_KDNA_DIR, 'runs');
19
19
 
20
20
  function cmdTestRun(args = []) {
@@ -38,8 +38,8 @@ function cmdTestRun(args = []) {
38
38
 
39
39
  const parsed = parseName(domain);
40
40
  if (!parsed) error(`Invalid name "${domain}".`, EXIT.INPUT_ERROR);
41
- const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
42
- if (!fs.existsSync(destDir)) {
41
+ const installed = getInstalled(parsed.full);
42
+ if (!installed) {
43
43
  error(`${parsed.full} not installed. Run: kdna install ${domain}`, EXIT.INPUT_ERROR);
44
44
  }
45
45
 
@@ -64,7 +64,7 @@ function cmdTestRun(args = []) {
64
64
  const result = {
65
65
  test_id: testCase.id || `test_${Date.now()}`,
66
66
  domain: parsed.full,
67
- domain_path: destDir,
67
+ domain_asset: installed.asset_path,
68
68
  input: typeof testCase.input === 'string' ? testCase.input : JSON.stringify(testCase.input),
69
69
  run_at: new Date().toISOString(),
70
70
  expected: {
@@ -115,8 +115,11 @@ function cmdTestRun(args = []) {
115
115
  if (!jsonMode) {
116
116
  console.log(`Test run recorded: ${result.test_id}`);
117
117
  console.log(` Domain: ${result.domain}`);
118
- console.log(` Input: ${result.input.slice(0, 100)}${result.input.length > 100 ? '...' : ''}`);
119
- if (result.expected.classification) console.log(` Expected classification: ${result.expected.classification}`);
118
+ console.log(
119
+ ` Input: ${result.input.slice(0, 100)}${result.input.length > 100 ? '...' : ''}`,
120
+ );
121
+ if (result.expected.classification)
122
+ console.log(` Expected classification: ${result.expected.classification}`);
120
123
  }
121
124
  }
122
125
 
package/src/cmds/trace.js CHANGED
@@ -1,10 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const crypto = require('crypto');
4
- const { EXIT, error, readJson } = require('./_common');
3
+ const { EXIT } = require('./_common');
4
+ const PATHS = require('../paths');
5
5
 
6
- const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
7
- const TRACES_DIR = path.join(USER_KDNA_DIR, 'traces');
6
+ const TRACES_DIR = PATHS.traces;
8
7
 
9
8
  function ensureTracesDir() {
10
9
  fs.mkdirSync(TRACES_DIR, { recursive: true });
@@ -60,9 +59,14 @@ function readAllTraces(opts = {}) {
60
59
  }
61
60
 
62
61
  function recordTrace(entry) {
63
- ensureTracesDir();
64
- const line = JSON.stringify(entry) + '\n';
65
- fs.appendFileSync(todayFile(), line);
62
+ try {
63
+ ensureTracesDir();
64
+ const line = JSON.stringify(entry) + '\n';
65
+ fs.appendFileSync(todayFile(), line);
66
+ } catch {
67
+ // Traces are observability data. Loading and comparing KDNA assets must not
68
+ // fail just because the local trace directory is unavailable or read-only.
69
+ }
66
70
  }
67
71
 
68
72
  function parseSinceFlag(args) {
package/src/compare.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * kdna compare <name> --input "<text>" — Reasoning trajectory diff.
2
+ * kdna compare <name|file.kdna> --input "<text>" — Reasoning trajectory diff.
3
3
  *
4
4
  * Runs the same prompt twice on a real LLM:
5
5
  * 1. Without KDNA loaded (baseline)
@@ -24,7 +24,7 @@ const path = require('path');
24
24
  const https = require('https');
25
25
 
26
26
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
27
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
27
+ const { readContainer, resolveAsset } = require('./package-store');
28
28
  const CONFIG_FILE = path.join(USER_KDNA_DIR, 'config.json');
29
29
 
30
30
  const { parseName } = require('./registry');
@@ -179,10 +179,10 @@ async function callLlm(cfg, systemPrompt, userMessage) {
179
179
 
180
180
  // ─── KDNA → system prompt ─────────────────────────────────────────────
181
181
 
182
- function buildKdnaPrompt(destDir) {
183
- const core = readJson(path.join(destDir, 'KDNA_Core.json'));
184
- const pat = readJson(path.join(destDir, 'KDNA_Patterns.json'));
185
- const manifest = readJson(path.join(destDir, 'kdna.json'));
182
+ function buildKdnaPrompt(container) {
183
+ const core = container.core;
184
+ const pat = container.patterns;
185
+ const manifest = container.manifest;
186
186
 
187
187
  if (!core || !pat) return '';
188
188
 
@@ -302,7 +302,6 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
302
302
  const bannedTerms = (pat.terminology?.banned_terms || []).map((t) =>
303
303
  typeof t === 'string' ? t : t.term,
304
304
  );
305
- const misunderstandings = pat.misunderstandings || [];
306
305
 
307
306
  const lines = [];
308
307
  lines.push('# KDNA Judgment Comparison Report');
@@ -487,27 +486,30 @@ async function cmdCompare(input, args = []) {
487
486
  const idxInput = args.indexOf('--input');
488
487
  if (idxInput < 0 || !args[idxInput + 1]) {
489
488
  error(
490
- 'Usage: kdna compare <name> --input "<text>" [--report-md|--report-json] [--output <file>]',
489
+ 'Usage: kdna compare <name|file.kdna> --input "<text>" [--report-md|--report-json] [--output <file>]',
491
490
  EXIT.INPUT_ERROR,
492
491
  );
493
492
  }
494
493
  const userInput = args[idxInput + 1];
495
494
 
496
- const parsed = parseName(input);
497
- if (!parsed) error(`Invalid name "${input}".`, EXIT.INPUT_ERROR);
498
- const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
499
- if (!fs.existsSync(destDir)) {
500
- error(`${parsed.full} not installed. Run: kdna install ${input}`, EXIT.INPUT_ERROR);
501
- }
495
+ const asset = resolveAsset(input);
496
+ if (!asset)
497
+ error(
498
+ `KDNA asset not found: ${input}. Use an installed name or a .kdna file.`,
499
+ EXIT.INPUT_ERROR,
500
+ );
501
+ const parsed = asset.parsed || parseName(asset.name || '');
502
+ const label = parsed?.full || asset.name || input;
502
503
 
503
504
  const llm = loadLlmConfig();
504
- const manifest = readJson(path.join(destDir, 'kdna.json')) || {};
505
- const core = readJson(path.join(destDir, 'KDNA_Core.json')) || {};
506
- const pat = readJson(path.join(destDir, 'KDNA_Patterns.json')) || {};
505
+ const container = readContainer(asset.asset_path);
506
+ const manifest = container.manifest || {};
507
+ const core = container.core || {};
508
+ const pat = container.patterns || {};
507
509
 
508
510
  if (!jsonMode && !reportMd && !reportJson) {
509
511
  console.log('═'.repeat(64));
510
- console.log(` kdna compare ${parsed.full}`);
512
+ console.log(` kdna compare ${label}`);
511
513
  console.log(` provider: ${llm.provider} / ${llm.model}`);
512
514
  console.log(` input length: ${userInput.length} chars`);
513
515
  console.log('═'.repeat(64));
@@ -516,7 +518,7 @@ async function cmdCompare(input, args = []) {
516
518
 
517
519
  const BASELINE_SYSTEM =
518
520
  'You are a helpful assistant. Respond to the user request concisely and specifically.';
519
- const kdnaPrompt = buildKdnaPrompt(destDir);
521
+ const kdnaPrompt = buildKdnaPrompt(container);
520
522
  if (!kdnaPrompt) error('Could not build KDNA prompt — missing KDNA_Core or KDNA_Patterns.');
521
523
  const TREATMENT_SYSTEM =
522
524
  'You are a helpful assistant. The following domain judgment is loaded and you MUST apply it when relevant.\n\n' +
@@ -540,13 +542,30 @@ async function cmdCompare(input, args = []) {
540
542
  recordTrace({
541
543
  timestamp: new Date().toISOString(),
542
544
  agent: 'cli',
543
- domain: parsed.full,
545
+ domain: label,
544
546
  type: 'compare',
547
+ asset: {
548
+ asset_path: asset.asset_path,
549
+ asset_digest: asset.asset_digest || null,
550
+ content_digest: asset.content_digest || null,
551
+ version: manifest.version || asset.version || null,
552
+ judgment_version: manifest.judgment_version || asset.judgment_version || null,
553
+ access: manifest.access || asset.access || null,
554
+ },
545
555
  compare: { model: llm.model, input_length: userInput.length },
546
556
  });
547
557
 
548
558
  if (reportMd) {
549
- const report = emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, diff, llm);
559
+ const report = emitMarkdownReport(
560
+ parsed || { full: label },
561
+ manifest,
562
+ core,
563
+ pat,
564
+ responseA,
565
+ responseB,
566
+ diff,
567
+ llm,
568
+ );
550
569
  if (outputFile) {
551
570
  fs.writeFileSync(outputFile, report);
552
571
  console.log(`Report saved to ${outputFile}`);
@@ -558,7 +577,7 @@ async function cmdCompare(input, args = []) {
558
577
 
559
578
  if (reportJson) {
560
579
  const report = emitJsonReport(
561
- parsed,
580
+ parsed || { full: label },
562
581
  manifest,
563
582
  core,
564
583
  pat,