@aikdna/kdna-cli 0.17.0 → 0.19.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 +120 -101
- package/SECURITY.md +1 -1
- package/package.json +6 -4
- package/skills/kdna-loader/SKILL.md +23 -22
- package/src/agent.js +290 -159
- package/src/cli.js +117 -67
- package/src/cmds/_common.js +40 -18
- package/src/cmds/badge.js +14 -9
- package/src/cmds/changelog.js +32 -12
- package/src/cmds/cluster.js +80 -85
- package/src/cmds/doctor.js +10 -27
- package/src/cmds/domain.js +114 -427
- package/src/cmds/explain.js +119 -0
- package/src/cmds/governance.js +111 -42
- package/src/cmds/legacy.js +8 -9
- package/src/cmds/license.js +491 -26
- package/src/cmds/quality.js +10 -3
- package/src/cmds/registry.js +15 -67
- package/src/cmds/studio.js +99 -47
- package/src/cmds/test.js +9 -6
- package/src/cmds/trace.js +11 -7
- package/src/compare.js +41 -22
- package/src/diff.js +38 -24
- package/src/identity.js +9 -7
- package/src/init.js +2 -2
- package/src/install.js +147 -459
- package/src/loader.js +10 -10
- package/src/package-store.js +232 -0
- package/src/paths.js +44 -0
- package/src/publish.js +150 -51
- package/src/registry.js +81 -9
- package/src/setup.js +19 -20
- package/src/verify.js +293 -140
- package/src/version.js +15 -7
- package/templates/minimal-domain/kdna.json +7 -7
- package/templates/standard-domain/README.md +10 -10
- package/templates/standard-domain/kdna.json +7 -3
- package/validators/kdna-lint.js +45 -11
- package/src/cmds/encrypt.js +0 -199
package/src/cmds/registry.js
CHANGED
|
@@ -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,
|
|
2
|
+
const { error, loadRegistry, EXIT } = require('./_common');
|
|
3
|
+
const { listInstalled, readContainer } = require('../package-store');
|
|
5
4
|
|
|
6
|
-
function cmdList(showAvailable, jsonMode = false,
|
|
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
|
|
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
|
-
|
|
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
|
|
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((
|
|
114
|
-
const core =
|
|
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:
|
|
119
|
-
version: manifest?.version ||
|
|
120
|
-
type:
|
|
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(
|
|
86
|
+
console.log('Assets are stored under ~/.kdna/packages/.');
|
|
139
87
|
}
|
|
140
88
|
|
|
141
89
|
function cmdRegistry(subcommand) {
|
package/src/cmds/studio.js
CHANGED
|
@@ -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
|
|
|
@@ -79,9 +79,16 @@ const CARD_TEMPLATES = {
|
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
function cmdStudioScaffold(name, args = []) {
|
|
82
|
-
if (!name)
|
|
82
|
+
if (!name)
|
|
83
|
+
error(
|
|
84
|
+
'Usage: kdna studio scaffold <name> [--type=domain|cluster] [--minimal]',
|
|
85
|
+
EXIT.INPUT_ERROR,
|
|
86
|
+
);
|
|
83
87
|
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
84
|
-
error(
|
|
88
|
+
error(
|
|
89
|
+
`Invalid name "${name}". Use lowercase letters, numbers, hyphens, underscores. Start with letter.`,
|
|
90
|
+
EXIT.INPUT_ERROR,
|
|
91
|
+
);
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
const type = args.includes('--type=cluster') ? 'cluster' : 'domain';
|
|
@@ -154,15 +161,22 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
154
161
|
|
|
155
162
|
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
156
163
|
const project = readJson(abs);
|
|
157
|
-
if (!project || !project.kdna_studio)
|
|
164
|
+
if (!project || !project.kdna_studio)
|
|
165
|
+
error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
158
166
|
|
|
159
167
|
const errors = [];
|
|
160
168
|
const warnings = [];
|
|
161
169
|
const passed = [];
|
|
162
170
|
|
|
163
|
-
function fail(msg) {
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
function fail(msg) {
|
|
172
|
+
errors.push(msg);
|
|
173
|
+
}
|
|
174
|
+
function warn(msg) {
|
|
175
|
+
warnings.push(msg);
|
|
176
|
+
}
|
|
177
|
+
function ok(msg) {
|
|
178
|
+
passed.push(msg);
|
|
179
|
+
}
|
|
166
180
|
|
|
167
181
|
// Validate each card set
|
|
168
182
|
for (const [cardType, cardFile] of Object.entries(project.cards || {})) {
|
|
@@ -192,7 +206,11 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
192
206
|
} else {
|
|
193
207
|
ok(`axiom ${label}: applies_when has ${ax.applies_when.length} entries`);
|
|
194
208
|
}
|
|
195
|
-
if (
|
|
209
|
+
if (
|
|
210
|
+
!ax.does_not_apply_when ||
|
|
211
|
+
!Array.isArray(ax.does_not_apply_when) ||
|
|
212
|
+
ax.does_not_apply_when.length === 0
|
|
213
|
+
) {
|
|
196
214
|
fail(`axiom ${label}: missing does_not_apply_when`);
|
|
197
215
|
} else {
|
|
198
216
|
ok(`axiom ${label}: does_not_apply_when has ${ax.does_not_apply_when.length} entries`);
|
|
@@ -208,9 +226,11 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
208
226
|
case 'misunderstandings':
|
|
209
227
|
for (const ms of cards) {
|
|
210
228
|
const label = ms.id || '?';
|
|
211
|
-
if (!ms.wrong || ms.wrong.includes('[TODO]'))
|
|
229
|
+
if (!ms.wrong || ms.wrong.includes('[TODO]'))
|
|
230
|
+
warn(`misunderstanding ${label}: wrong is placeholder`);
|
|
212
231
|
else ok(`misunderstanding ${label}: wrong OK`);
|
|
213
|
-
if (!ms.correct || ms.correct.includes('[TODO]'))
|
|
232
|
+
if (!ms.correct || ms.correct.includes('[TODO]'))
|
|
233
|
+
warn(`misunderstanding ${label}: correct is placeholder`);
|
|
214
234
|
else ok(`misunderstanding ${label}: correct OK`);
|
|
215
235
|
if (!ms.key_distinction || ms.key_distinction.length < 15) {
|
|
216
236
|
fail(`misunderstanding ${label}: key_distinction missing or too short`);
|
|
@@ -223,14 +243,18 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
223
243
|
case 'boundaries':
|
|
224
244
|
for (const bd of cards) {
|
|
225
245
|
const label = bd.id || '?';
|
|
226
|
-
if (!bd.scope || bd.scope.includes('[TODO]'))
|
|
246
|
+
if (!bd.scope || bd.scope.includes('[TODO]'))
|
|
247
|
+
warn(`boundary ${label}: scope is placeholder`);
|
|
227
248
|
else ok(`boundary ${label}: scope OK`);
|
|
228
|
-
if (!bd.out_of_scope || bd.out_of_scope.includes('[TODO]'))
|
|
249
|
+
if (!bd.out_of_scope || bd.out_of_scope.includes('[TODO]'))
|
|
250
|
+
warn(`boundary ${label}: out_of_scope is placeholder`);
|
|
229
251
|
else ok(`boundary ${label}: out_of_scope OK`);
|
|
230
252
|
if (!bd.acceptable_exceptions || !Array.isArray(bd.acceptable_exceptions)) {
|
|
231
253
|
warn(`boundary ${label}: acceptable_exceptions not declared`);
|
|
232
254
|
} else {
|
|
233
|
-
ok(
|
|
255
|
+
ok(
|
|
256
|
+
`boundary ${label}: acceptable_exceptions has ${bd.acceptable_exceptions.length} entries`,
|
|
257
|
+
);
|
|
234
258
|
}
|
|
235
259
|
}
|
|
236
260
|
break;
|
|
@@ -240,8 +264,8 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
240
264
|
const label = sc.id || '?';
|
|
241
265
|
if (!sc.question || sc.question.includes('[TODO]')) {
|
|
242
266
|
warn(`self_check ${label}: question is placeholder`);
|
|
243
|
-
} else if (!sc.question
|
|
244
|
-
fail(`self_check ${label}: question should
|
|
267
|
+
} else if (!isYesNoSelfCheck(sc.question)) {
|
|
268
|
+
fail(`self_check ${label}: question should be answerable with yes/no`);
|
|
245
269
|
} else {
|
|
246
270
|
ok(`self_check ${label}: question OK`);
|
|
247
271
|
}
|
|
@@ -251,11 +275,14 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
251
275
|
case 'ontology':
|
|
252
276
|
for (const ont of cards) {
|
|
253
277
|
const label = ont.id || '?';
|
|
254
|
-
if (!ont.essence || ont.essence.includes('[TODO]'))
|
|
278
|
+
if (!ont.essence || ont.essence.includes('[TODO]'))
|
|
279
|
+
warn(`ontology ${label}: essence is placeholder`);
|
|
255
280
|
else ok(`ontology ${label}: essence OK`);
|
|
256
|
-
if (!ont.boundary || ont.boundary.includes('[TODO]'))
|
|
281
|
+
if (!ont.boundary || ont.boundary.includes('[TODO]'))
|
|
282
|
+
warn(`ontology ${label}: boundary is placeholder`);
|
|
257
283
|
else ok(`ontology ${label}: boundary OK`);
|
|
258
|
-
if (!ont.trigger_signal || ont.trigger_signal.includes('[TODO]'))
|
|
284
|
+
if (!ont.trigger_signal || ont.trigger_signal.includes('[TODO]'))
|
|
285
|
+
warn(`ontology ${label}: trigger_signal is placeholder`);
|
|
259
286
|
else ok(`ontology ${label}: trigger_signal OK`);
|
|
260
287
|
}
|
|
261
288
|
break;
|
|
@@ -263,14 +290,20 @@ function cmdCardsValidate(projectPath, args = []) {
|
|
|
263
290
|
}
|
|
264
291
|
|
|
265
292
|
if (jsonMode) {
|
|
266
|
-
console.log(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
293
|
+
console.log(
|
|
294
|
+
JSON.stringify(
|
|
295
|
+
{
|
|
296
|
+
project: path.basename(abs),
|
|
297
|
+
valid: errors.length === 0,
|
|
298
|
+
errors,
|
|
299
|
+
warnings,
|
|
300
|
+
passed: passed.length,
|
|
301
|
+
total_checks: errors.length + warnings.length + passed.length,
|
|
302
|
+
},
|
|
303
|
+
null,
|
|
304
|
+
2,
|
|
305
|
+
),
|
|
306
|
+
);
|
|
274
307
|
process.exit(errors.length ? EXIT.VALIDATION_FAILED : EXIT.OK);
|
|
275
308
|
}
|
|
276
309
|
|
|
@@ -298,7 +331,8 @@ function cmdLockVerify(projectPath, args = []) {
|
|
|
298
331
|
|
|
299
332
|
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
300
333
|
const project = readJson(abs);
|
|
301
|
-
if (!project || !project.kdna_studio)
|
|
334
|
+
if (!project || !project.kdna_studio)
|
|
335
|
+
error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
302
336
|
|
|
303
337
|
const locked = [];
|
|
304
338
|
const unlocked = [];
|
|
@@ -324,7 +358,10 @@ function cmdLockVerify(projectPath, args = []) {
|
|
|
324
358
|
locked.push(label);
|
|
325
359
|
} else {
|
|
326
360
|
// Check Feynman restatement for axioms and misunderstandings
|
|
327
|
-
if (
|
|
361
|
+
if (
|
|
362
|
+
(cardType === 'axioms' || cardType === 'misunderstandings') &&
|
|
363
|
+
!card.feynman_restatement
|
|
364
|
+
) {
|
|
328
365
|
unlocked.push(label);
|
|
329
366
|
blocking.push(`${label} missing Feynman restatement`);
|
|
330
367
|
} else {
|
|
@@ -341,15 +378,21 @@ function cmdLockVerify(projectPath, args = []) {
|
|
|
341
378
|
const publishable = blocking.length === 0 && locked.length > 0;
|
|
342
379
|
|
|
343
380
|
if (jsonMode) {
|
|
344
|
-
console.log(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
381
|
+
console.log(
|
|
382
|
+
JSON.stringify(
|
|
383
|
+
{
|
|
384
|
+
project: path.basename(abs),
|
|
385
|
+
locked_cards: locked.length,
|
|
386
|
+
unlocked_cards: unlocked.length,
|
|
387
|
+
publishable,
|
|
388
|
+
blocking,
|
|
389
|
+
locked: locked.sort(),
|
|
390
|
+
unlocked: unlocked.sort(),
|
|
391
|
+
},
|
|
392
|
+
null,
|
|
393
|
+
2,
|
|
394
|
+
),
|
|
395
|
+
);
|
|
353
396
|
process.exit(publishable ? EXIT.OK : EXIT.HUMAN_LOCK_REQUIRED);
|
|
354
397
|
}
|
|
355
398
|
|
|
@@ -381,13 +424,15 @@ function cmdStudioCompile(projectPath, args = []) {
|
|
|
381
424
|
|
|
382
425
|
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
383
426
|
const project = readJson(abs);
|
|
384
|
-
if (!project || !project.kdna_studio)
|
|
427
|
+
if (!project || !project.kdna_studio)
|
|
428
|
+
error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
385
429
|
|
|
386
430
|
// Determine output directory
|
|
387
431
|
const outIdx = args.indexOf('--out');
|
|
388
|
-
const outDir =
|
|
389
|
-
|
|
390
|
-
|
|
432
|
+
const outDir =
|
|
433
|
+
outIdx >= 0
|
|
434
|
+
? path.resolve(args[outIdx + 1])
|
|
435
|
+
: path.join(path.dirname(abs), project.exports?.dir || 'exports');
|
|
391
436
|
|
|
392
437
|
fs.mkdirSync(outDir, { recursive: true });
|
|
393
438
|
|
|
@@ -398,7 +443,6 @@ function cmdStudioCompile(projectPath, args = []) {
|
|
|
398
443
|
// Compile axioms → KDNA_Core.json
|
|
399
444
|
const axioms = loadCards(project, path.dirname(abs), 'axioms');
|
|
400
445
|
const ontology = loadCards(project, path.dirname(abs), 'ontology');
|
|
401
|
-
const _boundaries = loadCards(project, path.dirname(abs), 'boundaries');
|
|
402
446
|
|
|
403
447
|
const core = {
|
|
404
448
|
meta: {
|
|
@@ -460,12 +504,17 @@ function cmdStudioCompile(projectPath, args = []) {
|
|
|
460
504
|
|
|
461
505
|
// Compile manifest → kdna.json
|
|
462
506
|
const manifest = {
|
|
463
|
-
|
|
507
|
+
format: 'kdna',
|
|
508
|
+
format_version: '1.0',
|
|
509
|
+
spec_version: '1.0-rc',
|
|
464
510
|
name: `@aikdna/${project.name}`,
|
|
465
511
|
version: '0.1.0',
|
|
512
|
+
judgment_version: '0.1.0',
|
|
466
513
|
status: 'experimental',
|
|
514
|
+
quality_badge: 'untested',
|
|
467
515
|
access: 'open',
|
|
468
|
-
|
|
516
|
+
languages: ['en'],
|
|
517
|
+
default_language: 'en',
|
|
469
518
|
author: { name: '', id: '' },
|
|
470
519
|
license: { type: 'CC-BY-4.0' },
|
|
471
520
|
description: '',
|
|
@@ -495,7 +544,7 @@ function cmdStudioCompile(projectPath, args = []) {
|
|
|
495
544
|
|
|
496
545
|
console.log('');
|
|
497
546
|
console.log('Next:');
|
|
498
|
-
console.log(` kdna validate ${outDir}`);
|
|
547
|
+
console.log(` kdna dev validate ${outDir}`);
|
|
499
548
|
}
|
|
500
549
|
|
|
501
550
|
function loadCards(project, projectDir, cardType) {
|
|
@@ -520,7 +569,8 @@ function cmdStudioReadiness(projectPath, args = []) {
|
|
|
520
569
|
|
|
521
570
|
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
522
571
|
const project = readJson(abs);
|
|
523
|
-
if (!project || !project.kdna_studio)
|
|
572
|
+
if (!project || !project.kdna_studio)
|
|
573
|
+
error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
524
574
|
|
|
525
575
|
const readiness = {
|
|
526
576
|
axioms: loadCardStats(project, path.dirname(abs), 'axioms'),
|
|
@@ -534,7 +584,9 @@ function cmdStudioReadiness(projectPath, args = []) {
|
|
|
534
584
|
};
|
|
535
585
|
|
|
536
586
|
// Determine publishability
|
|
537
|
-
const allTypes = Object.values(readiness).filter(
|
|
587
|
+
const allTypes = Object.values(readiness).filter(
|
|
588
|
+
(v) => v && typeof v === 'object' && 'total' in v,
|
|
589
|
+
);
|
|
538
590
|
let allLocked = allTypes.every((t) => t.total > 0 && t.total === t.locked);
|
|
539
591
|
if (allTypes.length === 0) allLocked = false;
|
|
540
592
|
readiness.publishable = allLocked;
|
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
|
|
42
|
-
if (!
|
|
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
|
-
|
|
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: {
|
|
@@ -115,8 +115,11 @@ function cmdTestRun(args = []) {
|
|
|
115
115
|
if (!jsonMode) {
|
|
116
116
|
console.log(`Test run recorded: ${result.test_id}`);
|
|
117
117
|
console.log(` Domain: ${result.domain}`);
|
|
118
|
-
console.log(
|
|
119
|
-
|
|
118
|
+
console.log(
|
|
119
|
+
` Input: ${result.input.slice(0, 100)}${result.input.length > 100 ? '...' : ''}`,
|
|
120
|
+
);
|
|
121
|
+
if (result.expected.classification)
|
|
122
|
+
console.log(` Expected classification: ${result.expected.classification}`);
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
|
package/src/cmds/trace.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const { EXIT } = require('./_common');
|
|
4
|
+
const PATHS = require('../paths');
|
|
5
5
|
|
|
6
|
-
const
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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(
|
|
183
|
-
const core =
|
|
184
|
-
const pat =
|
|
185
|
-
const manifest =
|
|
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,30 @@ 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
|
|
497
|
-
if (!
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
495
|
+
const asset = resolveAsset(input);
|
|
496
|
+
if (!asset)
|
|
497
|
+
error(
|
|
498
|
+
`KDNA asset not found: ${input}. Use an installed name or a .kdna file.`,
|
|
499
|
+
EXIT.INPUT_ERROR,
|
|
500
|
+
);
|
|
501
|
+
const parsed = asset.parsed || parseName(asset.name || '');
|
|
502
|
+
const label = parsed?.full || asset.name || input;
|
|
502
503
|
|
|
503
504
|
const llm = loadLlmConfig();
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
const
|
|
505
|
+
const container = readContainer(asset.asset_path);
|
|
506
|
+
const manifest = container.manifest || {};
|
|
507
|
+
const core = container.core || {};
|
|
508
|
+
const pat = container.patterns || {};
|
|
507
509
|
|
|
508
510
|
if (!jsonMode && !reportMd && !reportJson) {
|
|
509
511
|
console.log('═'.repeat(64));
|
|
510
|
-
console.log(` kdna compare ${
|
|
512
|
+
console.log(` kdna compare ${label}`);
|
|
511
513
|
console.log(` provider: ${llm.provider} / ${llm.model}`);
|
|
512
514
|
console.log(` input length: ${userInput.length} chars`);
|
|
513
515
|
console.log('═'.repeat(64));
|
|
@@ -516,7 +518,7 @@ async function cmdCompare(input, args = []) {
|
|
|
516
518
|
|
|
517
519
|
const BASELINE_SYSTEM =
|
|
518
520
|
'You are a helpful assistant. Respond to the user request concisely and specifically.';
|
|
519
|
-
const kdnaPrompt = buildKdnaPrompt(
|
|
521
|
+
const kdnaPrompt = buildKdnaPrompt(container);
|
|
520
522
|
if (!kdnaPrompt) error('Could not build KDNA prompt — missing KDNA_Core or KDNA_Patterns.');
|
|
521
523
|
const TREATMENT_SYSTEM =
|
|
522
524
|
'You are a helpful assistant. The following domain judgment is loaded and you MUST apply it when relevant.\n\n' +
|
|
@@ -540,13 +542,30 @@ async function cmdCompare(input, args = []) {
|
|
|
540
542
|
recordTrace({
|
|
541
543
|
timestamp: new Date().toISOString(),
|
|
542
544
|
agent: 'cli',
|
|
543
|
-
domain:
|
|
545
|
+
domain: label,
|
|
544
546
|
type: 'compare',
|
|
547
|
+
asset: {
|
|
548
|
+
asset_path: asset.asset_path,
|
|
549
|
+
asset_digest: asset.asset_digest || null,
|
|
550
|
+
content_digest: asset.content_digest || null,
|
|
551
|
+
version: manifest.version || asset.version || null,
|
|
552
|
+
judgment_version: manifest.judgment_version || asset.judgment_version || null,
|
|
553
|
+
access: manifest.access || asset.access || null,
|
|
554
|
+
},
|
|
545
555
|
compare: { model: llm.model, input_length: userInput.length },
|
|
546
556
|
});
|
|
547
557
|
|
|
548
558
|
if (reportMd) {
|
|
549
|
-
const report = emitMarkdownReport(
|
|
559
|
+
const report = emitMarkdownReport(
|
|
560
|
+
parsed || { full: label },
|
|
561
|
+
manifest,
|
|
562
|
+
core,
|
|
563
|
+
pat,
|
|
564
|
+
responseA,
|
|
565
|
+
responseB,
|
|
566
|
+
diff,
|
|
567
|
+
llm,
|
|
568
|
+
);
|
|
550
569
|
if (outputFile) {
|
|
551
570
|
fs.writeFileSync(outputFile, report);
|
|
552
571
|
console.log(`Report saved to ${outputFile}`);
|
|
@@ -558,7 +577,7 @@ async function cmdCompare(input, args = []) {
|
|
|
558
577
|
|
|
559
578
|
if (reportJson) {
|
|
560
579
|
const report = emitJsonReport(
|
|
561
|
-
parsed,
|
|
580
|
+
parsed || { full: label },
|
|
562
581
|
manifest,
|
|
563
582
|
core,
|
|
564
583
|
pat,
|