@aikdna/kdna-cli 0.9.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.
Files changed (46) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +73 -0
  4. package/package.json +58 -0
  5. package/skills/kdna-loader/SKILL.md +257 -0
  6. package/src/agent.js +434 -0
  7. package/src/cli.js +260 -0
  8. package/src/cluster.js +235 -0
  9. package/src/cmds/_common.js +100 -0
  10. package/src/cmds/cluster.js +235 -0
  11. package/src/cmds/domain.js +638 -0
  12. package/src/cmds/identity.js +31 -0
  13. package/src/cmds/legacy.js +83 -0
  14. package/src/cmds/quality.js +87 -0
  15. package/src/cmds/registry.js +114 -0
  16. package/src/cmds/setup.js +8 -0
  17. package/src/compare.js +324 -0
  18. package/src/diff.js +288 -0
  19. package/src/identity.js +211 -0
  20. package/src/init.js +168 -0
  21. package/src/install.js +849 -0
  22. package/src/loader.js +70 -0
  23. package/src/publish.js +600 -0
  24. package/src/registry.js +258 -0
  25. package/src/search.js +73 -0
  26. package/src/setup.js +197 -0
  27. package/src/verify.js +423 -0
  28. package/src/version.js +112 -0
  29. package/templates/cluster/KDNA_Cluster.json +25 -0
  30. package/templates/cluster/README.md +32 -0
  31. package/templates/minimal-domain/KDNA_Core.json +54 -0
  32. package/templates/minimal-domain/KDNA_Patterns.json +37 -0
  33. package/templates/minimal-domain/kdna.json +31 -0
  34. package/templates/minimal-domain/tests/before-after.json +16 -0
  35. package/templates/standard-domain/KDNA_Core.json +76 -0
  36. package/templates/standard-domain/KDNA_Patterns.json +44 -0
  37. package/templates/standard-domain/README.md +74 -0
  38. package/templates/standard-domain/USAGE.md +59 -0
  39. package/templates/standard-domain/evals/1_excluded_case.json +16 -0
  40. package/templates/standard-domain/evals/3_boundary_cases.json +38 -0
  41. package/templates/standard-domain/evals/3_core_cases.json +35 -0
  42. package/templates/standard-domain/evals/3_failure_cases.json +35 -0
  43. package/templates/standard-domain/evals/scoring.json +60 -0
  44. package/templates/standard-domain/kdna.json +28 -0
  45. package/validators/kdna-lint.js +53 -0
  46. package/validators/kdna-validate.js +92 -0
@@ -0,0 +1,638 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+ const { error, readJson, writeJson } = require('./_common');
5
+
6
+ // ─── Validate ────────────────────────────────────────────────────────
7
+
8
+ function cmdValidate(dir, schemaOnly) {
9
+ const abs = path.resolve(dir);
10
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
11
+ error(`Not a directory: ${abs}`);
12
+ }
13
+
14
+ const { lintDomain, validateDomainSchema, validateCrossFile } = require('@aikdna/kdna-core');
15
+
16
+ // Resolve schemas from @aikdna/kdna-core package
17
+ const SCHEMA_DIR = path.join(
18
+ path.dirname(require.resolve('@aikdna/kdna-core/package.json')),
19
+ 'schema',
20
+ );
21
+
22
+ // Read all KDNA JSON files
23
+ const files = fs.readdirSync(abs).filter((f) => f.endsWith('.json') && f !== 'kdna.json');
24
+ const dataMap = {};
25
+ const schemaMap = {};
26
+
27
+ for (const f of files) {
28
+ try {
29
+ dataMap[f] = JSON.parse(fs.readFileSync(path.join(abs, f), 'utf8'));
30
+ } catch (e) {
31
+ dataMap[f] = null;
32
+ console.error(` JSON parse error in ${f}: ${e.message}`);
33
+ }
34
+ }
35
+
36
+ // Schema validation — always load all available schemas
37
+ const FILE_TO_SCHEMA = {
38
+ 'KDNA_Core.json': 'KDNA_Core.schema.json',
39
+ 'KDNA_Patterns.json': 'KDNA_Patterns.schema.json',
40
+ 'KDNA_Scenarios.json': 'KDNA_Scenarios.schema.json',
41
+ 'KDNA_Cases.json': 'KDNA_Cases.schema.json',
42
+ 'KDNA_Reasoning.json': 'KDNA_Reasoning.schema.json',
43
+ 'KDNA_Evolution.json': 'KDNA_Evolution.schema.json',
44
+ };
45
+
46
+ const loadedSchemas = [];
47
+ const missingSchemas = [];
48
+ for (const [, schemaFile] of Object.entries(FILE_TO_SCHEMA)) {
49
+ const schemaPath = path.join(SCHEMA_DIR, schemaFile);
50
+ if (fs.existsSync(schemaPath)) {
51
+ try {
52
+ schemaMap[schemaFile] = JSON.parse(fs.readFileSync(schemaPath, 'utf8'));
53
+ loadedSchemas.push(schemaFile);
54
+ } catch {
55
+ missingSchemas.push(schemaFile);
56
+ }
57
+ } else {
58
+ missingSchemas.push(schemaFile);
59
+ }
60
+ }
61
+
62
+ if (missingSchemas.length) {
63
+ console.log(
64
+ ` Note: ${missingSchemas.length} schema file(s) not found (optional file schemas): ${missingSchemas.join(', ')}`,
65
+ );
66
+ console.log(` Schema dir: ${SCHEMA_DIR}`);
67
+ }
68
+
69
+ // Validation layers
70
+ const errors = [];
71
+ const warnings = [];
72
+
73
+ // Layer 1: Lint (structural + content checks)
74
+ if (!schemaOnly) {
75
+ const lintResult = lintDomain(dataMap);
76
+ errors.push(...lintResult.errors);
77
+ warnings.push(...lintResult.warnings);
78
+ }
79
+
80
+ // Layer 2: JSON Schema validation against loaded schemas
81
+ const schemaResult = validateDomainSchema(dataMap, schemaMap);
82
+ errors.push(...schemaResult.errors);
83
+ warnings.push(...schemaResult.warnings);
84
+
85
+ // Layer 3: Cross-file consistency
86
+ const crossResult = validateCrossFile(dataMap);
87
+ errors.push(...crossResult.errors);
88
+ warnings.push(...crossResult.warnings);
89
+
90
+ if (warnings.length) {
91
+ console.log('Warnings:');
92
+ warnings.forEach((w) => console.log(` - ${w}`));
93
+ }
94
+ if (errors.length) {
95
+ console.error('Errors:');
96
+ errors.forEach((e) => console.error(` - ${e}`));
97
+ process.exit(1);
98
+ }
99
+
100
+ const validCount = Object.keys(dataMap).filter((k) => dataMap[k]).length;
101
+ const schemaInfo = schemaOnly
102
+ ? ` (schema-only mode, ${loadedSchemas.length} schemas loaded)`
103
+ : '';
104
+ console.log(`✓ KDNA domain valid: ${abs} (${validCount} files, schema OK${schemaInfo})`);
105
+ }
106
+
107
+ // ─── Pack / Unpack (.kdna ZIP container) ──────────────────────────────────
108
+
109
+ function cmdPack(dir, outputDir) {
110
+ const abs = path.resolve(dir);
111
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
112
+ error(`Not a directory: ${abs}`);
113
+ }
114
+
115
+ const core = readJson(path.join(abs, 'KDNA_Core.json'));
116
+ const pat = readJson(path.join(abs, 'KDNA_Patterns.json'));
117
+ if (!core) error('KDNA_Core.json not found or invalid');
118
+ if (!pat) error('KDNA_Patterns.json not found or invalid');
119
+
120
+ const domainName = core.meta?.domain || path.basename(abs);
121
+
122
+ // Ensure kdna.json manifest exists (generate if missing)
123
+ let manifest = readJson(path.join(abs, 'kdna.json'));
124
+ if (!manifest) {
125
+ const jsonCount = fs
126
+ .readdirSync(abs)
127
+ .filter((f) => f.endsWith('.json') && f !== 'kdna.json').length;
128
+ manifest = {
129
+ kdna_spec: '0.4',
130
+ name: domainName,
131
+ version: core.meta?.version || '0.1.0',
132
+ status: 'experimental',
133
+ access: 'open',
134
+ language: 'en',
135
+ author: { name: '', id: '' },
136
+ license: { type: 'CC-BY-4.0' },
137
+ description: core.meta?.purpose || `${domainName} domain cognition`,
138
+ file_count: jsonCount,
139
+ created: core.meta?.created || new Date().toISOString().slice(0, 10),
140
+ updated: new Date().toISOString().slice(0, 10),
141
+ };
142
+ writeJson(path.join(abs, 'kdna.json'), manifest);
143
+ }
144
+
145
+ // Create ZIP container — try python3, then zip command, then Node.js native
146
+ const outName = `${domainName}.kdna`;
147
+ const outPath = outputDir ? path.join(outputDir, outName) : path.join(process.cwd(), outName);
148
+ if (outputDir && !fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
149
+
150
+ let packed = false;
151
+
152
+ // Strategy 1: python3 zipfile (built-in on macOS, most Linux) — use temp file
153
+ const tmpPyFile = path.join(
154
+ fs.existsSync('/tmp') ? '/tmp' : require('os').tmpdir(),
155
+ `kdna-pack-${Date.now()}.py`,
156
+ );
157
+ try {
158
+ const pyScript = `import zipfile, os
159
+ src = ${JSON.stringify(abs)}
160
+ out = ${JSON.stringify(outPath)}
161
+ with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED) as zf:
162
+ for f in sorted(os.listdir(src)):
163
+ fp = os.path.join(src, f)
164
+ if os.path.isfile(fp) and (f.endswith('.json') or f in ('README.md', 'LICENSE', 'kdna.json')):
165
+ zf.write(fp, f)
166
+ `;
167
+ fs.writeFileSync(tmpPyFile, pyScript);
168
+ execSync(`python3 ${tmpPyFile}`, { stdio: 'pipe' });
169
+ packed = true;
170
+ } catch {
171
+ /* Strategy 1 failed, try next */
172
+ } finally {
173
+ try {
174
+ fs.unlinkSync(tmpPyFile);
175
+ } catch {
176
+ /* cleanup */
177
+ }
178
+ }
179
+
180
+ // Strategy 2: system zip command
181
+ if (!packed) {
182
+ const cwd = process.cwd();
183
+ try {
184
+ process.chdir(abs);
185
+ execSync(
186
+ `zip -q -r "${outPath}" *.json README.md LICENSE 2>/dev/null || zip -q -r "${outPath}" *.json`,
187
+ { stdio: 'pipe' },
188
+ );
189
+ process.chdir(cwd);
190
+ packed = true;
191
+ } catch {
192
+ process.chdir(cwd);
193
+ }
194
+ }
195
+
196
+ // #22: Strategy 3 — Node.js native ZIP (no external dependencies)
197
+ if (!packed) {
198
+ try {
199
+ createNodeZip(abs, outPath);
200
+ packed = true;
201
+ } catch {
202
+ /* last attempt failed */
203
+ }
204
+ }
205
+
206
+ if (!packed) {
207
+ const platform = process.platform;
208
+ const hints = {
209
+ darwin: 'macOS includes python3 — ensure it is in PATH.',
210
+ linux: 'Install python3 or zip: apt install python3 / yum install python3 / apk add python3',
211
+ win32: 'Install python3 from python.org, or use WSL.',
212
+ };
213
+ error(`Cannot create ZIP.\n${hints[platform] || 'Install python3 or zip command.'}`);
214
+ }
215
+
216
+ const fileCount = manifest.file_count || 0;
217
+ console.log(`✓ Packed: ${outPath}`);
218
+ console.log(` Domain: ${domainName} v${manifest.version}`);
219
+ console.log(` Files: ${fileCount} KDNA JSONs`);
220
+ console.log(` Container: ZIP (DEFLATE)`);
221
+ }
222
+
223
+ // #22: Node.js-native ZIP creator (zero dependencies, fallback when python3/zip unavailable)
224
+ function createNodeZip(srcDir, outPath) {
225
+ const zlib = require('zlib');
226
+ const files = fs
227
+ .readdirSync(srcDir)
228
+ .filter((f) => f.endsWith('.json'))
229
+ .concat(['README.md', 'LICENSE'].filter((f) => fs.existsSync(path.join(srcDir, f))));
230
+
231
+ const centralDir = [];
232
+ const fileData = [];
233
+ let offset = 0;
234
+
235
+ for (const f of files) {
236
+ const raw = fs.readFileSync(path.join(srcDir, f));
237
+ const crc = crc32(raw);
238
+ const compressed = zlib.deflateRawSync(raw);
239
+ const useStore = compressed.length >= raw.length;
240
+
241
+ const nameBytes = Buffer.from(f, 'utf8');
242
+ const localHeader = Buffer.alloc(30);
243
+ localHeader.writeUInt32LE(0x04034b50, 0); // local file header signature
244
+ localHeader.writeUInt16LE(20, 4); // version needed
245
+ localHeader.writeUInt16LE(0x0800, 6); // general purpose bit flag (UTF-8)
246
+ localHeader.writeUInt16LE(useStore ? 0 : 8, 8); // compression method: stored or deflated
247
+ // skip mod time, mod date
248
+ localHeader.writeUInt32LE(crc, 14);
249
+ localHeader.writeUInt32LE(useStore ? raw.length : compressed.length, 18); // compressed size
250
+ localHeader.writeUInt32LE(raw.length, 22); // uncompressed size
251
+ localHeader.writeUInt16LE(nameBytes.length, 26);
252
+
253
+ const stored = useStore ? raw : compressed;
254
+
255
+ fileData.push(Buffer.concat([localHeader, nameBytes, stored]));
256
+ offset += localHeader.length + nameBytes.length + stored.length;
257
+
258
+ // Central directory entry
259
+ const cdEntry = Buffer.alloc(46);
260
+ cdEntry.writeUInt32LE(0x02014b50, 0); // central dir signature
261
+ cdEntry.writeUInt16LE(20, 4); // version made by
262
+ cdEntry.writeUInt16LE(20, 6); // version needed
263
+ cdEntry.writeUInt16LE(0x0800, 8); // UTF-8
264
+ cdEntry.writeUInt16LE(useStore ? 0 : 8, 10);
265
+ cdEntry.writeUInt32LE(crc, 16);
266
+ cdEntry.writeUInt32LE(useStore ? raw.length : compressed.length, 20);
267
+ cdEntry.writeUInt32LE(raw.length, 24);
268
+ cdEntry.writeUInt16LE(nameBytes.length, 28);
269
+ cdEntry.writeUInt32LE(offset - stored.length - nameBytes.length - localHeader.length, 42);
270
+ centralDir.push(Buffer.concat([cdEntry, nameBytes]));
271
+ }
272
+
273
+ const cdOffset = offset;
274
+ const cdSize = centralDir.reduce((s, e) => s + e.length, 0);
275
+ const eocd = Buffer.alloc(22);
276
+ eocd.writeUInt32LE(0x06054b50, 0); // EOCD signature
277
+ eocd.writeUInt16LE(0, 4); // disk number
278
+ eocd.writeUInt16LE(0, 6); // disk with CD
279
+ eocd.writeUInt16LE(files.length, 8); // entries on disk
280
+ eocd.writeUInt16LE(files.length, 10); // total entries
281
+ eocd.writeUInt32LE(cdSize, 12); // CD size
282
+ eocd.writeUInt32LE(cdOffset, 16); // CD offset
283
+ eocd.writeUInt16LE(0, 20); // comment length
284
+
285
+ const all = Buffer.concat([...fileData, ...centralDir, eocd]);
286
+ fs.writeFileSync(outPath, all);
287
+ }
288
+
289
+ function crc32(buf) {
290
+ let crc = 0xffffffff;
291
+ for (let i = 0; i < buf.length; i++) {
292
+ crc ^= buf[i];
293
+ for (let j = 0; j < 8; j++) {
294
+ crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
295
+ }
296
+ }
297
+ return (crc ^ 0xffffffff) >>> 0;
298
+ }
299
+
300
+ function cmdUnpack(filePath, force) {
301
+ const abs = path.resolve(filePath);
302
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
303
+ error(`Not a file: ${abs}`);
304
+ }
305
+ if (!abs.endsWith('.kdna')) {
306
+ error(`Not a .kdna file: ${abs}`);
307
+ }
308
+
309
+ const domainName = path.basename(abs, '.kdna');
310
+ const outDir = path.join(path.dirname(abs), domainName);
311
+
312
+ if (fs.existsSync(outDir)) {
313
+ if (!force) error(`Directory already exists: ${outDir}\nUse --force to overwrite.`);
314
+ fs.rmSync(outDir, { recursive: true, force: true });
315
+ }
316
+
317
+ fs.mkdirSync(outDir, { recursive: true });
318
+
319
+ // Unzip using python3 zipfile (built-in) — use temp file to avoid -c multiline escaping issues
320
+ const tmpUnpackPy = path.join(
321
+ fs.existsSync('/tmp') ? '/tmp' : require('os').tmpdir(),
322
+ `kdna-unpack-${Date.now()}.py`,
323
+ );
324
+ try {
325
+ const script = `import zipfile, os
326
+ zf = zipfile.ZipFile(${JSON.stringify(abs)}, 'r')
327
+ zf.extractall(${JSON.stringify(outDir)})
328
+ zf.close()
329
+ `;
330
+ fs.writeFileSync(tmpUnpackPy, script);
331
+ execSync(`python3 ${tmpUnpackPy}`, { stdio: 'pipe' });
332
+ } catch {
333
+ // Fallback: use system unzip
334
+ try {
335
+ execSync(`unzip -q -o "${abs}" -d "${outDir}"`, { stdio: 'pipe' });
336
+ } catch {
337
+ error('Cannot unpack ZIP. Install python3 or unzip command.');
338
+ }
339
+ } finally {
340
+ try {
341
+ fs.unlinkSync(tmpUnpackPy);
342
+ } catch {
343
+ /* cleanup */
344
+ }
345
+ }
346
+
347
+ console.log(`✓ Unpacked: ${outDir}`);
348
+ const files = fs.readdirSync(outDir);
349
+ console.log(` Files: ${files.length}`);
350
+ files.forEach((f) => console.log(` ${f}`));
351
+ }
352
+
353
+ // ─── Inspect .kdna file (ZIP container or legacy merged JSON) ────────────
354
+
355
+ function inspectKdnaFile(filePath) {
356
+ const abs = path.resolve(filePath);
357
+ fs.statSync(abs); // verify file exists
358
+
359
+ // Detect format: ZIP container (binary header PK\x03\x04) vs text
360
+ const head = Buffer.alloc(4);
361
+ const fd = fs.openSync(abs, 'r');
362
+ fs.readSync(fd, head, 0, 4, 0);
363
+ fs.closeSync(fd);
364
+ const isZip = head[0] === 0x50 && head[1] === 0x4b;
365
+
366
+ let core, patterns, manifest;
367
+ const presentFiles = [];
368
+
369
+ if (isZip) {
370
+ // ZIP container — extract to temp, read files
371
+ const os = require('os');
372
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-inspect-'));
373
+ try {
374
+ const tmpInspectPy = path.join(
375
+ fs.existsSync('/tmp') ? '/tmp' : require('os').tmpdir(),
376
+ `kdna-inspect-${Date.now()}.py`,
377
+ );
378
+ try {
379
+ const script = `import zipfile, os
380
+ zf = zipfile.ZipFile(${JSON.stringify(abs)}, 'r')
381
+ zf.extractall(${JSON.stringify(tmpDir)})
382
+ zf.close()
383
+ `;
384
+ fs.writeFileSync(tmpInspectPy, script);
385
+ execSync(`python3 ${tmpInspectPy}`, { stdio: 'pipe' });
386
+ } finally {
387
+ try {
388
+ fs.unlinkSync(tmpInspectPy);
389
+ } catch {
390
+ /* cleanup */
391
+ }
392
+ }
393
+ } catch {
394
+ try {
395
+ execSync(`unzip -q -o "${abs}" -d "${tmpDir}"`, { stdio: 'pipe' });
396
+ } catch {
397
+ fs.rmSync(tmpDir, { recursive: true, force: true });
398
+ error('Cannot read .kdna container. Install python3 or unzip.');
399
+ }
400
+ }
401
+
402
+ core = readJson(path.join(tmpDir, 'KDNA_Core.json'));
403
+ patterns = readJson(path.join(tmpDir, 'KDNA_Patterns.json'));
404
+ manifest = readJson(path.join(tmpDir, 'kdna.json'));
405
+
406
+ for (const f of fs.readdirSync(tmpDir)) {
407
+ if (f.startsWith('KDNA_') && f.endsWith('.json')) {
408
+ presentFiles.push(f);
409
+ }
410
+ if (f === 'README.md' || f === 'LICENSE') presentFiles.push(f);
411
+ }
412
+
413
+ fs.rmSync(tmpDir, { recursive: true, force: true });
414
+ } else {
415
+ // Legacy merged JSON/YAML format (deprecated)
416
+ const raw = fs.readFileSync(abs, 'utf8');
417
+ let data;
418
+ try {
419
+ data = JSON.parse(raw);
420
+ } catch {
421
+ data = parseSimpleYaml(raw);
422
+ }
423
+
424
+ if (!data || !data.meta) error(`Invalid .kdna file: missing meta section`);
425
+
426
+ const m = data.meta || {};
427
+ manifest = {
428
+ name: m.name || m.domain,
429
+ version: m.version || '?',
430
+ status: data.status || '?',
431
+ access: data.access || '?',
432
+ language: data.language || '?',
433
+ author: data.author || { name: '?' },
434
+ license: data.license || { type: '?' },
435
+ description: data.description || m.purpose || '?',
436
+ spec_version: m.spec_version || data.kdna_spec || '?',
437
+ };
438
+ core = data.core || {};
439
+ patterns = data.patterns || {};
440
+ presentFiles.push('.kdna (legacy merged format)');
441
+ if (data.scenarios) {
442
+ presentFiles.push('scenarios (inline)');
443
+ }
444
+ if (data.cases) {
445
+ presentFiles.push('cases (inline)');
446
+ }
447
+ if (data.reasoning) {
448
+ presentFiles.push('reasoning (inline)');
449
+ }
450
+ if (data.evolution) {
451
+ presentFiles.push('evolution (inline)');
452
+ }
453
+ }
454
+
455
+ if (!core) error('KDNA_Core.json not found in container');
456
+
457
+ const m = manifest || {};
458
+ const c = core;
459
+ const p = patterns || {};
460
+
461
+ console.log('═'.repeat(50));
462
+ console.log(` ${m.name || c.meta?.domain || path.basename(abs, '.kdna')} — KDNA Domain`);
463
+ console.log('═'.repeat(50));
464
+ console.log('');
465
+ console.log(` Format: .kdna ${isZip ? '(ZIP container)' : '(legacy merged)'}`);
466
+ console.log(` Spec: ${m.spec_version || m.kdna_spec || '0.4'}`);
467
+ console.log(` Version: ${m.version || '?'}`);
468
+ console.log(` Status: ${m.status || 'experimental'}`);
469
+ console.log(` Access: ${m.access || 'open'}`);
470
+ console.log(` Author: ${m.author?.name || '?'}`);
471
+ console.log(` License: ${m.license?.type || '?'}`);
472
+ console.log(` Created: ${m.created || c.meta?.created || '?'}`);
473
+ console.log(` Description: ${m.description || c.meta?.purpose || '?'}`);
474
+ console.log('');
475
+ console.log(' ── Content ──');
476
+ console.log(` Axioms: ${(c.axioms || []).length}`);
477
+ console.log(` Ontology concepts: ${(c.ontology || []).length}`);
478
+ console.log(` Frameworks: ${(c.frameworks || []).length}`);
479
+ console.log(` Stances: ${(c.stances || []).length}`);
480
+ console.log(` Banned terms: ${(p.terminology?.banned_terms || []).length}`);
481
+ console.log(` Misunderstandings: ${(p.misunderstandings || []).length}`);
482
+ console.log(` Self-checks: ${(p.self_check || []).length}`);
483
+ console.log('');
484
+ console.log(' ── Files ──');
485
+ presentFiles.forEach((f) => console.log(` ${f}`));
486
+ console.log('');
487
+ console.log('═'.repeat(50));
488
+ }
489
+
490
+ function parseSimpleYaml(raw) {
491
+ // Parse a simple subset of YAML (no nesting beyond 1 level for sections)
492
+ const result = {};
493
+ let currentSection = null;
494
+
495
+ const lines = raw.split('\n');
496
+ for (const line of lines) {
497
+ const trimmed = line.trim();
498
+ if (!trimmed || trimmed.startsWith('#')) continue;
499
+
500
+ // Section header: "core:" or " core:" etc
501
+ if (/^[a-z_]+:$/.test(trimmed)) {
502
+ currentSection = trimmed.slice(0, -1);
503
+ if (!result[currentSection]) result[currentSection] = {};
504
+ continue;
505
+ }
506
+
507
+ // Key: value
508
+ const kv = trimmed.match(/^([a-z_]+):\s*(.*)/i);
509
+ if (kv && !kv[1].startsWith('-')) {
510
+ const key = kv[1];
511
+ const val = kv[2].trim().replace(/^["']|["']$/g, '');
512
+ if (currentSection) {
513
+ if (key === 'version' && typeof result[currentSection] === 'object') {
514
+ result[currentSection][key] = val;
515
+ } else if (!result[currentSection][key]) {
516
+ result[currentSection][key] = val;
517
+ }
518
+ } else {
519
+ result[key] = val;
520
+ }
521
+ continue;
522
+ }
523
+
524
+ // Array item: "- value"
525
+ if (trimmed.startsWith('- ') && currentSection) {
526
+ // For counts only, we don't parse full arrays
527
+ if (currentSection === 'axioms' || currentSection === 'stances') {
528
+ if (!result.core) result.core = {};
529
+ if (!result.core[currentSection]) result.core[currentSection] = [];
530
+ result.core[currentSection].push({ _parsed: true });
531
+ }
532
+ }
533
+ }
534
+
535
+ return result;
536
+ }
537
+
538
+ // ─── Inspect ───────────────────────────────────────────────────────────
539
+
540
+ function cmdInspect(dir) {
541
+ const abs = path.resolve(dir);
542
+ const stat = fs.existsSync(abs) ? fs.statSync(abs) : null;
543
+ if (!stat) error(`Path not found: ${abs}`);
544
+
545
+ // Single .kdna file
546
+ if (stat.isFile() && abs.endsWith('.kdna')) {
547
+ inspectKdnaFile(abs);
548
+ return;
549
+ }
550
+
551
+ // Directory — existing logic
552
+ if (!stat.isDirectory()) error(`Not a KDNA domain: ${abs}`);
553
+
554
+ const core = readJson(path.join(abs, 'KDNA_Core.json'));
555
+ const manifest = readJson(path.join(abs, 'kdna.json'));
556
+
557
+ if (!core) {
558
+ error(`Not a KDNA domain (KDNA_Core.json not found in ${abs})`);
559
+ }
560
+
561
+ const m = manifest || {};
562
+ const c = core;
563
+
564
+ console.log('═'.repeat(50));
565
+ console.log(` ${m.name || c.meta?.domain || path.basename(abs)} — KDNA Domain`);
566
+ console.log('═'.repeat(50));
567
+ console.log('');
568
+ console.log(` Version: ${m.version || c.meta?.version || '?'}`);
569
+ console.log(` Status: ${m.status || 'experimental'}`);
570
+ console.log(` Access: ${m.access || 'open'}`);
571
+ console.log(` Language: ${m.language || c.meta?.language || '?'}`);
572
+ console.log(` Author: ${m.author?.name || '?'}`);
573
+ if (m.author?.id) console.log(` ${m.author.id}`);
574
+ console.log(` License: ${m.license?.type || '?'}`);
575
+ console.log(` Created: ${c.meta?.created || '?'}`);
576
+ console.log(` Description: ${m.description || c.meta?.purpose || '?'}`);
577
+ console.log('');
578
+
579
+ const expected = [
580
+ 'KDNA_Core.json',
581
+ 'KDNA_Patterns.json',
582
+ 'KDNA_Scenarios.json',
583
+ 'KDNA_Cases.json',
584
+ 'KDNA_Reasoning.json',
585
+ 'KDNA_Evolution.json',
586
+ ];
587
+
588
+ console.log(' ── File Set ──');
589
+ for (const f of expected) {
590
+ const exists = fs.existsSync(path.join(abs, f));
591
+ console.log(` ${exists ? '✓' : '○'} ${f}`);
592
+ }
593
+
594
+ console.log('');
595
+ console.log(' ── Content ──');
596
+ console.log(` Axioms: ${(c.axioms || []).length}`);
597
+ console.log(` Ontology concepts: ${(c.ontology || []).length}`);
598
+ console.log(` Frameworks: ${(c.frameworks || []).length}`);
599
+ console.log(` Core structures: ${(c.core_structure || []).length}`);
600
+ console.log(` Stances: ${(c.stances || []).length}`);
601
+
602
+ const pat = readJson(path.join(abs, 'KDNA_Patterns.json'));
603
+ if (pat) {
604
+ const preferred = pat.terminology?.preferred_terms || pat.terminology?.standard_terms || [];
605
+ console.log(` Preferred terms: ${preferred.length}`);
606
+ console.log(` Banned terms: ${(pat.terminology?.banned_terms || []).length}`);
607
+ console.log(` Misunderstandings: ${(pat.misunderstandings || []).length}`);
608
+ console.log(` Self-checks: ${(pat.self_check || []).length}`);
609
+ }
610
+
611
+ const sce = readJson(path.join(abs, 'KDNA_Scenarios.json'));
612
+ if (sce) console.log(` Scenarios: ${(sce.scenes || []).length}`);
613
+
614
+ const cas = readJson(path.join(abs, 'KDNA_Cases.json'));
615
+ if (cas) console.log(` Cases: ${(cas.cases || []).length}`);
616
+
617
+ const rea = readJson(path.join(abs, 'KDNA_Reasoning.json'));
618
+ if (rea) console.log(` Reasoning chains: ${(rea.reasoning_chains || []).length}`);
619
+
620
+ const evo = readJson(path.join(abs, 'KDNA_Evolution.json'));
621
+ if (evo) console.log(` Evolution stages: ${(evo.stages || []).length}`);
622
+
623
+ console.log('');
624
+ console.log(' ── Axioms ──');
625
+ for (const a of c.axioms || []) {
626
+ console.log(` • ${a.one_sentence}`);
627
+ }
628
+
629
+ console.log('');
630
+ console.log('═'.repeat(50));
631
+ }
632
+
633
+ module.exports = {
634
+ cmdValidate,
635
+ cmdPack,
636
+ cmdUnpack,
637
+ cmdInspect,
638
+ };
@@ -0,0 +1,31 @@
1
+ const { error } = require('./_common');
2
+
3
+ function cmdIdentity(args) {
4
+ const {
5
+ cmdIdentityInit,
6
+ cmdIdentityShow,
7
+ cmdIdentityExport,
8
+ cmdIdentityImport,
9
+ } = require('../identity');
10
+ const sub = args[1];
11
+ if (sub === 'init') {
12
+ cmdIdentityInit();
13
+ } else if (sub === 'show') {
14
+ cmdIdentityShow();
15
+ } else if (sub === 'export') {
16
+ const outIdx = args.indexOf('--out');
17
+ cmdIdentityExport(outIdx >= 0 ? args[outIdx + 1] : null);
18
+ } else if (sub === 'import') {
19
+ const target = args[2];
20
+ if (!target) error('Usage: kdna identity import <file>');
21
+ cmdIdentityImport(target);
22
+ } else {
23
+ error(
24
+ `Usage: kdna identity init\n kdna identity show\n kdna identity export [--out <file>]\n kdna identity import <file>`,
25
+ );
26
+ }
27
+ }
28
+
29
+ module.exports = {
30
+ cmdIdentity,
31
+ };