@aikdna/kdna-cli 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/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(JSON.stringify({
151
- current_version: currentVersion,
152
- suggested_bump: changes.suggestion,
153
- reasons: changes.reasons,
154
- change_summary: changes.summary,
155
- }, null, 2));
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((a) => a.applies_when?.length && a.does_not_apply_when?.length).length;
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
- "kdna_spec": "1.0-rc",
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
- "language": "en",
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
- "kdna_file_count": 3,
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 | 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] |
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 | Purpose |
66
- |------|---------|
67
- | `KDNA_Core.json` | Axioms (with v2.1 boundaries), ontology, frameworks, causal structure, stances |
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/` | Test cases for `kdna compare` and quality scoring |
70
- | `kdna.json` | Domain manifest (name, version, judgment_version, signature) |
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
- "kdna_spec": "1.0",
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
- "language": "en",
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
- "files": ["KDNA_Core.json", "KDNA_Patterns.json"]
31
+ "risk_level": "R0"
28
32
  }
@@ -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
- let validateManifestFn;
61
- try {
62
- validateManifestFn = require('@aikdna/kdna-core').validateManifest;
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') {
@@ -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
- };