@aikdna/kdna-cli 0.16.10 → 0.18.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.
@@ -1,9 +1,8 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
1
  const { CANONICAL_REGISTRY_URL, REGISTRY_CACHE, fetchRegistry } = require('../registry');
4
- const { error, readJson, loadRegistry, INSTALL_DIR, EXIT } = require('./_common');
2
+ const { error, loadRegistry, EXIT } = require('./_common');
3
+ const { listInstalled, readContainer } = require('../package-store');
5
4
 
6
- function cmdList(showAvailable, jsonMode = false, locale = null) {
5
+ function cmdList(showAvailable, jsonMode = false, _locale = null) {
7
6
  if (showAvailable) {
8
7
  const domains = loadRegistry({ allowNetwork: true });
9
8
  if (!domains || !domains.length) {
@@ -33,9 +32,7 @@ function cmdList(showAvailable, jsonMode = false, locale = null) {
33
32
  console.log('');
34
33
  for (const d of domains) {
35
34
  const name = d.name || d.id || '?';
36
- const [scope, ident] = name.includes('/') ? name.split('/') : [null, name];
37
- const installedPath = scope ? path.join(INSTALL_DIR, scope, ident) : null;
38
- const installed = installedPath && fs.existsSync(installedPath) ? '[installed]' : '';
35
+ const installed = listInstalled().some((entry) => entry.full === name) ? '[installed]' : '';
39
36
  const yanked = d.yanked ? '[yanked] ' : '';
40
37
  const dep = d.deprecated ? '[deprecated] ' : '';
41
38
  console.log(
@@ -47,78 +44,29 @@ function cmdList(showAvailable, jsonMode = false, locale = null) {
47
44
  return;
48
45
  }
49
46
 
50
- if (!fs.existsSync(INSTALL_DIR)) {
51
- if (jsonMode) {
52
- console.log(JSON.stringify([]));
53
- process.exit(EXIT.OK);
54
- }
55
- console.log('No domains installed.');
56
- console.log(`Installation directory: ${INSTALL_DIR}`);
57
- return;
58
- }
59
-
60
- // v0.7 layout: ~/.kdna/domains/@scope/name/
61
- const scopes = fs.readdirSync(INSTALL_DIR).filter((d) => {
62
- if (!d.startsWith('@')) return false;
63
- try {
64
- return fs.statSync(path.join(INSTALL_DIR, d)).isDirectory();
65
- } catch {
66
- return false;
67
- }
68
- });
69
-
70
- const installed = [];
71
- for (const scope of scopes) {
72
- const sd = path.join(INSTALL_DIR, scope);
73
- for (const ident of fs.readdirSync(sd)) {
74
- if (ident.startsWith('.')) continue;
75
- const full = path.join(sd, ident);
76
- try {
77
- if (!fs.statSync(full).isDirectory()) continue;
78
- } catch {
79
- continue;
80
- }
81
- installed.push({ scope, ident, full });
82
- }
83
- }
84
-
85
- // Detect and warn about legacy (un-scoped) installs
86
- if (!jsonMode) {
87
- const legacy = fs.readdirSync(INSTALL_DIR).filter((d) => {
88
- if (d.startsWith('@') || d.startsWith('.')) return false;
89
- try {
90
- return fs.statSync(path.join(INSTALL_DIR, d)).isDirectory();
91
- } catch {
92
- return false;
93
- }
94
- });
95
- if (legacy.length) {
96
- console.log('⚠ Legacy (un-scoped) directories detected — please remove + re-install:');
97
- legacy.forEach((d) => console.log(` ~/.kdna/domains/${d}/`));
98
- console.log('');
99
- }
100
- }
47
+ const installed = listInstalled();
101
48
 
102
49
  if (!installed.length) {
103
50
  if (jsonMode) {
104
51
  console.log(JSON.stringify([]));
105
52
  process.exit(EXIT.OK);
106
53
  }
107
- console.log('No v0.7 domains installed.');
54
+ console.log('No KDNA assets installed.');
108
55
  console.log(`Run: kdna install <name> # e.g. kdna install writing`);
109
56
  return;
110
57
  }
111
58
 
112
59
  // Build structured data for installed domains
113
- const domains = installed.map(({ scope, ident, full }) => {
114
- const core = readJson(path.join(full, 'KDNA_Core.json'));
115
- const manifest = readJson(path.join(full, 'kdna.json'));
116
- const cluster = readJson(path.join(full, 'cluster.json'));
60
+ const domains = installed.map((entry) => {
61
+ const { core = {}, manifest = {} } = readContainer(entry.asset_path);
117
62
  return {
118
- name: `${scope}/${ident}`,
119
- version: manifest?.version || manifest?._source?.version || core?.meta?.version || '?',
120
- type: cluster ? 'cluster' : 'domain',
63
+ name: entry.full,
64
+ version: manifest?.version || entry.version || core?.meta?.version || '?',
65
+ type: 'domain',
121
66
  description: manifest?.description || core?.meta?.purpose || '',
67
+ asset: entry.asset_path,
68
+ asset_digest: entry.asset_digest || null,
69
+ content_digest: entry.content_digest || null,
122
70
  };
123
71
  });
124
72
 
@@ -135,7 +83,7 @@ function cmdList(showAvailable, jsonMode = false, locale = null) {
135
83
  if (d.description) console.log(` ${d.description}`);
136
84
  }
137
85
  console.log('');
138
- console.log(`Location: ${INSTALL_DIR}`);
86
+ console.log('Assets are stored under ~/.kdna/packages/.');
139
87
  }
140
88
 
141
89
  function cmdRegistry(subcommand) {
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { error, readJson, writeJson, EXIT } = require('./_common');
13
+ const { error, readJson, writeJson, EXIT, isYesNoSelfCheck } = require('./_common');
14
14
 
15
15
  // ─── Scaffold ─────────────────────────────────────────────────────────
16
16
 
@@ -240,8 +240,8 @@ function cmdCardsValidate(projectPath, args = []) {
240
240
  const label = sc.id || '?';
241
241
  if (!sc.question || sc.question.includes('[TODO]')) {
242
242
  warn(`self_check ${label}: question is placeholder`);
243
- } else if (!sc.question.trim().endsWith('?')) {
244
- fail(`self_check ${label}: question should end with "?"`);
243
+ } else if (!isYesNoSelfCheck(sc.question)) {
244
+ fail(`self_check ${label}: question should be answerable with yes/no`);
245
245
  } else {
246
246
  ok(`self_check ${label}: question OK`);
247
247
  }
@@ -398,7 +398,6 @@ function cmdStudioCompile(projectPath, args = []) {
398
398
  // Compile axioms → KDNA_Core.json
399
399
  const axioms = loadCards(project, path.dirname(abs), 'axioms');
400
400
  const ontology = loadCards(project, path.dirname(abs), 'ontology');
401
- const _boundaries = loadCards(project, path.dirname(abs), 'boundaries');
402
401
 
403
402
  const core = {
404
403
  meta: {
@@ -495,7 +494,7 @@ function cmdStudioCompile(projectPath, args = []) {
495
494
 
496
495
  console.log('');
497
496
  console.log('Next:');
498
- console.log(` kdna validate ${outDir}`);
497
+ console.log(` kdna dev validate ${outDir}`);
499
498
  }
500
499
 
501
500
  function loadCards(project, projectDir, cardType) {
package/src/cmds/test.js CHANGED
@@ -12,9 +12,9 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { error, readJson, writeJson, EXIT } = require('./_common');
14
14
  const { parseName } = require('../registry');
15
+ const { getInstalled } = require('../package-store');
15
16
 
16
17
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
17
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
18
18
  const RUNS_DIR = path.join(USER_KDNA_DIR, 'runs');
19
19
 
20
20
  function cmdTestRun(args = []) {
@@ -38,8 +38,8 @@ function cmdTestRun(args = []) {
38
38
 
39
39
  const parsed = parseName(domain);
40
40
  if (!parsed) error(`Invalid name "${domain}".`, EXIT.INPUT_ERROR);
41
- const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
42
- if (!fs.existsSync(destDir)) {
41
+ const installed = getInstalled(parsed.full);
42
+ if (!installed) {
43
43
  error(`${parsed.full} not installed. Run: kdna install ${domain}`, EXIT.INPUT_ERROR);
44
44
  }
45
45
 
@@ -64,7 +64,7 @@ function cmdTestRun(args = []) {
64
64
  const result = {
65
65
  test_id: testCase.id || `test_${Date.now()}`,
66
66
  domain: parsed.full,
67
- domain_path: destDir,
67
+ domain_asset: installed.asset_path,
68
68
  input: typeof testCase.input === 'string' ? testCase.input : JSON.stringify(testCase.input),
69
69
  run_at: new Date().toISOString(),
70
70
  expected: {
package/src/cmds/trace.js CHANGED
@@ -1,10 +1,9 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const crypto = require('crypto');
4
- const { EXIT, error, readJson } = require('./_common');
3
+ const { EXIT } = require('./_common');
4
+ const PATHS = require('../paths');
5
5
 
6
- const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
7
- const TRACES_DIR = path.join(USER_KDNA_DIR, 'traces');
6
+ const TRACES_DIR = PATHS.traces;
8
7
 
9
8
  function ensureTracesDir() {
10
9
  fs.mkdirSync(TRACES_DIR, { recursive: true });
@@ -60,9 +59,14 @@ function readAllTraces(opts = {}) {
60
59
  }
61
60
 
62
61
  function recordTrace(entry) {
63
- ensureTracesDir();
64
- const line = JSON.stringify(entry) + '\n';
65
- fs.appendFileSync(todayFile(), line);
62
+ try {
63
+ ensureTracesDir();
64
+ const line = JSON.stringify(entry) + '\n';
65
+ fs.appendFileSync(todayFile(), line);
66
+ } catch {
67
+ // Traces are observability data. Loading and comparing KDNA assets must not
68
+ // fail just because the local trace directory is unavailable or read-only.
69
+ }
66
70
  }
67
71
 
68
72
  function parseSinceFlag(args) {
package/src/compare.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * kdna compare <name> --input "<text>" — Reasoning trajectory diff.
2
+ * kdna compare <name|file.kdna> --input "<text>" — Reasoning trajectory diff.
3
3
  *
4
4
  * Runs the same prompt twice on a real LLM:
5
5
  * 1. Without KDNA loaded (baseline)
@@ -24,7 +24,7 @@ const path = require('path');
24
24
  const https = require('https');
25
25
 
26
26
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
27
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
27
+ const { readContainer, resolveAsset } = require('./package-store');
28
28
  const CONFIG_FILE = path.join(USER_KDNA_DIR, 'config.json');
29
29
 
30
30
  const { parseName } = require('./registry');
@@ -179,10 +179,10 @@ async function callLlm(cfg, systemPrompt, userMessage) {
179
179
 
180
180
  // ─── KDNA → system prompt ─────────────────────────────────────────────
181
181
 
182
- function buildKdnaPrompt(destDir) {
183
- const core = readJson(path.join(destDir, 'KDNA_Core.json'));
184
- const pat = readJson(path.join(destDir, 'KDNA_Patterns.json'));
185
- const manifest = readJson(path.join(destDir, 'kdna.json'));
182
+ function buildKdnaPrompt(container) {
183
+ const core = container.core;
184
+ const pat = container.patterns;
185
+ const manifest = container.manifest;
186
186
 
187
187
  if (!core || !pat) return '';
188
188
 
@@ -302,7 +302,6 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
302
302
  const bannedTerms = (pat.terminology?.banned_terms || []).map((t) =>
303
303
  typeof t === 'string' ? t : t.term,
304
304
  );
305
- const misunderstandings = pat.misunderstandings || [];
306
305
 
307
306
  const lines = [];
308
307
  lines.push('# KDNA Judgment Comparison Report');
@@ -487,27 +486,26 @@ async function cmdCompare(input, args = []) {
487
486
  const idxInput = args.indexOf('--input');
488
487
  if (idxInput < 0 || !args[idxInput + 1]) {
489
488
  error(
490
- 'Usage: kdna compare <name> --input "<text>" [--report-md|--report-json] [--output <file>]',
489
+ 'Usage: kdna compare <name|file.kdna> --input "<text>" [--report-md|--report-json] [--output <file>]',
491
490
  EXIT.INPUT_ERROR,
492
491
  );
493
492
  }
494
493
  const userInput = args[idxInput + 1];
495
494
 
496
- const parsed = parseName(input);
497
- if (!parsed) error(`Invalid name "${input}".`, EXIT.INPUT_ERROR);
498
- const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
499
- if (!fs.existsSync(destDir)) {
500
- error(`${parsed.full} not installed. Run: kdna install ${input}`, EXIT.INPUT_ERROR);
501
- }
495
+ const asset = resolveAsset(input);
496
+ if (!asset) error(`KDNA asset not found: ${input}. Use an installed name or a .kdna file.`, EXIT.INPUT_ERROR);
497
+ const parsed = asset.parsed || parseName(asset.name || '');
498
+ const label = parsed?.full || asset.name || input;
502
499
 
503
500
  const llm = loadLlmConfig();
504
- const manifest = readJson(path.join(destDir, 'kdna.json')) || {};
505
- const core = readJson(path.join(destDir, 'KDNA_Core.json')) || {};
506
- const pat = readJson(path.join(destDir, 'KDNA_Patterns.json')) || {};
501
+ const container = readContainer(asset.asset_path);
502
+ const manifest = container.manifest || {};
503
+ const core = container.core || {};
504
+ const pat = container.patterns || {};
507
505
 
508
506
  if (!jsonMode && !reportMd && !reportJson) {
509
507
  console.log('═'.repeat(64));
510
- console.log(` kdna compare ${parsed.full}`);
508
+ console.log(` kdna compare ${label}`);
511
509
  console.log(` provider: ${llm.provider} / ${llm.model}`);
512
510
  console.log(` input length: ${userInput.length} chars`);
513
511
  console.log('═'.repeat(64));
@@ -516,7 +514,7 @@ async function cmdCompare(input, args = []) {
516
514
 
517
515
  const BASELINE_SYSTEM =
518
516
  'You are a helpful assistant. Respond to the user request concisely and specifically.';
519
- const kdnaPrompt = buildKdnaPrompt(destDir);
517
+ const kdnaPrompt = buildKdnaPrompt(container);
520
518
  if (!kdnaPrompt) error('Could not build KDNA prompt — missing KDNA_Core or KDNA_Patterns.');
521
519
  const TREATMENT_SYSTEM =
522
520
  'You are a helpful assistant. The following domain judgment is loaded and you MUST apply it when relevant.\n\n' +
@@ -540,13 +538,21 @@ async function cmdCompare(input, args = []) {
540
538
  recordTrace({
541
539
  timestamp: new Date().toISOString(),
542
540
  agent: 'cli',
543
- domain: parsed.full,
541
+ domain: label,
544
542
  type: 'compare',
543
+ asset: {
544
+ asset_path: asset.asset_path,
545
+ asset_digest: asset.asset_digest || null,
546
+ content_digest: asset.content_digest || null,
547
+ version: manifest.version || asset.version || null,
548
+ judgment_version: manifest.judgment_version || asset.judgment_version || null,
549
+ access: manifest.access || asset.access || null,
550
+ },
545
551
  compare: { model: llm.model, input_length: userInput.length },
546
552
  });
547
553
 
548
554
  if (reportMd) {
549
- const report = emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, diff, llm);
555
+ const report = emitMarkdownReport(parsed || { full: label }, manifest, core, pat, responseA, responseB, diff, llm);
550
556
  if (outputFile) {
551
557
  fs.writeFileSync(outputFile, report);
552
558
  console.log(`Report saved to ${outputFile}`);
@@ -558,7 +564,7 @@ async function cmdCompare(input, args = []) {
558
564
 
559
565
  if (reportJson) {
560
566
  const report = emitJsonReport(
561
- parsed,
567
+ parsed || { full: label },
562
568
  manifest,
563
569
  core,
564
570
  pat,
package/src/diff.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * kdna diff <name>@<ver1> <name>@<ver2> — Judgment-level diff between versions.
3
3
  *
4
- * Downloads two .kdna packages from the registry, extracts to temp dirs,
4
+ * Downloads two .kdna dev packages from the registry, extracts to temp dirs,
5
5
  * compares axioms / misunderstandings / banned_terms / stances / boundary,
6
6
  * and surfaces what would change for an agent loading the new version.
7
7
  *
@@ -19,11 +19,10 @@
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
21
  const { execSync, execFileSync } = require('child_process');
22
- const { RegistryResolver, parseName } = require('./registry');
22
+ const { RegistryResolver } = require('./registry');
23
23
  const { EXIT } = require('./cmds/_common');
24
+ const { getInstalled, readContainer } = require('./package-store');
24
25
 
25
- const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
26
- const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
27
26
  const TMP_DIR = '/tmp';
28
27
 
29
28
  function error(msg, code = EXIT.VALIDATION_FAILED) {
@@ -58,14 +57,15 @@ function parseNameVersion(input) {
58
57
  // ─── Download specific version ────────────────────────────────────────
59
58
 
60
59
  function downloadVersion(entry, version, destDir) {
61
- // entry.kdna_url is for the registry-current version. For older versions
60
+ const assetUrl = entry.asset_url;
61
+ // assetUrl is for the registry-current version. For older versions
62
62
  // we infer the URL pattern from the registry-current URL.
63
63
  if (entry.version === version) {
64
- return downloadAndExtract(entry.kdna_url, destDir);
64
+ return downloadAndExtract(assetUrl, destDir);
65
65
  }
66
66
 
67
67
  // Infer pattern: replace v<current> in the URL with v<requested>
68
- const inferredUrl = entry.kdna_url
68
+ const inferredUrl = assetUrl
69
69
  .replace(`/v${entry.version}/`, `/v${version}/`)
70
70
  .replace(`-${entry.version}.kdna`, `-${version}.kdna`);
71
71
 
@@ -232,12 +232,11 @@ async function cmdDiff(a, b, args = []) {
232
232
  newEntry = entryB;
233
233
  } else {
234
234
  // single-arg form: installed vs registry-current
235
- const parsed = parseName(aParsed.full);
236
- const localDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
237
- if (!fs.existsSync(localDir)) {
235
+ const installed = getInstalled(aParsed.full);
236
+ if (!installed) {
238
237
  error(`${aParsed.full} not installed. Run: kdna install ${aParsed.full}`, EXIT.INPUT_ERROR);
239
238
  }
240
- const localManifest = readJson(path.join(localDir, 'kdna.json'));
239
+ const localManifest = readContainer(installed.asset_path).manifest || {};
241
240
  oldVersion = localManifest?.version || '?';
242
241
  newVersion = entryA.version;
243
242
  oldEntry = entryA;
@@ -285,7 +284,7 @@ async function cmdDiff(a, b, args = []) {
285
284
  }
286
285
 
287
286
  const axiomsDiff = diffMaps('axioms', oldJ.axioms, newJ.axioms, (a) => a.one_sentence || a.id, jsonMode);
288
- const ontologyDiff = diffMaps('ontology', oldJ.ontology, newJ.ontology, (o) => o.one_sentence || o.id, jsonMode);
287
+ diffMaps('ontology', oldJ.ontology, newJ.ontology, (o) => o.one_sentence || o.id, jsonMode);
289
288
  const misunderstandingsDiff = diffMaps(
290
289
  'misunderstandings',
291
290
  oldJ.misunderstandings,
@@ -345,7 +344,6 @@ async function cmdDiff(a, b, args = []) {
345
344
  }));
346
345
 
347
346
  // Determine recommended version bump
348
- const axiomDrift = Object.keys(newJ.axioms).length - Object.keys(oldJ.axioms).length;
349
347
  const hasRemoved = axiomsDiff.removed.length > 0 || misunderstandingsDiff.removed.length > 0;
350
348
  const hasAdded = axiomsDiff.added.length > 0 || misunderstandingsDiff.added.length > 0;
351
349
  const hasChanged = axiomsDiff.changed.length > 0 || bannedDiff.changed.length > 0;
package/src/init.js CHANGED
@@ -91,7 +91,7 @@ function cmdInit(name) {
91
91
  ` 2. Edit ${targetDir}/KDNA_Patterns.json — replace terminology and misunderstandings`,
92
92
  );
93
93
  console.log(` 3. Edit ${targetDir}/kdna.json — set author, description, repo`);
94
- console.log(` 4. Run: kdna validate ${name} (structural check)`);
94
+ console.log(` 4. Run: kdna dev validate ${name} (structural check)`);
95
95
  console.log(` 5. Run: kdna publish --check ${name} (content quality gate)`);
96
96
  console.log(` 6. Run: kdna verify ${name} (full judgment scoring)`);
97
97
  }
@@ -162,7 +162,7 @@ function cmdClusterInit(name) {
162
162
  console.log(
163
163
  ` 3. Add more sub-domains: cp -r ${targetDir}/domain_one ${targetDir}/your_new_domain`,
164
164
  );
165
- console.log(` 4. Run: kdna validate ${name} (check all sub-domains)`);
165
+ console.log(` 4. Run: kdna dev validate ${name} (check all sub-domains)`);
166
166
  }
167
167
 
168
168
  module.exports = { cmdInit, cmdClusterInit };