@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/diff.js
CHANGED
|
@@ -20,14 +20,15 @@ const fs = require('fs');
|
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const { execSync, execFileSync } = require('child_process');
|
|
22
22
|
const { RegistryResolver, parseName } = require('./registry');
|
|
23
|
+
const { EXIT } = require('./cmds/_common');
|
|
23
24
|
|
|
24
25
|
const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
|
|
25
26
|
const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
|
|
26
27
|
const TMP_DIR = '/tmp';
|
|
27
28
|
|
|
28
|
-
function error(msg) {
|
|
29
|
+
function error(msg, code = EXIT.VALIDATION_FAILED) {
|
|
29
30
|
console.error(`Error: ${msg}`);
|
|
30
|
-
process.exit(
|
|
31
|
+
process.exit(code);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function readJson(p) {
|
|
@@ -79,7 +80,7 @@ function downloadAndExtract(url, destDir) {
|
|
|
79
80
|
stdio: 'pipe',
|
|
80
81
|
});
|
|
81
82
|
} catch (e) {
|
|
82
|
-
error(`Failed to download ${url}: ${e.stderr?.toString().trim() || e.message}
|
|
83
|
+
error(`Failed to download ${url}: ${e.stderr?.toString().trim() || e.message}`, EXIT.PROVIDER_ERROR);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
fs.mkdirSync(destDir, { recursive: true });
|
|
@@ -117,7 +118,7 @@ function loadJudgment(domainDir) {
|
|
|
117
118
|
|
|
118
119
|
// ─── Set diff helpers ─────────────────────────────────────────────────
|
|
119
120
|
|
|
120
|
-
function diffMaps(label, oldMap, newMap, render) {
|
|
121
|
+
function diffMaps(label, oldMap, newMap, render, jsonMode = false) {
|
|
121
122
|
const oldIds = new Set(Object.keys(oldMap));
|
|
122
123
|
const newIds = new Set(Object.keys(newMap));
|
|
123
124
|
const added = [...newIds].filter((id) => !oldIds.has(id));
|
|
@@ -125,7 +126,24 @@ function diffMaps(label, oldMap, newMap, render) {
|
|
|
125
126
|
const both = [...newIds].filter((id) => oldIds.has(id));
|
|
126
127
|
const changed = both.filter((id) => JSON.stringify(oldMap[id]) !== JSON.stringify(newMap[id]));
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
// Collect boundary-level diffs for JSON output
|
|
130
|
+
const changedDetails = changed.map((id) => {
|
|
131
|
+
const a = oldMap[id],
|
|
132
|
+
b = newMap[id];
|
|
133
|
+
const boundaryChanges = {};
|
|
134
|
+
for (const field of ['applies_when', 'does_not_apply_when', 'failure_risk', 'confidence']) {
|
|
135
|
+
const before = a[field] ?? null;
|
|
136
|
+
const after = b[field] ?? null;
|
|
137
|
+
if (JSON.stringify(before) !== JSON.stringify(after)) {
|
|
138
|
+
boundaryChanges[field] = { before, after };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { id, before: a, after: b, boundary_changes: boundaryChanges };
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const out = { label, added, removed, changed, changedDetails };
|
|
145
|
+
|
|
146
|
+
if (jsonMode) return out;
|
|
129
147
|
|
|
130
148
|
console.log('');
|
|
131
149
|
console.log('─'.repeat(64));
|
|
@@ -162,34 +180,39 @@ function diffMaps(label, oldMap, newMap, render) {
|
|
|
162
180
|
return out;
|
|
163
181
|
}
|
|
164
182
|
|
|
165
|
-
function diffStanceList(oldList, newList) {
|
|
183
|
+
function diffStanceList(oldList, newList, jsonMode = false) {
|
|
166
184
|
const oldSet = new Set(oldList);
|
|
167
185
|
const newSet = new Set(newList);
|
|
168
186
|
const added = newList.filter((s) => !oldSet.has(s));
|
|
169
187
|
const removed = oldList.filter((s) => !newSet.has(s));
|
|
188
|
+
const out = { added, removed };
|
|
189
|
+
if (jsonMode) return out;
|
|
170
190
|
console.log('');
|
|
171
191
|
console.log('─'.repeat(64));
|
|
172
192
|
console.log(` STANCES added:${added.length} removed:${removed.length}`);
|
|
173
193
|
console.log('─'.repeat(64));
|
|
174
194
|
for (const s of added) console.log(` + "${s}"`);
|
|
175
195
|
for (const s of removed) console.log(` - "${s}"`);
|
|
196
|
+
return out;
|
|
176
197
|
}
|
|
177
198
|
|
|
178
199
|
// ─── Main ──────────────────────────────────────────────────────────────
|
|
179
200
|
|
|
180
|
-
async function cmdDiff(a, b) {
|
|
181
|
-
|
|
201
|
+
async function cmdDiff(a, b, args = []) {
|
|
202
|
+
const jsonMode = args.includes('--json');
|
|
203
|
+
|
|
204
|
+
if (!a) error('Usage: kdna diff <name>@<v1> <name>@<v2> or kdna diff <name>', EXIT.INPUT_ERROR);
|
|
182
205
|
|
|
183
206
|
const aParsed = parseNameVersion(a);
|
|
184
207
|
const bParsed = b ? parseNameVersion(b) : null;
|
|
185
|
-
if (!aParsed) error(`Cannot parse "${a}"
|
|
208
|
+
if (!aParsed) error(`Cannot parse "${a}"`, EXIT.INPUT_ERROR);
|
|
186
209
|
|
|
187
210
|
const resolver = new RegistryResolver({ allowNetwork: true });
|
|
188
211
|
let entryA;
|
|
189
212
|
try {
|
|
190
213
|
({ entry: entryA } = resolver.resolve(aParsed.full));
|
|
191
214
|
} catch (e) {
|
|
192
|
-
error(e.message);
|
|
215
|
+
error(e.message, EXIT.REGISTRY_ERROR);
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
// Determine targets
|
|
@@ -199,10 +222,10 @@ async function cmdDiff(a, b) {
|
|
|
199
222
|
try {
|
|
200
223
|
({ entry: entryB } = resolver.resolve(bParsed.full));
|
|
201
224
|
} catch (e) {
|
|
202
|
-
error(e.message);
|
|
225
|
+
error(e.message, EXIT.REGISTRY_ERROR);
|
|
203
226
|
}
|
|
204
227
|
if (aParsed.full !== bParsed.full)
|
|
205
|
-
error('Comparing across different domains is not supported.');
|
|
228
|
+
error('Comparing across different domains is not supported.', EXIT.INPUT_ERROR);
|
|
206
229
|
oldVersion = aParsed.version || entryA.version;
|
|
207
230
|
newVersion = bParsed.version || entryB.version;
|
|
208
231
|
oldEntry = entryA;
|
|
@@ -212,7 +235,7 @@ async function cmdDiff(a, b) {
|
|
|
212
235
|
const parsed = parseName(aParsed.full);
|
|
213
236
|
const localDir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
|
|
214
237
|
if (!fs.existsSync(localDir)) {
|
|
215
|
-
error(`${aParsed.full} not installed. Run: kdna install ${aParsed.full}
|
|
238
|
+
error(`${aParsed.full} not installed. Run: kdna install ${aParsed.full}`, EXIT.INPUT_ERROR);
|
|
216
239
|
}
|
|
217
240
|
const localManifest = readJson(path.join(localDir, 'kdna.json'));
|
|
218
241
|
oldVersion = localManifest?.version || '?';
|
|
@@ -220,6 +243,10 @@ async function cmdDiff(a, b) {
|
|
|
220
243
|
oldEntry = entryA;
|
|
221
244
|
newEntry = entryA;
|
|
222
245
|
if (oldVersion === newVersion) {
|
|
246
|
+
if (jsonMode) {
|
|
247
|
+
console.log(JSON.stringify({ error: `${aParsed.full}@${oldVersion}: only one version found.` }));
|
|
248
|
+
process.exit(EXIT.OK);
|
|
249
|
+
}
|
|
223
250
|
console.log(
|
|
224
251
|
`${aParsed.full}@${oldVersion}: only one version found.\n` +
|
|
225
252
|
`To compare across versions, specify two: kdna diff ${aParsed.full}@${oldVersion} ${aParsed.full}@<other>`,
|
|
@@ -228,41 +255,46 @@ async function cmdDiff(a, b) {
|
|
|
228
255
|
}
|
|
229
256
|
}
|
|
230
257
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
258
|
+
if (!jsonMode) {
|
|
259
|
+
console.log('═'.repeat(64));
|
|
260
|
+
console.log(` kdna diff ${aParsed.full}`);
|
|
261
|
+
console.log(` ${oldVersion} → ${newVersion}`);
|
|
262
|
+
console.log('═'.repeat(64));
|
|
263
|
+
}
|
|
235
264
|
|
|
236
265
|
// Download both versions to temp dirs
|
|
237
266
|
const tmpOld = path.join(TMP_DIR, `kdna-diff-${Date.now()}-old`);
|
|
238
267
|
const tmpNew = path.join(TMP_DIR, `kdna-diff-${Date.now()}-new`);
|
|
239
268
|
|
|
240
|
-
console.log('Downloading old version...');
|
|
269
|
+
if (!jsonMode) console.log('Downloading old version...');
|
|
241
270
|
downloadVersion(oldEntry, oldVersion, tmpOld);
|
|
242
|
-
console.log('Downloading new version...');
|
|
271
|
+
if (!jsonMode) console.log('Downloading new version...');
|
|
243
272
|
downloadVersion(newEntry, newVersion, tmpNew);
|
|
244
273
|
|
|
245
274
|
const oldJ = loadJudgment(tmpOld);
|
|
246
275
|
const newJ = loadJudgment(tmpNew);
|
|
247
276
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
277
|
+
if (!jsonMode) {
|
|
278
|
+
console.log('');
|
|
279
|
+
console.log(
|
|
280
|
+
' judgment_version: ' +
|
|
281
|
+
(oldJ.judgment_version || '(not declared)') +
|
|
282
|
+
' → ' +
|
|
283
|
+
(newJ.judgment_version || '(not declared)'),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
255
286
|
|
|
256
|
-
diffMaps('axioms', oldJ.axioms, newJ.axioms, (a) => a.one_sentence || a.id);
|
|
257
|
-
diffMaps('ontology', oldJ.ontology, newJ.ontology, (o) => o.one_sentence || o.id);
|
|
258
|
-
diffMaps(
|
|
287
|
+
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);
|
|
289
|
+
const misunderstandingsDiff = diffMaps(
|
|
259
290
|
'misunderstandings',
|
|
260
291
|
oldJ.misunderstandings,
|
|
261
292
|
newJ.misunderstandings,
|
|
262
293
|
(m) => m.wrong || m.id,
|
|
294
|
+
jsonMode,
|
|
263
295
|
);
|
|
264
|
-
diffMaps('banned_terms', oldJ.banned_terms, newJ.banned_terms, (t) => t.term || '');
|
|
265
|
-
diffStanceList(oldJ.stances, newJ.stances);
|
|
296
|
+
const bannedDiff = diffMaps('banned_terms', oldJ.banned_terms, newJ.banned_terms, (t) => t.term || '', jsonMode);
|
|
297
|
+
const stancesDiff = diffStanceList(oldJ.stances, newJ.stances, jsonMode);
|
|
266
298
|
|
|
267
299
|
// Cleanup
|
|
268
300
|
try {
|
|
@@ -276,13 +308,79 @@ async function cmdDiff(a, b) {
|
|
|
276
308
|
/* ignore */
|
|
277
309
|
}
|
|
278
310
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
311
|
+
// Derive structured JSON fields
|
|
312
|
+
const changedAxioms = axiomsDiff.changedDetails.map((d) => ({
|
|
313
|
+
id: d.id,
|
|
314
|
+
changes: d.boundary_changes,
|
|
315
|
+
}));
|
|
316
|
+
|
|
317
|
+
const changedBoundaries = axiomsDiff.changedDetails
|
|
318
|
+
.filter((d) => Object.keys(d.boundary_changes).length > 0)
|
|
319
|
+
.map((d) => ({
|
|
320
|
+
axiom_id: d.id,
|
|
321
|
+
boundary_changes: d.boundary_changes,
|
|
322
|
+
}));
|
|
323
|
+
|
|
324
|
+
const newMisunderstandings = misunderstandingsDiff.added;
|
|
325
|
+
const deprecatedSelfChecks = []; // self_checks are not part of diffMaps; would need separate tracking
|
|
326
|
+
|
|
327
|
+
const riskModelChanges = axiomsDiff.changedDetails
|
|
328
|
+
.filter((d) => d.boundary_changes.failure_risk)
|
|
329
|
+
.map((d) => ({
|
|
330
|
+
axiom_id: d.id,
|
|
331
|
+
before: d.boundary_changes.failure_risk.before,
|
|
332
|
+
after: d.boundary_changes.failure_risk.after,
|
|
333
|
+
}));
|
|
334
|
+
|
|
335
|
+
const affectedScenarios = axiomsDiff.changedDetails
|
|
336
|
+
.filter(
|
|
337
|
+
(d) =>
|
|
338
|
+
d.boundary_changes.applies_when ||
|
|
339
|
+
d.boundary_changes.does_not_apply_when,
|
|
340
|
+
)
|
|
341
|
+
.map((d) => ({
|
|
342
|
+
axiom_id: d.id,
|
|
343
|
+
applies_when: d.boundary_changes.applies_when || null,
|
|
344
|
+
does_not_apply_when: d.boundary_changes.does_not_apply_when || null,
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
// Determine recommended version bump
|
|
348
|
+
const axiomDrift = Object.keys(newJ.axioms).length - Object.keys(oldJ.axioms).length;
|
|
349
|
+
const hasRemoved = axiomsDiff.removed.length > 0 || misunderstandingsDiff.removed.length > 0;
|
|
350
|
+
const hasAdded = axiomsDiff.added.length > 0 || misunderstandingsDiff.added.length > 0;
|
|
351
|
+
const hasChanged = axiomsDiff.changed.length > 0 || bannedDiff.changed.length > 0;
|
|
352
|
+
let recommendedVersionBump = 'none';
|
|
353
|
+
if (hasRemoved) recommendedVersionBump = 'major';
|
|
354
|
+
else if (hasAdded || hasChanged) recommendedVersionBump = 'minor';
|
|
355
|
+
else if (stancesDiff.added.length > 0 || stancesDiff.removed.length > 0) recommendedVersionBump = 'patch';
|
|
356
|
+
|
|
357
|
+
if (jsonMode) {
|
|
358
|
+
const result = {
|
|
359
|
+
domain: aParsed.full,
|
|
360
|
+
old_version: oldVersion,
|
|
361
|
+
new_version: newVersion,
|
|
362
|
+
judgment_version: {
|
|
363
|
+
before: oldJ.judgment_version || null,
|
|
364
|
+
after: newJ.judgment_version || null,
|
|
365
|
+
},
|
|
366
|
+
changed_axioms: changedAxioms,
|
|
367
|
+
changed_boundaries: changedBoundaries,
|
|
368
|
+
new_misunderstandings: newMisunderstandings,
|
|
369
|
+
deprecated_self_checks: deprecatedSelfChecks,
|
|
370
|
+
risk_model_changes: riskModelChanges,
|
|
371
|
+
affected_scenarios: affectedScenarios,
|
|
372
|
+
recommended_version_bump: recommendedVersionBump,
|
|
373
|
+
};
|
|
374
|
+
console.log(JSON.stringify(result, null, 2));
|
|
375
|
+
} else {
|
|
376
|
+
console.log('');
|
|
377
|
+
console.log('═'.repeat(64));
|
|
378
|
+
const drift = Object.keys(newJ.axioms).length - Object.keys(oldJ.axioms).length;
|
|
379
|
+
const note = drift !== 0 ? ` (axiom count drift: ${drift > 0 ? '+' : ''}${drift})` : '';
|
|
380
|
+
console.log(` Judgment surface change: ${oldVersion} → ${newVersion}${note}`);
|
|
381
|
+
console.log(` Agent loading the new version may classify, diagnose, or recommend differently.`);
|
|
382
|
+
console.log('═'.repeat(64));
|
|
383
|
+
}
|
|
286
384
|
}
|
|
287
385
|
|
|
288
386
|
module.exports = { cmdDiff, loadJudgment, parseNameVersion };
|
package/src/identity.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const crypto = require('crypto');
|
|
14
|
+
const { EXIT } = require('./cmds/_common');
|
|
14
15
|
|
|
15
16
|
const IDENTITY_DIR =
|
|
16
17
|
process.env.KDNA_IDENTITY_DIR ||
|
|
@@ -19,9 +20,9 @@ const IDENTITY_DIR =
|
|
|
19
20
|
const PRIVATE_KEY_PATH = path.join(IDENTITY_DIR, 'kdna.key');
|
|
20
21
|
const PUBLIC_KEY_PATH = path.join(IDENTITY_DIR, 'kdna.pub');
|
|
21
22
|
|
|
22
|
-
function error(msg) {
|
|
23
|
+
function error(msg, code = EXIT.VALIDATION_FAILED) {
|
|
23
24
|
console.error(`Error: ${msg}`);
|
|
24
|
-
process.exit(
|
|
25
|
+
process.exit(code);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
// ─── Key Generation ────────────────────────────────────────────────────
|
|
@@ -75,15 +76,30 @@ function cmdIdentityInit() {
|
|
|
75
76
|
|
|
76
77
|
// ─── Show ──────────────────────────────────────────────────────────────
|
|
77
78
|
|
|
78
|
-
function cmdIdentityShow() {
|
|
79
|
+
function cmdIdentityShow(jsonMode = false) {
|
|
79
80
|
if (!fs.existsSync(PUBLIC_KEY_PATH)) {
|
|
80
|
-
|
|
81
|
+
if (jsonMode) {
|
|
82
|
+
console.log(JSON.stringify({ error: 'No identity found. Run: kdna identity init' }));
|
|
83
|
+
process.exit(EXIT.INPUT_ERROR);
|
|
84
|
+
}
|
|
85
|
+
error('No identity found. Run: kdna identity init', EXIT.INPUT_ERROR);
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
const pub = fs.readFileSync(PUBLIC_KEY_PATH, 'utf8');
|
|
84
89
|
const id = deriveBuyerId(pub);
|
|
85
90
|
const fp = fingerprint(pub);
|
|
86
91
|
|
|
92
|
+
if (jsonMode) {
|
|
93
|
+
console.log(JSON.stringify({
|
|
94
|
+
pubkey: pub.trim(),
|
|
95
|
+
buyer_id: id,
|
|
96
|
+
fingerprint: fp,
|
|
97
|
+
public_key_path: PUBLIC_KEY_PATH,
|
|
98
|
+
private_key_exists: fs.existsSync(PRIVATE_KEY_PATH),
|
|
99
|
+
}));
|
|
100
|
+
process.exit(EXIT.OK);
|
|
101
|
+
}
|
|
102
|
+
|
|
87
103
|
console.log(`Buyer ID: ${id}`);
|
|
88
104
|
console.log(`Fingerprint: ${fp}`);
|
|
89
105
|
console.log(`Public key: ${PUBLIC_KEY_PATH}`);
|