@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/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,16 +1,20 @@
|
|
|
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]",
|
|
12
15
|
"core_insight": "[one-sentence core insight that distinguishes this domain]",
|
|
13
16
|
"keywords": ["[add 3-6 keywords for kdna search]"],
|
|
17
|
+
"quality_badge": "untested",
|
|
14
18
|
"author": {
|
|
15
19
|
"name": "Your Name",
|
|
16
20
|
"id": "your-id",
|
|
@@ -24,5 +28,5 @@
|
|
|
24
28
|
"allow_training": true
|
|
25
29
|
},
|
|
26
30
|
"file_count": 2,
|
|
27
|
-
"
|
|
31
|
+
"risk_level": "R0"
|
|
28
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') {
|
package/src/cmds/encrypt.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KDNA Encryption — Encrypted container format (.kdnae) for licensed domains.
|
|
3
|
-
*
|
|
4
|
-
* .kdnae extends .kdna with AES-256-GCM encryption on KDNA JSON files.
|
|
5
|
-
* The kdna.json manifest and license.json stay in plaintext for discovery.
|
|
6
|
-
*
|
|
7
|
-
* Encryption key is derived via PBKDF2 from:
|
|
8
|
-
* license_key + machine_fingerprint
|
|
9
|
-
*
|
|
10
|
-
* This module provides pure-crypto functions (no filesystem I/O).
|
|
11
|
-
* File operations are in domain.js (pack/unpack) and install.js.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const crypto = require('crypto');
|
|
15
|
-
|
|
16
|
-
const ALGORITHM = 'aes-256-gcm';
|
|
17
|
-
const IV_LENGTH = 16; // 96-bit IV for GCM
|
|
18
|
-
const TAG_LENGTH = 16; // 128-bit auth tag
|
|
19
|
-
const SALT_LENGTH = 32;
|
|
20
|
-
const PBKDF2_ITERATIONS = 600000;
|
|
21
|
-
const KEY_LENGTH = 32; // AES-256
|
|
22
|
-
|
|
23
|
-
// Files in a .kdna container that get encrypted (JSON content only)
|
|
24
|
-
const ENCRYPTED_FILES = [
|
|
25
|
-
'KDNA_Core.json',
|
|
26
|
-
'KDNA_Patterns.json',
|
|
27
|
-
'KDNA_Scenarios.json',
|
|
28
|
-
'KDNA_Cases.json',
|
|
29
|
-
'KDNA_Reasoning.json',
|
|
30
|
-
'KDNA_Evolution.json',
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
// Files that always stay in plaintext
|
|
34
|
-
const PLAINTEXT_FILES = ['kdna.json', 'license.json', 'README.md', 'LICENSE', 'signature.json'];
|
|
35
|
-
|
|
36
|
-
function isEncryptable(filename) {
|
|
37
|
-
return ENCRYPTED_FILES.includes(filename);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── Machine Fingerprint ────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
function machineFingerprint() {
|
|
43
|
-
const os = require('os');
|
|
44
|
-
const parts = [os.hostname(), os.userInfo().uid.toString(), os.platform(), os.arch()];
|
|
45
|
-
// Try to get hardware UUID on macOS
|
|
46
|
-
try {
|
|
47
|
-
const { execSync } = require('child_process');
|
|
48
|
-
if (os.platform() === 'darwin') {
|
|
49
|
-
const uuid = execSync('ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID', {
|
|
50
|
-
encoding: 'utf8',
|
|
51
|
-
timeout: 3000,
|
|
52
|
-
})
|
|
53
|
-
.match(/"[A-F0-9-]{36}"/)?.[0]
|
|
54
|
-
?.replace(/"/g, '');
|
|
55
|
-
if (uuid) parts.push(uuid);
|
|
56
|
-
}
|
|
57
|
-
if (os.platform() === 'linux') {
|
|
58
|
-
try {
|
|
59
|
-
const mid = require('fs').readFileSync('/etc/machine-id', 'utf8').trim();
|
|
60
|
-
if (mid) parts.push(mid);
|
|
61
|
-
} catch {
|
|
62
|
-
/* ignore */
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
/* non-critical */
|
|
67
|
-
}
|
|
68
|
-
return crypto.createHash('sha256').update(parts.join('|')).digest('hex');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ─── Key Derivation ─────────────────────────────────────────────────────
|
|
72
|
-
|
|
73
|
-
function deriveKey(licenseKey, fingerprint) {
|
|
74
|
-
const salt = crypto
|
|
75
|
-
.createHash('sha256')
|
|
76
|
-
.update(fingerprint || machineFingerprint())
|
|
77
|
-
.digest();
|
|
78
|
-
return crypto.pbkdf2Sync(licenseKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ─── Encrypt / Decrypt ──────────────────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
function encrypt(plaintext, key) {
|
|
84
|
-
const iv = crypto.randomBytes(IV_LENGTH);
|
|
85
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
86
|
-
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
87
|
-
const tag = cipher.getAuthTag();
|
|
88
|
-
// Format: iv (16) + tag (16) + ciphertext
|
|
89
|
-
return Buffer.concat([iv, tag, encrypted]);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function decrypt(encryptedData, key) {
|
|
93
|
-
if (encryptedData.length < IV_LENGTH + TAG_LENGTH) {
|
|
94
|
-
throw new Error('Invalid encrypted data: too short');
|
|
95
|
-
}
|
|
96
|
-
const iv = encryptedData.subarray(0, IV_LENGTH);
|
|
97
|
-
const tag = encryptedData.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
98
|
-
const ciphertext = encryptedData.subarray(IV_LENGTH + TAG_LENGTH);
|
|
99
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
100
|
-
decipher.setAuthTag(tag);
|
|
101
|
-
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Encrypted Container Detection ──────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
function isEncryptedContainer(filePath) {
|
|
107
|
-
return filePath.endsWith('.kdnae');
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ─── License File ──────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
function createLicense(domain, options = {}) {
|
|
113
|
-
const {
|
|
114
|
-
issuedTo = 'licensee@example.com',
|
|
115
|
-
expiresAt = null, // ISO date or null for perpetual
|
|
116
|
-
maxAgents = 1,
|
|
117
|
-
requireMachineBinding = true,
|
|
118
|
-
requireOnlineCheck = false,
|
|
119
|
-
offlineGraceDays = 7,
|
|
120
|
-
} = options;
|
|
121
|
-
|
|
122
|
-
const license = {
|
|
123
|
-
version: '1.0',
|
|
124
|
-
license_id: `lic_${crypto.randomBytes(8).toString('hex')}`,
|
|
125
|
-
domain,
|
|
126
|
-
issued_to: issuedTo,
|
|
127
|
-
issued_at: new Date().toISOString(),
|
|
128
|
-
expires_at: expiresAt,
|
|
129
|
-
max_agents: maxAgents,
|
|
130
|
-
require_machine_binding: requireMachineBinding,
|
|
131
|
-
require_online_check: requireOnlineCheck,
|
|
132
|
-
offline_grace_days: offlineGraceDays,
|
|
133
|
-
allowed_agents: options.allowedAgents || ['claude_code', 'codex', 'opencode'],
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
return license;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function verifyLicense(license, scopePubkey, fingerprint) {
|
|
140
|
-
const issues = [];
|
|
141
|
-
|
|
142
|
-
// Check expiration
|
|
143
|
-
if (license.expires_at) {
|
|
144
|
-
if (new Date(license.expires_at) < new Date()) {
|
|
145
|
-
issues.push('License has expired');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check machine binding
|
|
150
|
-
if (license.require_machine_binding) {
|
|
151
|
-
if (license.machine_fingerprint && license.machine_fingerprint !== fingerprint) {
|
|
152
|
-
issues.push('Machine fingerprint mismatch');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
valid: issues.length === 0,
|
|
158
|
-
issues,
|
|
159
|
-
domain: license.domain,
|
|
160
|
-
license_id: license.license_id,
|
|
161
|
-
issued_to: license.issued_to,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function signLicense(license, privateKeyPem) {
|
|
166
|
-
const { signature: _, ...payload } = license;
|
|
167
|
-
const data = JSON.stringify(payload, Object.keys(payload).sort());
|
|
168
|
-
// Use crypto.sign() for Ed25519 (supported via PEM keys)
|
|
169
|
-
const sig = crypto.sign(null, Buffer.from(data), privateKeyPem);
|
|
170
|
-
license.signature = `ed25519:${sig.toString('hex')}`;
|
|
171
|
-
return license;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function verifyLicenseSignature(license, publicKeyPem) {
|
|
175
|
-
const signature = license.signature?.replace('ed25519:', '') || '';
|
|
176
|
-
if (!signature) return false;
|
|
177
|
-
const { signature: _, ...payload } = license;
|
|
178
|
-
const data = JSON.stringify(payload, Object.keys(payload).sort());
|
|
179
|
-
try {
|
|
180
|
-
return crypto.verify(null, Buffer.from(data), publicKeyPem, Buffer.from(signature, 'hex'));
|
|
181
|
-
} catch {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
module.exports = {
|
|
187
|
-
encrypt,
|
|
188
|
-
decrypt,
|
|
189
|
-
deriveKey,
|
|
190
|
-
machineFingerprint,
|
|
191
|
-
isEncryptable,
|
|
192
|
-
isEncryptedContainer,
|
|
193
|
-
createLicense,
|
|
194
|
-
verifyLicense,
|
|
195
|
-
signLicense,
|
|
196
|
-
verifyLicenseSignature,
|
|
197
|
-
ENCRYPTED_FILES,
|
|
198
|
-
PLAINTEXT_FILES,
|
|
199
|
-
};
|