@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/README.md +120 -101
- package/SECURITY.md +1 -1
- package/package.json +6 -4
- package/skills/kdna-loader/SKILL.md +23 -22
- package/src/agent.js +290 -159
- package/src/cli.js +117 -67
- package/src/cmds/_common.js +40 -18
- package/src/cmds/badge.js +14 -9
- package/src/cmds/changelog.js +32 -12
- package/src/cmds/cluster.js +80 -85
- package/src/cmds/doctor.js +10 -27
- package/src/cmds/domain.js +114 -427
- package/src/cmds/explain.js +119 -0
- package/src/cmds/governance.js +111 -42
- package/src/cmds/legacy.js +8 -9
- package/src/cmds/license.js +491 -26
- package/src/cmds/quality.js +10 -3
- package/src/cmds/registry.js +15 -67
- package/src/cmds/studio.js +99 -47
- package/src/cmds/test.js +9 -6
- package/src/cmds/trace.js +11 -7
- package/src/compare.js +41 -22
- package/src/diff.js +38 -24
- package/src/identity.js +9 -7
- package/src/init.js +2 -2
- package/src/install.js +147 -459
- package/src/loader.js +10 -10
- package/src/package-store.js +232 -0
- package/src/paths.js +44 -0
- package/src/publish.js +150 -51
- package/src/registry.js +81 -9
- package/src/setup.js +19 -20
- package/src/verify.js +293 -140
- package/src/version.js +15 -7
- package/templates/minimal-domain/kdna.json +7 -7
- package/templates/standard-domain/README.md +10 -10
- package/templates/standard-domain/kdna.json +7 -3
- package/validators/kdna-lint.js +45 -11
- package/src/cmds/encrypt.js +0 -199
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
|
-
|
|
42
|
-
|
|
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
|
|
45
|
-
|
|
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
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
352
|
-
if (!
|
|
353
|
-
console.error(`
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
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(`${
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
390
|
-
const pat =
|
|
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(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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:
|
|
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
|
|
419
|
-
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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('##
|
|
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 ? `
|
|
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('##
|
|
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('
|
|
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('##
|
|
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 ? `
|
|
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('##
|
|
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('##
|
|
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 =
|
|
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
|
|
778
|
-
if (!
|
|
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
|
|
784
|
-
const
|
|
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) {
|
|
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',
|
|
980
|
-
'
|
|
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',
|
|
985
|
-
'
|
|
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) {
|
|
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) {
|
|
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 =
|
|
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:
|
|
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,
|
|
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
|
-
{
|
|
1128
|
-
|
|
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 =
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
|
1316
|
+
function checkTrust(domainName) {
|
|
1185
1317
|
const failures = [];
|
|
1186
1318
|
const warnings = [];
|
|
1187
|
-
const
|
|
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
|
|
1321
|
+
failures.push('domain asset not found in package index');
|
|
1191
1322
|
return { passed: false, failures, warnings };
|
|
1192
1323
|
}
|
|
1193
1324
|
|
|
1194
|
-
const manifest =
|
|
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(
|
|
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(
|
|
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(
|
|
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 ||
|
|
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
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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(
|
|
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 = {
|
|
1410
|
+
module.exports = {
|
|
1411
|
+
cmdAvailable,
|
|
1412
|
+
cmdMatch,
|
|
1413
|
+
cmdLoad,
|
|
1414
|
+
cmdSelect,
|
|
1415
|
+
cmdPostvalidate,
|
|
1416
|
+
cmdRoute,
|
|
1417
|
+
checkTrust,
|
|
1418
|
+
};
|