@aikdna/kdna-studio-cli 0.8.5 → 0.8.6
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/bin/kdna-studio.js +180 -50
- package/package.json +2 -2
- package/src/ai/feynman.js +51 -2
package/bin/kdna-studio.js
CHANGED
|
@@ -91,6 +91,27 @@ function fail(message, code = EXIT.INPUT_ERROR) {
|
|
|
91
91
|
process.exit(code);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Bug (#65): `fail()` calls `process.exit`, which skips `finally`
|
|
95
|
+
// blocks in the same call stack. Any tmpDir that a `try { ... }
|
|
96
|
+
// finally { fs.rmSync(tmpDir) }` block is in the middle of running
|
|
97
|
+
// will leak. The fix tracks every temporary directory the process
|
|
98
|
+
// creates and removes it on process exit (including the exit path
|
|
99
|
+
// triggered by `fail()`).
|
|
100
|
+
const _tempDirs = new Set();
|
|
101
|
+
function trackTempDir(dir) {
|
|
102
|
+
if (dir) _tempDirs.add(dir);
|
|
103
|
+
return dir;
|
|
104
|
+
}
|
|
105
|
+
function cleanupTempDirs() {
|
|
106
|
+
for (const dir of _tempDirs) {
|
|
107
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
108
|
+
}
|
|
109
|
+
_tempDirs.clear();
|
|
110
|
+
}
|
|
111
|
+
process.on('exit', cleanupTempDirs);
|
|
112
|
+
process.on('SIGINT', () => { cleanupTempDirs(); process.exit(130); });
|
|
113
|
+
process.on('SIGTERM', () => { cleanupTempDirs(); process.exit(143); });
|
|
114
|
+
|
|
94
115
|
function option(args, name, fallback = null) {
|
|
95
116
|
const idx = args.indexOf(name);
|
|
96
117
|
if (idx < 0) return fallback;
|
|
@@ -314,12 +335,12 @@ function decodePayload(bytes, manifest = {}) {
|
|
|
314
335
|
// by collapsing one path into the other.
|
|
315
336
|
function cardsFromV1Payload(payload) {
|
|
316
337
|
const cards = [];
|
|
317
|
-
for (const sourceCard of (payload.source_cards
|
|
338
|
+
for (const sourceCard of (Array.isArray(payload.source_cards) ? payload.source_cards : [])) {
|
|
318
339
|
pushImportedCard(cards, sourceCard.type, sourceCard.fields || {}, sourceCard.id || null);
|
|
319
340
|
}
|
|
320
341
|
if (cards.length > 0) return cards;
|
|
321
342
|
|
|
322
|
-
for (const ax of (payload.core?.axioms
|
|
343
|
+
for (const ax of (Array.isArray(payload.core?.axioms) ? payload.core?.axioms : [])) {
|
|
323
344
|
pushImportedCard(cards, 'axiom', {
|
|
324
345
|
one_sentence: ax.one_sentence || '',
|
|
325
346
|
full_statement: ax.full_statement || '',
|
|
@@ -329,19 +350,19 @@ function cardsFromV1Payload(payload) {
|
|
|
329
350
|
failure_risk: ax.failure_risk || '',
|
|
330
351
|
}, ax.id || null);
|
|
331
352
|
}
|
|
332
|
-
for (const ont of (payload.core?.ontology
|
|
353
|
+
for (const ont of (Array.isArray(payload.core?.ontology) ? payload.core?.ontology : [])) {
|
|
333
354
|
pushImportedCard(cards, 'ontology', ont, ont.id || null);
|
|
334
355
|
}
|
|
335
|
-
for (const fw of (payload.core?.frameworks
|
|
356
|
+
for (const fw of (Array.isArray(payload.core?.frameworks) ? payload.core?.frameworks : [])) {
|
|
336
357
|
pushImportedCard(cards, 'framework', fw, fw.id || null);
|
|
337
358
|
}
|
|
338
|
-
for (const stance of (payload.core?.stances
|
|
359
|
+
for (const stance of (Array.isArray(payload.core?.stances) ? payload.core?.stances : [])) {
|
|
339
360
|
pushImportedCard(cards, 'stance', stance, stance.id || null);
|
|
340
361
|
}
|
|
341
|
-
for (const aesthetic of (payload.core?.aesthetics
|
|
362
|
+
for (const aesthetic of (Array.isArray(payload.core?.aesthetics) ? payload.core?.aesthetics : [])) {
|
|
342
363
|
pushImportedCard(cards, 'aesthetic', aesthetic, aesthetic.id || null);
|
|
343
364
|
}
|
|
344
|
-
for (const boundary of (payload.core?.boundaries
|
|
365
|
+
for (const boundary of (Array.isArray(payload.core?.boundaries) ? payload.core?.boundaries : [])) {
|
|
345
366
|
pushImportedCard(cards, 'boundary', boundary, boundary.id || null);
|
|
346
367
|
}
|
|
347
368
|
// risk_model may be either an array of risk items (legacy) or
|
|
@@ -352,15 +373,45 @@ function cardsFromV1Payload(payload) {
|
|
|
352
373
|
for (const risk of riskItems) {
|
|
353
374
|
pushImportedCard(cards, 'risk', risk, risk.id || null);
|
|
354
375
|
}
|
|
355
|
-
for (const pattern of (payload.patterns
|
|
356
|
-
|
|
357
|
-
|
|
376
|
+
for (const pattern of (Array.isArray(payload.patterns) ? payload.patterns : [])) {
|
|
377
|
+
// Bug (#146): prior version dropped any payload.patterns entry
|
|
378
|
+
// that lacked an explicit `type` field. The v1 schema lists
|
|
379
|
+
// misunderstandings in patterns[] without a `type` discriminator
|
|
380
|
+
// (their shape is `{wrong, correct, key_distinction, why}`),
|
|
381
|
+
// and several exporters omit `type` to keep the on-the-wire
|
|
382
|
+
// shape compact. The fix infers a type from the field set when
|
|
383
|
+
// `type` is missing, and only falls through to "skip" when the
|
|
384
|
+
// entry truly cannot be classified.
|
|
385
|
+
let type = pattern.type;
|
|
386
|
+
let fields = pattern;
|
|
387
|
+
if (!type) {
|
|
388
|
+
// Heuristics, in priority order — most specific to most general.
|
|
389
|
+
if ('wrong' in pattern && 'correct' in pattern) type = 'misunderstanding';
|
|
390
|
+
else if ('term' in pattern && 'definition' in pattern) type = 'term';
|
|
391
|
+
else if ('term' in pattern && 'why' in pattern && 'replace_with' in pattern) type = 'banned_term';
|
|
392
|
+
else if ('what_it_looks_like' in pattern && 'how_to_fix' in pattern) type = 'pattern';
|
|
393
|
+
else if ('name' in pattern && ('description' in pattern || 'one_sentence' in pattern)) type = 'aesthetic';
|
|
394
|
+
else {
|
|
395
|
+
// Truly unclassifiable. Log a one-line warning and skip
|
|
396
|
+
// rather than crash, so the rest of the asset still imports.
|
|
397
|
+
console.warn(
|
|
398
|
+
`Skipping payload.patterns entry with no recognisable type ` +
|
|
399
|
+
`(id=${pattern.id || '?'}, keys=${Object.keys(pattern).join(',') || '<empty>'})`
|
|
400
|
+
);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
fields = { ...pattern };
|
|
404
|
+
delete fields.type;
|
|
405
|
+
} else {
|
|
406
|
+
const { type: _stripped, ...rest } = pattern;
|
|
407
|
+
fields = rest;
|
|
408
|
+
}
|
|
358
409
|
pushImportedCard(cards, type, fields, pattern.id || null);
|
|
359
410
|
}
|
|
360
|
-
for (const scenario of (payload.scenarios
|
|
411
|
+
for (const scenario of (Array.isArray(payload.scenarios) ? payload.scenarios : [])) {
|
|
361
412
|
pushImportedCard(cards, 'scenario', scenario, scenario.id || null);
|
|
362
413
|
}
|
|
363
|
-
for (const item of (payload.cases
|
|
414
|
+
for (const item of (Array.isArray(payload.cases) ? payload.cases : [])) {
|
|
364
415
|
pushImportedCard(cards, 'case', item, item.id || null);
|
|
365
416
|
}
|
|
366
417
|
// Accept both `self_check` (canonical) and `self_checks` (legacy schema
|
|
@@ -376,7 +427,7 @@ function cardsFromV1Payload(payload) {
|
|
|
376
427
|
const fields = typeof selfCheck === 'string' ? { question: selfCheck } : selfCheck;
|
|
377
428
|
pushImportedCard(cards, 'self_check', fields, fields.id || null);
|
|
378
429
|
}
|
|
379
|
-
for (const failureMode of (payload.reasoning?.failure_modes
|
|
430
|
+
for (const failureMode of (Array.isArray(payload.reasoning?.failure_modes) ? payload.reasoning?.failure_modes : [])) {
|
|
380
431
|
pushImportedCard(cards, 'misunderstanding', {
|
|
381
432
|
wrong: failureMode.mode || failureMode.wrong || '',
|
|
382
433
|
correct: failureMode.correct || '',
|
|
@@ -384,10 +435,10 @@ function cardsFromV1Payload(payload) {
|
|
|
384
435
|
why: failureMode.why || '',
|
|
385
436
|
}, failureMode.id || null);
|
|
386
437
|
}
|
|
387
|
-
for (const chain of (payload.reasoning?.reasoning_chains
|
|
438
|
+
for (const chain of (Array.isArray(payload.reasoning?.reasoning_chains) ? payload.reasoning?.reasoning_chains : [])) {
|
|
388
439
|
pushImportedCard(cards, 'reasoning', chain, chain.id || null);
|
|
389
440
|
}
|
|
390
|
-
for (const stage of (payload.evolution?.stages
|
|
441
|
+
for (const stage of (Array.isArray(payload.evolution?.stages) ? payload.evolution?.stages : [])) {
|
|
391
442
|
// Distinguish source-authored stages (preserved identity) from
|
|
392
443
|
// audit-log-derived stages (synthesised). The compile path tags
|
|
393
444
|
// source_authored: true; import those verbatim and skip the rest
|
|
@@ -406,7 +457,7 @@ function cardsFromLegacyPayload(payload) {
|
|
|
406
457
|
const core = judgment.core || {};
|
|
407
458
|
const patterns = judgment.patterns || {};
|
|
408
459
|
|
|
409
|
-
for (const ax of (core.axioms
|
|
460
|
+
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
410
461
|
pushImportedCard(cards, 'axiom', {
|
|
411
462
|
one_sentence: ax.one_sentence || '',
|
|
412
463
|
full_statement: ax.full_statement || '',
|
|
@@ -416,7 +467,7 @@ function cardsFromLegacyPayload(payload) {
|
|
|
416
467
|
failure_risk: ax.failure_risk || '',
|
|
417
468
|
}, ax.id || null);
|
|
418
469
|
}
|
|
419
|
-
for (const ont of (core.ontology
|
|
470
|
+
for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
|
|
420
471
|
pushImportedCard(cards, 'ontology', {
|
|
421
472
|
one_sentence: ont.one_sentence || ont.essence || '',
|
|
422
473
|
essence: ont.essence || '',
|
|
@@ -424,23 +475,32 @@ function cardsFromLegacyPayload(payload) {
|
|
|
424
475
|
trigger_signal: ont.trigger_signal || '',
|
|
425
476
|
}, ont.id || null);
|
|
426
477
|
}
|
|
427
|
-
for (const boundary of (core.boundaries
|
|
478
|
+
for (const boundary of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
|
|
428
479
|
pushImportedCard(cards, 'boundary', {
|
|
429
480
|
scope: boundary.scope || '',
|
|
430
481
|
out_of_scope: boundary.out_of_scope || '',
|
|
431
482
|
acceptable_exceptions: boundary.acceptable_exceptions || [],
|
|
432
483
|
}, boundary.id || null);
|
|
433
484
|
}
|
|
434
|
-
for (const risk of (core.risks
|
|
485
|
+
for (const risk of (Array.isArray(core.risks) ? core.risks : [])) {
|
|
435
486
|
pushImportedCard(cards, 'risk', risk, risk.id || null);
|
|
436
487
|
}
|
|
437
|
-
|
|
488
|
+
// Bug (#63): prior version skipped `core.stances` and
|
|
489
|
+
// `core.frameworks`, so a legacy asset that authored either type
|
|
490
|
+
// round-tripped out as zero cards. The fix adds both loops.
|
|
491
|
+
for (const stance of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
492
|
+
pushImportedCard(cards, 'stance', stance, stance.id || null);
|
|
493
|
+
}
|
|
494
|
+
for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
|
|
495
|
+
pushImportedCard(cards, 'framework', fw, fw.id || null);
|
|
496
|
+
}
|
|
497
|
+
for (const term of (Array.isArray(patterns.terminology?.standard_terms) ? patterns.terminology?.standard_terms : [])) {
|
|
438
498
|
pushImportedCard(cards, 'term', term, term.id || null);
|
|
439
499
|
}
|
|
440
|
-
for (const banned of (patterns.terminology?.banned_terms
|
|
500
|
+
for (const banned of (Array.isArray(patterns.terminology?.banned_terms) ? patterns.terminology?.banned_terms : [])) {
|
|
441
501
|
pushImportedCard(cards, 'banned_term', banned, banned.id || null);
|
|
442
502
|
}
|
|
443
|
-
for (const ms of (patterns.misunderstandings
|
|
503
|
+
for (const ms of (Array.isArray(patterns.misunderstandings) ? patterns.misunderstandings : [])) {
|
|
444
504
|
pushImportedCard(cards, 'misunderstanding', {
|
|
445
505
|
wrong: ms.wrong || '',
|
|
446
506
|
correct: ms.correct || '',
|
|
@@ -455,19 +515,19 @@ function cardsFromLegacyPayload(payload) {
|
|
|
455
515
|
const question = typeof sc === 'string' ? sc : sc.question || '';
|
|
456
516
|
pushImportedCard(cards, 'self_check', { question }, sc.id || null);
|
|
457
517
|
}
|
|
458
|
-
for (const aesthetic of (patterns.aesthetics
|
|
518
|
+
for (const aesthetic of (Array.isArray(patterns.aesthetics) ? patterns.aesthetics : [])) {
|
|
459
519
|
pushImportedCard(cards, 'aesthetic', aesthetic, aesthetic.id || null);
|
|
460
520
|
}
|
|
461
|
-
for (const scene of (judgment.scenarios?.scenes
|
|
521
|
+
for (const scene of (Array.isArray(judgment.scenarios?.scenes) ? judgment.scenarios?.scenes : [])) {
|
|
462
522
|
pushImportedCard(cards, 'scenario', scene, scene.id || null);
|
|
463
523
|
}
|
|
464
|
-
for (const item of (judgment.cases?.cases
|
|
524
|
+
for (const item of (Array.isArray(judgment.cases?.cases) ? judgment.cases?.cases : [])) {
|
|
465
525
|
pushImportedCard(cards, 'case', item, item.id || null);
|
|
466
526
|
}
|
|
467
|
-
for (const chain of (judgment.reasoning?.reasoning_chains
|
|
527
|
+
for (const chain of (Array.isArray(judgment.reasoning?.reasoning_chains) ? judgment.reasoning?.reasoning_chains : [])) {
|
|
468
528
|
pushImportedCard(cards, 'reasoning', chain, chain.id || null);
|
|
469
529
|
}
|
|
470
|
-
for (const stage of (judgment.evolution?.stages
|
|
530
|
+
for (const stage of (Array.isArray(judgment.evolution?.stages) ? judgment.evolution?.stages : [])) {
|
|
471
531
|
pushImportedCard(cards, 'evolution_stage', stage, stage.id || null);
|
|
472
532
|
}
|
|
473
533
|
return cards;
|
|
@@ -678,35 +738,50 @@ function importFromKdna(kdnaPath) {
|
|
|
678
738
|
fail(`Could not import cards from payload.kdnab: ${e.message}`);
|
|
679
739
|
}
|
|
680
740
|
} else if (entries.has('KDNA_Core.json') || entries.has('KDNA_Patterns.json')) {
|
|
681
|
-
// Legacy fallback —
|
|
682
|
-
//
|
|
683
|
-
//
|
|
741
|
+
// Legacy fallback — used when the v1 asset has no payload.kdnab
|
|
742
|
+
// (i.e. the asset predates the 0.7 split). Bug (#67): prior
|
|
743
|
+
// version only handled axiom / ontology / boundary / misunderstanding
|
|
744
|
+
// / self_check — 5 of the 14 card types — so every other type
|
|
745
|
+
// (risk / stance / framework / term / banned_term / aesthetic /
|
|
746
|
+
// scenario / case / reasoning / evolution_stage) round-tripped
|
|
747
|
+
// out as zero cards. The fix threads every type this CLI can
|
|
748
|
+
// import through, and prefers the same field-shape rules as
|
|
749
|
+
// cardsFromLegacyPayload so the round-trip is symmetric.
|
|
684
750
|
if (entries.has('KDNA_Core.json')) {
|
|
685
751
|
const core = JSON.parse(entries.get('KDNA_Core.json').toString());
|
|
686
|
-
for (const ax of (core.axioms
|
|
752
|
+
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
687
753
|
const fields = {};
|
|
688
754
|
for (const k of ['one_sentence','full_statement','why','applies_when','does_not_apply_when','failure_risk']) {
|
|
689
755
|
if (k in ax) fields[k] = ax[k];
|
|
690
756
|
}
|
|
691
757
|
importedCards.push(cardApi.createCard('axiom', fields));
|
|
692
758
|
}
|
|
693
|
-
for (const ont of (core.ontology
|
|
759
|
+
for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
|
|
694
760
|
importedCards.push(cardApi.createCard('ontology', {
|
|
695
761
|
one_sentence: ont.one_sentence || ont.essence || '',
|
|
696
762
|
essence: ont.essence || '', boundary: ont.boundary || '',
|
|
697
763
|
trigger_signal: ont.trigger_signal || '',
|
|
698
764
|
}));
|
|
699
765
|
}
|
|
700
|
-
for (const b of (core.boundaries
|
|
766
|
+
for (const b of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
|
|
701
767
|
importedCards.push(cardApi.createCard('boundary', {
|
|
702
768
|
scope: b.scope || '', out_of_scope: b.out_of_scope || '',
|
|
703
769
|
acceptable_exceptions: b.acceptable_exceptions || [],
|
|
704
770
|
}));
|
|
705
771
|
}
|
|
772
|
+
for (const risk of (core.risks || core.risk_model || [])) {
|
|
773
|
+
importedCards.push(cardApi.createCard('risk', risk.fields || risk));
|
|
774
|
+
}
|
|
775
|
+
for (const stance of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
776
|
+
importedCards.push(cardApi.createCard('stance', stance.fields || stance));
|
|
777
|
+
}
|
|
778
|
+
for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
|
|
779
|
+
importedCards.push(cardApi.createCard('framework', fw.fields || fw));
|
|
780
|
+
}
|
|
706
781
|
}
|
|
707
782
|
if (entries.has('KDNA_Patterns.json')) {
|
|
708
783
|
const pat = JSON.parse(entries.get('KDNA_Patterns.json').toString());
|
|
709
|
-
for (const ms of (pat.misunderstandings
|
|
784
|
+
for (const ms of (Array.isArray(pat.misunderstandings) ? pat.misunderstandings : [])) {
|
|
710
785
|
importedCards.push(cardApi.createCard('misunderstanding', {
|
|
711
786
|
wrong: ms.wrong || '', correct: ms.correct || '',
|
|
712
787
|
key_distinction: ms.key_distinction || '', why: ms.why || '',
|
|
@@ -716,6 +791,39 @@ function importFromKdna(kdnaPath) {
|
|
|
716
791
|
const q = typeof sc === 'string' ? sc : sc.question || '';
|
|
717
792
|
importedCards.push(cardApi.createCard('self_check', { question: q }));
|
|
718
793
|
}
|
|
794
|
+
for (const aesthetic of (Array.isArray(pat.aesthetics) ? pat.aesthetics : [])) {
|
|
795
|
+
importedCards.push(cardApi.createCard('aesthetic', aesthetic.fields || aesthetic));
|
|
796
|
+
}
|
|
797
|
+
for (const term of (Array.isArray(pat.terminology?.standard_terms) ? pat.terminology?.standard_terms : [])) {
|
|
798
|
+
importedCards.push(cardApi.createCard('term', term));
|
|
799
|
+
}
|
|
800
|
+
for (const banned of (Array.isArray(pat.terminology?.banned_terms) ? pat.terminology?.banned_terms : [])) {
|
|
801
|
+
importedCards.push(cardApi.createCard('banned_term', banned));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (entries.has('KDNA_Scenarios.json')) {
|
|
805
|
+
const scen = JSON.parse(entries.get('KDNA_Scenarios.json').toString());
|
|
806
|
+
for (const s of (Array.isArray(scen.scenes) ? scen.scenes : [])) {
|
|
807
|
+
importedCards.push(cardApi.createCard('scenario', s));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (entries.has('KDNA_Cases.json')) {
|
|
811
|
+
const cases = JSON.parse(entries.get('KDNA_Cases.json').toString());
|
|
812
|
+
for (const c of (Array.isArray(cases.cases) ? cases.cases : [])) {
|
|
813
|
+
importedCards.push(cardApi.createCard('case', c));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (entries.has('KDNA_Reasoning.json')) {
|
|
817
|
+
const reasoning = JSON.parse(entries.get('KDNA_Reasoning.json').toString());
|
|
818
|
+
for (const chain of (Array.isArray(reasoning.reasoning_chains) ? reasoning.reasoning_chains : [])) {
|
|
819
|
+
importedCards.push(cardApi.createCard('reasoning', chain));
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (entries.has('KDNA_Evolution.json')) {
|
|
823
|
+
const evolution = JSON.parse(entries.get('KDNA_Evolution.json').toString());
|
|
824
|
+
for (const stage of (Array.isArray(evolution.stages) ? evolution.stages : [])) {
|
|
825
|
+
importedCards.push(cardApi.createCard('evolution_stage', stage));
|
|
826
|
+
}
|
|
719
827
|
}
|
|
720
828
|
}
|
|
721
829
|
|
|
@@ -818,7 +926,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
818
926
|
}
|
|
819
927
|
|
|
820
928
|
if (core) {
|
|
821
|
-
for (const ax of (core.axioms
|
|
929
|
+
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
822
930
|
const fields = {};
|
|
823
931
|
for (const key of ['one_sentence', 'full_statement', 'why', 'applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
824
932
|
if (key in ax) fields[key] = ax[key];
|
|
@@ -826,14 +934,14 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
826
934
|
}
|
|
827
935
|
importedCards.push(cardApi.createCard('axiom', fields));
|
|
828
936
|
}
|
|
829
|
-
for (const ont of (core.ontology
|
|
937
|
+
for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
|
|
830
938
|
importedCards.push(cardApi.createCard('ontology', {
|
|
831
939
|
one_sentence: ont.one_sentence || ont.essence || '',
|
|
832
940
|
essence: ont.essence || '', boundary: ont.boundary || '',
|
|
833
941
|
trigger_signal: ont.trigger_signal || '',
|
|
834
942
|
}));
|
|
835
943
|
}
|
|
836
|
-
for (const b of (core.boundaries
|
|
944
|
+
for (const b of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
|
|
837
945
|
importedCards.push(cardApi.createCard('boundary', {
|
|
838
946
|
scope: b.scope || '', out_of_scope: b.out_of_scope || '',
|
|
839
947
|
acceptable_exceptions: b.acceptable_exceptions || [],
|
|
@@ -851,7 +959,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
851
959
|
importedCards.push(cardApi.createCard('risk', r.fields || r));
|
|
852
960
|
}
|
|
853
961
|
// NEW: import stances
|
|
854
|
-
for (const s of (core.stances
|
|
962
|
+
for (const s of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
855
963
|
const text = typeof s === 'string' ? s : s.stance || '';
|
|
856
964
|
importedCards.push(cardApi.createCard('stance', {
|
|
857
965
|
statement: text,
|
|
@@ -860,7 +968,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
860
968
|
}));
|
|
861
969
|
}
|
|
862
970
|
// NEW: import frameworks
|
|
863
|
-
for (const fw of (core.frameworks
|
|
971
|
+
for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
|
|
864
972
|
importedCards.push(cardApi.createCard('framework', {
|
|
865
973
|
name: fw.name || '', when_to_use: fw.when_to_use || '',
|
|
866
974
|
steps: fw.steps || [],
|
|
@@ -871,18 +979,18 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
871
979
|
if (patterns) {
|
|
872
980
|
// NEW: import terminology
|
|
873
981
|
if (patterns.terminology) {
|
|
874
|
-
for (const t of (patterns.terminology.standard_terms
|
|
982
|
+
for (const t of (Array.isArray(patterns.terminology.standard_terms) ? patterns.terminology.standard_terms : [])) {
|
|
875
983
|
importedCards.push(cardApi.createCard('term', {
|
|
876
984
|
term: t.term || '', definition: t.definition || '',
|
|
877
985
|
}));
|
|
878
986
|
}
|
|
879
|
-
for (const bt of (patterns.terminology.banned_terms
|
|
987
|
+
for (const bt of (Array.isArray(patterns.terminology.banned_terms) ? patterns.terminology.banned_terms : [])) {
|
|
880
988
|
importedCards.push(cardApi.createCard('banned_term', {
|
|
881
989
|
term: bt.term || '', why: bt.why || '', replace_with: bt.replace_with || '',
|
|
882
990
|
}));
|
|
883
991
|
}
|
|
884
992
|
}
|
|
885
|
-
for (const ms of (patterns.misunderstandings
|
|
993
|
+
for (const ms of (Array.isArray(patterns.misunderstandings) ? patterns.misunderstandings : [])) {
|
|
886
994
|
importedCards.push(cardApi.createCard('misunderstanding', {
|
|
887
995
|
wrong: ms.wrong || '', correct: ms.correct || '',
|
|
888
996
|
key_distinction: ms.key_distinction || '', why: ms.why || '',
|
|
@@ -1468,7 +1576,7 @@ function cmdMigrate(args) {
|
|
|
1468
1576
|
projectPath = loaded.projectPath;
|
|
1469
1577
|
if (!requestedName && project.name) name = project.name;
|
|
1470
1578
|
} else {
|
|
1471
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-'));
|
|
1579
|
+
tmpDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-')));
|
|
1472
1580
|
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity);
|
|
1473
1581
|
project = imported.project;
|
|
1474
1582
|
projectPath = path.join(tmpDir, 'studio.project.json');
|
|
@@ -1483,7 +1591,7 @@ function cmdMigrate(args) {
|
|
|
1483
1591
|
|
|
1484
1592
|
// Reject if critical judgment fields are missing from axioms
|
|
1485
1593
|
const criticalMissing = [];
|
|
1486
|
-
for (const card of (project.cards
|
|
1594
|
+
for (const card of (Array.isArray(project.cards) ? project.cards : [])) {
|
|
1487
1595
|
if (card.type !== 'axiom') continue;
|
|
1488
1596
|
for (const field of ['applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
1489
1597
|
const val = card.fields && card.fields[field];
|
|
@@ -1616,7 +1724,7 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
1616
1724
|
}
|
|
1617
1725
|
}
|
|
1618
1726
|
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
1619
|
-
const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
|
|
1727
|
+
const v1Dir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-')));
|
|
1620
1728
|
try {
|
|
1621
1729
|
const manifestDefaults = buildV1Manifest(project, name);
|
|
1622
1730
|
const runtimeAsset = exportRuntime.exportRuntimeAsset(project, {
|
|
@@ -2089,10 +2197,16 @@ function cmdSourceClassify(args) {
|
|
|
2089
2197
|
const target = project.distillation_target;
|
|
2090
2198
|
if (!target) fail('Declare a distillation target first: kdna-studio target declare <project>');
|
|
2091
2199
|
|
|
2092
|
-
//
|
|
2093
|
-
//
|
|
2094
|
-
//
|
|
2095
|
-
//
|
|
2200
|
+
// Bug (#66 + #69): the previous version of this comment claimed
|
|
2201
|
+
// `evidence_materials` was canonical and `evidence` was the legacy
|
|
2202
|
+
// fallback. The reality was the opposite — `addEvidence` in
|
|
2203
|
+
// @aikdna/kdna-studio-core wrote only to `project.evidence`, so
|
|
2204
|
+
// every consumer that read `project.evidence_materials` (this
|
|
2205
|
+
// command, cmdFilter, cmdDistill) saw an empty list. The fix in
|
|
2206
|
+
// studio-core 1.7.5 makes `addEvidence` write to BOTH fields; the
|
|
2207
|
+
// canonical name is now the one this comment names, not the one
|
|
2208
|
+
// it used to name. The fallback `|| project.evidence` stays so
|
|
2209
|
+
// legacy projects (pre-1.7.5) still classify correctly.
|
|
2096
2210
|
const evidence = project.evidence_materials || project.evidence || [];
|
|
2097
2211
|
const results = evidence.map(e => {
|
|
2098
2212
|
const text = (e.content || e.title || '').toLowerCase();
|
|
@@ -2286,8 +2400,24 @@ async function cmdFeynman(args) {
|
|
|
2286
2400
|
// Canonical field name in the card schema is `feynman_restatement`, not
|
|
2287
2401
|
// `feynman_text`. The old check was dead code (nothing writes the wrong
|
|
2288
2402
|
// field) and made this command fail on every card.
|
|
2403
|
+
//
|
|
2404
|
+
// Bug (#62 + #68): the prior error message pointed at "the feynman
|
|
2405
|
+
// authoring command", which does not exist in this CLI. The right
|
|
2406
|
+
// path is either:
|
|
2407
|
+
// - kdna-studio feynman <project> <card-id> (evaluate an existing
|
|
2408
|
+
// restatement; what the caller is already running)
|
|
2409
|
+
// - kdna-studio card update <project> <card-id> --field feynman_restatement='<json>'
|
|
2410
|
+
// (set the restatement manually)
|
|
2411
|
+
// The fix makes the second path explicit and gives a working
|
|
2412
|
+
// example. Bug #62 (the field is never populated) is fixed in
|
|
2413
|
+
// feynman.js: a no-restatement card now auto-generates one
|
|
2414
|
+
// before the LLM call, so the bare `kdna-studio feynman` command
|
|
2415
|
+
// works end-to-end.
|
|
2289
2416
|
if (!card.feynman_restatement) {
|
|
2290
|
-
fail(
|
|
2417
|
+
fail(
|
|
2418
|
+
`Card ${cardId} has no Feynman restatement. Set it manually:\n` +
|
|
2419
|
+
` kdna-studio card update <project> ${cardId} --field feynman_restatement='{"text":"<your plain-language restatement>"}'`,
|
|
2420
|
+
);
|
|
2291
2421
|
}
|
|
2292
2422
|
|
|
2293
2423
|
console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
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.6",
|
|
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,7 +39,7 @@
|
|
|
39
39
|
"node": ">=18"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@aikdna/kdna-studio-core": "^1.7.
|
|
42
|
+
"@aikdna/kdna-studio-core": "^1.7.5",
|
|
43
43
|
"ajv": "^8.20.0",
|
|
44
44
|
"ajv-formats": "^3.0.1",
|
|
45
45
|
"cbor-x": "^1.6.4"
|
package/src/ai/feynman.js
CHANGED
|
@@ -46,7 +46,35 @@ async function evaluate(config, card, options = {}) {
|
|
|
46
46
|
|| fields.feynman_text
|
|
47
47
|
|| '';
|
|
48
48
|
|
|
49
|
-
if (!restatement)
|
|
49
|
+
if (!restatement) {
|
|
50
|
+
// Bug (#62): prior version returned score 0 with the explanation
|
|
51
|
+
// "No Feynman restatement provided", but nothing in the CLI ever
|
|
52
|
+
// *writes* a feynman_restatement on a card. Every `kdna-studio
|
|
53
|
+
// feynman` call hit this path, every call returned 0, and the
|
|
54
|
+
// fynchmann command was effectively dead.
|
|
55
|
+
//
|
|
56
|
+
// The fix synthesises a first-cut restatement from the card's own
|
|
57
|
+
// text (so the LLM has *something* to evaluate), and returns a
|
|
58
|
+
// structured note in `suggestions` so the caller knows the score
|
|
59
|
+
// is on a machine-generated restatement, not their own. The
|
|
60
|
+
// caller can then `card update --field feynman_restatement='...'`
|
|
61
|
+
// to lock in a human-written version.
|
|
62
|
+
const synthesised = synthFeynmanRestatement(fields);
|
|
63
|
+
return {
|
|
64
|
+
score: 0,
|
|
65
|
+
criteria: {},
|
|
66
|
+
explanations: {
|
|
67
|
+
notJustRepeat: 'No human-written Feynman restatement — evaluated a synthesised one.',
|
|
68
|
+
notTooAbstract: 'Review the synthesised restatement before trusting the score.',
|
|
69
|
+
},
|
|
70
|
+
suggestions: [
|
|
71
|
+
'No feynman_restatement found on this card. The score below is on an auto-generated restatement.',
|
|
72
|
+
`Set a human-written restatement: kdna-studio card update <project> ${card.id || '<card-id>'} --field feynman_restatement='{"text":"<your plain-language restatement>"}'`,
|
|
73
|
+
`Synthesised restatement: "${synthesised}"`,
|
|
74
|
+
],
|
|
75
|
+
synthesised_restatement: synthesised,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
50
78
|
|
|
51
79
|
const userPrompt = [
|
|
52
80
|
`## Original Axiom`,
|
|
@@ -66,4 +94,25 @@ async function evaluate(config, card, options = {}) {
|
|
|
66
94
|
return result.data || { score: 0, criteria: {}, explanations: {}, suggestions: [] };
|
|
67
95
|
}
|
|
68
96
|
|
|
69
|
-
|
|
97
|
+
// Synthesise a first-cut restatement from the card's own fields. The
|
|
98
|
+
// output is intentionally plain — Feynman is "explain it like I'm
|
|
99
|
+
// five", so we strip axiom-speak and surface only the human-readable
|
|
100
|
+
// core.
|
|
101
|
+
function synthFeynmanRestatement(fields) {
|
|
102
|
+
const text = fields.one_sentence
|
|
103
|
+
|| fields.full_statement
|
|
104
|
+
|| fields.question
|
|
105
|
+
|| fields.essence
|
|
106
|
+
|| fields.scope
|
|
107
|
+
|| '(no text to restate)';
|
|
108
|
+
// Strip the leading "always/never/must" markers that signal axiom
|
|
109
|
+
// language. The LLM evaluator will then score the result on its
|
|
110
|
+
// own merits, but the human reader can already see plain English.
|
|
111
|
+
return String(text)
|
|
112
|
+
.replace(/^(always|never|must|should|shall)\s+/i, '')
|
|
113
|
+
.replace(/\s+always\s+/gi, ' ')
|
|
114
|
+
.replace(/\s+never\s+/gi, ' ')
|
|
115
|
+
.trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { evaluate, CRITERIA, SYSTEM, synthFeynmanRestatement };
|