@aikdna/kdna-cli 0.9.0 → 0.11.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/verify.js CHANGED
@@ -7,15 +7,19 @@
7
7
  *
8
8
  * No flag = run all three.
9
9
  *
10
- * Exit codes:
10
+ * Exit codes (semantic, from cmds/_common.js):
11
11
  * 0 all checks passed (warnings allowed)
12
- * 1 one or more layers failed
12
+ * 1 VALIDATION_FAILED structure layer failed
13
+ * 2 INPUT_ERROR — invalid name or not installed
14
+ * 3 TRUST_FAILED — trust layer failed
15
+ * 4 JUDGMENT_QUALITY_FAILED — judgment layer failed
13
16
  */
14
17
 
15
18
  const fs = require('fs');
16
19
  const path = require('path');
17
20
  const crypto = require('crypto');
18
21
  const { RegistryResolver, parseName } = require('./registry');
22
+ const { EXIT } = require('./cmds/_common');
19
23
 
20
24
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
21
25
  const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
@@ -355,6 +359,8 @@ function renderLayer(result) {
355
359
  // ─── Main ──────────────────────────────────────────────────────────────
356
360
 
357
361
  function cmdVerify(input, args = []) {
362
+ const jsonMode = args.includes('--json');
363
+
358
364
  const want = {
359
365
  structure: args.includes('--structure'),
360
366
  trust: args.includes('--trust'),
@@ -366,14 +372,22 @@ function cmdVerify(input, args = []) {
366
372
  // Resolve name → installed path + scope/entry
367
373
  const parsed = parseName(input);
368
374
  if (!parsed) {
369
- console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
370
- process.exit(2);
375
+ if (jsonMode) {
376
+ console.log(JSON.stringify({ name: input, ok: false, error: `Invalid name "${input}". Use @scope/name or bare name.` }));
377
+ } else {
378
+ console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
379
+ }
380
+ process.exit(EXIT.INPUT_ERROR);
371
381
  }
372
382
 
373
383
  const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
374
384
  if (!fs.existsSync(destDir)) {
375
- console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
376
- process.exit(2);
385
+ if (jsonMode) {
386
+ console.log(JSON.stringify({ name: parsed.full, ok: false, error: `${parsed.full} is not installed. Run: kdna install ${input}` }));
387
+ } else {
388
+ console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
389
+ }
390
+ process.exit(EXIT.INPUT_ERROR);
377
391
  }
378
392
 
379
393
  let scope = null,
@@ -385,20 +399,55 @@ function cmdVerify(input, args = []) {
385
399
  scope = r.scope;
386
400
  entry = r.entry;
387
401
  } catch (e) {
388
- console.warn(` ⚠ registry lookup failed: ${e.message.split('\n')[0]}`);
402
+ if (!jsonMode) console.warn(` ⚠ registry lookup failed: ${e.message.split('\n')[0]}`);
389
403
  }
390
404
  }
391
405
 
392
- console.log('═'.repeat(64));
393
- console.log(` Verify ${parsed.full}`);
394
- console.log(` Path: ${destDir}`);
395
- console.log('═'.repeat(64));
396
-
397
406
  const results = [];
398
407
  if (want.structure) results.push(checkStructure(destDir));
399
408
  if (want.trust) results.push(checkTrust(destDir, scope, entry));
400
409
  if (want.judgment) results.push(checkJudgment(destDir));
401
410
 
411
+ // ── JSON output ──────────────────────────────────────────────────────
412
+ if (jsonMode) {
413
+ const layers = {};
414
+ for (const r of results) {
415
+ layers[r.layer] = {
416
+ passed: r.passed,
417
+ errors: r.issues.filter((i) => i.severity === 'error').map((i) => i.msg),
418
+ warnings: r.issues.filter((i) => i.severity === 'warn').map((i) => i.msg),
419
+ };
420
+ if (r.score) layers[r.layer].score = r.score;
421
+ }
422
+
423
+ const structureResult = results.find((r) => r.layer === 'structure');
424
+ const trustResult = results.find((r) => r.layer === 'trust');
425
+ const judgmentResult = results.find((r) => r.layer === 'judgment');
426
+
427
+ let exitCode = EXIT.OK;
428
+ if (structureResult && structureResult.issues.some((i) => i.severity === 'error')) {
429
+ exitCode = EXIT.VALIDATION_FAILED;
430
+ } else if (trustResult && trustResult.issues.some((i) => i.severity === 'error')) {
431
+ exitCode = EXIT.TRUST_FAILED;
432
+ } else if (judgmentResult && judgmentResult.issues.some((i) => i.severity === 'error')) {
433
+ exitCode = EXIT.JUDGMENT_QUALITY_FAILED;
434
+ }
435
+
436
+ console.log(JSON.stringify({
437
+ name: parsed.full,
438
+ path: destDir,
439
+ layers,
440
+ ok: exitCode === EXIT.OK,
441
+ }, null, 2));
442
+ process.exit(exitCode);
443
+ }
444
+
445
+ // ── Text output (default) ────────────────────────────────────────────
446
+ console.log('═'.repeat(64));
447
+ console.log(` Verify ${parsed.full}`);
448
+ console.log(` Path: ${destDir}`);
449
+ console.log('═'.repeat(64));
450
+
402
451
  for (const r of results) renderLayer(r);
403
452
 
404
453
  const totalErrors = results.reduce(
@@ -417,7 +466,21 @@ function cmdVerify(input, args = []) {
417
466
  }
418
467
  console.log('═'.repeat(64));
419
468
 
420
- process.exit(totalErrors === 0 ? 0 : 1);
469
+ // Semantic exit codes for text mode
470
+ const structureResult = results.find((r) => r.layer === 'structure');
471
+ const trustResult = results.find((r) => r.layer === 'trust');
472
+ const judgmentResult = results.find((r) => r.layer === 'judgment');
473
+
474
+ let exitCode = EXIT.OK;
475
+ if (structureResult && structureResult.issues.some((i) => i.severity === 'error')) {
476
+ exitCode = EXIT.VALIDATION_FAILED;
477
+ } else if (trustResult && trustResult.issues.some((i) => i.severity === 'error')) {
478
+ exitCode = EXIT.TRUST_FAILED;
479
+ } else if (judgmentResult && judgmentResult.issues.some((i) => i.severity === 'error')) {
480
+ exitCode = EXIT.JUDGMENT_QUALITY_FAILED;
481
+ }
482
+
483
+ process.exit(exitCode);
421
484
  }
422
485
 
423
486
  module.exports = { cmdVerify, checkStructure, checkTrust, checkJudgment };
package/src/version.js CHANGED
@@ -6,10 +6,11 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
+ const { EXIT } = require('./cmds/_common');
9
10
 
10
- function error(msg) {
11
+ function error(msg, code = EXIT.VALIDATION_FAILED) {
11
12
  console.error(`Error: ${msg}`);
12
- process.exit(1);
13
+ process.exit(code);
13
14
  }
14
15
 
15
16
  function readJson(filePath) {
@@ -109,4 +110,110 @@ function cmdVersionBump(level, domainPath) {
109
110
  console.log(`Done. Version: ${oldVersion} → ${newVersion}`);
110
111
  }
111
112
 
112
- module.exports = { cmdVersionBump };
113
+ /**
114
+ * kdna version bump --suggest [path]
115
+ * Suggest version bump based on judgment changes detected by kdna diff.
116
+ * Compares installed vs registry-current and suggests patch/minor/major.
117
+ */
118
+ function cmdVersionSuggest(domainPath = '.', args = []) {
119
+ const jsonMode = args.includes('--json');
120
+ const abs = path.resolve(domainPath);
121
+
122
+ const manifest = readJson(path.join(abs, 'kdna.json'));
123
+ if (!manifest) {
124
+ if (jsonMode) {
125
+ console.log(JSON.stringify({ error: 'No kdna.json found', suggestion: 'none' }));
126
+ process.exit(EXIT.OK);
127
+ }
128
+ console.log('No kdna.json found in current directory. Cannot suggest version bump.');
129
+ process.exit(EXIT.OK);
130
+ }
131
+
132
+ const currentVersion = manifest.version;
133
+ if (!currentVersion) {
134
+ if (jsonMode) {
135
+ console.log(JSON.stringify({ error: 'No version field', suggestion: 'none' }));
136
+ process.exit(EXIT.OK);
137
+ }
138
+ console.log('No version field in kdna.json.');
139
+ process.exit(EXIT.OK);
140
+ }
141
+
142
+ // Rules for suggesting:
143
+ // - If no previous version to diff against, suggest 'none'
144
+ // - Check for judgment_version changes
145
+ // - Check for axiom/ontology/misunderstanding changes
146
+
147
+ const changes = detectChanges(abs);
148
+
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));
156
+ return;
157
+ }
158
+
159
+ console.log(`Current version: ${currentVersion}`);
160
+ console.log(`Suggested bump: ${changes.suggestion}`);
161
+ console.log('');
162
+ if (changes.reasons.length) {
163
+ console.log('Reasons:');
164
+ changes.reasons.forEach((r) => console.log(` - ${r}`));
165
+ }
166
+ if (changes.suggestion !== 'none') {
167
+ console.log('');
168
+ console.log(`Run: kdna version bump ${changes.suggestion} ${domainPath}`);
169
+ }
170
+ }
171
+
172
+ function detectChanges(domainPath) {
173
+ const reasons = [];
174
+ let axiomChanges = 0;
175
+ const ontologyChanges = 0;
176
+ const misunderstandingChanges = 0;
177
+
178
+ // Simple heuristic: count content vs previous git state
179
+ // For now, use a heuristic based on file modification
180
+ const core = readJson(path.join(domainPath, 'KDNA_Core.json'));
181
+
182
+ // Check if evals/ dir has recent changes
183
+ const evalsDir = path.join(domainPath, 'evals');
184
+ if (fs.existsSync(evalsDir)) {
185
+ reasons.push('evals/ directory present');
186
+ }
187
+
188
+ // Check for judgment_version in manifest
189
+ const manifest = readJson(path.join(domainPath, 'kdna.json'));
190
+ if (manifest?.judgment_version) {
191
+ reasons.push(`judgment_version: ${manifest.judgment_version}`);
192
+ }
193
+
194
+ // Count axioms with applies_when (v2.1 governance) vs without
195
+ if (core?.axioms) {
196
+ const total = core.axioms.length;
197
+ const governed = core.axioms.filter((a) => a.applies_when?.length && a.does_not_apply_when?.length).length;
198
+ if (governed < total) {
199
+ axiomChanges = total - governed;
200
+ reasons.push(`${axiomChanges} axioms missing v2.1 governance fields`);
201
+ }
202
+ }
203
+
204
+ let suggestion = 'none';
205
+ if (axiomChanges > 0) suggestion = 'patch';
206
+ if (axiomChanges >= 3) suggestion = 'minor';
207
+
208
+ return {
209
+ suggestion,
210
+ reasons,
211
+ summary: {
212
+ axiom_changes: axiomChanges,
213
+ ontology_changes: ontologyChanges,
214
+ misunderstanding_changes: misunderstandingChanges,
215
+ },
216
+ };
217
+ }
218
+
219
+ module.exports = { cmdVersionBump, cmdVersionSuggest };