@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
package/src/agent.js ADDED
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Agent-facing commands — what the kdna-loader skill calls.
3
+ *
4
+ * kdna available --json
5
+ * List installed domains, lean JSON, includes v2.1 applies_when fields
6
+ * and yanked status. Excludes yanked. ~200 bytes per domain.
7
+ * The agent uses this as its primary discovery source and decides
8
+ * which domain (if any) fits the task by reading the applies_when
9
+ * and does_not_apply_when fields against the task in its own words.
10
+ *
11
+ * kdna match "<task>" [--json]
12
+ * Auxiliary signal — does NOT decide which domain to use. Returns:
13
+ * - dropped: domains whose does_not_apply_when clearly matches the
14
+ * task (hard disqualification — agent should respect)
15
+ * - hints: substring overlap signals per domain (weak — agent should
16
+ * not treat as a fit decision; many false positives expected)
17
+ * The agent makes the final call using its own language understanding.
18
+ *
19
+ * kdna load <name> [--as=prompt|json|raw]
20
+ * Read the domain's Core + Patterns and emit:
21
+ * --as=prompt (default): compact text suitable for system-prompt
22
+ * injection (axioms one-liners + stances +
23
+ * banned-terms + misunderstandings + self-checks)
24
+ * --as=json: raw JSON of Core + Patterns
25
+ * --as=raw: concatenated raw file contents
26
+ *
27
+ * These commands are the supported interface between the kdna-loader
28
+ * skill and the KDNA file format. The skill should not parse JSON
29
+ * directly.
30
+ */
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+ const { parseName } = require('./registry');
35
+
36
+ const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
37
+ const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
38
+
39
+ function readJson(p) {
40
+ try {
41
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ function listInstalled() {
48
+ if (!fs.existsSync(INSTALL_DIR)) return [];
49
+ const out = [];
50
+ for (const scopeName of fs.readdirSync(INSTALL_DIR)) {
51
+ if (!scopeName.startsWith('@')) continue;
52
+ const scopeDir = path.join(INSTALL_DIR, scopeName);
53
+ try {
54
+ if (!fs.statSync(scopeDir).isDirectory()) continue;
55
+ for (const ident of fs.readdirSync(scopeDir)) {
56
+ if (ident.startsWith('.')) continue;
57
+ const dir = path.join(scopeDir, ident);
58
+ if (!fs.statSync(dir).isDirectory()) continue;
59
+ out.push({ scope: scopeName, ident, dir, full: `${scopeName}/${ident}` });
60
+ }
61
+ } catch {
62
+ /* skip */
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+
68
+ // ─── kdna available ────────────────────────────────────────────────────
69
+
70
+ function cmdAvailable(args = []) {
71
+ const wantJson = args.includes('--json');
72
+ const installed = listInstalled();
73
+
74
+ const out = [];
75
+ for (const e of installed) {
76
+ const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
77
+ if (manifest.yanked === true) continue;
78
+
79
+ const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
80
+
81
+ // Pull applies_when across all axioms (this is what the agent needs
82
+ // for fit-check). Collapsing per-axiom into one set makes the agent's
83
+ // matching decision much cheaper.
84
+ const applies_when = [];
85
+ const does_not_apply_when = [];
86
+ const failure_risks = [];
87
+ for (const a of core.axioms || []) {
88
+ if (Array.isArray(a.applies_when)) applies_when.push(...a.applies_when);
89
+ if (Array.isArray(a.does_not_apply_when)) does_not_apply_when.push(...a.does_not_apply_when);
90
+ if (a.failure_risk) failure_risks.push(a.failure_risk);
91
+ }
92
+
93
+ out.push({
94
+ name: manifest.name || e.full,
95
+ version: manifest.version || null,
96
+ judgment_version: manifest.judgment_version || null,
97
+ status: manifest.status || 'experimental',
98
+ description: manifest.description || '',
99
+ core_insight: manifest.core_insight || '',
100
+ keywords: manifest.keywords || [],
101
+ applies_when,
102
+ does_not_apply_when,
103
+ failure_risks,
104
+ });
105
+ }
106
+
107
+ if (wantJson) {
108
+ const result = out.length
109
+ ? out
110
+ : {
111
+ count: 0,
112
+ domains: [],
113
+ note: 'No domains installed. Run: kdna install <name> See: kdna list --available',
114
+ };
115
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
116
+ return;
117
+ }
118
+
119
+ // Human format
120
+ if (!out.length) {
121
+ console.log('No KDNA domains installed.');
122
+ console.log('Install with: kdna install <name>');
123
+ return;
124
+ }
125
+ console.log(`${out.length} installed KDNA domain(s):`);
126
+ for (const d of out) {
127
+ console.log('');
128
+ console.log(` ${d.name} v${d.version || '?'} [${d.status}]`);
129
+ if (d.description) console.log(` ${d.description}`);
130
+ if (d.applies_when.length) {
131
+ console.log(` applies when: ${d.applies_when.length} situations declared`);
132
+ }
133
+ if (d.does_not_apply_when.length) {
134
+ console.log(` does NOT apply when: ${d.does_not_apply_when.length} situations declared`);
135
+ }
136
+ }
137
+ }
138
+
139
+ // ─── kdna match ────────────────────────────────────────────────────────
140
+
141
+ function tokenize(text) {
142
+ return (text || '')
143
+ .toLowerCase()
144
+ .split(/[^a-z0-9_一-鿿]+/g)
145
+ .filter(Boolean);
146
+ }
147
+
148
+ function overlapScore(taskTokens, declaredText) {
149
+ if (!declaredText) return { hits: 0, coverage: 0 };
150
+ const declaredTokens = tokenize(declaredText);
151
+ if (!declaredTokens.length) return { hits: 0, coverage: 0 };
152
+ const dSet = new Set(declaredTokens);
153
+ let hits = 0;
154
+ for (const t of taskTokens) if (dSet.has(t)) hits++;
155
+ const coverage = taskTokens.length ? hits / taskTokens.length : 0;
156
+ return { hits, coverage };
157
+ }
158
+
159
+ // Minimum signal strength to report a hint (avoid noise from single-word matches)
160
+ const MIN_HITS = 2;
161
+ const MIN_COVERAGE = 0.15;
162
+
163
+ function domainRelevanceScore(taskTokens, domain) {
164
+ let score = 0;
165
+ const sources = [
166
+ (domain.description || '').toLowerCase(),
167
+ (domain.core_insight || '').toLowerCase(),
168
+ (domain.keywords || []).join(' ').toLowerCase(),
169
+ ];
170
+ for (const source of sources) {
171
+ const tokens = tokenize(source);
172
+ const srcSet = new Set(tokens);
173
+ for (const t of taskTokens) {
174
+ if (srcSet.has(t)) score += 2;
175
+ }
176
+ }
177
+ return score;
178
+ }
179
+
180
+ function cmdMatch(taskText, args = []) {
181
+ const wantJson = args.includes('--json');
182
+ if (!taskText) {
183
+ console.error('Usage: kdna match "<task description>" [--json]');
184
+ process.exit(2);
185
+ }
186
+ const taskTokens = tokenize(taskText);
187
+ const installed = listInstalled();
188
+
189
+ const dropped = [];
190
+ const hints = [];
191
+
192
+ for (const e of installed) {
193
+ const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
194
+ if (manifest.yanked === true) {
195
+ dropped.push({ name: manifest.name || e.full, reason: 'yanked' });
196
+ continue;
197
+ }
198
+ const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
199
+
200
+ // does_not_apply_when disqualification (HARD signal)
201
+ let disqualified = null;
202
+ for (const a of core.axioms || []) {
203
+ for (const d of a.does_not_apply_when || []) {
204
+ const score = overlapScore(taskTokens, d);
205
+ if (score.hits >= 2) {
206
+ disqualified = { axiom: a.id, text: d };
207
+ break;
208
+ }
209
+ }
210
+ if (disqualified) break;
211
+ }
212
+ if (disqualified) {
213
+ dropped.push({
214
+ name: manifest.name || e.full,
215
+ reason: `does_not_apply_when matched on ${disqualified.axiom}`,
216
+ evidence: disqualified.text.slice(0, 120),
217
+ });
218
+ continue;
219
+ }
220
+
221
+ // applies_when hint signals (WEAK — for context only, not a decision)
222
+ const signals = [];
223
+ for (const a of core.axioms || []) {
224
+ for (const ap of a.applies_when || []) {
225
+ const score = overlapScore(taskTokens, ap);
226
+ if (score.hits >= MIN_HITS || score.coverage >= MIN_COVERAGE) {
227
+ signals.push({
228
+ source: `${a.id}.applies_when`,
229
+ hits: score.hits,
230
+ coverage: score.coverage,
231
+ text: ap.slice(0, 120),
232
+ });
233
+ }
234
+ }
235
+ }
236
+
237
+ // Domain-level relevance: check description, core_insight, keywords
238
+ const domainRelevance = domainRelevanceScore(taskTokens, manifest);
239
+
240
+ if (signals.length || domainRelevance >= 2) {
241
+ hints.push({
242
+ name: manifest.name || e.full,
243
+ description: manifest.description || '',
244
+ status: manifest.status || 'experimental',
245
+ domain_relevance: domainRelevance,
246
+ top_signals: signals.sort((a, b) => b.hits - a.hits).slice(0, 3),
247
+ });
248
+ }
249
+ }
250
+
251
+ // Sort hints by combined relevance (applies_when hits + domain relevance)
252
+ hints.sort((a, b) => {
253
+ const aScore = a.top_signals.reduce((s, sig) => s + sig.hits, 0) + (a.domain_relevance || 0);
254
+ const bScore = b.top_signals.reduce((s, sig) => s + sig.hits, 0) + (b.domain_relevance || 0);
255
+ return bScore - aScore;
256
+ });
257
+
258
+ const result = {
259
+ task: taskText.slice(0, 200),
260
+ dropped,
261
+ hints,
262
+ no_strong_matches: hints.length === 0,
263
+ note:
264
+ 'These are surface keyword signals only — many false positives are normal. ' +
265
+ "The agent must read each candidate domain's description + applies_when " +
266
+ 'in full and decide using language understanding. dropped is a hard signal: ' +
267
+ 'do not load any domain in dropped.',
268
+ };
269
+
270
+ if (wantJson) {
271
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
272
+ return;
273
+ }
274
+
275
+ // Human format — make it clear this is hint not decision
276
+ console.log(`Task: ${taskText.slice(0, 100)}${taskText.length > 100 ? '…' : ''}`);
277
+ console.log('');
278
+ console.log(`This is a HINT report. The agent makes the final fit decision`);
279
+ console.log(`by reading each candidate's full description and applies_when.`);
280
+ console.log('');
281
+
282
+ if (dropped.length) {
283
+ console.log(`Dropped (does_not_apply_when matched — do NOT load these):`);
284
+ for (const d of dropped) {
285
+ console.log(` ✗ ${d.name}: ${d.reason}`);
286
+ if (d.evidence) console.log(` "${d.evidence}"`);
287
+ }
288
+ console.log('');
289
+ }
290
+
291
+ if (!hints.length) {
292
+ console.log('No strong keyword matches from installed domains.');
293
+ console.log('This means one of:');
294
+ console.log(' - No KDNA domain fits this task yet');
295
+ console.log(' - The fit is by meaning, not by word overlap');
296
+ console.log(' - The matching domain is not installed');
297
+ console.log('');
298
+ console.log('Run: kdna available to see what is installed and decide.');
299
+ console.log('See the registry: kdna search "<query>" (searches all published domains)');
300
+ } else {
301
+ console.log(`Keyword hints (${hints.length} domains had some token overlap):`);
302
+ for (const h of hints) {
303
+ console.log(` ${h.name} [${h.status}]`);
304
+ console.log(` ${h.description}`);
305
+ const relevanceIndicator =
306
+ h.domain_relevance >= 2
307
+ ? ` (domain relevance: ${'★'.repeat(Math.min(h.domain_relevance, 5))})`
308
+ : '';
309
+ if (relevanceIndicator) console.log(relevanceIndicator);
310
+ for (const s of h.top_signals) {
311
+ const pct = Math.round((s.coverage || 0) * 100);
312
+ console.log(` ↳ ${s.source} (${s.hits} hits, ${pct}% coverage): ${s.text}`);
313
+ }
314
+ }
315
+ console.log('');
316
+ console.log('To consider any of these, read its full data: kdna load <name> --as=json');
317
+ }
318
+ }
319
+
320
+ // ─── kdna load ─────────────────────────────────────────────────────────
321
+
322
+ function cmdLoad(input, args = []) {
323
+ const formatIdx = args.findIndex((a) => a.startsWith('--as'));
324
+ let format = 'prompt';
325
+ if (formatIdx >= 0) {
326
+ const eq = args[formatIdx].indexOf('=');
327
+ format = eq > 0 ? args[formatIdx].slice(eq + 1) : args[formatIdx + 1];
328
+ }
329
+
330
+ const parsed = parseName(input);
331
+ if (!parsed) {
332
+ console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
333
+ process.exit(2);
334
+ }
335
+ const dir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
336
+ if (!fs.existsSync(dir)) {
337
+ console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
338
+ process.exit(2);
339
+ }
340
+
341
+ const manifest = readJson(path.join(dir, 'kdna.json')) || {};
342
+ if (manifest.yanked === true) {
343
+ console.error(`${parsed.full}@${manifest.version} has been yanked.`);
344
+ if (manifest.replaced_by) console.error(`Try: ${manifest.replaced_by}`);
345
+ process.exit(2);
346
+ }
347
+ const core = readJson(path.join(dir, 'KDNA_Core.json')) || {};
348
+ const pat = readJson(path.join(dir, 'KDNA_Patterns.json')) || {};
349
+
350
+ if (format === 'json') {
351
+ process.stdout.write(JSON.stringify({ manifest, core, patterns: pat }, null, 2) + '\n');
352
+ return;
353
+ }
354
+
355
+ if (format === 'raw') {
356
+ for (const f of ['KDNA_Core.json', 'KDNA_Patterns.json']) {
357
+ const p = path.join(dir, f);
358
+ if (fs.existsSync(p)) {
359
+ process.stdout.write(`\n=== ${f} ===\n`);
360
+ process.stdout.write(fs.readFileSync(p, 'utf8'));
361
+ }
362
+ }
363
+ return;
364
+ }
365
+
366
+ // Default: --as=prompt — compact text optimized for system-prompt injection.
367
+ // Goal: minimum token cost while preserving all judgment surface.
368
+ const lines = [];
369
+ lines.push(`# KDNA loaded: ${manifest.name || parsed.full}`);
370
+ if (manifest.judgment_version) lines.push(`# judgment_version: ${manifest.judgment_version}`);
371
+ if (manifest.core_insight) lines.push(`# core insight: ${manifest.core_insight}`);
372
+ lines.push('');
373
+
374
+ if (core.axioms?.length) {
375
+ lines.push('## Axioms (reason from these)');
376
+ for (const a of core.axioms) {
377
+ lines.push(`- ${a.one_sentence}`);
378
+ if (a.applies_when?.length) {
379
+ lines.push(` APPLIES WHEN: ${a.applies_when.join('; ')}`);
380
+ }
381
+ if (a.does_not_apply_when?.length) {
382
+ lines.push(` DOES NOT APPLY WHEN: ${a.does_not_apply_when.join('; ')}`);
383
+ }
384
+ if (a.failure_risk) lines.push(` RISK IF MISAPPLIED: ${a.failure_risk}`);
385
+ }
386
+ lines.push('');
387
+ }
388
+
389
+ if (core.stances?.length) {
390
+ lines.push('## Stances');
391
+ for (const s of core.stances) {
392
+ const text = typeof s === 'string' ? s : s.stance;
393
+ if (text) lines.push(`- ${text}`);
394
+ }
395
+ lines.push('');
396
+ }
397
+
398
+ if (pat.terminology?.banned_terms?.length) {
399
+ lines.push('## Banned terms (do not use even if user uses them)');
400
+ for (const t of pat.terminology.banned_terms) {
401
+ const term = typeof t === 'string' ? t : t.term;
402
+ const replace = typeof t === 'object' ? t.replace_with : null;
403
+ lines.push(`- "${term}"${replace ? ` → use: ${replace}` : ''}`);
404
+ }
405
+ lines.push('');
406
+ }
407
+
408
+ if (pat.misunderstandings?.length) {
409
+ lines.push('## Misunderstandings to detect and avoid');
410
+ for (const m of pat.misunderstandings) {
411
+ lines.push(`- WRONG: ${m.wrong}`);
412
+ lines.push(` CORRECT: ${m.correct}`);
413
+ if (m.failure_risk) lines.push(` RISK: ${m.failure_risk}`);
414
+ }
415
+ lines.push('');
416
+ }
417
+
418
+ if (pat.self_check?.length) {
419
+ lines.push('## Self-checks (answer before final output)');
420
+ for (const q of pat.self_check) {
421
+ const text = typeof q === 'string' ? q : q.question;
422
+ if (text) lines.push(`- ${text}`);
423
+ }
424
+ lines.push('');
425
+ }
426
+
427
+ lines.push('---');
428
+ lines.push('Apply silently. Do not quote KDNA to the user. Do not say "according to KDNA".');
429
+ lines.push('User intent + evidence always override KDNA axioms.');
430
+
431
+ process.stdout.write(lines.join('\n') + '\n');
432
+ }
433
+
434
+ module.exports = { cmdAvailable, cmdMatch, cmdLoad };
package/src/cli.js ADDED
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * kdna — Unified CLI for KDNA domain cognition assets.
4
+ *
5
+ * Commands:
6
+ * kdna validate <path> Validate a domain directory or .kdna file
7
+ * kdna verify <path> Verify a domain (alias for validate)
8
+ * kdna pack <path> Pack a domain folder into .kdna container (ZIP)
9
+ * kdna unpack <path> Unpack .kdna container to domain folder
10
+ * kdna install <domain-id> Install a domain from registry
11
+ * kdna inspect <path> Inspect a domain directory or .kdna file
12
+ * kdna list List installed domains
13
+ * kdna compare <before> <after> Compare judgment with/without KDNA
14
+ * kdna match "<task>" Match task against available domains
15
+ * kdna setup One-command setup: install CLI + skills + data root
16
+ * kdna cluster lint <path> Validate a cluster manifest
17
+ * kdna identity init Generate Ed25519 identity key pair
18
+ * kdna identity show Display public key and buyer ID
19
+ */
20
+
21
+ const { usage, error } = require('./cmds/_common');
22
+ const { cmdValidate, cmdPack, cmdUnpack, cmdInspect } = require('./cmds/domain');
23
+ const { cmdList, cmdRegistry } = require('./cmds/registry');
24
+ const {
25
+ cmdCompare,
26
+ cmdDiff,
27
+ cmdSearch,
28
+ cmdAvailable,
29
+ cmdMatch,
30
+ cmdLoad,
31
+ } = require('./cmds/quality');
32
+ const { cmdCluster } = require('./cmds/cluster');
33
+ const { cmdIdentity } = require('./cmds/identity');
34
+ const { cmdSetup } = require('./cmds/setup');
35
+ const { cmdPreview, cmdProject, cmdEval, cmdSelect, cmdExport, cmdDemo } = require('./cmds/legacy');
36
+
37
+ // ─── Main ─────────────────────────────────────────────────────────────
38
+
39
+ const args = process.argv.slice(2);
40
+ if (!args.length || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
41
+ usage();
42
+ process.exit(0);
43
+ }
44
+
45
+ const cmd = args[0];
46
+
47
+ switch (cmd) {
48
+ case 'validate': {
49
+ const schemaFlag = args.includes('--schema');
50
+ const target = args.filter((a, i) => i > 0 && a !== '--schema')[0];
51
+ if (!target) error('Usage: kdna validate <path>');
52
+ cmdValidate(target, schemaFlag);
53
+ break;
54
+ }
55
+ case 'pack': {
56
+ let output = null;
57
+ let target = null;
58
+ for (let i = 1; i < args.length; i++) {
59
+ if (args[i] === '--output' || args[i] === '-o') {
60
+ output = args[i + 1];
61
+ i++;
62
+ } else if (!target) {
63
+ target = args[i];
64
+ }
65
+ }
66
+ if (!target) error('Usage: kdna pack <path>');
67
+ cmdPack(target, output);
68
+ break;
69
+ }
70
+ case 'unpack': {
71
+ const target = args[1];
72
+ if (!target) error('Usage: kdna unpack <file.kdna>');
73
+ cmdUnpack(target, args.includes('--force'));
74
+ break;
75
+ }
76
+ case 'preview': {
77
+ cmdPreview();
78
+ break;
79
+ }
80
+ case 'install': {
81
+ let domainId = null;
82
+ let fromGit = null;
83
+ for (let i = 1; i < args.length; i++) {
84
+ if (args[i] === '--from-git') {
85
+ fromGit = args[i + 1];
86
+ i++;
87
+ } else if (!domainId) {
88
+ domainId = args[i];
89
+ }
90
+ }
91
+ if (!domainId) error('Usage: kdna install <domain-id|github:user/repo|./folder>');
92
+
93
+ const { cmdInstallExtended } = require('./install');
94
+ if (fromGit) {
95
+ // Legacy --from-git: treat as github: URL
96
+ const url = fromGit.replace(/^https:\/\/github\.com\//, '').replace(/\.git$/, '');
97
+ cmdInstallExtended(`github:${url}`, args);
98
+ } else {
99
+ cmdInstallExtended(domainId, args);
100
+ }
101
+ break;
102
+ }
103
+ case 'registry': {
104
+ cmdRegistry(args[1]);
105
+ break;
106
+ }
107
+ case 'remove': {
108
+ const { cmdRemove } = require('./install');
109
+ const target = args[1];
110
+ if (!target) error('Usage: kdna remove <domain>');
111
+ cmdRemove(target);
112
+ break;
113
+ }
114
+ case 'info': {
115
+ const { cmdInfo } = require('./install');
116
+ const target = args[1];
117
+ if (!target) error('Usage: kdna info <domain>');
118
+ cmdInfo(target);
119
+ break;
120
+ }
121
+ case 'update': {
122
+ const { cmdUpdate, cmdUpdateAll } = require('./install');
123
+ if (args.includes('--all')) {
124
+ cmdUpdateAll();
125
+ } else {
126
+ const target = args[1];
127
+ if (!target) error('Usage: kdna update <domain>');
128
+ cmdUpdate(target);
129
+ }
130
+ break;
131
+ }
132
+ case 'inspect': {
133
+ const target = args[1];
134
+ if (!target) error('Usage: kdna inspect <path>');
135
+ cmdInspect(target);
136
+ break;
137
+ }
138
+ case 'verify': {
139
+ const { cmdVerify } = require('./verify');
140
+ const target = args.filter((a) => !a.startsWith('--'))[1];
141
+ if (!target) {
142
+ error(
143
+ 'Usage:\n' +
144
+ ' kdna verify <name> Run all three layers (structure / trust / judgment)\n' +
145
+ ' kdna verify <name> --structure Files + schema only\n' +
146
+ ' kdna verify <name> --trust Signature + scope + Ed25519 only\n' +
147
+ ' kdna verify <name> --judgment v2.1 governance fields + eval cases only',
148
+ );
149
+ }
150
+ cmdVerify(target, args);
151
+ break;
152
+ }
153
+ case 'compare': {
154
+ cmdCompare(args);
155
+ break;
156
+ }
157
+ case 'diff': {
158
+ cmdDiff(args);
159
+ break;
160
+ }
161
+ case 'search': {
162
+ cmdSearch(args);
163
+ break;
164
+ }
165
+ case 'available': {
166
+ cmdAvailable(args);
167
+ break;
168
+ }
169
+ case 'match': {
170
+ cmdMatch(args);
171
+ break;
172
+ }
173
+ case 'load': {
174
+ cmdLoad(args);
175
+ break;
176
+ }
177
+ case 'project': {
178
+ cmdProject();
179
+ break;
180
+ }
181
+ case 'eval': {
182
+ cmdEval();
183
+ break;
184
+ }
185
+ case 'select': {
186
+ cmdSelect();
187
+ break;
188
+ }
189
+ case 'export': {
190
+ cmdExport();
191
+ break;
192
+ }
193
+ case 'list': {
194
+ cmdList(args.includes('--available'));
195
+ break;
196
+ }
197
+ case 'demo': {
198
+ cmdDemo();
199
+ break;
200
+ }
201
+ case 'setup': {
202
+ cmdSetup();
203
+ break;
204
+ }
205
+ case 'cluster': {
206
+ cmdCluster(args);
207
+ break;
208
+ }
209
+ case 'identity': {
210
+ cmdIdentity(args);
211
+ break;
212
+ }
213
+ case 'init': {
214
+ const { cmdInit } = require('./init');
215
+ cmdInit(args[1]);
216
+ break;
217
+ }
218
+ case 'publish': {
219
+ if (args.includes('--check')) {
220
+ const { cmdPublishCheck } = require('./publish');
221
+ const idx = args.indexOf('--check');
222
+ const target = args[idx + 1] || args.filter((a) => !a.startsWith('--'))[1] || '.';
223
+ if (!target || target.startsWith('--')) error('Usage: kdna publish --check <path>');
224
+ cmdPublishCheck(target);
225
+ } else {
226
+ const { cmdPublish } = require('./publish');
227
+ const target = args.filter((a) => !a.startsWith('--'))[1];
228
+ if (!target) {
229
+ error(
230
+ 'Usage:\n' +
231
+ ' kdna publish <path> Pack + sign, output patch JSON\n' +
232
+ ' kdna publish <path> --release-tag <tag> --repo <owner/name>\n' +
233
+ ' ...also upload to GitHub Release\n' +
234
+ ' kdna publish --check <path> Quality gate only',
235
+ );
236
+ }
237
+ cmdPublish(target, args);
238
+ }
239
+ break;
240
+ }
241
+ case 'version': {
242
+ const { cmdVersionBump } = require('./version');
243
+ const sub = args[1];
244
+ if (sub === 'bump') {
245
+ const level = args[2];
246
+ const target = args[3] || '.';
247
+ if (!level || !['patch', 'minor', 'major'].includes(level)) {
248
+ error('Usage: kdna version bump <patch|minor|major> [path]');
249
+ }
250
+ cmdVersionBump(level, target);
251
+ } else {
252
+ console.log(`kdna v${require('../package.json').version}`);
253
+ console.log('');
254
+ console.log('Usage: kdna version bump <patch|minor|major> [path]');
255
+ }
256
+ break;
257
+ }
258
+ default:
259
+ error(`Unknown command: ${cmd}\nRun: kdna help`);
260
+ }