@aikdna/kdna-studio-cli 0.8.8 → 0.8.10
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 +415 -60
- package/package.json +3 -3
- package/src/ai/feynman.js +3 -1
package/bin/kdna-studio.js
CHANGED
|
@@ -83,6 +83,8 @@ Create (three entry paths):
|
|
|
83
83
|
|
|
84
84
|
Migrate (dev source or Studio project → canonical .kdna in one command):
|
|
85
85
|
kdna-studio migrate <source-dir|project> --format v1 --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--allow-incomplete] [--sign] [--passphrase <pw>|--passphrase-stdin]
|
|
86
|
+
kdna-studio migrate <source-dir> --check --name <@scope/name> # pre-flight: report which fields would block the export without writing the .kdna
|
|
87
|
+
kdna-studio audit-locks <project> [--type axiom|risk|stance|...] [--json] # list cards with missing Human Lock fields (per-card-type, per-field)
|
|
86
88
|
|
|
87
89
|
Authoring:
|
|
88
90
|
kdna-studio import <project> <source-file-or-dir> # import evidence (txt/md/json/yaml/csv/log/srt/vtt/html)
|
|
@@ -114,6 +116,7 @@ Distillation:
|
|
|
114
116
|
kdna-studio candidate override <project> <candidate-id> # override scope gate
|
|
115
117
|
kdna-studio candidate promote <project> # promote accepted+scope_fit → cards
|
|
116
118
|
kdna-studio report <project>
|
|
119
|
+
kdna-studio audit-locks <project> # list all axioms/risk/stance missing Human Lock fields
|
|
117
120
|
|
|
118
121
|
Project may be a directory containing studio.project.json or a project JSON file.`);
|
|
119
122
|
}
|
|
@@ -255,23 +258,41 @@ function writeFiles(outDir, files) {
|
|
|
255
258
|
}
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
const ARRAY_FIELD_NAMES = new Set([
|
|
262
|
+
'applies_when',
|
|
263
|
+
'does_not_apply_when',
|
|
264
|
+
'acceptable_exceptions',
|
|
265
|
+
]);
|
|
266
|
+
|
|
258
267
|
function parseFields(args) {
|
|
259
268
|
const fields = {};
|
|
260
269
|
for (const pair of optionsAll(args, '--field')) {
|
|
261
270
|
const eq = pair.indexOf('=');
|
|
262
271
|
if (eq < 1) fail(`Invalid --field "${pair}". Use key=value.`);
|
|
263
|
-
|
|
272
|
+
let key = pair.slice(0, eq);
|
|
273
|
+
const appendArray = key.endsWith('[]');
|
|
274
|
+
if (appendArray) key = key.slice(0, -2);
|
|
264
275
|
const raw = pair.slice(eq + 1);
|
|
276
|
+
let value;
|
|
265
277
|
if (raw.startsWith('[') || raw.startsWith('{')) {
|
|
266
278
|
try {
|
|
267
|
-
|
|
279
|
+
value = JSON.parse(raw);
|
|
268
280
|
} catch (_) {
|
|
269
|
-
|
|
281
|
+
value = raw;
|
|
270
282
|
}
|
|
271
283
|
} else if (raw.includes('|')) {
|
|
272
|
-
|
|
284
|
+
value = raw.split('|').map((s) => s.trim()).filter(Boolean);
|
|
285
|
+
} else {
|
|
286
|
+
value = raw;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (appendArray) {
|
|
290
|
+
const values = Array.isArray(value) ? value : [value];
|
|
291
|
+
fields[key] = [...(Array.isArray(fields[key]) ? fields[key] : []), ...values];
|
|
292
|
+
} else if (ARRAY_FIELD_NAMES.has(key) && typeof value === 'string') {
|
|
293
|
+
fields[key] = value.trim() ? [value.trim()] : [];
|
|
273
294
|
} else {
|
|
274
|
-
fields[key] =
|
|
295
|
+
fields[key] = value;
|
|
275
296
|
}
|
|
276
297
|
}
|
|
277
298
|
return fields;
|
|
@@ -410,6 +431,8 @@ function cardsFromV1Payload(payload) {
|
|
|
410
431
|
applies_when: ax.applies_when || [],
|
|
411
432
|
does_not_apply_when: ax.does_not_apply_when || [],
|
|
412
433
|
failure_risk: ax.failure_risk || '',
|
|
434
|
+
confidence: ax.confidence || '',
|
|
435
|
+
evidence_type: ax.evidence_type || '',
|
|
413
436
|
}, ax.id || null);
|
|
414
437
|
}
|
|
415
438
|
for (const ont of (Array.isArray(payload.core?.ontology) ? payload.core?.ontology : [])) {
|
|
@@ -448,7 +471,8 @@ function cardsFromV1Payload(payload) {
|
|
|
448
471
|
let fields = pattern;
|
|
449
472
|
if (!type) {
|
|
450
473
|
// Heuristics, in priority order — most specific to most general.
|
|
451
|
-
if ('
|
|
474
|
+
if ('logic' in pattern && ('so_what' in pattern || 'conclusion' in pattern)) type = 'reasoning';
|
|
475
|
+
else if ('wrong' in pattern && 'correct' in pattern) type = 'misunderstanding';
|
|
452
476
|
else if ('term' in pattern && 'definition' in pattern) type = 'term';
|
|
453
477
|
else if ('term' in pattern && 'why' in pattern && 'replace_with' in pattern) type = 'banned_term';
|
|
454
478
|
else if ('what_it_looks_like' in pattern && 'how_to_fix' in pattern) type = 'pattern';
|
|
@@ -830,7 +854,7 @@ function importFromKdna(kdnaPath) {
|
|
|
830
854
|
const core = JSON.parse(entries.get('KDNA_Core.json').toString());
|
|
831
855
|
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
832
856
|
const fields = {};
|
|
833
|
-
for (const k of ['one_sentence','full_statement','why','applies_when','does_not_apply_when','failure_risk']) {
|
|
857
|
+
for (const k of ['one_sentence','full_statement','why','applies_when','does_not_apply_when','failure_risk','confidence','evidence_type']) {
|
|
834
858
|
if (k in ax) fields[k] = ax[k];
|
|
835
859
|
}
|
|
836
860
|
importedCards.push(cardApi.createCard('axiom', fields));
|
|
@@ -848,7 +872,16 @@ function importFromKdna(kdnaPath) {
|
|
|
848
872
|
acceptable_exceptions: b.acceptable_exceptions || [],
|
|
849
873
|
}));
|
|
850
874
|
}
|
|
851
|
-
|
|
875
|
+
// Accept risks as a top-level array (`risks`) or wrapped in `risk_model`
|
|
876
|
+
// (either as `{risks: [...]}` or as an array itself). Uses the same
|
|
877
|
+
// Array.isArray ladder pattern as the safe path at lines 1033-1040.
|
|
878
|
+
let risksSource = core.risks;
|
|
879
|
+
if (!Array.isArray(risksSource)) {
|
|
880
|
+
if (Array.isArray(core.risk_model)) risksSource = core.risk_model;
|
|
881
|
+
else if (Array.isArray(core.risk_model?.risks)) risksSource = core.risk_model.risks;
|
|
882
|
+
else risksSource = [];
|
|
883
|
+
}
|
|
884
|
+
for (const risk of risksSource) {
|
|
852
885
|
importedCards.push(cardApi.createCard('risk', risk.fields || risk));
|
|
853
886
|
}
|
|
854
887
|
for (const stance of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
@@ -941,7 +974,7 @@ function importFromKdna(kdnaPath) {
|
|
|
941
974
|
* Reads KDNA_*.json files and converts entries to draft judgment cards.
|
|
942
975
|
* Outputs a schema audit report.
|
|
943
976
|
*/
|
|
944
|
-
function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
977
|
+
function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, opts = {}) {
|
|
945
978
|
resetImportedIds();
|
|
946
979
|
const absSource = path.resolve(sourceDir);
|
|
947
980
|
if (!fs.existsSync(absSource)) fail(`Source folder not found: ${absSource}`);
|
|
@@ -1008,7 +1041,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1008
1041
|
if (core) {
|
|
1009
1042
|
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
1010
1043
|
const fields = {};
|
|
1011
|
-
for (const key of ['one_sentence', 'full_statement', 'why', 'applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
1044
|
+
for (const key of ['one_sentence', 'full_statement', 'why', 'applies_when', 'does_not_apply_when', 'failure_risk', 'confidence', 'evidence_type']) {
|
|
1012
1045
|
if (key in ax) fields[key] = ax[key];
|
|
1013
1046
|
else audit.missingFields.push(`axiom.${ax.id || '?'}.${key}`);
|
|
1014
1047
|
}
|
|
@@ -1040,7 +1073,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1040
1073
|
}
|
|
1041
1074
|
// NEW: import stances
|
|
1042
1075
|
for (const s of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
1043
|
-
const text = typeof s === 'string' ? s : s.stance || '';
|
|
1076
|
+
const text = typeof s === 'string' ? s : s.statement || s.stance || '';
|
|
1044
1077
|
importedCards.push(cardApi.createCard('stance', {
|
|
1045
1078
|
statement: text,
|
|
1046
1079
|
applies_when: s.applies_when || [],
|
|
@@ -1092,7 +1125,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1092
1125
|
}
|
|
1093
1126
|
|
|
1094
1127
|
// NEW: import scenarios
|
|
1095
|
-
if (scenarios && scenarios.scenes) {
|
|
1128
|
+
if (scenarios && Array.isArray(scenarios.scenes)) {
|
|
1096
1129
|
for (const scene of scenarios.scenes) {
|
|
1097
1130
|
importedCards.push(cardApi.createCard('scenario', {
|
|
1098
1131
|
name: scene.name || scene.id || '',
|
|
@@ -1102,7 +1135,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1102
1135
|
}
|
|
1103
1136
|
|
|
1104
1137
|
// NEW: import cases
|
|
1105
|
-
if (cases && cases.cases) {
|
|
1138
|
+
if (cases && Array.isArray(cases.cases)) {
|
|
1106
1139
|
for (const c of cases.cases) {
|
|
1107
1140
|
importedCards.push(cardApi.createCard('case', {
|
|
1108
1141
|
title: c.title || c.id || '',
|
|
@@ -1118,7 +1151,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1118
1151
|
// so the cards were silently rewritten as axiom-derived fallback chains
|
|
1119
1152
|
// — looking identical to misunderstanding cards and losing the source's
|
|
1120
1153
|
// identity.
|
|
1121
|
-
if (reasoning && reasoning.reasoning_chains) {
|
|
1154
|
+
if (reasoning && Array.isArray(reasoning.reasoning_chains)) {
|
|
1122
1155
|
for (const rc of reasoning.reasoning_chains) {
|
|
1123
1156
|
importedCards.push(cardApi.createCard('reasoning', {
|
|
1124
1157
|
axiom: rc.axiom || '',
|
|
@@ -1133,7 +1166,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1133
1166
|
}
|
|
1134
1167
|
|
|
1135
1168
|
// NEW: import evolution stages
|
|
1136
|
-
if (evolution && evolution.stages) {
|
|
1169
|
+
if (evolution && Array.isArray(evolution.stages)) {
|
|
1137
1170
|
for (const stage of evolution.stages) {
|
|
1138
1171
|
importedCards.push(cardApi.createCard('evolution_stage', {
|
|
1139
1172
|
name: stage.name || stage.id || '',
|
|
@@ -1177,7 +1210,9 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1177
1210
|
|
|
1178
1211
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
1179
1212
|
writeProject(path.join(projectDir, 'studio.project.json'), project);
|
|
1180
|
-
|
|
1213
|
+
if (!opts.silent) {
|
|
1214
|
+
console.log(JSON.stringify({ audit, imported: importedCards.length, source_mode: 'source_folder' }, null, 2));
|
|
1215
|
+
}
|
|
1181
1216
|
return { project, projectPath: path.join(projectDir, 'studio.project.json'), audit };
|
|
1182
1217
|
}
|
|
1183
1218
|
|
|
@@ -1392,18 +1427,74 @@ const CARD_REQUIRED_FIELDS = {
|
|
|
1392
1427
|
pattern: ['type', 'name', 'one_sentence', 'what_it_looks_like', 'how_to_fix', 'failure_risk'],
|
|
1393
1428
|
};
|
|
1394
1429
|
|
|
1430
|
+
const CARD_FIELD_RULES = {
|
|
1431
|
+
axiom: {
|
|
1432
|
+
applies_when: { type: 'array' },
|
|
1433
|
+
does_not_apply_when: { type: 'array' },
|
|
1434
|
+
confidence: { allowed: ['low', 'medium', 'high'] },
|
|
1435
|
+
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle', 'practice'] },
|
|
1436
|
+
},
|
|
1437
|
+
boundary: {
|
|
1438
|
+
acceptable_exceptions: { type: 'array', optional: true },
|
|
1439
|
+
},
|
|
1440
|
+
stance: {
|
|
1441
|
+
applies_when: { type: 'array', optional: true },
|
|
1442
|
+
},
|
|
1443
|
+
misunderstanding: {
|
|
1444
|
+
applies_when: { type: 'array', optional: true },
|
|
1445
|
+
does_not_apply_when: { type: 'array', optional: true },
|
|
1446
|
+
},
|
|
1447
|
+
};
|
|
1448
|
+
|
|
1449
|
+
function fieldIssue(fields, name, rule = {}) {
|
|
1450
|
+
const val = fields[name];
|
|
1451
|
+
if (val === undefined || val === null || val === '' ||
|
|
1452
|
+
(Array.isArray(val) && val.length === 0)) {
|
|
1453
|
+
return rule.optional ? null : { field: name, issue: 'missing' };
|
|
1454
|
+
}
|
|
1455
|
+
if (rule.type === 'array' && !Array.isArray(val)) {
|
|
1456
|
+
return { field: name, issue: 'wrong_type', have_type: typeof val, need_type: 'array' };
|
|
1457
|
+
}
|
|
1458
|
+
if (rule.allowed && !rule.allowed.includes(val)) {
|
|
1459
|
+
return { field: name, issue: 'invalid_value', have: val, allowed: rule.allowed };
|
|
1460
|
+
}
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
function formatFieldIssue(issue) {
|
|
1465
|
+
if (issue.issue === 'missing') return issue.field;
|
|
1466
|
+
if (issue.issue === 'wrong_type') return `${issue.field} (must be ${issue.need_type})`;
|
|
1467
|
+
if (issue.issue === 'invalid_value') return `${issue.field} (must be one of: ${issue.allowed.join('|')})`;
|
|
1468
|
+
return `${issue.field} (${issue.issue})`;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1395
1471
|
function checkRequiredFields(type, fields) {
|
|
1396
1472
|
const required = CARD_REQUIRED_FIELDS[type];
|
|
1397
1473
|
if (!required) return [];
|
|
1398
|
-
const
|
|
1474
|
+
const issues = [];
|
|
1399
1475
|
for (const f of required) {
|
|
1400
|
-
const
|
|
1401
|
-
if (
|
|
1402
|
-
|
|
1403
|
-
|
|
1476
|
+
const issue = fieldIssue(fields, f, CARD_FIELD_RULES[type]?.[f] || {});
|
|
1477
|
+
if (issue) issues.push(formatFieldIssue(issue));
|
|
1478
|
+
}
|
|
1479
|
+
for (const [name, rule] of Object.entries(CARD_FIELD_RULES[type] || {})) {
|
|
1480
|
+
if (required.includes(name)) continue;
|
|
1481
|
+
const issue = fieldIssue(fields, name, rule);
|
|
1482
|
+
if (issue) issues.push(formatFieldIssue(issue));
|
|
1483
|
+
}
|
|
1484
|
+
return issues;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function criticalAxiomIssues(project) {
|
|
1488
|
+
const issues = [];
|
|
1489
|
+
for (const card of (Array.isArray(project.cards) ? project.cards : [])) {
|
|
1490
|
+
if (card.type !== 'axiom') continue;
|
|
1491
|
+
const fields = card.fields || {};
|
|
1492
|
+
for (const field of ['applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
1493
|
+
const issue = fieldIssue(fields, field, CARD_FIELD_RULES.axiom[field] || {});
|
|
1494
|
+
if (issue) issues.push(`${card.id}: ${formatFieldIssue(issue)}`);
|
|
1404
1495
|
}
|
|
1405
1496
|
}
|
|
1406
|
-
return
|
|
1497
|
+
return issues;
|
|
1407
1498
|
}
|
|
1408
1499
|
|
|
1409
1500
|
function cardStrictTemplate(type, fields) {
|
|
@@ -1487,21 +1578,54 @@ function cmdCard(args) {
|
|
|
1487
1578
|
project.stages.judgment_cards.status = 'in_progress';
|
|
1488
1579
|
}
|
|
1489
1580
|
writeProject(projectPath, project);
|
|
1490
|
-
|
|
1581
|
+
if (args.includes('--json')) {
|
|
1582
|
+
console.log(JSON.stringify({ added: true, card }, null, 2));
|
|
1583
|
+
} else {
|
|
1584
|
+
console.log(`Added card: ${card.id}`);
|
|
1585
|
+
}
|
|
1491
1586
|
return;
|
|
1492
1587
|
}
|
|
1493
1588
|
if (sub === 'update') {
|
|
1494
1589
|
const projectInput = args[1];
|
|
1495
1590
|
const cardId = args[2];
|
|
1496
|
-
if (!projectInput || !cardId) fail('Usage: kdna-studio card update <project> <card-id> --field key=value');
|
|
1591
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card update <project> <card-id> --field key=value OR kdna-studio card update <project> <card-id> --from-file <path.json>');
|
|
1497
1592
|
const { projectPath, project } = readProject(projectInput);
|
|
1498
1593
|
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1499
1594
|
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1500
1595
|
const card = project.cards[idx];
|
|
1501
1596
|
if (card.locked) fail(`Cannot update locked card: ${cardId}. Unlock first with card unlock.`);
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1597
|
+
// Bug (UX pro-20 migration): prior version only accepted
|
|
1598
|
+
// --field key=value, so an author migrating 20 source-tree
|
|
1599
|
+
// assets had to run one CLI invocation per field. With
|
|
1600
|
+
// confidence + evidence_type as 8th / 9th required fields on
|
|
1601
|
+
// axiom, a 99-axiom asset became 99 * 2 = 198 invocations.
|
|
1602
|
+
// The fix accepts --from-file <path.json> so an author can
|
|
1603
|
+
// write a single JSON file per card (or per project) and
|
|
1604
|
+
// apply it in one call. JSON shape: {field: value, ...}.
|
|
1605
|
+
// kdna-studio card update . ax_xxx --from-file ./fills/ax_xxx.json
|
|
1606
|
+
// For batched updates across many cards, see the
|
|
1607
|
+
// --from-file-projects variant below, which reads a list
|
|
1608
|
+
// of {id, fields} records.
|
|
1609
|
+
const fromFileIdx = args.indexOf('--from-file');
|
|
1610
|
+
if (fromFileIdx >= 0) {
|
|
1611
|
+
const filePath = args[fromFileIdx + 1];
|
|
1612
|
+
if (!filePath) fail('--from-file requires a path');
|
|
1613
|
+
if (!fs.existsSync(filePath)) fail(`--from-file: file not found: ${filePath}`);
|
|
1614
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1615
|
+
if (typeof data !== 'object' || Array.isArray(data) || data === null) {
|
|
1616
|
+
fail(`--from-file: expected a JSON object mapping field names to values`);
|
|
1617
|
+
}
|
|
1618
|
+
for (const [k, v] of Object.entries(data)) {
|
|
1619
|
+
if (k === 'id' || k === 'type' || k === 'status' || k === 'locked') {
|
|
1620
|
+
fail(`--from-file: cannot edit reserved field "${k}"`);
|
|
1621
|
+
}
|
|
1622
|
+
if (card.fields) card.fields[k] = v;
|
|
1623
|
+
}
|
|
1624
|
+
} else {
|
|
1625
|
+
const updates = parseFields(args.slice(3));
|
|
1626
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
1627
|
+
if (card.fields) card.fields[k] = v;
|
|
1628
|
+
}
|
|
1505
1629
|
}
|
|
1506
1630
|
project.cards[idx] = card;
|
|
1507
1631
|
writeProject(projectPath, project);
|
|
@@ -1650,14 +1774,32 @@ function cmdLock(args) {
|
|
|
1650
1774
|
}
|
|
1651
1775
|
|
|
1652
1776
|
function cmdMigrate(args) {
|
|
1777
|
+
// Bug (UX pro-20 migration): prior version of `migrate` always
|
|
1778
|
+
// attempted the full pipeline — read source folder, import to
|
|
1779
|
+
// Studio project, lock all cards, then export. An author migrating
|
|
1780
|
+
// 20 source-tree assets had no way to know in advance whether
|
|
1781
|
+
// the assets would pass the v1.7.2+ Human Lock gate. The fix
|
|
1782
|
+
// adds `--check` which runs the import + the critical-missing
|
|
1783
|
+
// check + the lock-gate check, then prints a report and exits
|
|
1784
|
+
// without writing the .kdna file. Pair with `audit-locks` for
|
|
1785
|
+
// the full pre-migration report.
|
|
1786
|
+
//
|
|
1787
|
+
// Usage:
|
|
1788
|
+
// kdna-studio migrate <source-dir> --check --name <@scope/name>
|
|
1789
|
+
//
|
|
1790
|
+
// Exit codes:
|
|
1791
|
+
// 0 — would succeed
|
|
1792
|
+
// 2 — INPUT_ERROR (missing args)
|
|
1793
|
+
// 4 — would fail (Human Lock gate or critical axiom field)
|
|
1794
|
+
const checkOnly = args.includes('--check');
|
|
1653
1795
|
const sourceDir = args[0];
|
|
1654
1796
|
const out = option(args, '--out');
|
|
1655
1797
|
const by = option(args, '--by');
|
|
1656
1798
|
const statement = option(args, '--statement');
|
|
1657
1799
|
const requestedName = option(args, '--name');
|
|
1658
1800
|
let name = requestedName || path.basename(path.resolve(sourceDir || '.'));
|
|
1659
|
-
if (!sourceDir || !out || !by || !statement) {
|
|
1660
|
-
fail('Usage: kdna-studio migrate <source-dir> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--sign] [--passphrase <pw>|--passphrase-stdin]');
|
|
1801
|
+
if (!sourceDir || (!out && !checkOnly) || (!by && !checkOnly) || (!statement && !checkOnly)) {
|
|
1802
|
+
fail('Usage: kdna-studio migrate <source-dir> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--check] [--sign] [--passphrase <pw>|--passphrase-stdin]');
|
|
1661
1803
|
}
|
|
1662
1804
|
|
|
1663
1805
|
const sourcePath = path.resolve(sourceDir);
|
|
@@ -1683,7 +1825,7 @@ function cmdMigrate(args) {
|
|
|
1683
1825
|
if (!requestedName && project.name) name = project.name;
|
|
1684
1826
|
} else {
|
|
1685
1827
|
tmpDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-')));
|
|
1686
|
-
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity);
|
|
1828
|
+
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity, { silent: true });
|
|
1687
1829
|
project = imported.project;
|
|
1688
1830
|
projectPath = path.join(tmpDir, 'studio.project.json');
|
|
1689
1831
|
}
|
|
@@ -1695,17 +1837,8 @@ function cmdMigrate(args) {
|
|
|
1695
1837
|
// always failing.
|
|
1696
1838
|
const allowIncompleteEarly = args.includes('--allow-incomplete');
|
|
1697
1839
|
|
|
1698
|
-
// Reject if critical judgment fields are missing
|
|
1699
|
-
const criticalMissing =
|
|
1700
|
-
for (const card of (Array.isArray(project.cards) ? project.cards : [])) {
|
|
1701
|
-
if (card.type !== 'axiom') continue;
|
|
1702
|
-
for (const field of ['applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
1703
|
-
const val = card.fields && card.fields[field];
|
|
1704
|
-
if (!val || (Array.isArray(val) && val.length === 0) || val === '') {
|
|
1705
|
-
criticalMissing.push(`${card.id}: missing ${field}`);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1840
|
+
// Reject if critical judgment fields are missing or malformed on axioms.
|
|
1841
|
+
const criticalMissing = criticalAxiomIssues(project);
|
|
1709
1842
|
if (criticalMissing.length > 0) {
|
|
1710
1843
|
if (allowIncompleteEarly) {
|
|
1711
1844
|
console.warn(
|
|
@@ -1724,6 +1857,41 @@ function cmdMigrate(args) {
|
|
|
1724
1857
|
}
|
|
1725
1858
|
}
|
|
1726
1859
|
|
|
1860
|
+
// --check branch: print a pre-migration report and exit. The
|
|
1861
|
+
// project object already has every card from the source tree
|
|
1862
|
+
// imported, so we can run the same audit-locks logic here
|
|
1863
|
+
// without re-reading the source. This is the single-call
|
|
1864
|
+
// equivalent of `kdna-studio create --from-folder <src> &&
|
|
1865
|
+
// kdna-studio audit-locks .` — but it requires no
|
|
1866
|
+
// intermediate write.
|
|
1867
|
+
if (checkOnly) {
|
|
1868
|
+
const REQUIRED = {
|
|
1869
|
+
axiom: ['confidence', 'evidence_type', 'one_sentence', 'full_statement', 'why',
|
|
1870
|
+
'applies_when', 'does_not_apply_when', 'failure_risk'],
|
|
1871
|
+
risk: ['name', 'description', 'mitigation'],
|
|
1872
|
+
stance: ['statement', 'applies_when'],
|
|
1873
|
+
misunderstanding: ['wrong', 'correct', 'key_distinction', 'why'],
|
|
1874
|
+
boundary: ['scope', 'out_of_scope'],
|
|
1875
|
+
};
|
|
1876
|
+
const summary = { project: name, source: sourceDir, total: project.cards.length, by_type: {}, would_block: false };
|
|
1877
|
+
for (const card of (project.cards || [])) {
|
|
1878
|
+
const req = REQUIRED[card.type];
|
|
1879
|
+
if (!req) continue;
|
|
1880
|
+
const missing = [];
|
|
1881
|
+
for (const f of req) {
|
|
1882
|
+
const issue = fieldIssue(card.fields || {}, f, CARD_FIELD_RULES[card.type]?.[f] || {});
|
|
1883
|
+
if (issue) missing.push(formatFieldIssue(issue));
|
|
1884
|
+
}
|
|
1885
|
+
if (missing.length > 0) {
|
|
1886
|
+
if (!summary.by_type[card.type]) summary.by_type[card.type] = [];
|
|
1887
|
+
summary.by_type[card.type].push({ id: card.id, missing });
|
|
1888
|
+
summary.would_block = true;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
1892
|
+
process.exit(summary.would_block ? EXIT.HUMAN_LOCK_REQUIRED : EXIT.OK);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1727
1895
|
// Step 2: approve and Human Lock all cards
|
|
1728
1896
|
let locked = 0;
|
|
1729
1897
|
for (let i = 0; i < (project.cards || []).length; i++) {
|
|
@@ -1819,6 +1987,25 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
1819
1987
|
if (!exportRuntime || typeof exportRuntime.exportRuntimeAsset !== 'function') {
|
|
1820
1988
|
fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for v1 export.', 2);
|
|
1821
1989
|
}
|
|
1990
|
+
const criticalIssues = criticalAxiomIssues(project);
|
|
1991
|
+
if (criticalIssues.length > 0) {
|
|
1992
|
+
if (opts.allowIncomplete) {
|
|
1993
|
+
console.warn(
|
|
1994
|
+
`--allow-incomplete: bypassing ${criticalIssues.length} critical axiom-field check(s). ` +
|
|
1995
|
+
`The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
|
|
1996
|
+
);
|
|
1997
|
+
} else {
|
|
1998
|
+
fail(
|
|
1999
|
+
`Cannot export v1: ${criticalIssues.length} critical axiom field issue(s).\n` +
|
|
2000
|
+
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
2001
|
+
` decide when to load this domain). Fix them first, or pass\n` +
|
|
2002
|
+
` --allow-incomplete to bypass.\n` +
|
|
2003
|
+
` Issues:\n ` + criticalIssues.slice(0, 10).join('\n ') +
|
|
2004
|
+
(criticalIssues.length > 10 ? `\n ... and ${criticalIssues.length - 10} more` : ''),
|
|
2005
|
+
EXIT.HUMAN_LOCK_REQUIRED,
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1822
2009
|
const gate = projectApi.checkHumanLockGate(project);
|
|
1823
2010
|
if (gate.blocked) {
|
|
1824
2011
|
if (opts.allowIncomplete) {
|
|
@@ -1996,7 +2183,7 @@ function manifestForSigning(manifest) {
|
|
|
1996
2183
|
|
|
1997
2184
|
function canonicalPayload(files) {
|
|
1998
2185
|
return ['mimetype', ...Object.keys(files).filter((k) => k !== 'mimetype').sort()]
|
|
1999
|
-
.filter((name) => name !== 'signature.json' && name !== '.DS_Store')
|
|
2186
|
+
.filter((name) => name !== 'signature.json' && name !== '.DS_Store' && name !== 'build-receipt.json' && !name.startsWith('reports/'))
|
|
2000
2187
|
.map((name) => {
|
|
2001
2188
|
let content = name === 'mimetype' ? 'application/vnd.aikdna.kdna+zip' : files[name];
|
|
2002
2189
|
if (name.endsWith('.json')) {
|
|
@@ -2093,6 +2280,25 @@ function cmdExport(args) {
|
|
|
2093
2280
|
return;
|
|
2094
2281
|
}
|
|
2095
2282
|
const { project, result } = compileProject(projectInput);
|
|
2283
|
+
const criticalIssues = criticalAxiomIssues(project);
|
|
2284
|
+
if (criticalIssues.length > 0) {
|
|
2285
|
+
if (args.includes('--allow-incomplete')) {
|
|
2286
|
+
console.warn(
|
|
2287
|
+
`--allow-incomplete: bypassing ${criticalIssues.length} critical axiom-field check(s). ` +
|
|
2288
|
+
`The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
|
|
2289
|
+
);
|
|
2290
|
+
} else {
|
|
2291
|
+
fail(
|
|
2292
|
+
`Cannot export: ${criticalIssues.length} critical axiom field issue(s).\n` +
|
|
2293
|
+
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
2294
|
+
` decide when to load this domain). Fix them first, or pass\n` +
|
|
2295
|
+
` --allow-incomplete to bypass.\n` +
|
|
2296
|
+
` Issues:\n ` + criticalIssues.slice(0, 10).join('\n ') +
|
|
2297
|
+
(criticalIssues.length > 10 ? `\n ... and ${criticalIssues.length - 10} more` : ''),
|
|
2298
|
+
EXIT.HUMAN_LOCK_REQUIRED,
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2096
2302
|
const files = { ...result.files };
|
|
2097
2303
|
files['README.md'] = compileApi.generateReadme(project);
|
|
2098
2304
|
files.LICENSE = project.license?.type || 'UNSPECIFIED';
|
|
@@ -2167,15 +2373,16 @@ function cmdStudioInstall(args) {
|
|
|
2167
2373
|
function cmdStudioUpdate(args) {
|
|
2168
2374
|
const target = args[0];
|
|
2169
2375
|
if (!target) fail('Usage: kdna-studio update <@scope/name>');
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2376
|
+
// kdna update does not exist in kdna-cli v0.28.x. The correct
|
|
2377
|
+
// path is to re-install via npm or use kdna install to pull
|
|
2378
|
+
// the latest published version of the asset.
|
|
2379
|
+
console.log(`To update ${target}, run:`);
|
|
2380
|
+
console.log(` npm install -g @aikdna/kdna-cli@latest`);
|
|
2381
|
+
console.log(` kdna install ${target}`);
|
|
2382
|
+
console.log(``);
|
|
2383
|
+
console.log(`Or for a project-local install:`);
|
|
2384
|
+
console.log(` cd <project-dir> && npm install @aikdna/kdna-cli@latest`);
|
|
2385
|
+
process.exit(0);
|
|
2179
2386
|
}
|
|
2180
2387
|
|
|
2181
2388
|
function cmdLlm(args) {
|
|
@@ -2250,6 +2457,140 @@ function cmdReport(args) {
|
|
|
2250
2457
|
process.exit(gate.blocked ? EXIT.HUMAN_LOCK_REQUIRED : EXIT.OK);
|
|
2251
2458
|
}
|
|
2252
2459
|
|
|
2460
|
+
// Audit-locks: list every card that would fail the v1.7.2+ Human Lock
|
|
2461
|
+
// gate because of missing or empty fields. Used during the
|
|
2462
|
+
// pre-migration review of source-tree assets (e.g. pro-20) so an
|
|
2463
|
+
// author can fill in the missing confidence / evidence_type / risk
|
|
2464
|
+
// description / stance statement BEFORE running `migrate --format v1`.
|
|
2465
|
+
//
|
|
2466
|
+
// Bug (UX pro-20 migration): prior version of `kdna-studio report`
|
|
2467
|
+
// returned a flat human_lock_gate.issues list, which mixed card
|
|
2468
|
+
// types and field names. An author migrating 20 source-tree
|
|
2469
|
+
// assets with 99 axioms + 140 risk cards + 70 stance cards had no
|
|
2470
|
+
// way to see, for a single asset, "which axioms are missing
|
|
2471
|
+
// confidence?" — they had to grep the project JSON. The fix adds
|
|
2472
|
+
// a per-type, per-field breakdown.
|
|
2473
|
+
function cmdAuditLocks(args) {
|
|
2474
|
+
const { project } = readProject(args[0]);
|
|
2475
|
+
const useJson = args.includes('--json');
|
|
2476
|
+
const onlyType = option(args, '--type'); // 'axiom' | 'risk' | 'stance' | 'misunderstanding' | null
|
|
2477
|
+
|
|
2478
|
+
const cards = project.cards || [];
|
|
2479
|
+
const findings = {
|
|
2480
|
+
project: project.name,
|
|
2481
|
+
card_count: cards.length,
|
|
2482
|
+
locked_count: cards.filter(c => c.locked).length,
|
|
2483
|
+
by_type: {},
|
|
2484
|
+
};
|
|
2485
|
+
|
|
2486
|
+
// Per-card-type required field lists. Mirrors the v1.7.2+ lockCard
|
|
2487
|
+
// gate (kdna-studio-core/src/cards/index.js) so the output is
|
|
2488
|
+
// exactly the field set the gate will reject.
|
|
2489
|
+
const REQUIRED = {
|
|
2490
|
+
axiom: ['one_sentence', 'full_statement', 'why', 'applies_when',
|
|
2491
|
+
'does_not_apply_when', 'failure_risk', 'confidence', 'evidence_type'],
|
|
2492
|
+
risk: ['name', 'description', 'mitigation'],
|
|
2493
|
+
stance: ['statement', 'applies_when'],
|
|
2494
|
+
misunderstanding: ['wrong', 'correct', 'key_distinction', 'why'],
|
|
2495
|
+
boundary: ['scope', 'out_of_scope'],
|
|
2496
|
+
self_check: ['question'],
|
|
2497
|
+
};
|
|
2498
|
+
const THRESHOLDS = {
|
|
2499
|
+
axiom: {
|
|
2500
|
+
one_sentence: { min: 5, type: 'string' },
|
|
2501
|
+
full_statement: { min: 20, type: 'string' },
|
|
2502
|
+
why: { min: 20, type: 'string' },
|
|
2503
|
+
applies_when: { type: 'array', minLength: 1 },
|
|
2504
|
+
does_not_apply_when: { type: 'array', minLength: 1 },
|
|
2505
|
+
failure_risk: { min: 1, type: 'string' },
|
|
2506
|
+
confidence: { allowed: ['low', 'medium', 'high'] },
|
|
2507
|
+
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle', 'practice'] },
|
|
2508
|
+
},
|
|
2509
|
+
risk: {
|
|
2510
|
+
name: { min: 1, type: 'string' },
|
|
2511
|
+
description: { min: 20, type: 'string' },
|
|
2512
|
+
mitigation: { min: 1, type: 'string' },
|
|
2513
|
+
},
|
|
2514
|
+
stance: {
|
|
2515
|
+
statement: { min: 1, type: 'string' },
|
|
2516
|
+
applies_when: { type: 'array', minLength: 0 },
|
|
2517
|
+
},
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2520
|
+
function checkField(type, fields, name, rule) {
|
|
2521
|
+
const value = fields[name];
|
|
2522
|
+
if (value === undefined || value === null || value === '') {
|
|
2523
|
+
return { field: name, issue: 'missing' };
|
|
2524
|
+
}
|
|
2525
|
+
if (rule.type === 'string' && rule.min && String(value).length < rule.min) {
|
|
2526
|
+
return { field: name, issue: 'too_short', have_length: String(value).length, need_min: rule.min };
|
|
2527
|
+
}
|
|
2528
|
+
if (rule.allowed && !rule.allowed.includes(value)) {
|
|
2529
|
+
return { field: name, issue: 'invalid_value', have: value, allowed: rule.allowed };
|
|
2530
|
+
}
|
|
2531
|
+
if (rule.type === 'array' && !Array.isArray(value)) {
|
|
2532
|
+
return { field: name, issue: 'wrong_type', have_type: typeof value, need_type: 'array' };
|
|
2533
|
+
}
|
|
2534
|
+
if (rule.type === 'array' && rule.minLength && value.length < rule.minLength) {
|
|
2535
|
+
return { field: name, issue: 'too_short', have_length: value.length, need_min: rule.minLength };
|
|
2536
|
+
}
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
for (const card of cards) {
|
|
2541
|
+
const req = REQUIRED[card.type];
|
|
2542
|
+
if (!req) continue;
|
|
2543
|
+
if (onlyType && card.type !== onlyType) continue;
|
|
2544
|
+
const rule = THRESHOLDS[card.type] || {};
|
|
2545
|
+
const missing = [];
|
|
2546
|
+
for (const name of req) {
|
|
2547
|
+
const issue = checkField(card.type, card.fields || {}, name, rule[name] || {});
|
|
2548
|
+
if (issue) missing.push(issue);
|
|
2549
|
+
}
|
|
2550
|
+
if (missing.length > 0) {
|
|
2551
|
+
if (!findings.by_type[card.type]) {
|
|
2552
|
+
findings.by_type[card.type] = { missing_count: 0, cards: [] };
|
|
2553
|
+
}
|
|
2554
|
+
findings.by_type[card.type].missing_count += 1;
|
|
2555
|
+
findings.by_type[card.type].cards.push({
|
|
2556
|
+
id: card.id,
|
|
2557
|
+
one_sentence: card.fields?.one_sentence || card.fields?.statement || card.fields?.question || '',
|
|
2558
|
+
missing,
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
if (useJson) {
|
|
2564
|
+
console.log(JSON.stringify(findings, null, 2));
|
|
2565
|
+
} else {
|
|
2566
|
+
console.log(`Project: ${findings.project}`);
|
|
2567
|
+
console.log(`Cards: ${findings.card_count} (locked: ${findings.locked_count})`);
|
|
2568
|
+
const types = Object.keys(findings.by_type);
|
|
2569
|
+
if (types.length === 0) {
|
|
2570
|
+
console.log('\nAll cards pass the v1.7.2+ Human Lock gate. Ready to migrate.');
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
for (const t of types) {
|
|
2574
|
+
const block = findings.by_type[t];
|
|
2575
|
+
console.log(`\n${t}: ${block.missing_count} card(s) with missing fields`);
|
|
2576
|
+
for (const card of block.cards) {
|
|
2577
|
+
console.log(` ${card.id} ${(card.one_sentence || '').slice(0, 60)}`);
|
|
2578
|
+
for (const m of card.missing) {
|
|
2579
|
+
if (m.issue === 'missing') {
|
|
2580
|
+
console.log(` - ${m.field}: missing`);
|
|
2581
|
+
} else if (m.issue === 'too_short') {
|
|
2582
|
+
console.log(` - ${m.field}: too short (have ${m.have_length}, need ≥ ${m.need_min})`);
|
|
2583
|
+
} else if (m.issue === 'invalid_value') {
|
|
2584
|
+
console.log(` - ${m.field}: invalid value "${m.have}", allowed: ${m.allowed.join('|')}`);
|
|
2585
|
+
} else {
|
|
2586
|
+
console.log(` - ${m.field}: ${m.issue}`);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2253
2594
|
// ─── Distillation Commands ──────────────────────────────────────────
|
|
2254
2595
|
|
|
2255
2596
|
function cmdTarget(args) {
|
|
@@ -2504,6 +2845,7 @@ async function cmdFeynman(args) {
|
|
|
2504
2845
|
const projectInput = args[0];
|
|
2505
2846
|
const cardId = args[1];
|
|
2506
2847
|
if (!projectInput || !cardId) fail('Usage: kdna-studio feynman <project> <card-id>');
|
|
2848
|
+
const useJson = args.includes('--json');
|
|
2507
2849
|
const { projectPath, project } = readProject(projectInput);
|
|
2508
2850
|
const cardIdx = (project.cards || []).findIndex(c => c.id === cardId);
|
|
2509
2851
|
if (cardIdx < 0) fail(`Card not found: ${cardId}`);
|
|
@@ -2539,7 +2881,7 @@ async function cmdFeynman(args) {
|
|
|
2539
2881
|
console.warn(`No feynman_restatement on ${cardId}; using synthesised:\n "${synthesised}"`);
|
|
2540
2882
|
}
|
|
2541
2883
|
|
|
2542
|
-
console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2884
|
+
if (!useJson) console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2543
2885
|
|
|
2544
2886
|
// Bug (#1 UX follow-up): if no LLM is configured and the caller
|
|
2545
2887
|
// did not pass --no-llm, route through cmdNeedsLlm which either
|
|
@@ -2568,14 +2910,26 @@ async function cmdFeynman(args) {
|
|
|
2568
2910
|
const score = result.score || 0;
|
|
2569
2911
|
const passed = score >= 4;
|
|
2570
2912
|
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2913
|
+
if (useJson) {
|
|
2914
|
+
console.log(JSON.stringify({
|
|
2915
|
+
card_id: cardId,
|
|
2916
|
+
score,
|
|
2917
|
+
passed,
|
|
2918
|
+
no_llm: !!llmGate.noLlm,
|
|
2919
|
+
criteria: result.criteria || {},
|
|
2920
|
+
suggestions: result.suggestions || [],
|
|
2921
|
+
feynman_restatement: card.feynman_restatement,
|
|
2922
|
+
}, null, 2));
|
|
2923
|
+
} else {
|
|
2924
|
+
console.log(`Score: ${score}/5 ${passed ? '✓ publishable' : '✗ below threshold (need 4/5)'}${llmGate.noLlm ? ' (--no-llm: static result)' : ''}`);
|
|
2925
|
+
for (const [criterion, desc] of Object.entries(ai.feynman.CRITERIA)) {
|
|
2926
|
+
const r = (result.criteria || {})[criterion] ? '✓' : '✗';
|
|
2927
|
+
console.log(` ${r} ${criterion}`);
|
|
2928
|
+
}
|
|
2929
|
+
if (result.suggestions && result.suggestions.length > 0) {
|
|
2930
|
+
console.log('\nSuggestions:');
|
|
2931
|
+
result.suggestions.forEach(s => console.log(` - ${s}`));
|
|
2932
|
+
}
|
|
2579
2933
|
}
|
|
2580
2934
|
|
|
2581
2935
|
card.feynman_evaluation = { score, criteria: result.criteria, suggestions: result.suggestions, evaluated_at: new Date().toISOString() };
|
|
@@ -2677,6 +3031,7 @@ try {
|
|
|
2677
3031
|
else if (cmd === 'llm') cmdLlm(args.slice(1));
|
|
2678
3032
|
else if (cmd === 'identity') cmdIdentity(args.slice(1));
|
|
2679
3033
|
else if (cmd === 'report') cmdReport(args.slice(1));
|
|
3034
|
+
else if (cmd === 'audit-locks') cmdAuditLocks(args.slice(1));
|
|
2680
3035
|
else if (cmd === 'install') cmdStudioInstall(args.slice(1));
|
|
2681
3036
|
else if (cmd === 'update') cmdStudioUpdate(args.slice(1));
|
|
2682
3037
|
else {
|
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.10",
|
|
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.7",
|
|
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.9"
|
|
49
49
|
}
|
|
50
50
|
}
|
package/src/ai/feynman.js
CHANGED
|
@@ -43,7 +43,9 @@ async function evaluate(config, card, options = {}) {
|
|
|
43
43
|
const axiomText = fields.one_sentence || fields.full_statement || fields.question || '';
|
|
44
44
|
const restatement = card.feynman_restatement
|
|
45
45
|
|| fields.feynman_restatement
|
|
46
|
-
|
|
46
|
+
// NOTE: fields.feynman_text was previously in this fallback chain
|
|
47
|
+
// but is intentionally excluded — no code path in @aikdna writes
|
|
48
|
+
// feynman_text. The canonical field is feynman_restatement.
|
|
47
49
|
|| '';
|
|
48
50
|
|
|
49
51
|
if (!restatement) {
|