@aikdna/kdna-cli 0.9.0 → 0.11.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 +109 -31
- package/package.json +1 -1
- package/src/agent.js +410 -2
- package/src/cli.js +267 -38
- package/src/cmds/_common.js +65 -3
- package/src/cmds/badge.js +244 -0
- package/src/cmds/changelog.js +226 -0
- package/src/cmds/cluster.js +214 -3
- package/src/cmds/doctor.js +160 -0
- package/src/cmds/domain.js +110 -33
- package/src/cmds/governance.js +471 -0
- package/src/cmds/identity.js +5 -4
- package/src/cmds/quality.js +34 -9
- package/src/cmds/registry.js +62 -22
- package/src/cmds/studio.js +577 -0
- package/src/cmds/test.js +177 -0
- package/src/compare.js +46 -32
- package/src/diff.js +136 -38
- package/src/identity.js +20 -4
- package/src/install.js +181 -91
- package/src/publish.js +4 -3
- package/src/search.js +33 -2
- package/src/verify.js +76 -13
- package/src/version.js +110 -3
package/src/install.js
CHANGED
|
@@ -19,6 +19,7 @@ const path = require('path');
|
|
|
19
19
|
const crypto = require('crypto');
|
|
20
20
|
const { execSync, execFileSync } = require('child_process');
|
|
21
21
|
const { RegistryResolver, parseName } = require('./registry');
|
|
22
|
+
const { EXIT, error } = require('./cmds/_common');
|
|
22
23
|
|
|
23
24
|
const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
|
|
24
25
|
const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
|
|
@@ -139,11 +140,6 @@ function ensureLoaderSkill() {
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
function error(msg) {
|
|
143
|
-
console.error(`Error: ${msg}`);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
143
|
function ensureDir(dir) {
|
|
148
144
|
fs.mkdirSync(dir, { recursive: true });
|
|
149
145
|
}
|
|
@@ -296,7 +292,7 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
|
|
|
296
292
|
console.warn(' ⚠ No kdna.json — cannot verify signature.');
|
|
297
293
|
return;
|
|
298
294
|
}
|
|
299
|
-
error('No kdna.json in package — cannot verify signature.');
|
|
295
|
+
error('No kdna.json in package — cannot verify signature.', EXIT.TRUST_FAILED);
|
|
300
296
|
}
|
|
301
297
|
|
|
302
298
|
const trustKey = scope.trust_pubkey;
|
|
@@ -318,7 +314,7 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
|
|
|
318
314
|
|
|
319
315
|
// Author pubkey fingerprint must match scope trust_pubkey
|
|
320
316
|
if (manifest.author?.pubkey !== trustKey) {
|
|
321
|
-
error(`${entry.name}: author.pubkey does not match scope trust key. Refusing to install
|
|
317
|
+
error(`${entry.name}: author.pubkey does not match scope trust key. Refusing to install.`, EXIT.TRUST_FAILED);
|
|
322
318
|
}
|
|
323
319
|
|
|
324
320
|
// Full Ed25519 verify (requires public_key_pem embedded in the package)
|
|
@@ -334,6 +330,7 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
|
|
|
334
330
|
if (computedFingerprint !== manifest.author.pubkey) {
|
|
335
331
|
error(
|
|
336
332
|
`${entry.name}: embedded public_key_pem does not match author.pubkey fingerprint. Refusing.`,
|
|
333
|
+
EXIT.TRUST_FAILED,
|
|
337
334
|
);
|
|
338
335
|
}
|
|
339
336
|
|
|
@@ -369,12 +366,12 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
|
|
|
369
366
|
const publicKey = crypto.createPublicKey(pem);
|
|
370
367
|
const ok = crypto.verify(null, Buffer.from(payload), publicKey, Buffer.from(sigHex, 'hex'));
|
|
371
368
|
if (!ok) {
|
|
372
|
-
error(`${entry.name}: Ed25519 signature INVALID. Package may be tampered. Refusing
|
|
369
|
+
error(`${entry.name}: Ed25519 signature INVALID. Package may be tampered. Refusing.`, EXIT.TRUST_FAILED);
|
|
373
370
|
}
|
|
374
371
|
console.log(' ✓ Signature OK (Ed25519 verified)');
|
|
375
372
|
} catch (e) {
|
|
376
373
|
if (e.message?.includes('INVALID')) throw e;
|
|
377
|
-
error(`${entry.name}: signature verification failed: ${e.message}
|
|
374
|
+
error(`${entry.name}: signature verification failed: ${e.message}`, EXIT.TRUST_FAILED);
|
|
378
375
|
}
|
|
379
376
|
}
|
|
380
377
|
|
|
@@ -431,42 +428,45 @@ function cmdInstallExtended(input, args = []) {
|
|
|
431
428
|
ensureLoaderSkill();
|
|
432
429
|
|
|
433
430
|
const yes = args.includes('--yes');
|
|
431
|
+
const jsonMode = args.includes('--json');
|
|
434
432
|
const source = parseSource(input);
|
|
435
433
|
|
|
436
434
|
switch (source.type) {
|
|
437
435
|
case 'registry':
|
|
438
|
-
return installFromRegistry(source.parsed, yes);
|
|
436
|
+
return installFromRegistry(source.parsed, yes, jsonMode);
|
|
439
437
|
case 'local-file':
|
|
440
|
-
return installFromLocalFile(source.path, yes);
|
|
438
|
+
return installFromLocalFile(source.path, yes, jsonMode);
|
|
441
439
|
case 'local-dir':
|
|
442
|
-
return installFromLocalDir(source.path, yes);
|
|
440
|
+
return installFromLocalDir(source.path, yes, jsonMode);
|
|
443
441
|
}
|
|
444
442
|
}
|
|
445
443
|
|
|
446
|
-
function installFromRegistry(parsed, yes) {
|
|
444
|
+
function installFromRegistry(parsed, yes, jsonMode = false) {
|
|
447
445
|
const resolver = new RegistryResolver({ allowNetwork: true });
|
|
448
446
|
let scope, entry;
|
|
449
447
|
try {
|
|
450
448
|
({ scope, entry } = resolver.resolve(parsed.full));
|
|
451
449
|
} catch (e) {
|
|
452
|
-
error(e.message);
|
|
450
|
+
error(e.message, EXIT.REGISTRY_ERROR);
|
|
453
451
|
}
|
|
454
452
|
|
|
455
453
|
if (parsed.wasShort) {
|
|
456
|
-
console.log(` Resolved "${parsed.ident}" → ${entry.name}`);
|
|
454
|
+
if (!jsonMode) console.log(` Resolved "${parsed.ident}" → ${entry.name}`);
|
|
457
455
|
}
|
|
458
456
|
|
|
459
457
|
if (entry.deprecated) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
458
|
+
if (!jsonMode) {
|
|
459
|
+
console.warn(
|
|
460
|
+
` ⚠ ${entry.name} is deprecated.${entry.replaced_by ? ` Use ${entry.replaced_by} instead.` : ''}`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
463
|
}
|
|
464
464
|
if (entry.access && entry.access !== 'open') {
|
|
465
|
-
error(`${entry.name} requires "${entry.access}" access. Not installable via CLI yet
|
|
465
|
+
error(`${entry.name} requires "${entry.access}" access. Not installable via CLI yet.`, EXIT.POLICY_VIOLATION);
|
|
466
466
|
}
|
|
467
467
|
|
|
468
468
|
if (entry.type === 'cluster') {
|
|
469
|
-
return installCluster(entry, resolver, yes);
|
|
469
|
+
return installCluster(entry, resolver, yes, jsonMode);
|
|
470
470
|
}
|
|
471
471
|
|
|
472
472
|
if (!entry.kdna_url) {
|
|
@@ -474,6 +474,7 @@ function installFromRegistry(parsed, yes) {
|
|
|
474
474
|
`${entry.name}@${entry.version} has no kdna_url in registry.\n` +
|
|
475
475
|
`release_status: ${entry.release_status || 'unknown'}\n` +
|
|
476
476
|
`(This domain has not been published as a .kdna file yet. It will be available after v0.7 republish.)`,
|
|
477
|
+
EXIT.REGISTRY_ERROR,
|
|
477
478
|
);
|
|
478
479
|
}
|
|
479
480
|
|
|
@@ -482,20 +483,20 @@ function installFromRegistry(parsed, yes) {
|
|
|
482
483
|
process.exit(0);
|
|
483
484
|
}
|
|
484
485
|
|
|
485
|
-
installSingleFromUrl({ entry, scope });
|
|
486
|
+
installSingleFromUrl({ entry, scope }, jsonMode);
|
|
486
487
|
}
|
|
487
488
|
|
|
488
|
-
function installSingleFromUrl({ entry, scope }) {
|
|
489
|
+
function installSingleFromUrl({ entry, scope }, jsonMode = false) {
|
|
489
490
|
const [scopeName, ident] = entry.name.split('/');
|
|
490
491
|
const dest = domainDir(scopeName, ident);
|
|
491
492
|
const tmpFile = path.join(scopeDir(scopeName), `.${ident}-${Date.now()}.kdna.tmp`);
|
|
492
493
|
|
|
493
|
-
console.log(` Downloading ${entry.name}@${entry.version}...`);
|
|
494
|
+
if (!jsonMode) console.log(` Downloading ${entry.name}@${entry.version}...`);
|
|
494
495
|
ensureDir(scopeDir(scopeName));
|
|
495
496
|
try {
|
|
496
497
|
downloadFile(entry.kdna_url, tmpFile);
|
|
497
498
|
} catch {
|
|
498
|
-
error(`Failed to download ${entry.kdna_url}
|
|
499
|
+
error(`Failed to download ${entry.kdna_url}`, EXIT.REGISTRY_ERROR);
|
|
499
500
|
}
|
|
500
501
|
|
|
501
502
|
// sha256 check
|
|
@@ -508,7 +509,7 @@ function installSingleFromUrl({ entry, scope }) {
|
|
|
508
509
|
}
|
|
509
510
|
error(`sha256 mismatch for ${entry.name}: expected ${entry.sha256}, got ${actual}`);
|
|
510
511
|
}
|
|
511
|
-
console.log(` ✓ sha256 verified`);
|
|
512
|
+
if (!jsonMode) console.log(` ✓ sha256 verified`);
|
|
512
513
|
|
|
513
514
|
// Replace existing install atomically-ish
|
|
514
515
|
if (fs.existsSync(dest)) {
|
|
@@ -537,29 +538,39 @@ function installSingleFromUrl({ entry, scope }) {
|
|
|
537
538
|
};
|
|
538
539
|
fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
539
540
|
|
|
540
|
-
|
|
541
|
-
|
|
541
|
+
if (jsonMode) {
|
|
542
|
+
console.log(JSON.stringify({
|
|
543
|
+
name: entry.name,
|
|
544
|
+
version: entry.version,
|
|
545
|
+
installed: true,
|
|
546
|
+
path: dest,
|
|
547
|
+
type: entry.type || 'domain',
|
|
548
|
+
}));
|
|
549
|
+
} else {
|
|
550
|
+
console.log(`✓ Installed ${entry.name}@${entry.version}`);
|
|
551
|
+
console.log(` Location: ${dest}`);
|
|
552
|
+
}
|
|
542
553
|
}
|
|
543
554
|
|
|
544
|
-
function installCluster(clusterEntry, resolver, _yes) {
|
|
555
|
+
function installCluster(clusterEntry, resolver, _yes, jsonMode = false) {
|
|
545
556
|
const subdomains = clusterEntry.cluster?.domains || [];
|
|
546
557
|
if (!subdomains.length) {
|
|
547
558
|
error(`Cluster ${clusterEntry.name} has no sub-domains listed.`);
|
|
548
559
|
}
|
|
549
560
|
|
|
550
|
-
console.log(`Cluster ${clusterEntry.name} → ${subdomains.length} sub-domains`);
|
|
561
|
+
if (!jsonMode) console.log(`Cluster ${clusterEntry.name} → ${subdomains.length} sub-domains`);
|
|
551
562
|
|
|
552
563
|
for (const sub of subdomains) {
|
|
553
564
|
try {
|
|
554
565
|
const resolved = resolver.resolve(sub);
|
|
555
566
|
if (!resolved.entry.kdna_url) {
|
|
556
|
-
console.warn(` ⚠ ${sub}: no kdna_url (skipping)`);
|
|
567
|
+
if (!jsonMode) console.warn(` ⚠ ${sub}: no kdna_url (skipping)`);
|
|
557
568
|
continue;
|
|
558
569
|
}
|
|
559
|
-
console.log('');
|
|
560
|
-
installSingleFromUrl({ entry: resolved.entry, scope: resolved.scope });
|
|
570
|
+
if (!jsonMode) console.log('');
|
|
571
|
+
installSingleFromUrl({ entry: resolved.entry, scope: resolved.scope }, jsonMode);
|
|
561
572
|
} catch (e) {
|
|
562
|
-
console.warn(` ⚠ ${sub}: ${e.message.split('\n')[0]}`);
|
|
573
|
+
if (!jsonMode) console.warn(` ⚠ ${sub}: ${e.message.split('\n')[0]}`);
|
|
563
574
|
}
|
|
564
575
|
}
|
|
565
576
|
|
|
@@ -582,11 +593,23 @@ function installCluster(clusterEntry, resolver, _yes) {
|
|
|
582
593
|
2,
|
|
583
594
|
) + '\n',
|
|
584
595
|
);
|
|
585
|
-
|
|
586
|
-
|
|
596
|
+
|
|
597
|
+
if (jsonMode) {
|
|
598
|
+
console.log(JSON.stringify({
|
|
599
|
+
name: clusterEntry.name,
|
|
600
|
+
version: clusterEntry.version,
|
|
601
|
+
type: 'cluster',
|
|
602
|
+
installed: true,
|
|
603
|
+
path: clusterDest,
|
|
604
|
+
subdomains: subdomains.length,
|
|
605
|
+
}));
|
|
606
|
+
} else {
|
|
607
|
+
console.log('');
|
|
608
|
+
console.log(`✓ Cluster ${clusterEntry.name} installed`);
|
|
609
|
+
}
|
|
587
610
|
}
|
|
588
611
|
|
|
589
|
-
function installFromLocalFile(filePath, _yes) {
|
|
612
|
+
function installFromLocalFile(filePath, _yes, jsonMode = false) {
|
|
590
613
|
const abs = path.resolve(filePath);
|
|
591
614
|
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) error(`Not a file: ${abs}`);
|
|
592
615
|
|
|
@@ -617,11 +640,21 @@ function installFromLocalFile(filePath, _yes) {
|
|
|
617
640
|
};
|
|
618
641
|
fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
|
|
619
642
|
|
|
620
|
-
|
|
621
|
-
|
|
643
|
+
if (jsonMode) {
|
|
644
|
+
console.log(JSON.stringify({
|
|
645
|
+
name: declared,
|
|
646
|
+
installed: true,
|
|
647
|
+
path: dest,
|
|
648
|
+
source: 'local-file',
|
|
649
|
+
source_path: abs,
|
|
650
|
+
}));
|
|
651
|
+
} else {
|
|
652
|
+
console.log(`✓ Installed ${declared} from local file`);
|
|
653
|
+
console.log(` Location: ${dest}`);
|
|
654
|
+
}
|
|
622
655
|
}
|
|
623
656
|
|
|
624
|
-
function installFromLocalDir(dirPath, _yes) {
|
|
657
|
+
function installFromLocalDir(dirPath, _yes, jsonMode = false) {
|
|
625
658
|
const abs = path.resolve(dirPath);
|
|
626
659
|
const manifest = readJson(path.join(abs, 'kdna.json'));
|
|
627
660
|
const declared = manifest?.name;
|
|
@@ -642,8 +675,18 @@ function installFromLocalDir(dirPath, _yes) {
|
|
|
642
675
|
};
|
|
643
676
|
fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
|
|
644
677
|
|
|
645
|
-
|
|
646
|
-
|
|
678
|
+
if (jsonMode) {
|
|
679
|
+
console.log(JSON.stringify({
|
|
680
|
+
name: declared,
|
|
681
|
+
installed: true,
|
|
682
|
+
path: dest,
|
|
683
|
+
source: 'local-dir',
|
|
684
|
+
source_path: abs,
|
|
685
|
+
}));
|
|
686
|
+
} else {
|
|
687
|
+
console.log(`✓ Installed ${declared} from local directory (dev mode)`);
|
|
688
|
+
console.log(` Location: ${dest}`);
|
|
689
|
+
}
|
|
647
690
|
}
|
|
648
691
|
|
|
649
692
|
// ─── Remove ─────────────────────────────────────────────────────────────
|
|
@@ -663,18 +706,102 @@ function cmdRemove(input) {
|
|
|
663
706
|
|
|
664
707
|
// ─── Info ───────────────────────────────────────────────────────────────
|
|
665
708
|
|
|
666
|
-
function cmdInfo(input) {
|
|
709
|
+
function cmdInfo(input, jsonMode = false) {
|
|
667
710
|
warnLegacy();
|
|
668
711
|
const parsed = parseName(input);
|
|
669
|
-
if (!parsed) error(`Invalid name "${input}"
|
|
712
|
+
if (!parsed) error(`Invalid name "${input}".`, EXIT.INPUT_ERROR);
|
|
670
713
|
const dest = domainDir(parsed.scope, parsed.ident);
|
|
671
|
-
if (!fs.existsSync(dest)) error(`${parsed.full} is not installed
|
|
714
|
+
if (!fs.existsSync(dest)) error(`${parsed.full} is not installed.`, EXIT.INPUT_ERROR);
|
|
672
715
|
|
|
673
716
|
const manifest = readJson(path.join(dest, 'kdna.json'));
|
|
674
717
|
const core = readJson(path.join(dest, 'KDNA_Core.json'));
|
|
675
718
|
const pat = readJson(path.join(dest, 'KDNA_Patterns.json'));
|
|
676
719
|
const source = manifest?._source || {};
|
|
677
720
|
|
|
721
|
+
// ─── Judgment surface (computed for both modes) ────────────────────
|
|
722
|
+
const axiomCount = (core?.axioms || []).length;
|
|
723
|
+
const ontologyCount = (core?.ontology || []).length;
|
|
724
|
+
const stanceCount = (core?.stances || []).length;
|
|
725
|
+
const misCount = (pat?.misunderstandings || []).length;
|
|
726
|
+
const selfCheckCount = (pat?.self_check || []).length;
|
|
727
|
+
|
|
728
|
+
// ─── v2.1 governance score ─────────────────────────────────────────
|
|
729
|
+
let governance = null;
|
|
730
|
+
if (axiomCount > 0) {
|
|
731
|
+
const withApplies = (core?.axioms || []).filter(
|
|
732
|
+
(a) => Array.isArray(a.applies_when) && a.applies_when.length,
|
|
733
|
+
).length;
|
|
734
|
+
const withDoesNotApply = (core?.axioms || []).filter(
|
|
735
|
+
(a) => Array.isArray(a.does_not_apply_when) && a.does_not_apply_when.length,
|
|
736
|
+
).length;
|
|
737
|
+
const withFailureRisk = (core?.axioms || []).filter((a) => a.failure_risk).length;
|
|
738
|
+
const pct = Math.round(
|
|
739
|
+
((withApplies + withDoesNotApply + withFailureRisk) / (axiomCount * 3)) * 100,
|
|
740
|
+
);
|
|
741
|
+
governance = { withApplies, withDoesNotApply, withFailureRisk, coverage: pct };
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ─── Eval cases ────────────────────────────────────────────────────
|
|
745
|
+
const evalDir = path.join(dest, 'evals');
|
|
746
|
+
let evalInfo = null;
|
|
747
|
+
if (fs.existsSync(evalDir)) {
|
|
748
|
+
const evalFiles = fs.readdirSync(evalDir).filter((f) => f.endsWith('.json'));
|
|
749
|
+
let totalCases = 0;
|
|
750
|
+
for (const f of evalFiles) {
|
|
751
|
+
const data = readJson(path.join(evalDir, f));
|
|
752
|
+
if (data?.cases) totalCases += data.cases.length;
|
|
753
|
+
}
|
|
754
|
+
evalInfo = { files: evalFiles.length, totalCases };
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ─── Known risks ───────────────────────────────────────────────────
|
|
758
|
+
const risks = [];
|
|
759
|
+
if (core?.axioms) {
|
|
760
|
+
for (const a of core.axioms) {
|
|
761
|
+
if (a.failure_risk) risks.push({ source: a.id, text: a.failure_risk });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// ─── Files ─────────────────────────────────────────────────────────
|
|
766
|
+
const expected = [
|
|
767
|
+
'KDNA_Core.json',
|
|
768
|
+
'KDNA_Patterns.json',
|
|
769
|
+
'KDNA_Scenarios.json',
|
|
770
|
+
'KDNA_Cases.json',
|
|
771
|
+
'KDNA_Reasoning.json',
|
|
772
|
+
'KDNA_Evolution.json',
|
|
773
|
+
];
|
|
774
|
+
const present = expected.filter((f) => fs.existsSync(path.join(dest, f)));
|
|
775
|
+
|
|
776
|
+
// ─── JSON mode: emit structured output only, then exit ─────────────
|
|
777
|
+
if (jsonMode) {
|
|
778
|
+
const result = {
|
|
779
|
+
name: parsed.full,
|
|
780
|
+
version: manifest?.version || core?.meta?.version || '?',
|
|
781
|
+
judgment_version: manifest?.judgment_version || null,
|
|
782
|
+
status: manifest?.status || '?',
|
|
783
|
+
license: manifest?.license?.type || '?',
|
|
784
|
+
author: manifest?.author?.name || '?',
|
|
785
|
+
pubkey: manifest?.author?.pubkey || null,
|
|
786
|
+
has_pem: !!manifest?.author?.public_key_pem,
|
|
787
|
+
source_url: source.kdna_url || null,
|
|
788
|
+
sha256: source.sha256 || null,
|
|
789
|
+
installed_at: source.installed_at || null,
|
|
790
|
+
path: dest,
|
|
791
|
+
axioms: axiomCount,
|
|
792
|
+
ontology: ontologyCount,
|
|
793
|
+
stances: stanceCount,
|
|
794
|
+
misunderstandings: misCount,
|
|
795
|
+
self_checks: selfCheckCount,
|
|
796
|
+
governance,
|
|
797
|
+
evals: evalInfo,
|
|
798
|
+
risks: risks.slice(0, 10),
|
|
799
|
+
files: { present: present.length, total: expected.length, list: present },
|
|
800
|
+
};
|
|
801
|
+
console.log(JSON.stringify(result));
|
|
802
|
+
process.exit(EXIT.OK);
|
|
803
|
+
}
|
|
804
|
+
|
|
678
805
|
// ─── Header ─────────────────────────────────────────────────────
|
|
679
806
|
console.log('═'.repeat(64));
|
|
680
807
|
console.log(` ${parsed.full}`);
|
|
@@ -706,11 +833,6 @@ function cmdInfo(input) {
|
|
|
706
833
|
// ─── Judgment surface ──────────────────────────────────────────
|
|
707
834
|
console.log('');
|
|
708
835
|
console.log(' ── Judgment surface ──');
|
|
709
|
-
const axiomCount = (core?.axioms || []).length;
|
|
710
|
-
const ontologyCount = (core?.ontology || []).length;
|
|
711
|
-
const stanceCount = (core?.stances || []).length;
|
|
712
|
-
const misCount = (pat?.misunderstandings || []).length;
|
|
713
|
-
const selfCheckCount = (pat?.self_check || []).length;
|
|
714
836
|
console.log(` Axioms: ${axiomCount}`);
|
|
715
837
|
console.log(` Ontology: ${ontologyCount}`);
|
|
716
838
|
console.log(` Stances: ${stanceCount}`);
|
|
@@ -718,47 +840,24 @@ function cmdInfo(input) {
|
|
|
718
840
|
console.log(` Self-checks: ${selfCheckCount}`);
|
|
719
841
|
|
|
720
842
|
// ─── v2.1 governance score ─────────────────────────────────────
|
|
721
|
-
if (
|
|
722
|
-
const withApplies = (core?.axioms || []).filter(
|
|
723
|
-
(a) => Array.isArray(a.applies_when) && a.applies_when.length,
|
|
724
|
-
).length;
|
|
725
|
-
const withDoesNotApply = (core?.axioms || []).filter(
|
|
726
|
-
(a) => Array.isArray(a.does_not_apply_when) && a.does_not_apply_when.length,
|
|
727
|
-
).length;
|
|
728
|
-
const withFailureRisk = (core?.axioms || []).filter((a) => a.failure_risk).length;
|
|
729
|
-
const pct = Math.round(
|
|
730
|
-
((withApplies + withDoesNotApply + withFailureRisk) / (axiomCount * 3)) * 100,
|
|
731
|
-
);
|
|
843
|
+
if (governance) {
|
|
732
844
|
console.log('');
|
|
733
845
|
console.log(' ── v2.1 governance ──');
|
|
734
|
-
console.log(` axioms with applies_when: ${withApplies}/${axiomCount}`);
|
|
735
|
-
console.log(` axioms with does_not_apply: ${withDoesNotApply}/${axiomCount}`);
|
|
736
|
-
console.log(` axioms with failure_risk: ${withFailureRisk}/${axiomCount}`);
|
|
737
|
-
console.log(` governance coverage: ${
|
|
846
|
+
console.log(` axioms with applies_when: ${governance.withApplies}/${axiomCount}`);
|
|
847
|
+
console.log(` axioms with does_not_apply: ${governance.withDoesNotApply}/${axiomCount}`);
|
|
848
|
+
console.log(` axioms with failure_risk: ${governance.withFailureRisk}/${axiomCount}`);
|
|
849
|
+
console.log(` governance coverage: ${governance.coverage}%`);
|
|
738
850
|
}
|
|
739
851
|
|
|
740
852
|
// ─── Eval cases ────────────────────────────────────────────────
|
|
741
|
-
|
|
742
|
-
if (fs.existsSync(evalDir)) {
|
|
743
|
-
const evalFiles = fs.readdirSync(evalDir).filter((f) => f.endsWith('.json'));
|
|
744
|
-
let totalCases = 0;
|
|
745
|
-
for (const f of evalFiles) {
|
|
746
|
-
const data = readJson(path.join(evalDir, f));
|
|
747
|
-
if (data?.cases) totalCases += data.cases.length;
|
|
748
|
-
}
|
|
853
|
+
if (evalInfo) {
|
|
749
854
|
console.log('');
|
|
750
855
|
console.log(' ── Eval cases ──');
|
|
751
|
-
console.log(` Files: ${
|
|
752
|
-
console.log(` Total cases: ${totalCases}`);
|
|
856
|
+
console.log(` Files: ${evalInfo.files}`);
|
|
857
|
+
console.log(` Total cases: ${evalInfo.totalCases}`);
|
|
753
858
|
}
|
|
754
859
|
|
|
755
|
-
// ─── Known risks
|
|
756
|
-
const risks = [];
|
|
757
|
-
if (core?.axioms) {
|
|
758
|
-
for (const a of core.axioms) {
|
|
759
|
-
if (a.failure_risk) risks.push({ source: a.id, text: a.failure_risk });
|
|
760
|
-
}
|
|
761
|
-
}
|
|
860
|
+
// ─── Known risks ───────────────────────────────────────────────
|
|
762
861
|
if (risks.length) {
|
|
763
862
|
console.log('');
|
|
764
863
|
console.log(' ── Known failure risks ──');
|
|
@@ -771,15 +870,6 @@ function cmdInfo(input) {
|
|
|
771
870
|
}
|
|
772
871
|
|
|
773
872
|
// ─── Files ─────────────────────────────────────────────────────
|
|
774
|
-
const expected = [
|
|
775
|
-
'KDNA_Core.json',
|
|
776
|
-
'KDNA_Patterns.json',
|
|
777
|
-
'KDNA_Scenarios.json',
|
|
778
|
-
'KDNA_Cases.json',
|
|
779
|
-
'KDNA_Reasoning.json',
|
|
780
|
-
'KDNA_Evolution.json',
|
|
781
|
-
];
|
|
782
|
-
const present = expected.filter((f) => fs.existsSync(path.join(dest, f)));
|
|
783
873
|
console.log('');
|
|
784
874
|
console.log(` Files: ${present.length}/${expected.length} (${present.join(', ') || 'none'})`);
|
|
785
875
|
|
|
@@ -806,7 +896,7 @@ function cmdUpdate(input) {
|
|
|
806
896
|
try {
|
|
807
897
|
({ entry } = resolver.resolve(parsed.full));
|
|
808
898
|
} catch (e) {
|
|
809
|
-
error(e.message);
|
|
899
|
+
error(e.message, EXIT.REGISTRY_ERROR);
|
|
810
900
|
}
|
|
811
901
|
|
|
812
902
|
if (entry.version === installedVersion) {
|
package/src/publish.js
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const { EXIT } = require('./cmds/_common');
|
|
10
11
|
|
|
11
|
-
function error(msg) {
|
|
12
|
+
function error(msg, code = EXIT.VALIDATION_FAILED) {
|
|
12
13
|
console.error(`Error: ${msg}`);
|
|
13
|
-
process.exit(
|
|
14
|
+
process.exit(code);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function readJson(filePath) {
|
|
@@ -373,7 +374,7 @@ function cmdPublishCheck(domainPath) {
|
|
|
373
374
|
}
|
|
374
375
|
console.log('═'.repeat(60));
|
|
375
376
|
|
|
376
|
-
if (errors > 0) process.exit(
|
|
377
|
+
if (errors > 0) process.exit(EXIT.POLICY_VIOLATION);
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
// ═══════════════════════════════════════════════════════════════════════
|
package/src/search.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const { RegistryResolver } = require('./registry');
|
|
9
|
+
const { EXIT } = require('./cmds/_common');
|
|
9
10
|
|
|
10
11
|
function matchScore(d, q) {
|
|
11
12
|
const ql = q.toLowerCase();
|
|
@@ -23,17 +24,25 @@ function matchScore(d, q) {
|
|
|
23
24
|
return score;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function cmdSearch(query) {
|
|
27
|
+
function cmdSearch(query, json) {
|
|
27
28
|
if (!query) {
|
|
29
|
+
if (json) {
|
|
30
|
+
console.log(JSON.stringify({ error: 'Usage: kdna search <keyword>' }));
|
|
31
|
+
process.exit(EXIT.INPUT_ERROR);
|
|
32
|
+
}
|
|
28
33
|
console.error('Usage: kdna search <keyword>');
|
|
29
34
|
console.error(' kdna search "content strategy"');
|
|
30
|
-
process.exit(
|
|
35
|
+
process.exit(EXIT.INPUT_ERROR);
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
const resolver = new RegistryResolver({ allowNetwork: true });
|
|
34
39
|
const domains = resolver.listAllDomains() || [];
|
|
35
40
|
|
|
36
41
|
if (!domains.length) {
|
|
42
|
+
if (json) {
|
|
43
|
+
console.log(JSON.stringify([]));
|
|
44
|
+
process.exit(EXIT.OK);
|
|
45
|
+
}
|
|
37
46
|
console.log('No registry entries found. Run: kdna registry refresh');
|
|
38
47
|
return;
|
|
39
48
|
}
|
|
@@ -44,6 +53,10 @@ function cmdSearch(query) {
|
|
|
44
53
|
.sort((a, b) => b.score - a.score);
|
|
45
54
|
|
|
46
55
|
if (!matches.length) {
|
|
56
|
+
if (json) {
|
|
57
|
+
console.log(JSON.stringify([]));
|
|
58
|
+
process.exit(EXIT.OK);
|
|
59
|
+
}
|
|
47
60
|
console.log(`No domains match "${query}".`);
|
|
48
61
|
console.log('');
|
|
49
62
|
console.log('Try:');
|
|
@@ -51,6 +64,24 @@ function cmdSearch(query) {
|
|
|
51
64
|
return;
|
|
52
65
|
}
|
|
53
66
|
|
|
67
|
+
if (json) {
|
|
68
|
+
const result = matches.map(({ d, score }) => ({
|
|
69
|
+
name: d.name || d.id || null,
|
|
70
|
+
version: d.version || null,
|
|
71
|
+
type: d.type || 'domain',
|
|
72
|
+
description: d.description || null,
|
|
73
|
+
core_insight: d.core_insight || null,
|
|
74
|
+
keywords: d.keywords || [],
|
|
75
|
+
domain_field: d.domain_field || [],
|
|
76
|
+
judgment_patterns: d.judgment_patterns || [],
|
|
77
|
+
yanked: d.yanked || false,
|
|
78
|
+
deprecated: d.deprecated || false,
|
|
79
|
+
score,
|
|
80
|
+
}));
|
|
81
|
+
console.log(JSON.stringify(result));
|
|
82
|
+
process.exit(EXIT.OK);
|
|
83
|
+
}
|
|
84
|
+
|
|
54
85
|
console.log(`Found ${matches.length} matching domain(s) for "${query}":`);
|
|
55
86
|
console.log('');
|
|
56
87
|
|