@aikdna/kdna-studio-cli 0.8.6 → 0.8.8
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 +41 -0
- package/bin/kdna-studio.js +186 -26
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,47 @@ kdna load dist/my_domain.kdna --profile=compact --as=prompt
|
|
|
72
72
|
runtime control plane for inspecting, validating, packing, unpacking, and
|
|
73
73
|
loading existing `.kdna` assets.
|
|
74
74
|
|
|
75
|
+
## 5-minute first asset
|
|
76
|
+
|
|
77
|
+
If you just installed and want to ship a v1 asset without an LLM:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 1. Init identity (required for the export signature)
|
|
81
|
+
kdna-studio identity init --name "Your Name"
|
|
82
|
+
|
|
83
|
+
# 2. Create a project
|
|
84
|
+
kdna-studio create my_domain --name @yourscope/my_domain
|
|
85
|
+
|
|
86
|
+
# 3. Add at least one judgment card
|
|
87
|
+
kdna-studio card add . axiom \
|
|
88
|
+
--field one_sentence='specific evidence outranks broad claims' \
|
|
89
|
+
--field full_statement='Always cite the specific source that supports a judgment; broad claims without evidence are the most common cause of bad agent advice.' \
|
|
90
|
+
--field why='because vague advice fails in production' \
|
|
91
|
+
--field applies_when='["reviewing"]' \
|
|
92
|
+
--field does_not_apply_when='["formatting"]' \
|
|
93
|
+
--field failure_risk='praise without diagnosis' \
|
|
94
|
+
--field confidence='high' \
|
|
95
|
+
--field evidence_type='observation'
|
|
96
|
+
|
|
97
|
+
# 4. Approve and lock
|
|
98
|
+
kdna-studio card approve . --all --by me --statement "i confirm"
|
|
99
|
+
|
|
100
|
+
# 5. Export the v1 asset
|
|
101
|
+
kdna-studio export . --format v1 --out my_domain.kdna
|
|
102
|
+
|
|
103
|
+
# 6. Verify with the runtime CLI
|
|
104
|
+
kdna load my_domain.kdna --profile=compact
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The first three AI commands (feynman, distill --ai, test, interview) work
|
|
108
|
+
without `KDNA_LLM_PROVIDER` / `KDNA_LLM_API_KEY` by passing `--no-llm`. They
|
|
109
|
+
return a structured but unsynthesised result. To enable real evaluation:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
kdna-studio llm config --provider openai --model gpt-4 --key <your-key>
|
|
113
|
+
# or set KDNA_LLM_PROVIDER, KDNA_LLM_API_KEY, KDNA_LLM_MODEL
|
|
114
|
+
```
|
|
115
|
+
|
|
75
116
|
## Runtime Export Contract
|
|
76
117
|
|
|
77
118
|
`kdna-studio export --format v1` is the canonical runtime export path. It uses
|
package/bin/kdna-studio.js
CHANGED
|
@@ -29,6 +29,35 @@ const {
|
|
|
29
29
|
} = loadStudioCore();
|
|
30
30
|
|
|
31
31
|
const llm = require('../src/llm');
|
|
32
|
+
|
|
33
|
+
// Bug (#1 UX): the LLM-requiring commands (distill, interview, feynman,
|
|
34
|
+
// test) silently fail with a stack trace when no LLM is configured.
|
|
35
|
+
// First-time users hit this and have no way to know what to do. The
|
|
36
|
+
// fix routes every LLM call through this helper, which:
|
|
37
|
+
// 1. Checks whether an LLM is configured at all
|
|
38
|
+
// 2. If not and --no-llm is given, returns a static / synthesised
|
|
39
|
+
// result so the command can still be useful (5-minute path)
|
|
40
|
+
// 3. If not and --no-llm is absent, exits with a clear
|
|
41
|
+
// "configure LLM with: kdna-studio llm config" message
|
|
42
|
+
function cmdNeedsLlm(args, cmdName) {
|
|
43
|
+
const cfg = llm.config();
|
|
44
|
+
const hasLlm = cfg && cfg.provider && cfg.apiKey && cfg.model;
|
|
45
|
+
if (hasLlm) return { hasLlm: true, cfg };
|
|
46
|
+
if (args.includes('--no-llm')) {
|
|
47
|
+
return {
|
|
48
|
+
hasLlm: false,
|
|
49
|
+
cfg,
|
|
50
|
+
noLlm: true,
|
|
51
|
+
message: `${cmdName} invoked with --no-llm. Output will be static / synthesised; configure an LLM with \`kdna-studio llm config\` to enable real evaluation.`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
fail(
|
|
55
|
+
`${cmdName} requires an LLM. Configure one with:\n` +
|
|
56
|
+
` kdna-studio llm config --provider openai --key <api-key> --model <model>\n` +
|
|
57
|
+
`Or set env vars: KDNA_LLM_PROVIDER, KDNA_LLM_API_KEY, KDNA_LLM_MODEL.\n` +
|
|
58
|
+
`Or run with --no-llm to get a static / synthesised result.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
32
61
|
const ai = require('../src/ai');
|
|
33
62
|
|
|
34
63
|
const EXIT = { OK: 0, INPUT_ERROR: 2, HUMAN_LOCK_REQUIRED: 4, TRUST_FAILED: 5 };
|
|
@@ -36,9 +65,12 @@ const EXIT = { OK: 0, INPUT_ERROR: 2, HUMAN_LOCK_REQUIRED: 4, TRUST_FAILED: 5 };
|
|
|
36
65
|
function usage() {
|
|
37
66
|
console.log(`kdna-studio — Studio-compatible KDNA authoring CLI
|
|
38
67
|
|
|
39
|
-
LLM (AI-powered authoring):
|
|
68
|
+
LLM (AI-powered authoring; every AI command accepts --no-llm for a static result):
|
|
40
69
|
kdna-studio llm config [--provider <name>] [--model <name>] [--key-pipe] [--url <base-url>]
|
|
41
70
|
kdna-studio llm show
|
|
71
|
+
(run 'kdna-studio llm config --provider openai --key <key> --model gpt-4' to enable
|
|
72
|
+
feynman, distill --ai, interview, and test. With --no-llm these commands
|
|
73
|
+
still produce a structured but unsynthesised result.)
|
|
42
74
|
|
|
43
75
|
Identity:
|
|
44
76
|
kdna-studio identity init [--name <display-name>]
|
|
@@ -311,11 +343,40 @@ function importedCard(type, fields = {}, id = null) {
|
|
|
311
343
|
}
|
|
312
344
|
}
|
|
313
345
|
|
|
346
|
+
// Bug (#3 + #4 UX): payload.kdnab intentionally carries the same
|
|
347
|
+
// misunderstanding card in two places — `patterns[]` (the v1 schema's
|
|
348
|
+
// primary card list) and `reasoning.failure_modes[]` (the v1 schema's
|
|
349
|
+
// "what bad judgment looks like" view). Both share the same `id`.
|
|
350
|
+
// Prior version imported both, doubling every misunderstanding on
|
|
351
|
+
// re-import. The dedup is by id: when a card with the same id is
|
|
352
|
+
// pushed twice, the second push merges fields (the failure_modes
|
|
353
|
+
// entry carries a different field shape than patterns; merging
|
|
354
|
+
// keeps the richer failure_risk / applies_when / does_not_apply_when
|
|
355
|
+
// from the patterns entry).
|
|
356
|
+
const _importedIds = new Set();
|
|
314
357
|
function pushImportedCard(cards, type, fields = {}, id = null) {
|
|
358
|
+
if (id && _importedIds.has(id)) {
|
|
359
|
+
// Already imported — merge richer fields into the existing card.
|
|
360
|
+
const existing = cards.find(c => c.id === id);
|
|
361
|
+
if (existing) {
|
|
362
|
+
existing.fields = existing.fields || {};
|
|
363
|
+
for (const [k, v] of Object.entries(fields || {})) {
|
|
364
|
+
if (v !== undefined && v !== null && v !== '' &&
|
|
365
|
+
(existing.fields[k] === undefined || existing.fields[k] === '' ||
|
|
366
|
+
(Array.isArray(existing.fields[k]) && existing.fields[k].length === 0))) {
|
|
367
|
+
existing.fields[k] = v;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (id) _importedIds.add(id);
|
|
315
374
|
const card = importedCard(type, fields, id);
|
|
316
375
|
if (card) cards.push(card);
|
|
317
376
|
}
|
|
318
377
|
|
|
378
|
+
function resetImportedIds() { _importedIds.clear(); }
|
|
379
|
+
|
|
319
380
|
function decodePayload(bytes, manifest = {}) {
|
|
320
381
|
const raw = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
|
|
321
382
|
const encoding = manifest.payload?.encoding || manifest.container?.payload_encoding || null;
|
|
@@ -334,6 +395,7 @@ function decodePayload(bytes, manifest = {}) {
|
|
|
334
395
|
// intentional. Documented here so a future reader does not "fix" it
|
|
335
396
|
// by collapsing one path into the other.
|
|
336
397
|
function cardsFromV1Payload(payload) {
|
|
398
|
+
resetImportedIds();
|
|
337
399
|
const cards = [];
|
|
338
400
|
for (const sourceCard of (Array.isArray(payload.source_cards) ? payload.source_cards : [])) {
|
|
339
401
|
pushImportedCard(cards, sourceCard.type, sourceCard.fields || {}, sourceCard.id || null);
|
|
@@ -428,14 +490,29 @@ function cardsFromV1Payload(payload) {
|
|
|
428
490
|
pushImportedCard(cards, 'self_check', fields, fields.id || null);
|
|
429
491
|
}
|
|
430
492
|
for (const failureMode of (Array.isArray(payload.reasoning?.failure_modes) ? payload.reasoning?.failure_modes : [])) {
|
|
493
|
+
// Bug (#145 follow-up): the prior version dropped three
|
|
494
|
+
// judgment fields (failure_risk, applies_when, does_not_apply_when)
|
|
495
|
+
// when reading failure_modes back out of a v1 asset. A Studio
|
|
496
|
+
// project that round-tripped through `migrate --format v1` lost
|
|
497
|
+
// these fields every time. The fix reads them through, matching
|
|
498
|
+
// the export-side map in kdna-studio-core's buildPayload.
|
|
431
499
|
pushImportedCard(cards, 'misunderstanding', {
|
|
432
500
|
wrong: failureMode.mode || failureMode.wrong || '',
|
|
433
501
|
correct: failureMode.correct || '',
|
|
434
502
|
key_distinction: failureMode.key_distinction || '',
|
|
435
503
|
why: failureMode.why || '',
|
|
504
|
+
failure_risk: failureMode.failure_risk || '',
|
|
505
|
+
applies_when: Array.isArray(failureMode.applies_when) ? failureMode.applies_when : [],
|
|
506
|
+
does_not_apply_when: Array.isArray(failureMode.does_not_apply_when) ? failureMode.does_not_apply_when : [],
|
|
436
507
|
}, failureMode.id || null);
|
|
437
508
|
}
|
|
438
509
|
for (const chain of (Array.isArray(payload.reasoning?.reasoning_chains) ? payload.reasoning?.reasoning_chains : [])) {
|
|
510
|
+
// Bug (#4 UX): compile/index.js synthesises one reasoning chain
|
|
511
|
+
// per locked axiom (the legacy 1.0.0 path) and tags it
|
|
512
|
+
// `source_authored: false`. Importing those synthesised chains
|
|
513
|
+
// doubled the card count on round-trip. The fix skips chains
|
|
514
|
+
// that the producer explicitly marked as synthesised.
|
|
515
|
+
if (chain.source_authored === false) continue;
|
|
439
516
|
pushImportedCard(cards, 'reasoning', chain, chain.id || null);
|
|
440
517
|
}
|
|
441
518
|
for (const stage of (Array.isArray(payload.evolution?.stages) ? payload.evolution?.stages : [])) {
|
|
@@ -452,6 +529,7 @@ function cardsFromV1Payload(payload) {
|
|
|
452
529
|
}
|
|
453
530
|
|
|
454
531
|
function cardsFromLegacyPayload(payload) {
|
|
532
|
+
resetImportedIds();
|
|
455
533
|
const cards = [];
|
|
456
534
|
const judgment = payload.judgment || {};
|
|
457
535
|
const core = judgment.core || {};
|
|
@@ -701,6 +779,7 @@ function cmdCreate(args) {
|
|
|
701
779
|
* Cards are imported as draft — they do NOT inherit trust from the source.
|
|
702
780
|
*/
|
|
703
781
|
function importFromKdna(kdnaPath) {
|
|
782
|
+
resetImportedIds();
|
|
704
783
|
const absKdna = path.resolve(kdnaPath);
|
|
705
784
|
if (!fs.existsSync(absKdna)) fail(`KDNA asset not found: ${absKdna}`);
|
|
706
785
|
if (!absKdna.endsWith('.kdna')) fail('--from-kdna requires a .kdna file');
|
|
@@ -863,6 +942,7 @@ function importFromKdna(kdnaPath) {
|
|
|
863
942
|
* Outputs a schema audit report.
|
|
864
943
|
*/
|
|
865
944
|
function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
945
|
+
resetImportedIds();
|
|
866
946
|
const absSource = path.resolve(sourceDir);
|
|
867
947
|
if (!fs.existsSync(absSource)) fail(`Source folder not found: ${absSource}`);
|
|
868
948
|
if (!fs.statSync(absSource).isDirectory()) fail('--from-folder requires a directory');
|
|
@@ -1000,6 +1080,15 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1000
1080
|
const q = typeof sc === 'string' ? sc : sc.question || '';
|
|
1001
1081
|
importedCards.push(cardApi.createCard('self_check', { question: q }));
|
|
1002
1082
|
}
|
|
1083
|
+
// Bug (aesthetic round-trip): prior version dropped
|
|
1084
|
+
// `patterns.aesthetics` on import. `cardsFromV1Payload` and
|
|
1085
|
+
// `cardsFromLegacyPayload` both read the field, so a domain
|
|
1086
|
+
// that authored aesthetic cards in either format would lose
|
|
1087
|
+
// them when imported through `create --from-folder`. The fix
|
|
1088
|
+
// adds the missing loop here.
|
|
1089
|
+
for (const aesthetic of (Array.isArray(patterns.aesthetics) ? patterns.aesthetics : [])) {
|
|
1090
|
+
importedCards.push(cardApi.createCard('aesthetic', aesthetic.fields || aesthetic));
|
|
1091
|
+
}
|
|
1003
1092
|
}
|
|
1004
1093
|
|
|
1005
1094
|
// NEW: import scenarios
|
|
@@ -1335,8 +1424,25 @@ function cmdCard(args) {
|
|
|
1335
1424
|
const sub = args[0];
|
|
1336
1425
|
if (sub === 'list') {
|
|
1337
1426
|
const { project } = readProject(args[1]);
|
|
1338
|
-
|
|
1339
|
-
|
|
1427
|
+
// Bug (#2 UX): the --json flag was accepted but ignored — the
|
|
1428
|
+
// command printed the same TSV either way. A script consumer
|
|
1429
|
+
// that wanted to pipe the result into `jq` or a downstream
|
|
1430
|
+
// tool got garbage. The fix respects --json and emits a real
|
|
1431
|
+
// JSON document.
|
|
1432
|
+
const useJson = args.includes('--json');
|
|
1433
|
+
const rows = (project.cards || []).map(card => ({
|
|
1434
|
+
id: card.id,
|
|
1435
|
+
type: card.type,
|
|
1436
|
+
status: card.status,
|
|
1437
|
+
locked: !!card.locked,
|
|
1438
|
+
fields: card.fields || {},
|
|
1439
|
+
}));
|
|
1440
|
+
if (useJson) {
|
|
1441
|
+
console.log(JSON.stringify({ count: rows.length, cards: rows }, null, 2));
|
|
1442
|
+
} else {
|
|
1443
|
+
for (const r of rows) {
|
|
1444
|
+
console.log(`${r.id}\t${r.type}\t${r.status}\t${r.locked ? 'locked' : 'unlocked'}`);
|
|
1445
|
+
}
|
|
1340
1446
|
}
|
|
1341
1447
|
return;
|
|
1342
1448
|
}
|
|
@@ -2236,9 +2342,15 @@ async function cmdDistill(args) {
|
|
|
2236
2342
|
const projectInput = args[0];
|
|
2237
2343
|
const candidatesFile = option(args, '--candidates');
|
|
2238
2344
|
const useAI = args.includes('--ai');
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2345
|
+
// Bug (#1 UX): prior version called resolveApiKey(args)
|
|
2346
|
+
// unconditionally, which is fine when --candidates is set (the
|
|
2347
|
+
// call is a no-op) but it also surfaced the env-var resolution
|
|
2348
|
+
// on every run, so a caller that never asked for the LLM was
|
|
2349
|
+
// still at the mercy of `resolveApiKey` warnings. Defer the
|
|
2350
|
+
// LLM-key resolution to the --ai branch.
|
|
2351
|
+
const provider = useAI ? option(args, '--provider') : null;
|
|
2352
|
+
const model = useAI ? option(args, '--model') : null;
|
|
2353
|
+
const apiKey = useAI ? resolveApiKey(args) : null;
|
|
2242
2354
|
if (!projectInput) fail('Usage: kdna-studio distill <project> [--ai] [--candidates <file.json>]');
|
|
2243
2355
|
if (!candidatesFile && !useAI) fail('Either --ai or --candidates <file.json> required.');
|
|
2244
2356
|
|
|
@@ -2401,31 +2513,62 @@ async function cmdFeynman(args) {
|
|
|
2401
2513
|
// `feynman_text`. The old check was dead code (nothing writes the wrong
|
|
2402
2514
|
// field) and made this command fail on every card.
|
|
2403
2515
|
//
|
|
2404
|
-
// Bug (#62 + #68): the prior
|
|
2405
|
-
//
|
|
2406
|
-
//
|
|
2407
|
-
//
|
|
2408
|
-
//
|
|
2409
|
-
//
|
|
2410
|
-
//
|
|
2411
|
-
//
|
|
2412
|
-
//
|
|
2413
|
-
//
|
|
2414
|
-
//
|
|
2415
|
-
//
|
|
2516
|
+
// Bug (#62 + #68 follow-up): the prior `if (!card.feynman_restatement)
|
|
2517
|
+
// fail(...)` block rejected every card that had no human-written
|
|
2518
|
+
// restatement, even when the AI evaluator in feynman.js was
|
|
2519
|
+
// prepared to auto-synthesise one. A caller without a configured
|
|
2520
|
+
// LLM (no `KDNA_API_KEY`, no `kdna-studio llm config`) would
|
|
2521
|
+
// get a `fail` instead of a structured note. The fix removes the
|
|
2522
|
+
// guard: a missing restatement is now handed off to the evaluator
|
|
2523
|
+
// (which either asks the LLM to evaluate a synthesised one or,
|
|
2524
|
+
// without an LLM, returns a structured `synthesised_restatement`
|
|
2525
|
+
// for the caller to use as a starting point). The error message
|
|
2526
|
+
// is kept as a secondary path: only if the evaluator itself fails
|
|
2527
|
+
// does the command exit non-zero, and only then with a clear
|
|
2528
|
+
// pointer to the manual `card update` path.
|
|
2529
|
+
//
|
|
2530
|
+
// We do still need a restatement-shaped object to feed the
|
|
2531
|
+
// evaluator, so we synthesise one in-memory when the card has
|
|
2532
|
+
// none. This does NOT persist the synthesised restatement to the
|
|
2533
|
+
// project (so the user still has to opt in via `card update` to
|
|
2534
|
+
// keep one across runs), but it lets the no-LLM path return a
|
|
2535
|
+
// useful result instead of a hard fail.
|
|
2416
2536
|
if (!card.feynman_restatement) {
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
);
|
|
2537
|
+
const synthesised = ai.feynman.synthFeynmanRestatement(card.fields || {});
|
|
2538
|
+
card.feynman_restatement = { text: synthesised, synthesised: true };
|
|
2539
|
+
console.warn(`No feynman_restatement on ${cardId}; using synthesised:\n "${synthesised}"`);
|
|
2421
2540
|
}
|
|
2422
2541
|
|
|
2423
2542
|
console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2424
|
-
|
|
2543
|
+
|
|
2544
|
+
// Bug (#1 UX follow-up): if no LLM is configured and the caller
|
|
2545
|
+
// did not pass --no-llm, route through cmdNeedsLlm which either
|
|
2546
|
+
// gives a clear "configure LLM" error or accepts the --no-llm
|
|
2547
|
+
// path. Without this branch, the no-LLM caller would fall
|
|
2548
|
+
// through to the LLM call below and get a stack trace.
|
|
2549
|
+
const llmGate = cmdNeedsLlm(args, 'feynmann');
|
|
2550
|
+
|
|
2551
|
+
let result;
|
|
2552
|
+
if (llmGate.noLlm) {
|
|
2553
|
+
// Static evaluation: every criterion is "unsure" (✗), score 0,
|
|
2554
|
+
// but the command still produces a useful structured result and
|
|
2555
|
+
// saves the synthesised restatement to the card.
|
|
2556
|
+
result = {
|
|
2557
|
+
score: 0,
|
|
2558
|
+
criteria: Object.fromEntries(Object.keys(ai.feynman.CRITERIA).map(k => [k, false])),
|
|
2559
|
+
explanations: { noLlm: llmGate.message },
|
|
2560
|
+
suggestions: [
|
|
2561
|
+
'No LLM configured — every criterion shows ✗. Run `kdna-studio llm config` and re-evaluate for a real score.',
|
|
2562
|
+
'Or set the synthesised restatement via: kdna-studio card update <project> ' + cardId + ' --field feynman_restatement=\'{"text":"<your own restatement>"}\'',
|
|
2563
|
+
],
|
|
2564
|
+
};
|
|
2565
|
+
} else {
|
|
2566
|
+
result = await ai.feynman.evaluate(llmGate.cfg, card, {});
|
|
2567
|
+
}
|
|
2425
2568
|
const score = result.score || 0;
|
|
2426
2569
|
const passed = score >= 4;
|
|
2427
2570
|
|
|
2428
|
-
console.log(`Score: ${score}/5 ${passed ? '✓ publishable' : '✗ below threshold (need 4/5)'}`);
|
|
2571
|
+
console.log(`Score: ${score}/5 ${passed ? '✓ publishable' : '✗ below threshold (need 4/5)'}${llmGate.noLlm ? ' (--no-llm: static result)' : ''}`);
|
|
2429
2572
|
for (const [criterion, desc] of Object.entries(ai.feynman.CRITERIA)) {
|
|
2430
2573
|
const r = (result.criteria || {})[criterion] ? '✓' : '✗';
|
|
2431
2574
|
console.log(` ${r} ${criterion}`);
|
|
@@ -2471,18 +2614,35 @@ async function cmdTest(args) {
|
|
|
2471
2614
|
const canonical = PRESET_ALIASES[preset] || 'baseline';
|
|
2472
2615
|
console.log(`Testing: ${domainName} [${preset}]`);
|
|
2473
2616
|
|
|
2617
|
+
// Bug (#1 UX): gate LLM-requiring paths through cmdNeedsLlm.
|
|
2618
|
+
// The test command always needs an LLM (it compares without/with
|
|
2619
|
+
// the loaded domain). --no-llm gives a static "no-op" comparison
|
|
2620
|
+
// that lists the cards that *would* be applied.
|
|
2621
|
+
const llmGate = cmdNeedsLlm(args, 'test');
|
|
2622
|
+
const cfg = llmGate.noLlm ? null : llmGate.cfg;
|
|
2623
|
+
|
|
2474
2624
|
if (canonical === 'baseline') {
|
|
2475
2625
|
// Baseline is a single comparison: keep the prior single-result
|
|
2476
2626
|
// shape so existing parsers / consumers see the same output.
|
|
2477
2627
|
const prompt = `Test the core judgment: ${input}`;
|
|
2478
|
-
|
|
2628
|
+
if (cfg === null) {
|
|
2629
|
+
// No-LLM static result
|
|
2630
|
+
const cards = (project.cards || []).filter(c => c.locked).map(c => ({ type: c.type, id: c.id, one_sentence: c.fields?.one_sentence || c.fields?.question || '' }));
|
|
2631
|
+
console.log(JSON.stringify({ no_llm: true, message: llmGate.message, would_apply: cards }, null, 2));
|
|
2632
|
+
return;
|
|
2633
|
+
}
|
|
2634
|
+
const result = await ai.testlab.compare(cfg, domainName, prompt, domainPrompt, {});
|
|
2479
2635
|
console.log(JSON.stringify(result, null, 2));
|
|
2480
2636
|
return;
|
|
2481
2637
|
}
|
|
2482
2638
|
// For non-baseline presets, run the preset + the baseline so the user
|
|
2483
2639
|
// can see both side by side. The preset prompt is the one in
|
|
2484
2640
|
// ai/testlab.js — that's the code path that was dead before.
|
|
2485
|
-
|
|
2641
|
+
if (cfg === null) {
|
|
2642
|
+
console.log(JSON.stringify({ no_llm: true, message: llmGate.message }, null, 2));
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
const result = await ai.testlab.testPreset(cfg, domainName, input, domainPrompt, {});
|
|
2486
2646
|
console.log(JSON.stringify(result[canonical] || result, null, 2));
|
|
2487
2647
|
}
|
|
2488
2648
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-studio-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
4
4
|
"description": "Official KDNA Studio command-line authoring entry for creating, reviewing, compiling, and exporting .kdna judgment assets through the KDNA toolchain.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"node": ">=18"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@aikdna/kdna-studio-core": "^1.7.
|
|
42
|
+
"@aikdna/kdna-studio-core": "^1.7.6",
|
|
43
43
|
"ajv": "^8.20.0",
|
|
44
44
|
"ajv-formats": "^3.0.1",
|
|
45
45
|
"cbor-x": "^1.6.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@aikdna/kdna-core": "^0.15.
|
|
48
|
+
"@aikdna/kdna-core": "^0.15.5"
|
|
49
49
|
}
|
|
50
50
|
}
|