@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.
package/src/agent.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * not treat as a fit decision; many false positives expected)
17
17
  * The agent makes the final call using its own language understanding.
18
18
  *
19
- * kdna load <name> [--as=prompt|json|raw]
19
+ * kdna load <name|file.kdna> [--as=prompt|json|raw]
20
20
  * Read the domain's Core + Patterns and emit:
21
21
  * --as=prompt (default): compact text suitable for system-prompt
22
22
  * injection (axioms one-liners + stances +
@@ -30,44 +30,44 @@
30
30
  */
31
31
 
32
32
  const fs = require('fs');
33
- const path = require('path');
34
33
  const { parseName } = require('./registry');
35
34
  const { recordTrace } = require('./cmds/trace');
35
+ const {
36
+ getInstalled,
37
+ listInstalled: listInstalledAssets,
38
+ readContainer,
39
+ readContainerEntry,
40
+ readContainerJson,
41
+ resolveAsset,
42
+ } = require('./package-store');
43
+ const { licenseDecryptOptionsForManifest } = require('./cmds/license');
36
44
 
37
45
  function detectAgent() {
38
46
  return process.env.KDNA_AGENT || 'cli';
39
47
  }
40
48
 
41
- const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
42
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
49
+ function listInstalled() {
50
+ return listInstalledAssets().map((entry) => {
51
+ const parsed = parseName(entry.full);
52
+ return { ...entry, scope: parsed.scope, ident: parsed.ident };
53
+ });
54
+ }
43
55
 
44
- function readJson(p) {
45
- try {
46
- return JSON.parse(fs.readFileSync(p, 'utf8'));
47
- } catch {
48
- return null;
49
- }
56
+ function assetLabel(asset, fallback) {
57
+ return asset.name || asset.parsed?.full || fallback;
50
58
  }
51
59
 
52
- function listInstalled() {
53
- if (!fs.existsSync(INSTALL_DIR)) return [];
54
- const out = [];
55
- for (const scopeName of fs.readdirSync(INSTALL_DIR)) {
56
- if (!scopeName.startsWith('@')) continue;
57
- const scopeDir = path.join(INSTALL_DIR, scopeName);
58
- try {
59
- if (!fs.statSync(scopeDir).isDirectory()) continue;
60
- for (const ident of fs.readdirSync(scopeDir)) {
61
- if (ident.startsWith('.')) continue;
62
- const dir = path.join(scopeDir, ident);
63
- if (!fs.statSync(dir).isDirectory()) continue;
64
- out.push({ scope: scopeName, ident, dir, full: `${scopeName}/${ident}` });
65
- }
66
- } catch {
67
- /* skip */
68
- }
69
- }
70
- return out;
60
+ function traceAssetFields(asset, manifest = {}, license = null) {
61
+ const fields = {
62
+ asset_path: asset.asset_path,
63
+ asset_digest: asset.asset_digest || null,
64
+ content_digest: asset.content_digest || null,
65
+ version: manifest.version || asset.version || null,
66
+ judgment_version: manifest.judgment_version || asset.judgment_version || null,
67
+ access: manifest.access || asset.access || null,
68
+ };
69
+ if (license?.license_id) fields.license_id = license.license_id;
70
+ return fields;
71
71
  }
72
72
 
73
73
  // ─── kdna available ────────────────────────────────────────────────────
@@ -78,11 +78,9 @@ function cmdAvailable(args = []) {
78
78
 
79
79
  const out = [];
80
80
  for (const e of installed) {
81
- const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
81
+ const { manifest = {}, core = {} } = readContainer(e.asset_path);
82
82
  if (manifest.yanked === true) continue;
83
83
 
84
- const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
85
-
86
84
  // Pull applies_when across all axioms (this is what the agent needs
87
85
  // for fit-check). Collapsing per-axiom into one set makes the agent's
88
86
  // matching decision much cheaper.
@@ -110,14 +108,7 @@ function cmdAvailable(args = []) {
110
108
  }
111
109
 
112
110
  if (wantJson) {
113
- const result = out.length
114
- ? out
115
- : {
116
- count: 0,
117
- domains: [],
118
- note: 'No domains installed. Run: kdna install <name> See: kdna list --available',
119
- };
120
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
111
+ process.stdout.write(JSON.stringify(out, null, 2) + '\n');
121
112
  return;
122
113
  }
123
114
 
@@ -195,12 +186,11 @@ function cmdMatch(taskText, args = []) {
195
186
  const hints = [];
196
187
 
197
188
  for (const e of installed) {
198
- const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
189
+ const { manifest = {}, core = {} } = readContainer(e.asset_path);
199
190
  if (manifest.yanked === true) {
200
191
  dropped.push({ name: manifest.name || e.full, reason: 'yanked' });
201
192
  continue;
202
193
  }
203
- const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
204
194
 
205
195
  // does_not_apply_when disqualification (HARD signal)
206
196
  let disqualified = null;
@@ -318,7 +308,9 @@ function cmdMatch(taskText, args = []) {
318
308
  }
319
309
  }
320
310
  console.log('');
321
- console.log('To consider any of these, read its full data: kdna load <name> --as=json');
311
+ console.log(
312
+ 'To consider any of these, read its full data: kdna load <name|file.kdna> --as=json',
313
+ );
322
314
  }
323
315
  }
324
316
 
@@ -348,20 +340,38 @@ function cmdLoad(input, args = []) {
348
340
  profileInput = args[inputIdx + 1] || null;
349
341
  }
350
342
 
351
- const parsed = parseName(input);
352
- if (!parsed) {
353
- console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
343
+ const asset = resolveAsset(input);
344
+ if (!asset) {
345
+ console.error(`KDNA asset not found: ${input}. Use an installed name or a .kdna file.`);
354
346
  process.exit(2);
355
347
  }
356
- const dir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
357
- if (!fs.existsSync(dir)) {
358
- console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
359
- process.exit(2);
348
+
349
+ const manifest = readContainerJson(asset.asset_path, 'kdna.json') || {};
350
+ const encryptedEntries = manifest.encryption?.encrypted_entries || [];
351
+ let decryptOptions = {};
352
+ let licenseActivation = null;
353
+ if (manifest.access === 'licensed' || encryptedEntries.length > 0) {
354
+ const activation = licenseDecryptOptionsForManifest(manifest);
355
+ if (!activation.ok) {
356
+ console.error(`KDNA license required for ${manifest.name || input}: ${activation.error}`);
357
+ console.error(`Install a license with: kdna license install <license.json>`);
358
+ process.exit(3);
359
+ }
360
+ decryptOptions = { decryptEntry: activation.decryptEntry };
361
+ licenseActivation = activation.license;
360
362
  }
361
363
 
362
- const manifest = readJson(path.join(dir, 'kdna.json')) || {};
364
+ let container;
365
+ try {
366
+ container = readContainer(asset.asset_path, decryptOptions);
367
+ } catch (e) {
368
+ console.error(`Failed to load KDNA asset: ${e.message}`);
369
+ process.exit(3);
370
+ }
371
+ const parsed = asset.parsed || parseName(manifest.name || '');
372
+ const label = assetLabel(asset, input);
363
373
  if (manifest.yanked === true) {
364
- console.error(`${parsed.full}@${manifest.version} has been yanked.`);
374
+ console.error(`${label}@${manifest.version} has been yanked.`);
365
375
  if (manifest.replaced_by) console.error(`Try: ${manifest.replaced_by}`);
366
376
  process.exit(2);
367
377
  }
@@ -371,43 +381,61 @@ function cmdLoad(input, args = []) {
371
381
  const signature = manifest.signature;
372
382
  const isPlaceholder = !signature || signature === '' || signature.includes('placeholder');
373
383
  if (isPlaceholder) {
374
- loadWarnings.push('⚠ Domain is unsigned — no cryptographic proof of authorship. Trust depends on source.');
384
+ loadWarnings.push(
385
+ '⚠ Domain is unsigned — no cryptographic proof of authorship. Trust depends on source.',
386
+ );
375
387
  }
376
388
  if (manifest.status === 'deprecated') {
377
- loadWarnings.push(`⚠ Domain is deprecated${manifest.replaced_by ? ', replaced by ' + manifest.replaced_by : ''}.`);
389
+ loadWarnings.push(
390
+ `⚠ Domain is deprecated${manifest.replaced_by ? ', replaced by ' + manifest.replaced_by : ''}.`,
391
+ );
378
392
  }
379
393
  const riskLevel = manifest.risk_level || 'R1';
380
394
  if (riskLevel === 'R3' || riskLevel === 'R4') {
381
- loadWarnings.push(`⚠ High risk domain (${riskLevel}) — may influence agent behavior in safety-critical ways.`);
395
+ loadWarnings.push(
396
+ `⚠ High risk domain (${riskLevel}) — may influence agent behavior in safety-critical ways.`,
397
+ );
382
398
  if (manifest.quality_badge === 'untested' || !manifest.quality_badge) {
383
- loadWarnings.push('⚠ High risk + untested — load only if you trust the source and understand the risks.');
399
+ loadWarnings.push(
400
+ '⚠ High risk + untested — load only if you trust the source and understand the risks.',
401
+ );
384
402
  }
385
403
  }
386
404
  if (loadWarnings.length > 0) {
387
405
  console.error(loadWarnings.join('\n'));
388
406
  }
389
- const core = readJson(path.join(dir, 'KDNA_Core.json')) || {};
390
- const pat = readJson(path.join(dir, 'KDNA_Patterns.json')) || {};
407
+ const core = container.core || {};
408
+ const pat = container.patterns || {};
391
409
 
392
410
  // JSON format
393
411
  if (format === 'json') {
394
- process.stdout.write(JSON.stringify({
395
- manifest,
396
- core,
397
- patterns: pat,
398
- trust: {
399
- signature: isPlaceholder ? 'unsigned' : 'present',
400
- risk_level: riskLevel,
401
- deprecated: manifest.status === 'deprecated',
402
- yanked: false,
403
- warnings: loadWarnings,
404
- },
405
- }, null, 2) + '\n');
412
+ process.stdout.write(
413
+ JSON.stringify(
414
+ {
415
+ manifest,
416
+ core,
417
+ patterns: pat,
418
+ trust: {
419
+ signature: isPlaceholder ? 'unsigned' : 'present',
420
+ risk_level: riskLevel,
421
+ deprecated: manifest.status === 'deprecated',
422
+ yanked: false,
423
+ warnings: loadWarnings,
424
+ asset_digest: asset.asset_digest || null,
425
+ content_digest: asset.content_digest || null,
426
+ license_id: licenseActivation?.license_id || null,
427
+ },
428
+ },
429
+ null,
430
+ 2,
431
+ ) + '\n',
432
+ );
406
433
  recordTrace({
407
434
  timestamp: new Date().toISOString(),
408
435
  agent: detectAgent(),
409
- domain: parsed.full,
436
+ domain: label,
410
437
  format: 'json',
438
+ asset: traceAssetFields(asset, manifest, licenseActivation),
411
439
  });
412
440
  return;
413
441
  }
@@ -415,40 +443,48 @@ function cmdLoad(input, args = []) {
415
443
  // Raw format
416
444
  if (format === 'raw') {
417
445
  for (const f of ['KDNA_Core.json', 'KDNA_Patterns.json']) {
418
- const p = path.join(dir, f);
419
- if (fs.existsSync(p)) {
446
+ const encrypted = encryptedEntries.includes(f);
447
+ const buf = encrypted
448
+ ? Buffer.from(
449
+ JSON.stringify(container[f === 'KDNA_Core.json' ? 'core' : 'patterns'], null, 2),
450
+ )
451
+ : readContainerEntry(asset.asset_path, f);
452
+ if (buf) {
420
453
  process.stdout.write(`\n=== ${f} ===\n`);
421
- process.stdout.write(fs.readFileSync(p, 'utf8'));
454
+ process.stdout.write(buf.toString('utf8'));
422
455
  }
423
456
  }
424
457
  recordTrace({
425
458
  timestamp: new Date().toISOString(),
426
459
  agent: detectAgent(),
427
- domain: parsed.full,
460
+ domain: label,
428
461
  format: 'raw',
462
+ asset: traceAssetFields(asset, manifest, licenseActivation),
429
463
  });
430
464
  return;
431
465
  }
432
466
 
433
467
  // Load profiles
434
468
  if (profile) {
435
- emitProfile(parsed, manifest, core, pat, profile, profileInput);
469
+ emitProfile(parsed || { full: label }, manifest, core, pat, profile, profileInput);
436
470
  recordTrace({
437
471
  timestamp: new Date().toISOString(),
438
472
  agent: detectAgent(),
439
- domain: parsed.full,
473
+ domain: label,
440
474
  format: `profile:${profile}`,
475
+ asset: traceAssetFields(asset, manifest, licenseActivation),
441
476
  });
442
477
  return;
443
478
  }
444
479
 
445
480
  // Default: --as=prompt — compact text optimized for system-prompt injection.
446
- emitCompact(parsed, manifest, core, pat);
481
+ emitCompact(parsed || { full: label }, manifest, core, pat);
447
482
  recordTrace({
448
483
  timestamp: new Date().toISOString(),
449
484
  agent: detectAgent(),
450
- domain: parsed.full,
485
+ domain: label,
451
486
  format: 'prompt',
487
+ asset: traceAssetFields(asset, manifest, licenseActivation),
452
488
  });
453
489
  }
454
490
 
@@ -461,6 +497,7 @@ function emitProfile(parsed, manifest, core, pat, profile, input) {
461
497
  lines.push('');
462
498
 
463
499
  const axioms = core.axioms || [];
500
+ emitRequiredOutput(lines, manifest, core, pat);
464
501
 
465
502
  switch (profile) {
466
503
  case 'index':
@@ -546,11 +583,11 @@ function emitProfile(parsed, manifest, core, pat, profile, input) {
546
583
  }
547
584
 
548
585
  if (pat.terminology?.banned_terms?.length) {
549
- lines.push('## Banned terms');
586
+ lines.push('## MUST NOT SAY');
550
587
  for (const t of pat.terminology.banned_terms) {
551
588
  const term = typeof t === 'string' ? t : t.term;
552
589
  const replace = typeof t === 'object' ? t.replace_with : null;
553
- lines.push(`- "${term}"${replace ? ` use: ${replace}` : ''}`);
590
+ lines.push(`- "${term}"${replace ? ` -> use: ${replace}` : ''}`);
554
591
  }
555
592
  lines.push('');
556
593
  }
@@ -589,8 +626,11 @@ function emitCompact(parsed, manifest, core, pat) {
589
626
  if (manifest.core_insight) lines.push(`# core insight: ${manifest.core_insight}`);
590
627
  lines.push('');
591
628
 
629
+ emitRequiredOutput(lines, manifest, core, pat);
630
+
592
631
  if (core.axioms?.length) {
593
- lines.push('## Axioms (reason from these)');
632
+ lines.push('## JUDGMENT GUIDANCE');
633
+ lines.push('### Axioms (reason from these)');
594
634
  for (const a of core.axioms) {
595
635
  lines.push(`- ${a.one_sentence}`);
596
636
  if (a.applies_when?.length) {
@@ -605,7 +645,7 @@ function emitCompact(parsed, manifest, core, pat) {
605
645
  }
606
646
 
607
647
  if (core.stances?.length) {
608
- lines.push('## Stances');
648
+ lines.push('### Stances');
609
649
  for (const s of core.stances) {
610
650
  const text = typeof s === 'string' ? s : s.stance;
611
651
  if (text) lines.push(`- ${text}`);
@@ -614,17 +654,18 @@ function emitCompact(parsed, manifest, core, pat) {
614
654
  }
615
655
 
616
656
  if (pat.terminology?.banned_terms?.length) {
617
- lines.push('## Banned terms (do not use even if user uses them)');
657
+ lines.push('## MUST NOT SAY');
618
658
  for (const t of pat.terminology.banned_terms) {
619
659
  const term = typeof t === 'string' ? t : t.term;
620
660
  const replace = typeof t === 'object' ? t.replace_with : null;
621
- lines.push(`- "${term}"${replace ? ` use: ${replace}` : ''}`);
661
+ lines.push(`- "${term}"${replace ? ` -> use: ${replace}` : ''}`);
622
662
  }
623
663
  lines.push('');
624
664
  }
625
665
 
626
666
  if (pat.misunderstandings?.length) {
627
- lines.push('## Misunderstandings to detect and avoid');
667
+ if (!core.axioms?.length) lines.push('## JUDGMENT GUIDANCE');
668
+ lines.push('### Misunderstandings to detect and avoid');
628
669
  for (const m of pat.misunderstandings) {
629
670
  lines.push(`- WRONG: ${m.wrong}`);
630
671
  lines.push(` CORRECT: ${m.correct}`);
@@ -634,7 +675,8 @@ function emitCompact(parsed, manifest, core, pat) {
634
675
  }
635
676
 
636
677
  if (pat.self_check?.length) {
637
- lines.push('## Self-checks (answer before final output)');
678
+ lines.push('## SELF-CHECK');
679
+ lines.push('Answer before final output.');
638
680
  for (const q of pat.self_check) {
639
681
  const text = typeof q === 'string' ? q : q.question;
640
682
  if (text) lines.push(`- ${text}`);
@@ -649,6 +691,37 @@ function emitCompact(parsed, manifest, core, pat) {
649
691
  process.stdout.write(lines.join('\n') + '\n');
650
692
  }
651
693
 
694
+ function emitRequiredOutput(lines, manifest, core, pat) {
695
+ const required = uniqueStrings([
696
+ ...asStringArray(manifest.required_output),
697
+ ...asStringArray(manifest.must_include),
698
+ ...asStringArray(core.required_output),
699
+ ...asStringArray(core.must_include),
700
+ ...asStringArray(pat.required_output),
701
+ ...asStringArray(pat.must_include),
702
+ ...asStringArray(pat.output_constraints?.required_output),
703
+ ...asStringArray(pat.output_constraints?.must_include),
704
+ ]);
705
+
706
+ if (!required.length) return;
707
+
708
+ lines.push('## REQUIRED OUTPUT');
709
+ lines.push('Include these statements when they are relevant to the user request.');
710
+ for (const item of required) lines.push(`- ${item}`);
711
+ lines.push('');
712
+ }
713
+
714
+ function asStringArray(value) {
715
+ if (!value) return [];
716
+ if (typeof value === 'string') return [value];
717
+ if (!Array.isArray(value)) return [];
718
+ return value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim());
719
+ }
720
+
721
+ function uniqueStrings(items) {
722
+ return Array.from(new Set(items.map((item) => item.trim()).filter(Boolean)));
723
+ }
724
+
652
725
  // ─── kdna select ───────────────────────────────────────────────────────
653
726
 
654
727
  function cmdSelect(args = []) {
@@ -674,9 +747,8 @@ function cmdSelect(args = []) {
674
747
  const scores = [];
675
748
 
676
749
  for (const e of installed) {
677
- const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
750
+ const { manifest = {}, core = {} } = readContainer(e.asset_path);
678
751
  if (manifest.yanked === true) continue;
679
- const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
680
752
 
681
753
  // Check does_not_apply_when hard exclusion
682
754
  let excluded = false;
@@ -774,14 +846,15 @@ function cmdPostvalidate(args = []) {
774
846
  console.error(`Invalid name "${input}".`);
775
847
  process.exit(2);
776
848
  }
777
- const dir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
778
- if (!fs.existsSync(dir)) {
849
+ const installed = getInstalled(parsed.full);
850
+ if (!installed) {
779
851
  console.error(`${parsed.full} is not installed.`);
780
852
  process.exit(2);
781
853
  }
782
854
 
783
- const core = readJson(path.join(dir, 'KDNA_Core.json')) || {};
784
- const pat = readJson(path.join(dir, 'KDNA_Patterns.json')) || {};
855
+ const container = readContainer(installed.asset_path);
856
+ const core = container.core || {};
857
+ const pat = container.patterns || {};
785
858
 
786
859
  // Read agent output
787
860
  let agentOutput = '';
@@ -949,7 +1022,10 @@ function cmdRoute(taskText, args = []) {
949
1022
 
950
1023
  if (!taskText) {
951
1024
  const err = { error: 'Usage: kdna route "<task description>" [--json] [--discover]' };
952
- if (wantJson) { console.log(JSON.stringify(err)); process.exit(2); }
1025
+ if (wantJson) {
1026
+ console.log(JSON.stringify(err));
1027
+ process.exit(2);
1028
+ }
953
1029
  console.error(err.error);
954
1030
  process.exit(2);
955
1031
  }
@@ -976,19 +1052,53 @@ function cmdRoute(taskText, args = []) {
976
1052
 
977
1053
  // ═══ Gate 1: Intent — does this task need domain judgment? ═══
978
1054
  const judgmentKeywords = [
979
- 'review', 'diagnose', 'critique', 'evaluate', 'assess', 'judge',
980
- 'should i', 'is this good', 'is this correct', 'how would you rate',
981
- '分析', '诊断', '评估', '判断', '审查', '该怎么', '好不好',
1055
+ 'review',
1056
+ 'diagnose',
1057
+ 'critique',
1058
+ 'evaluate',
1059
+ 'assess',
1060
+ 'judge',
1061
+ 'should i',
1062
+ 'is this good',
1063
+ 'is this correct',
1064
+ 'how would you rate',
1065
+ '分析',
1066
+ '诊断',
1067
+ '评估',
1068
+ '判断',
1069
+ '审查',
1070
+ '该怎么',
1071
+ '好不好',
982
1072
  ];
983
1073
  const mechanicalKeywords = [
984
- 'format', 'translate', 'convert', 'list', 'find', 'lookup', 'search',
985
- 'run', 'execute', 'compile', 'build', 'fix syntax', 'fix the bug',
986
- '格式化', '翻译', '转换', '列出', '查找', '搜索', '运行', '执行', '编译', '修复语法',
1074
+ 'format',
1075
+ 'translate',
1076
+ 'convert',
1077
+ 'list',
1078
+ 'find',
1079
+ 'lookup',
1080
+ 'search',
1081
+ 'run',
1082
+ 'execute',
1083
+ 'compile',
1084
+ 'build',
1085
+ 'fix syntax',
1086
+ 'fix the bug',
1087
+ '格式化',
1088
+ '翻译',
1089
+ '转换',
1090
+ '列出',
1091
+ '查找',
1092
+ '搜索',
1093
+ '运行',
1094
+ '执行',
1095
+ '编译',
1096
+ '修复语法',
987
1097
  ];
988
1098
 
989
1099
  const taskLower = taskText.toLowerCase();
990
- const hasJudgmentSignal = judgmentKeywords.some(k => taskLower.includes(k));
991
- const hasMechanicalSignal = mechanicalKeywords.some(k => taskLower.includes(k));
1100
+ const hasJudgmentSignal = judgmentKeywords.some((k) => taskLower.includes(k));
1101
+ const hasMechanicalSignal = mechanicalKeywords.some((k) => taskLower.includes(k));
992
1102
 
993
1103
  result.needs_kdna = hasJudgmentSignal && !hasMechanicalSignal;
994
1104
 
@@ -998,7 +1108,10 @@ function cmdRoute(taskText, args = []) {
998
1108
  result.reason = hasMechanicalSignal
999
1109
  ? 'task is mechanical — no domain judgment required'
1000
1110
  : 'task does not appear to need domain judgment';
1001
- if (wantJson) { console.log(JSON.stringify(result, null, 2)); return; }
1111
+ if (wantJson) {
1112
+ console.log(JSON.stringify(result, null, 2));
1113
+ return;
1114
+ }
1002
1115
  console.log('SKIP (no judgment needed)');
1003
1116
  return;
1004
1117
  }
@@ -1007,7 +1120,10 @@ function cmdRoute(taskText, args = []) {
1007
1120
  result.status = 'SKIP_NO_LOCAL_DOMAIN';
1008
1121
  result.action = 'skip';
1009
1122
  result.reason = 'task may benefit from judgment, but no KDNA domains are installed';
1010
- if (wantJson) { console.log(JSON.stringify(result, null, 2)); return; }
1123
+ if (wantJson) {
1124
+ console.log(JSON.stringify(result, null, 2));
1125
+ return;
1126
+ }
1011
1127
  console.log('SKIP (no domains installed)');
1012
1128
  return;
1013
1129
  }
@@ -1017,7 +1133,7 @@ function cmdRoute(taskText, args = []) {
1017
1133
  const candidates = [];
1018
1134
 
1019
1135
  for (const e of installed) {
1020
- const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
1136
+ const { manifest = {}, core = {} } = readContainer(e.asset_path);
1021
1137
  if (manifest.yanked === true) {
1022
1138
  result.rejected_domains.push({
1023
1139
  domain: manifest.name || e.full,
@@ -1027,8 +1143,6 @@ function cmdRoute(taskText, args = []) {
1027
1143
  continue;
1028
1144
  }
1029
1145
 
1030
- const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
1031
-
1032
1146
  // Negative match: does_not_apply_when
1033
1147
  let disqualified = null;
1034
1148
  for (const a of core.axioms || []) {
@@ -1084,8 +1198,8 @@ function cmdRoute(taskText, args = []) {
1084
1198
  candidates.sort((a, b) => b.score - a.score);
1085
1199
 
1086
1200
  // ═══ Gate 4: Decision ═══
1087
- const strongCandidates = candidates.filter(c => c.score >= 6);
1088
- const weakCandidates = candidates.filter(c => c.score > 0 && c.score < 6);
1201
+ const strongCandidates = candidates.filter((c) => c.score >= 6);
1202
+ const weakCandidates = candidates.filter((c) => c.score > 0 && c.score < 6);
1089
1203
 
1090
1204
  if (strongCandidates.length === 0 && weakCandidates.length === 0) {
1091
1205
  // No matches at all
@@ -1095,7 +1209,7 @@ function cmdRoute(taskText, args = []) {
1095
1209
  if (result.rejected_domains.length > 0) {
1096
1210
  result.reason += ` (${result.rejected_domains.length} domains explicitly excluded by does_not_apply_when)`;
1097
1211
  }
1098
- result.candidates = candidates.map(c => ({
1212
+ result.candidates = candidates.map((c) => ({
1099
1213
  domain: c.domain,
1100
1214
  decision: 'rejected',
1101
1215
  reason: 'insufficient match score',
@@ -1108,24 +1222,38 @@ function cmdRoute(taskText, args = []) {
1108
1222
  result.reason = `${strongCandidates.length} domains strongly match this task with different judgment frames`;
1109
1223
 
1110
1224
  result.ambiguity = {
1111
- domains: strongCandidates.slice(0, 3).map(c => ({
1225
+ domains: strongCandidates.slice(0, 3).map((c) => ({
1112
1226
  domain: c.domain,
1113
1227
  description: c.description,
1114
1228
  judgment_frame: c.reasons.length > 0 ? c.reasons[0].text : c.description,
1115
1229
  risk_if_wrong: `may misclassify the task as a ${c.domain.split('/').pop()} problem`,
1116
1230
  })),
1117
- recommendation: 'Choose the domain whose judgment frame best matches the task intent. Do not blend domains.',
1231
+ recommendation:
1232
+ 'Choose the domain whose judgment frame best matches the task intent. Do not blend domains.',
1118
1233
  };
1119
1234
 
1120
- result.candidates = strongCandidates.map(c => ({
1121
- domain: c.domain, decision: 'ambiguous', reason: `score ${c.score}`, confidence: c.confidence,
1235
+ result.candidates = strongCandidates.map((c) => ({
1236
+ domain: c.domain,
1237
+ decision: 'ambiguous',
1238
+ reason: `score ${c.score}`,
1239
+ confidence: c.confidence,
1122
1240
  }));
1123
1241
  } else if (strongCandidates.length === 1) {
1124
1242
  // One strong match + possible weak matches
1125
1243
  const selected = strongCandidates[0];
1126
1244
  result.candidates = [
1127
- { domain: selected.domain, decision: 'strong_match', reason: `score ${selected.score}`, confidence: selected.confidence },
1128
- ...weakCandidates.map(c => ({ domain: c.domain, decision: 'weak_match', reason: `score ${c.score}`, confidence: c.confidence })),
1245
+ {
1246
+ domain: selected.domain,
1247
+ decision: 'strong_match',
1248
+ reason: `score ${selected.score}`,
1249
+ confidence: selected.confidence,
1250
+ },
1251
+ ...weakCandidates.map((c) => ({
1252
+ domain: c.domain,
1253
+ decision: 'weak_match',
1254
+ reason: `score ${c.score}`,
1255
+ confidence: c.confidence,
1256
+ })),
1129
1257
  ];
1130
1258
 
1131
1259
  // ═══ Trust Gate ═══
@@ -1147,11 +1275,15 @@ function cmdRoute(taskText, args = []) {
1147
1275
  // Only weak matches — skip
1148
1276
  result.status = 'SKIP_WEAK_FIT';
1149
1277
  result.action = 'skip';
1150
- result.reason = weakCandidates.length > 0
1151
- ? `${weakCandidates.length} domain(s) have weak match only — skipping to avoid contamination`
1152
- : 'no installed domain matches this task';
1153
- result.candidates = weakCandidates.map(c => ({
1154
- domain: c.domain, decision: 'weak_match', reason: `score ${c.score}`, confidence: c.confidence,
1278
+ result.reason =
1279
+ weakCandidates.length > 0
1280
+ ? `${weakCandidates.length} domain(s) have weak match only — skipping to avoid contamination`
1281
+ : 'no installed domain matches this task';
1282
+ result.candidates = weakCandidates.map((c) => ({
1283
+ domain: c.domain,
1284
+ decision: 'weak_match',
1285
+ reason: `score ${c.score}`,
1286
+ confidence: c.confidence,
1155
1287
  }));
1156
1288
  }
1157
1289
 
@@ -1177,23 +1309,20 @@ function cmdRoute(taskText, args = []) {
1177
1309
  if (result.reason) console.log(`Reason: ${result.reason}`);
1178
1310
  if (result.selected_domain) console.log(`Domain: ${result.selected_domain}`);
1179
1311
  if (result.rejected_domains.length) {
1180
- console.log(`Rejected: ${result.rejected_domains.map(r => r.domain).join(', ')}`);
1312
+ console.log(`Rejected: ${result.rejected_domains.map((r) => r.domain).join(', ')}`);
1181
1313
  }
1182
1314
  }
1183
1315
 
1184
- function checkTrust(domainName, options = {}) {
1316
+ function checkTrust(domainName) {
1185
1317
  const failures = [];
1186
1318
  const warnings = [];
1187
- const installed = listInstalled();
1188
- const entry = installed.find(e => `${e.scope}/${e.ident}` === domainName || e.full === domainName);
1319
+ const entry = getInstalled(domainName);
1189
1320
  if (!entry) {
1190
- failures.push('domain not found in installed directory');
1321
+ failures.push('domain asset not found in package index');
1191
1322
  return { passed: false, failures, warnings };
1192
1323
  }
1193
1324
 
1194
- const manifest = readJson(path.join(entry.dir, 'kdna.json')) || {};
1195
- const core = readJson(path.join(entry.dir, 'KDNA_Core.json')) || {};
1196
- const evolution = readJson(path.join(entry.dir, 'KDNA_Evolution.json')) || {};
1325
+ const { manifest = {}, core = {}, evolution = {} } = readContainer(entry.asset_path);
1197
1326
 
1198
1327
  // 1. Yank check
1199
1328
  if (manifest.yanked === true) {
@@ -1202,7 +1331,9 @@ function checkTrust(domainName, options = {}) {
1202
1331
 
1203
1332
  // 2. Deprecation check
1204
1333
  if (manifest.status === 'deprecated') {
1205
- warnings.push(`domain is deprecated${manifest.replaced_by ? ', replaced by ' + manifest.replaced_by : ''}`);
1334
+ warnings.push(
1335
+ `domain is deprecated${manifest.replaced_by ? ', replaced by ' + manifest.replaced_by : ''}`,
1336
+ );
1206
1337
  }
1207
1338
 
1208
1339
  // 3. Signature check
@@ -1221,14 +1352,18 @@ function checkTrust(domainName, options = {}) {
1221
1352
  const riskMap = { R0: 0, R1: 1, R2: 2, R3: 3, R4: 4 };
1222
1353
  const riskNum = riskMap[riskLevel] || 1;
1223
1354
  if (riskNum >= 3) {
1224
- warnings.push(`domain risk level is ${riskLevel} — high-risk judgment may influence agent behavior`);
1355
+ warnings.push(
1356
+ `domain risk level is ${riskLevel} — high-risk judgment may influence agent behavior`,
1357
+ );
1225
1358
  }
1226
1359
  if (riskNum >= 2 && (manifest.quality_badge === 'untested' || !manifest.quality_badge)) {
1227
- warnings.push(`risk level ${riskLevel} with quality_badge '${manifest.quality_badge || 'none'}' — consider requiring review`);
1360
+ warnings.push(
1361
+ `risk level ${riskLevel} with quality_badge '${manifest.quality_badge || 'none'}' — consider requiring review`,
1362
+ );
1228
1363
  }
1229
1364
 
1230
1365
  // 5. SPEC compatibility check
1231
- const specVersion = manifest.spec_version || manifest.kdna_spec || 'unknown';
1366
+ const specVersion = manifest.spec_version || 'unknown';
1232
1367
  const supportedSpecs = ['1.0-rc', '1.0', '0.7'];
1233
1368
  if (!supportedSpecs.includes(specVersion)) {
1234
1369
  warnings.push(`SPEC version '${specVersion}' may not be fully compatible with current loader`);
@@ -1236,41 +1371,29 @@ function checkTrust(domainName, options = {}) {
1236
1371
 
1237
1372
  // 6. License validity (commercial domains)
1238
1373
  if (manifest.access === 'licensed' || manifest.access === 'runtime') {
1239
- const licenseStorePath = path.join(process.env.HOME || '.', '.kdna', 'licenses.json');
1240
- let licenseOk = false;
1241
- try {
1242
- if (require('fs').existsSync(licenseStorePath)) {
1243
- const store = JSON.parse(require('fs').readFileSync(licenseStorePath, 'utf8'));
1244
- const keys = store.keys || {};
1245
- for (const [hash, entry] of Object.entries(keys)) {
1246
- if (entry.active && entry.domain_id === domainName) {
1247
- if (!entry.expires_at || new Date(entry.expires_at) > new Date()) {
1248
- licenseOk = true;
1249
- break;
1250
- }
1251
- }
1252
- }
1253
- if (!licenseOk) {
1254
- warnings.push('commercial domain has no active license — install with: kdna install ' + domainName + ' --license <key>');
1255
- }
1256
- } else {
1257
- warnings.push('no license store found — commercial domain may require a license');
1258
- }
1259
- } catch { /* license check unavailable */ }
1374
+ const licenseCheck = licenseDecryptOptionsForManifest({ ...manifest, name: domainName });
1375
+ if (!licenseCheck.ok) {
1376
+ warnings.push(
1377
+ 'commercial domain has no active entitlement — run: kdna license activate ' +
1378
+ domainName +
1379
+ ' --key <license-key> --server <url>',
1380
+ );
1381
+ }
1260
1382
  }
1261
1383
 
1262
1384
  // 7. Human Lock check (judgment-class cards)
1263
- const judgmentCardTypes = ['axiom', 'boundary', 'risk'];
1264
1385
  const axioms = core.axioms || [];
1265
1386
  const hasJudgmentCards = axioms.length > 0;
1266
1387
  if (hasJudgmentCards) {
1267
1388
  const humanLocks = evolution.human_locks || [];
1268
- const lockedAxioms = axioms.filter(a => {
1389
+ const lockedAxioms = axioms.filter((a) => {
1269
1390
  // Check if axiom has a human_lock field OR if an evolution lock covers it
1270
- return a.human_lock || humanLocks.some(hl => hl.lock_type === 'accept');
1391
+ return a.human_lock || humanLocks.some((hl) => hl.lock_type === 'accept');
1271
1392
  }).length;
1272
1393
  if (lockedAxioms === 0 && humanLocks.length === 0) {
1273
- warnings.push('domain has no Human Lock records — judgment-class content may not be human-verified');
1394
+ warnings.push(
1395
+ 'domain has no Human Lock records — judgment-class content may not be human-verified',
1396
+ );
1274
1397
  }
1275
1398
  }
1276
1399
 
@@ -1284,4 +1407,12 @@ function checkTrust(domainName, options = {}) {
1284
1407
  };
1285
1408
  }
1286
1409
 
1287
- module.exports = { cmdAvailable, cmdMatch, cmdLoad, cmdSelect, cmdPostvalidate, cmdRoute, checkTrust };
1410
+ module.exports = {
1411
+ cmdAvailable,
1412
+ cmdMatch,
1413
+ cmdLoad,
1414
+ cmdSelect,
1415
+ cmdPostvalidate,
1416
+ cmdRoute,
1417
+ checkTrust,
1418
+ };