@aikdna/kdna-cli 0.18.0 → 0.19.1
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 +101 -101
- package/SECURITY.md +1 -1
- package/package.json +3 -2
- package/skills/kdna-loader/SKILL.md +18 -16
- package/src/agent.js +152 -57
- package/src/cli.js +17 -11
- package/src/cmds/_common.js +9 -3
- package/src/cmds/badge.js +8 -3
- package/src/cmds/changelog.js +31 -11
- package/src/cmds/cluster.js +67 -40
- package/src/cmds/domain.js +37 -27
- package/src/cmds/explain.js +1 -4
- package/src/cmds/governance.js +111 -42
- package/src/cmds/legacy.js +1 -2
- package/src/cmds/license.js +16 -8
- package/src/cmds/quality.js +9 -2
- package/src/cmds/registry.js +11 -5
- package/src/cmds/studio.js +95 -42
- package/src/cmds/test.js +5 -2
- package/src/compare.js +15 -2
- package/src/diff.js +27 -11
- package/src/identity.js +9 -7
- package/src/install.js +17 -7
- package/src/package-store.js +4 -1
- package/src/paths.js +2 -2
- package/src/publish.js +90 -42
- package/src/registry.js +13 -8
- package/src/search.js +4 -5
- package/src/verify.js +61 -17
- 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 +6 -3
- package/validators/kdna-lint.js +45 -11
package/src/publish.js
CHANGED
|
@@ -53,17 +53,17 @@ const SLOGAN_PATTERNS = [
|
|
|
53
53
|
// PRIORITIZE or AVOID, not steps to follow.
|
|
54
54
|
|
|
55
55
|
const SOP_PATTERNS = [
|
|
56
|
-
/^Step\s+\d/i,
|
|
56
|
+
/^Step\s+\d/i, // "Step 1: identify the topic"
|
|
57
57
|
/^First,?\s|^Next,?\s|^Then,?\s|^Finally,?\s/i, // "First, do X. Then do Y."
|
|
58
|
-
/^Check\s(for|if|whether)\s/i,
|
|
58
|
+
/^Check\s(for|if|whether)\s/i, // "Check for spelling errors"
|
|
59
59
|
/^Always\s+(use|do|make|include)/i, // "Always use active voice"
|
|
60
|
-
/^Never\s+(use|do|make)/i,
|
|
61
|
-
/^Generate\s/i,
|
|
62
|
-
/^Create\s+(a|the)\s/i,
|
|
63
|
-
/^Make\s+sure\s/i,
|
|
64
|
-
/^Remember\s+to\s/i,
|
|
60
|
+
/^Never\s+(use|do|make)/i, // "Never use passive voice"
|
|
61
|
+
/^Generate\s/i, // "Generate three options"
|
|
62
|
+
/^Create\s+(a|the)\s/i, // "Create a list of..."
|
|
63
|
+
/^Make\s+sure\s/i, // "Make sure to check..."
|
|
64
|
+
/^Remember\s+to\s/i, // "Remember to validate..."
|
|
65
65
|
/^(You|The agent)\s+should\s+(use|do|make|include)/i, // "You should use X"
|
|
66
|
-
/^Avoid\s+(using|doing)/i,
|
|
66
|
+
/^Avoid\s+(using|doing)/i, // "Avoid using X" (too procedural)
|
|
67
67
|
];
|
|
68
68
|
|
|
69
69
|
function isSOP(text) {
|
|
@@ -188,13 +188,19 @@ function checkHumanLock(domainPath) {
|
|
|
188
188
|
// Rule 3: Lock must confirm judgment fields were reviewed
|
|
189
189
|
const checked = card.human_lock.checked || {};
|
|
190
190
|
if (!checked.applies_when) {
|
|
191
|
-
issues.push(
|
|
191
|
+
issues.push(
|
|
192
|
+
`${card.type} "${card.id}" Human Lock does not confirm applies_when was reviewed.`,
|
|
193
|
+
);
|
|
192
194
|
}
|
|
193
195
|
if (!checked.does_not_apply_when) {
|
|
194
|
-
issues.push(
|
|
196
|
+
issues.push(
|
|
197
|
+
`${card.type} "${card.id}" Human Lock does not confirm does_not_apply_when was reviewed.`,
|
|
198
|
+
);
|
|
195
199
|
}
|
|
196
200
|
if (!checked.failure_risk) {
|
|
197
|
-
issues.push(
|
|
201
|
+
issues.push(
|
|
202
|
+
`${card.type} "${card.id}" Human Lock does not confirm failure_risk was reviewed.`,
|
|
203
|
+
);
|
|
198
204
|
}
|
|
199
205
|
}
|
|
200
206
|
|
|
@@ -527,29 +533,24 @@ function identityPaths() {
|
|
|
527
533
|
}
|
|
528
534
|
|
|
529
535
|
/**
|
|
530
|
-
* Canonical signing payload: sorted (filename, sha256) pairs of all
|
|
531
|
-
* inside the .kdna ZIP, joined as `name:hex\n`.
|
|
536
|
+
* Canonical signing payload: sorted (filename, sha256) pairs of all published
|
|
537
|
+
* content entries inside the .kdna ZIP, joined as `name:hex\n`.
|
|
532
538
|
*
|
|
533
539
|
* Excludes the `signature` field from kdna.json itself (computed by removing it
|
|
534
540
|
* before hashing). Digest self-reference fields are also excluded. All other files included as-is.
|
|
535
541
|
*/
|
|
536
542
|
function canonicalPayload(srcDir, opts = {}) {
|
|
537
|
-
const files =
|
|
538
|
-
.readdirSync(srcDir)
|
|
539
|
-
.filter((f) => f.endsWith('.json'))
|
|
540
|
-
.sort();
|
|
543
|
+
const files = listPublishEntries(srcDir);
|
|
541
544
|
const parts = [];
|
|
542
545
|
for (const f of files) {
|
|
543
|
-
const full = path.join(srcDir, f);
|
|
546
|
+
const full = f === 'mimetype' ? null : path.join(srcDir, f);
|
|
544
547
|
let buf;
|
|
545
|
-
if (f === '
|
|
548
|
+
if (f === 'mimetype') {
|
|
549
|
+
buf = Buffer.from('application/vnd.aikdna.kdna+zip');
|
|
550
|
+
} else if (f.endsWith('.json')) {
|
|
546
551
|
const obj = JSON.parse(fs.readFileSync(full, 'utf8'));
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
delete obj.container_sha256;
|
|
550
|
-
if (!opts.includeContentDigest) delete obj.content_digest;
|
|
551
|
-
delete obj._source;
|
|
552
|
-
buf = Buffer.from(JSON.stringify(obj));
|
|
552
|
+
const value = f === 'kdna.json' ? manifestForSigning(obj, opts) : obj;
|
|
553
|
+
buf = Buffer.from(stableStringify(value));
|
|
553
554
|
} else {
|
|
554
555
|
buf = fs.readFileSync(full);
|
|
555
556
|
}
|
|
@@ -559,6 +560,16 @@ function canonicalPayload(srcDir, opts = {}) {
|
|
|
559
560
|
return parts.join('\n');
|
|
560
561
|
}
|
|
561
562
|
|
|
563
|
+
function manifestForSigning(manifest, opts = {}) {
|
|
564
|
+
const copy = { ...(manifest || {}) };
|
|
565
|
+
delete copy.signature;
|
|
566
|
+
delete copy.asset_digest;
|
|
567
|
+
delete copy.container_sha256;
|
|
568
|
+
if (!opts.includeContentDigest) delete copy.content_digest;
|
|
569
|
+
delete copy._source;
|
|
570
|
+
return copy;
|
|
571
|
+
}
|
|
572
|
+
|
|
562
573
|
function stableStringify(value) {
|
|
563
574
|
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
|
|
564
575
|
if (value && typeof value === 'object') {
|
|
@@ -581,22 +592,55 @@ function manifestForContentDigest(manifest) {
|
|
|
581
592
|
}
|
|
582
593
|
|
|
583
594
|
function sourceContentDigest(srcDir) {
|
|
584
|
-
const files =
|
|
585
|
-
.readdirSync(srcDir)
|
|
586
|
-
.filter((f) => f.endsWith('.json') || f === 'README.md' || f === 'LICENSE')
|
|
587
|
-
.sort();
|
|
595
|
+
const files = listPublishEntries(srcDir);
|
|
588
596
|
const parts = [];
|
|
589
597
|
for (const f of files) {
|
|
590
598
|
let buf;
|
|
591
|
-
if (f === '
|
|
599
|
+
if (f === 'mimetype') {
|
|
600
|
+
buf = Buffer.from('application/vnd.aikdna.kdna+zip');
|
|
601
|
+
} else if (f.endsWith('.json')) {
|
|
592
602
|
const obj = JSON.parse(fs.readFileSync(path.join(srcDir, f), 'utf8'));
|
|
593
|
-
|
|
603
|
+
const value = f === 'kdna.json' ? manifestForContentDigest(obj) : obj;
|
|
604
|
+
buf = Buffer.from(stableStringify(value));
|
|
594
605
|
} else {
|
|
595
606
|
buf = fs.readFileSync(path.join(srcDir, f));
|
|
596
607
|
}
|
|
597
608
|
parts.push(`${f}:${crypto.createHash('sha256').update(buf).digest('hex')}`);
|
|
598
609
|
}
|
|
599
|
-
return `sha256:${crypto
|
|
610
|
+
return `sha256:${crypto
|
|
611
|
+
.createHash('sha256')
|
|
612
|
+
.update(Buffer.from(parts.join('\n')))
|
|
613
|
+
.digest('hex')}`;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function listPublishEntries(domainDir) {
|
|
617
|
+
const entries = ['mimetype'];
|
|
618
|
+
const skipDirs = new Set(['.git', 'node_modules', 'dist']);
|
|
619
|
+
function walk(dir, prefix = '') {
|
|
620
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
621
|
+
if (name === 'mimetype') continue;
|
|
622
|
+
if (name === '.DS_Store' || name === 'signature.json') continue;
|
|
623
|
+
const abs = path.join(dir, name);
|
|
624
|
+
const rel = prefix ? `${prefix}/${name}` : name;
|
|
625
|
+
const stat = fs.statSync(abs);
|
|
626
|
+
if (stat.isDirectory()) {
|
|
627
|
+
if (!skipDirs.has(name)) walk(abs, rel);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (
|
|
631
|
+
rel.endsWith('.json') ||
|
|
632
|
+
rel === 'README.md' ||
|
|
633
|
+
rel === 'LICENSE' ||
|
|
634
|
+
rel.startsWith('evals/') ||
|
|
635
|
+
rel.startsWith('examples/') ||
|
|
636
|
+
rel.startsWith('reports/')
|
|
637
|
+
) {
|
|
638
|
+
entries.push(rel);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
walk(domainDir);
|
|
643
|
+
return entries;
|
|
600
644
|
}
|
|
601
645
|
|
|
602
646
|
function signPayload(payload, privateKeyPem) {
|
|
@@ -623,17 +667,17 @@ function publicKeyToScopeFormat(publicKeyPem) {
|
|
|
623
667
|
}
|
|
624
668
|
|
|
625
669
|
function packToFile(domainDir, outPath) {
|
|
626
|
-
const files =
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (!files.includes('kdna.json')) error('kdna.json required in dev source directory for publish.');
|
|
670
|
+
const files = listPublishEntries(domainDir).filter((f) => f !== 'mimetype');
|
|
671
|
+
if (!files.includes('kdna.json'))
|
|
672
|
+
error('kdna.json required in dev source directory for publish.');
|
|
630
673
|
|
|
631
674
|
const script = `import zipfile, os
|
|
632
675
|
src = ${JSON.stringify(domainDir)}
|
|
633
676
|
out = ${JSON.stringify(outPath)}
|
|
634
677
|
files = ${JSON.stringify(files)}
|
|
635
678
|
with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as zf:
|
|
636
|
-
|
|
679
|
+
zf.writestr(zipfile.ZipInfo('mimetype'), 'application/vnd.aikdna.kdna+zip', compress_type=zipfile.ZIP_STORED)
|
|
680
|
+
for f in files:
|
|
637
681
|
zf.write(os.path.join(src, f), f)
|
|
638
682
|
`;
|
|
639
683
|
const tmpPy = `/tmp/kdna-publish-pack-${Date.now()}.py`;
|
|
@@ -729,13 +773,11 @@ function cmdPublish(domainPath, args = []) {
|
|
|
729
773
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
730
774
|
manifest.content_digest = sourceContentDigest(abs);
|
|
731
775
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
732
|
-
const signedPayload = canonicalPayload(abs
|
|
776
|
+
const signedPayload = canonicalPayload(abs);
|
|
733
777
|
const sig = signPayload(signedPayload, privateKey);
|
|
734
778
|
manifest.signature = 'ed25519:' + sig;
|
|
735
779
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
736
|
-
console.log(
|
|
737
|
-
` ✓ Signed (payload covers ${fs.readdirSync(abs).filter((f) => f.endsWith('.json')).length} json files)`,
|
|
738
|
-
);
|
|
780
|
+
console.log(` ✓ Signed (payload covers ${listPublishEntries(abs).length} content entries)`);
|
|
739
781
|
|
|
740
782
|
// 3. Pack
|
|
741
783
|
const fileName = `${m[2]}-${manifest.version}.kdna`;
|
|
@@ -795,4 +837,10 @@ function cmdPublish(domainPath, args = []) {
|
|
|
795
837
|
);
|
|
796
838
|
}
|
|
797
839
|
|
|
798
|
-
module.exports = {
|
|
840
|
+
module.exports = {
|
|
841
|
+
cmdPublishCheck,
|
|
842
|
+
cmdPublish,
|
|
843
|
+
checkHumanLock,
|
|
844
|
+
canonicalPayload,
|
|
845
|
+
publicKeyToScopeFormat,
|
|
846
|
+
};
|
package/src/registry.js
CHANGED
|
@@ -69,7 +69,8 @@ function registryTrustIssues(registry, { now = new Date() } = {}) {
|
|
|
69
69
|
const snapshotExpires = parseDate(trust.snapshot?.expires_at);
|
|
70
70
|
const timestampExpires = parseDate(trust.timestamp?.expires_at);
|
|
71
71
|
if (!snapshotExpires) issues.push('registry.trust.snapshot.expires_at must be an ISO timestamp');
|
|
72
|
-
if (!timestampExpires)
|
|
72
|
+
if (!timestampExpires)
|
|
73
|
+
issues.push('registry.trust.timestamp.expires_at must be an ISO timestamp');
|
|
73
74
|
if (snapshotExpires && snapshotExpires <= now) {
|
|
74
75
|
issues.push(`registry snapshot expired at ${trust.snapshot.expires_at}`);
|
|
75
76
|
}
|
|
@@ -86,12 +87,14 @@ function registryRevocations(registry) {
|
|
|
86
87
|
|
|
87
88
|
function isEntryRevoked(registry, entry) {
|
|
88
89
|
const revocations = registryRevocations(registry);
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
return (
|
|
91
|
+
revocations.find((rev) => {
|
|
92
|
+
if (rev.name && rev.name !== entry.name) return false;
|
|
93
|
+
if (rev.version && rev.version !== entry.version) return false;
|
|
94
|
+
if (rev.asset_digest && rev.asset_digest !== entry.asset_digest) return false;
|
|
95
|
+
return rev.name || rev.asset_digest;
|
|
96
|
+
}) || null
|
|
97
|
+
);
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
// ─── Name parsing ───────────────────────────────────────────────────────
|
|
@@ -213,7 +216,9 @@ class RegistryResolver {
|
|
|
213
216
|
trustIssues = data ? registryTrustIssues(data) : [];
|
|
214
217
|
}
|
|
215
218
|
if (trustIssues.length) {
|
|
216
|
-
throw new Error(
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Registry trust check failed:\n${trustIssues.map((i) => `- ${i}`).join('\n')}`,
|
|
221
|
+
);
|
|
217
222
|
}
|
|
218
223
|
this._registries.set(scopeName, data);
|
|
219
224
|
return data;
|
package/src/search.js
CHANGED
|
@@ -36,14 +36,14 @@ function cmdSearch(query, json) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const resolver = new RegistryResolver({ allowNetwork: true });
|
|
39
|
-
const domains = resolver.listAllDomains() || [];
|
|
39
|
+
const domains = (resolver.listAllDomains() || []).filter((d) => d.yanked !== true);
|
|
40
40
|
|
|
41
41
|
if (!domains.length) {
|
|
42
42
|
if (json) {
|
|
43
43
|
console.log(JSON.stringify([]));
|
|
44
44
|
process.exit(EXIT.OK);
|
|
45
45
|
}
|
|
46
|
-
console.log('No registry entries found. Run: kdna registry refresh');
|
|
46
|
+
console.log('No installable registry entries found. Run: kdna registry refresh');
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -74,7 +74,7 @@ function cmdSearch(query, json) {
|
|
|
74
74
|
keywords: d.keywords || [],
|
|
75
75
|
domain_field: d.domain_field || [],
|
|
76
76
|
judgment_patterns: d.judgment_patterns || [],
|
|
77
|
-
yanked:
|
|
77
|
+
yanked: false,
|
|
78
78
|
deprecated: d.deprecated || false,
|
|
79
79
|
score,
|
|
80
80
|
}));
|
|
@@ -86,10 +86,9 @@ function cmdSearch(query, json) {
|
|
|
86
86
|
console.log('');
|
|
87
87
|
|
|
88
88
|
for (const { d, score } of matches) {
|
|
89
|
-
const yanked = d.yanked ? ' [yanked]' : '';
|
|
90
89
|
const dep = d.deprecated ? ' [deprecated]' : '';
|
|
91
90
|
console.log(
|
|
92
|
-
` ${(d.name || d.id || '?').padEnd(36)} v${d.version || '?'} ${(d.type || 'domain').padEnd(8)} score:${score}${
|
|
91
|
+
` ${(d.name || d.id || '?').padEnd(36)} v${d.version || '?'} ${(d.type || 'domain').padEnd(8)} score:${score}${dep}`,
|
|
93
92
|
);
|
|
94
93
|
if (d.description) console.log(` ${d.description}`);
|
|
95
94
|
if (d.core_insight) console.log(` » ${d.core_insight}`);
|
package/src/verify.js
CHANGED
|
@@ -21,13 +21,6 @@ const { RegistryResolver, parseName, registryTrustIssues, isEntryRevoked } = req
|
|
|
21
21
|
const { EXIT, isYesNoSelfCheck } = require('./cmds/_common');
|
|
22
22
|
const { licenseDecryptOptionsForManifest } = require('./cmds/license');
|
|
23
23
|
|
|
24
|
-
let validateManifestFn;
|
|
25
|
-
try {
|
|
26
|
-
validateManifestFn = require('@aikdna/kdna-core').validateManifest;
|
|
27
|
-
} catch {
|
|
28
|
-
// kdna-core not available — manifest validation skipped
|
|
29
|
-
}
|
|
30
|
-
|
|
31
24
|
const {
|
|
32
25
|
getInstalled,
|
|
33
26
|
listContainerEntries,
|
|
@@ -37,6 +30,50 @@ const {
|
|
|
37
30
|
verifyAsset,
|
|
38
31
|
} = require('./package-store');
|
|
39
32
|
|
|
33
|
+
function validateManifestFn(manifest) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const required = [
|
|
37
|
+
'format',
|
|
38
|
+
'format_version',
|
|
39
|
+
'spec_version',
|
|
40
|
+
'name',
|
|
41
|
+
'version',
|
|
42
|
+
'judgment_version',
|
|
43
|
+
'description',
|
|
44
|
+
'author',
|
|
45
|
+
'license',
|
|
46
|
+
'status',
|
|
47
|
+
'quality_badge',
|
|
48
|
+
'access',
|
|
49
|
+
'languages',
|
|
50
|
+
'default_language',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
if (manifest.kdna_spec) errors.push('kdna.json: kdna_spec is not allowed. Use spec_version.');
|
|
54
|
+
if (manifest.language)
|
|
55
|
+
errors.push('kdna.json: language is not allowed. Use default_language and languages.');
|
|
56
|
+
for (const field of required) {
|
|
57
|
+
if (!(field in manifest) || manifest[field] === undefined || manifest[field] === '') {
|
|
58
|
+
errors.push(`kdna.json: missing required field "${field}"`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (manifest.format && manifest.format !== 'kdna') {
|
|
62
|
+
errors.push(`kdna.json.format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
63
|
+
}
|
|
64
|
+
if (manifest.format_version && manifest.format_version !== '1.0') {
|
|
65
|
+
errors.push(
|
|
66
|
+
`kdna.json.format_version: invalid value "${manifest.format_version}". Expected "1.0".`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (manifest.spec_version && manifest.spec_version !== '1.0-rc') {
|
|
70
|
+
warnings.push(
|
|
71
|
+
`kdna.json.spec_version: non-standard value "${manifest.spec_version}". Expected "1.0-rc".`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return { errors, warnings };
|
|
75
|
+
}
|
|
76
|
+
|
|
40
77
|
function readJson(p) {
|
|
41
78
|
try {
|
|
42
79
|
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
@@ -672,15 +709,21 @@ function cmdVerify(input, args = []) {
|
|
|
672
709
|
}
|
|
673
710
|
const { checkTrust: agentCheckTrust } = require('./agent');
|
|
674
711
|
const trust = agentCheckTrust(parsed.full);
|
|
675
|
-
console.log(
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
712
|
+
console.log(
|
|
713
|
+
JSON.stringify(
|
|
714
|
+
{
|
|
715
|
+
domain: parsed.full,
|
|
716
|
+
passed: trust.passed,
|
|
717
|
+
failures: trust.failures,
|
|
718
|
+
warnings: trust.warnings,
|
|
719
|
+
risk_level: trust.riskLevel,
|
|
720
|
+
spec_version: trust.specVersion,
|
|
721
|
+
signature_valid: trust.signatureValid,
|
|
722
|
+
},
|
|
723
|
+
null,
|
|
724
|
+
2,
|
|
725
|
+
),
|
|
726
|
+
);
|
|
684
727
|
process.exit(trust.passed ? 0 : EXIT.TRUST_FAILED);
|
|
685
728
|
}
|
|
686
729
|
|
|
@@ -743,7 +786,8 @@ function cmdVerify(input, args = []) {
|
|
|
743
786
|
? manifest.encryption.encrypted_entries
|
|
744
787
|
: [];
|
|
745
788
|
const requiresProtectedRead =
|
|
746
|
-
encryptedEntries.length > 0 &&
|
|
789
|
+
encryptedEntries.length > 0 &&
|
|
790
|
+
(want.structure || want.judgment || want.i18n || want.governance);
|
|
747
791
|
if (requiresProtectedRead) {
|
|
748
792
|
const licensed = licenseDecryptOptionsForManifest(manifest);
|
|
749
793
|
if (licensed.ok) {
|
package/src/version.js
CHANGED
|
@@ -147,12 +147,18 @@ function cmdVersionSuggest(domainPath = '.', args = []) {
|
|
|
147
147
|
const changes = detectChanges(abs);
|
|
148
148
|
|
|
149
149
|
if (jsonMode) {
|
|
150
|
-
console.log(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
150
|
+
console.log(
|
|
151
|
+
JSON.stringify(
|
|
152
|
+
{
|
|
153
|
+
current_version: currentVersion,
|
|
154
|
+
suggested_bump: changes.suggestion,
|
|
155
|
+
reasons: changes.reasons,
|
|
156
|
+
change_summary: changes.summary,
|
|
157
|
+
},
|
|
158
|
+
null,
|
|
159
|
+
2,
|
|
160
|
+
),
|
|
161
|
+
);
|
|
156
162
|
return;
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -194,7 +200,9 @@ function detectChanges(domainPath) {
|
|
|
194
200
|
// Count axioms with applies_when (v2.1 governance) vs without
|
|
195
201
|
if (core?.axioms) {
|
|
196
202
|
const total = core.axioms.length;
|
|
197
|
-
const governed = core.axioms.filter(
|
|
203
|
+
const governed = core.axioms.filter(
|
|
204
|
+
(a) => a.applies_when?.length && a.does_not_apply_when?.length,
|
|
205
|
+
).length;
|
|
198
206
|
if (governed < total) {
|
|
199
207
|
axiomChanges = total - governed;
|
|
200
208
|
reasons.push(`${axiomChanges} axioms missing v2.1 governance fields`);
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"format": "kdna",
|
|
3
|
+
"format_version": "1.0",
|
|
4
|
+
"spec_version": "1.0-rc",
|
|
3
5
|
"name": "@aikdna/example_domain",
|
|
4
6
|
"version": "0.1.0",
|
|
5
|
-
"
|
|
7
|
+
"judgment_version": "0.1.0",
|
|
6
8
|
"languages": ["en"],
|
|
9
|
+
"default_language": "en",
|
|
7
10
|
"created": "YYYY-MM-DD",
|
|
8
11
|
"updated": "YYYY-MM-DD",
|
|
9
12
|
"description": "[TODO: describe what judgment this domain improves in one sentence]",
|
|
@@ -11,6 +14,7 @@
|
|
|
11
14
|
"keywords": ["[TODO: add relevant keywords]"],
|
|
12
15
|
"access": "open",
|
|
13
16
|
"status": "experimental",
|
|
17
|
+
"quality_badge": "untested",
|
|
14
18
|
"author": {
|
|
15
19
|
"name": "Your Name",
|
|
16
20
|
"id": "your-id"
|
|
@@ -22,10 +26,6 @@
|
|
|
22
26
|
"allow_redistribution": true,
|
|
23
27
|
"allow_training": true
|
|
24
28
|
},
|
|
25
|
-
"registry": {
|
|
26
|
-
"repo": "https://github.com/your-org/your-repo"
|
|
27
|
-
},
|
|
28
29
|
"file_count": 3,
|
|
29
|
-
"
|
|
30
|
-
"files": ["KDNA_Core.json", "KDNA_Patterns.json", "tests/before-after.json"]
|
|
30
|
+
"risk_level": "R0"
|
|
31
31
|
}
|
|
@@ -54,20 +54,20 @@ If any of the above is true, the agent should decline to load this domain.
|
|
|
54
54
|
|
|
55
55
|
## Known Failure Risks
|
|
56
56
|
|
|
57
|
-
| Risk
|
|
58
|
-
|
|
59
|
-
| [risk 1 from axiom_one.failure_risk]
|
|
60
|
-
| [risk 2 from axiom_two.failure_risk]
|
|
61
|
-
| [risk 3 from misread_one.failure_risk] | [trigger]
|
|
57
|
+
| Risk | When it shows up |
|
|
58
|
+
| -------------------------------------- | ---------------- |
|
|
59
|
+
| [risk 1 from axiom_one.failure_risk] | [trigger] |
|
|
60
|
+
| [risk 2 from axiom_two.failure_risk] | [trigger] |
|
|
61
|
+
| [risk 3 from misread_one.failure_risk] | [trigger] |
|
|
62
62
|
|
|
63
63
|
## Files
|
|
64
64
|
|
|
65
|
-
| File
|
|
66
|
-
|
|
67
|
-
| `KDNA_Core.json`
|
|
65
|
+
| File | Purpose |
|
|
66
|
+
| -------------------- | -------------------------------------------------------------------------------- |
|
|
67
|
+
| `KDNA_Core.json` | Axioms (with v2.1 boundaries), ontology, frameworks, causal structure, stances |
|
|
68
68
|
| `KDNA_Patterns.json` | Terminology, banned terms, misunderstandings (with v2.1 boundaries), self-checks |
|
|
69
|
-
| `evals/`
|
|
70
|
-
| `kdna.json`
|
|
69
|
+
| `evals/` | Test cases for `kdna compare` and quality scoring |
|
|
70
|
+
| `kdna.json` | Domain manifest (name, version, judgment_version, signature) |
|
|
71
71
|
|
|
72
72
|
## License
|
|
73
73
|
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
2
|
+
"format": "kdna",
|
|
3
|
+
"format_version": "1.0",
|
|
4
|
+
"spec_version": "1.0-rc",
|
|
3
5
|
"name": "@yourscope/your_domain_id",
|
|
4
6
|
"version": "0.1.0",
|
|
5
7
|
"judgment_version": "YYYY.MM",
|
|
6
8
|
"status": "experimental",
|
|
7
9
|
"access": "open",
|
|
8
|
-
"
|
|
10
|
+
"languages": ["en"],
|
|
11
|
+
"default_language": "en",
|
|
9
12
|
"created": "YYYY-MM-DD",
|
|
10
13
|
"updated": "YYYY-MM-DD",
|
|
11
14
|
"description": "[one-sentence description; appears on registry and in install output]",
|
|
@@ -25,5 +28,5 @@
|
|
|
25
28
|
"allow_training": true
|
|
26
29
|
},
|
|
27
30
|
"file_count": 2,
|
|
28
|
-
"
|
|
31
|
+
"risk_level": "R0"
|
|
29
32
|
}
|
package/validators/kdna-lint.js
CHANGED
|
@@ -51,23 +51,57 @@ for (const f of files) {
|
|
|
51
51
|
|
|
52
52
|
const result = lintDomain(dataMap);
|
|
53
53
|
|
|
54
|
+
function validateManifest(manifest) {
|
|
55
|
+
const errors = [];
|
|
56
|
+
const warnings = [];
|
|
57
|
+
const required = [
|
|
58
|
+
'format',
|
|
59
|
+
'format_version',
|
|
60
|
+
'spec_version',
|
|
61
|
+
'name',
|
|
62
|
+
'version',
|
|
63
|
+
'judgment_version',
|
|
64
|
+
'description',
|
|
65
|
+
'author',
|
|
66
|
+
'license',
|
|
67
|
+
'status',
|
|
68
|
+
'quality_badge',
|
|
69
|
+
'access',
|
|
70
|
+
'languages',
|
|
71
|
+
'default_language',
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
if (manifest.kdna_spec) errors.push('kdna_spec is not allowed. Use spec_version.');
|
|
75
|
+
if (manifest.language)
|
|
76
|
+
errors.push('language is not allowed. Use default_language and languages.');
|
|
77
|
+
for (const field of required) {
|
|
78
|
+
if (!(field in manifest) || manifest[field] === undefined || manifest[field] === '') {
|
|
79
|
+
errors.push(`missing required field "${field}"`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (manifest.format && manifest.format !== 'kdna') {
|
|
83
|
+
errors.push(`format: invalid value "${manifest.format}". Expected "kdna".`);
|
|
84
|
+
}
|
|
85
|
+
if (manifest.format_version && manifest.format_version !== '1.0') {
|
|
86
|
+
errors.push(`format_version: invalid value "${manifest.format_version}". Expected "1.0".`);
|
|
87
|
+
}
|
|
88
|
+
if (manifest.spec_version && manifest.spec_version !== '1.0-rc') {
|
|
89
|
+
warnings.push(
|
|
90
|
+
`spec_version: non-standard value "${manifest.spec_version}". Expected "1.0-rc".`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return { errors, warnings };
|
|
94
|
+
}
|
|
95
|
+
|
|
54
96
|
// Also validate kdna.json manifest if present and validateManifest is available
|
|
55
97
|
let manifestPath;
|
|
56
98
|
try {
|
|
57
99
|
manifestPath = path.join(domainDir, 'kdna.json');
|
|
58
100
|
if (fs.existsSync(manifestPath)) {
|
|
59
101
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
} catch {
|
|
64
|
-
// validateManifest not yet available in installed kdna-core — skip manifest check
|
|
65
|
-
}
|
|
66
|
-
if (validateManifestFn) {
|
|
67
|
-
const mResult = validateManifestFn(manifest);
|
|
68
|
-
for (const e of mResult.errors) result.errors.push(`kdna.json: ${e}`);
|
|
69
|
-
for (const w of mResult.warnings) result.warnings.push(`kdna.json: ${w}`);
|
|
70
|
-
}
|
|
102
|
+
const mResult = validateManifest(manifest);
|
|
103
|
+
for (const e of mResult.errors) result.errors.push(`kdna.json: ${e}`);
|
|
104
|
+
for (const w of mResult.warnings) result.warnings.push(`kdna.json: ${w}`);
|
|
71
105
|
}
|
|
72
106
|
} catch (e) {
|
|
73
107
|
if (e.code !== 'ENOENT') {
|