@aikdna/kdna-cli 0.9.0 → 0.12.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/README.md +109 -31
- package/SECURITY.md +41 -0
- package/package.json +3 -2
- package/src/agent.js +430 -2
- package/src/cli.js +280 -38
- package/src/cmds/_common.js +68 -3
- package/src/cmds/badge.js +244 -0
- package/src/cmds/changelog.js +226 -0
- package/src/cmds/cluster.js +214 -3
- package/src/cmds/doctor.js +233 -0
- package/src/cmds/domain.js +110 -33
- package/src/cmds/governance.js +471 -0
- package/src/cmds/identity.js +5 -4
- package/src/cmds/quality.js +34 -9
- package/src/cmds/registry.js +62 -22
- package/src/cmds/studio.js +577 -0
- package/src/cmds/test.js +177 -0
- package/src/cmds/trace.js +225 -0
- package/src/compare.js +46 -32
- package/src/diff.js +136 -38
- package/src/identity.js +20 -4
- package/src/install.js +181 -91
- package/src/publish.js +39 -3
- package/src/search.js +33 -2
- package/src/verify.js +98 -24
- package/src/version.js +110 -3
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
|
|
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');
|
|
@@ -213,7 +217,7 @@ function checkJudgment(destDir) {
|
|
|
213
217
|
const pat = readJson(path.join(destDir, 'KDNA_Patterns.json'));
|
|
214
218
|
const manifest = readJson(path.join(destDir, 'kdna.json'));
|
|
215
219
|
|
|
216
|
-
// 1. Boundary declaration in README
|
|
220
|
+
// 1. Boundary declaration in README (REQUIRED)
|
|
217
221
|
// Either classic "## Scope" + "## Out of Scope" pair,
|
|
218
222
|
// OR v2.1 "Four Questions" section (#2 = applies, #4 = does not).
|
|
219
223
|
const readmePath = path.join(destDir, 'README.md');
|
|
@@ -234,9 +238,12 @@ function checkJudgment(destDir) {
|
|
|
234
238
|
if ((hasScope && hasOutOfScope) || hasFourQuestions) {
|
|
235
239
|
bump(2, 2, 'README declares boundary (Scope+Out-of-Scope, or v2.1 Four Questions)');
|
|
236
240
|
} else if (hasScope || hasOutOfScope) {
|
|
237
|
-
|
|
241
|
+
score.max += 2;
|
|
242
|
+
score.total += 1;
|
|
243
|
+
issues.push({ severity: 'warn', msg: 'partial: README boundary declaration incomplete (missing Scope or Out-of-Scope section)' });
|
|
238
244
|
} else {
|
|
239
|
-
|
|
245
|
+
score.max += 2;
|
|
246
|
+
issues.push({ severity: 'error', msg: 'README missing boundary declaration: require ## Scope + ## Out of Scope (or v2.1 Four Questions)' });
|
|
240
247
|
}
|
|
241
248
|
|
|
242
249
|
// 2. v2.1 axiom governance fields
|
|
@@ -305,23 +312,31 @@ function checkJudgment(destDir) {
|
|
|
305
312
|
issues.push({ severity: 'warn', msg: `only ${total} self_check entries (recommend ≥3)` });
|
|
306
313
|
}
|
|
307
314
|
|
|
308
|
-
// 5. eval cases present
|
|
315
|
+
// 5. eval cases present (REQUIRED: ≥4 cases)
|
|
309
316
|
const evalDir = path.join(destDir, 'evals');
|
|
310
317
|
if (fs.existsSync(evalDir)) {
|
|
311
318
|
const files = fs.readdirSync(evalDir).filter((f) => f.endsWith('.json'));
|
|
312
|
-
if (files.length >= 4)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
if (files.length >= 4) {
|
|
320
|
+
bump(2, 2, `evals/ directory has ${files.length} case files`);
|
|
321
|
+
} else if (files.length > 0) {
|
|
322
|
+
score.max += 2;
|
|
323
|
+
score.total += 1;
|
|
324
|
+
issues.push({ severity: 'warn', msg: `evals/ has only ${files.length} files (require ≥4: core/boundary/failure/excluded)` });
|
|
325
|
+
} else {
|
|
326
|
+
score.max += 2;
|
|
327
|
+
issues.push({ severity: 'error', msg: 'evals/ directory exists but contains no case files' });
|
|
328
|
+
}
|
|
316
329
|
} else {
|
|
317
|
-
|
|
330
|
+
score.max += 2;
|
|
331
|
+
issues.push({ severity: 'error', msg: 'evals/ directory missing: require ≥4 evaluation cases' });
|
|
318
332
|
}
|
|
319
333
|
|
|
320
|
-
// 6. judgment_version manifest field
|
|
334
|
+
// 6. judgment_version manifest field (REQUIRED)
|
|
321
335
|
if (manifest?.judgment_version) {
|
|
322
336
|
bump(1, 1, `judgment_version: ${manifest.judgment_version}`);
|
|
323
337
|
} else {
|
|
324
|
-
|
|
338
|
+
score.max += 1;
|
|
339
|
+
issues.push({ severity: 'error', msg: 'kdna.json missing required field: judgment_version' });
|
|
325
340
|
}
|
|
326
341
|
|
|
327
342
|
return { layer: 'judgment', issues, passed, score };
|
|
@@ -355,6 +370,8 @@ function renderLayer(result) {
|
|
|
355
370
|
// ─── Main ──────────────────────────────────────────────────────────────
|
|
356
371
|
|
|
357
372
|
function cmdVerify(input, args = []) {
|
|
373
|
+
const jsonMode = args.includes('--json');
|
|
374
|
+
|
|
358
375
|
const want = {
|
|
359
376
|
structure: args.includes('--structure'),
|
|
360
377
|
trust: args.includes('--trust'),
|
|
@@ -366,14 +383,22 @@ function cmdVerify(input, args = []) {
|
|
|
366
383
|
// Resolve name → installed path + scope/entry
|
|
367
384
|
const parsed = parseName(input);
|
|
368
385
|
if (!parsed) {
|
|
369
|
-
|
|
370
|
-
|
|
386
|
+
if (jsonMode) {
|
|
387
|
+
console.log(JSON.stringify({ name: input, ok: false, error: `Invalid name "${input}". Use @scope/name or bare name.` }));
|
|
388
|
+
} else {
|
|
389
|
+
console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
|
|
390
|
+
}
|
|
391
|
+
process.exit(EXIT.INPUT_ERROR);
|
|
371
392
|
}
|
|
372
393
|
|
|
373
394
|
const destDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
|
|
374
395
|
if (!fs.existsSync(destDir)) {
|
|
375
|
-
|
|
376
|
-
|
|
396
|
+
if (jsonMode) {
|
|
397
|
+
console.log(JSON.stringify({ name: parsed.full, ok: false, error: `${parsed.full} is not installed. Run: kdna install ${input}` }));
|
|
398
|
+
} else {
|
|
399
|
+
console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
|
|
400
|
+
}
|
|
401
|
+
process.exit(EXIT.INPUT_ERROR);
|
|
377
402
|
}
|
|
378
403
|
|
|
379
404
|
let scope = null,
|
|
@@ -385,20 +410,55 @@ function cmdVerify(input, args = []) {
|
|
|
385
410
|
scope = r.scope;
|
|
386
411
|
entry = r.entry;
|
|
387
412
|
} catch (e) {
|
|
388
|
-
console.warn(` ⚠ registry lookup failed: ${e.message.split('\n')[0]}`);
|
|
413
|
+
if (!jsonMode) console.warn(` ⚠ registry lookup failed: ${e.message.split('\n')[0]}`);
|
|
389
414
|
}
|
|
390
415
|
}
|
|
391
416
|
|
|
392
|
-
console.log('═'.repeat(64));
|
|
393
|
-
console.log(` Verify ${parsed.full}`);
|
|
394
|
-
console.log(` Path: ${destDir}`);
|
|
395
|
-
console.log('═'.repeat(64));
|
|
396
|
-
|
|
397
417
|
const results = [];
|
|
398
418
|
if (want.structure) results.push(checkStructure(destDir));
|
|
399
419
|
if (want.trust) results.push(checkTrust(destDir, scope, entry));
|
|
400
420
|
if (want.judgment) results.push(checkJudgment(destDir));
|
|
401
421
|
|
|
422
|
+
// ── JSON output ──────────────────────────────────────────────────────
|
|
423
|
+
if (jsonMode) {
|
|
424
|
+
const layers = {};
|
|
425
|
+
for (const r of results) {
|
|
426
|
+
layers[r.layer] = {
|
|
427
|
+
passed: r.passed,
|
|
428
|
+
errors: r.issues.filter((i) => i.severity === 'error').map((i) => i.msg),
|
|
429
|
+
warnings: r.issues.filter((i) => i.severity === 'warn').map((i) => i.msg),
|
|
430
|
+
};
|
|
431
|
+
if (r.score) layers[r.layer].score = r.score;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const structureResult = results.find((r) => r.layer === 'structure');
|
|
435
|
+
const trustResult = results.find((r) => r.layer === 'trust');
|
|
436
|
+
const judgmentResult = results.find((r) => r.layer === 'judgment');
|
|
437
|
+
|
|
438
|
+
let exitCode = EXIT.OK;
|
|
439
|
+
if (structureResult && structureResult.issues.some((i) => i.severity === 'error')) {
|
|
440
|
+
exitCode = EXIT.VALIDATION_FAILED;
|
|
441
|
+
} else if (trustResult && trustResult.issues.some((i) => i.severity === 'error')) {
|
|
442
|
+
exitCode = EXIT.TRUST_FAILED;
|
|
443
|
+
} else if (judgmentResult && judgmentResult.issues.some((i) => i.severity === 'error')) {
|
|
444
|
+
exitCode = EXIT.JUDGMENT_QUALITY_FAILED;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log(JSON.stringify({
|
|
448
|
+
name: parsed.full,
|
|
449
|
+
path: destDir,
|
|
450
|
+
layers,
|
|
451
|
+
ok: exitCode === EXIT.OK,
|
|
452
|
+
}, null, 2));
|
|
453
|
+
process.exit(exitCode);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ── Text output (default) ────────────────────────────────────────────
|
|
457
|
+
console.log('═'.repeat(64));
|
|
458
|
+
console.log(` Verify ${parsed.full}`);
|
|
459
|
+
console.log(` Path: ${destDir}`);
|
|
460
|
+
console.log('═'.repeat(64));
|
|
461
|
+
|
|
402
462
|
for (const r of results) renderLayer(r);
|
|
403
463
|
|
|
404
464
|
const totalErrors = results.reduce(
|
|
@@ -417,7 +477,21 @@ function cmdVerify(input, args = []) {
|
|
|
417
477
|
}
|
|
418
478
|
console.log('═'.repeat(64));
|
|
419
479
|
|
|
420
|
-
|
|
480
|
+
// Semantic exit codes for text mode
|
|
481
|
+
const structureResult = results.find((r) => r.layer === 'structure');
|
|
482
|
+
const trustResult = results.find((r) => r.layer === 'trust');
|
|
483
|
+
const judgmentResult = results.find((r) => r.layer === 'judgment');
|
|
484
|
+
|
|
485
|
+
let exitCode = EXIT.OK;
|
|
486
|
+
if (structureResult && structureResult.issues.some((i) => i.severity === 'error')) {
|
|
487
|
+
exitCode = EXIT.VALIDATION_FAILED;
|
|
488
|
+
} else if (trustResult && trustResult.issues.some((i) => i.severity === 'error')) {
|
|
489
|
+
exitCode = EXIT.TRUST_FAILED;
|
|
490
|
+
} else if (judgmentResult && judgmentResult.issues.some((i) => i.severity === 'error')) {
|
|
491
|
+
exitCode = EXIT.JUDGMENT_QUALITY_FAILED;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
process.exit(exitCode);
|
|
421
495
|
}
|
|
422
496
|
|
|
423
497
|
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(
|
|
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
|
-
|
|
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 };
|