@aikdna/kdna-studio-cli 0.8.9 → 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 +197 -58
- package/package.json +3 -3
- package/src/ai/feynman.js +3 -1
package/bin/kdna-studio.js
CHANGED
|
@@ -258,23 +258,41 @@ function writeFiles(outDir, files) {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
const ARRAY_FIELD_NAMES = new Set([
|
|
262
|
+
'applies_when',
|
|
263
|
+
'does_not_apply_when',
|
|
264
|
+
'acceptable_exceptions',
|
|
265
|
+
]);
|
|
266
|
+
|
|
261
267
|
function parseFields(args) {
|
|
262
268
|
const fields = {};
|
|
263
269
|
for (const pair of optionsAll(args, '--field')) {
|
|
264
270
|
const eq = pair.indexOf('=');
|
|
265
271
|
if (eq < 1) fail(`Invalid --field "${pair}". Use key=value.`);
|
|
266
|
-
|
|
272
|
+
let key = pair.slice(0, eq);
|
|
273
|
+
const appendArray = key.endsWith('[]');
|
|
274
|
+
if (appendArray) key = key.slice(0, -2);
|
|
267
275
|
const raw = pair.slice(eq + 1);
|
|
276
|
+
let value;
|
|
268
277
|
if (raw.startsWith('[') || raw.startsWith('{')) {
|
|
269
278
|
try {
|
|
270
|
-
|
|
279
|
+
value = JSON.parse(raw);
|
|
271
280
|
} catch (_) {
|
|
272
|
-
|
|
281
|
+
value = raw;
|
|
273
282
|
}
|
|
274
283
|
} else if (raw.includes('|')) {
|
|
275
|
-
|
|
284
|
+
value = raw.split('|').map((s) => s.trim()).filter(Boolean);
|
|
276
285
|
} else {
|
|
277
|
-
|
|
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()] : [];
|
|
294
|
+
} else {
|
|
295
|
+
fields[key] = value;
|
|
278
296
|
}
|
|
279
297
|
}
|
|
280
298
|
return fields;
|
|
@@ -413,6 +431,8 @@ function cardsFromV1Payload(payload) {
|
|
|
413
431
|
applies_when: ax.applies_when || [],
|
|
414
432
|
does_not_apply_when: ax.does_not_apply_when || [],
|
|
415
433
|
failure_risk: ax.failure_risk || '',
|
|
434
|
+
confidence: ax.confidence || '',
|
|
435
|
+
evidence_type: ax.evidence_type || '',
|
|
416
436
|
}, ax.id || null);
|
|
417
437
|
}
|
|
418
438
|
for (const ont of (Array.isArray(payload.core?.ontology) ? payload.core?.ontology : [])) {
|
|
@@ -451,7 +471,8 @@ function cardsFromV1Payload(payload) {
|
|
|
451
471
|
let fields = pattern;
|
|
452
472
|
if (!type) {
|
|
453
473
|
// Heuristics, in priority order — most specific to most general.
|
|
454
|
-
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';
|
|
455
476
|
else if ('term' in pattern && 'definition' in pattern) type = 'term';
|
|
456
477
|
else if ('term' in pattern && 'why' in pattern && 'replace_with' in pattern) type = 'banned_term';
|
|
457
478
|
else if ('what_it_looks_like' in pattern && 'how_to_fix' in pattern) type = 'pattern';
|
|
@@ -833,7 +854,7 @@ function importFromKdna(kdnaPath) {
|
|
|
833
854
|
const core = JSON.parse(entries.get('KDNA_Core.json').toString());
|
|
834
855
|
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
835
856
|
const fields = {};
|
|
836
|
-
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']) {
|
|
837
858
|
if (k in ax) fields[k] = ax[k];
|
|
838
859
|
}
|
|
839
860
|
importedCards.push(cardApi.createCard('axiom', fields));
|
|
@@ -851,7 +872,16 @@ function importFromKdna(kdnaPath) {
|
|
|
851
872
|
acceptable_exceptions: b.acceptable_exceptions || [],
|
|
852
873
|
}));
|
|
853
874
|
}
|
|
854
|
-
|
|
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) {
|
|
855
885
|
importedCards.push(cardApi.createCard('risk', risk.fields || risk));
|
|
856
886
|
}
|
|
857
887
|
for (const stance of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
@@ -944,7 +974,7 @@ function importFromKdna(kdnaPath) {
|
|
|
944
974
|
* Reads KDNA_*.json files and converts entries to draft judgment cards.
|
|
945
975
|
* Outputs a schema audit report.
|
|
946
976
|
*/
|
|
947
|
-
function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
977
|
+
function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, opts = {}) {
|
|
948
978
|
resetImportedIds();
|
|
949
979
|
const absSource = path.resolve(sourceDir);
|
|
950
980
|
if (!fs.existsSync(absSource)) fail(`Source folder not found: ${absSource}`);
|
|
@@ -1011,7 +1041,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1011
1041
|
if (core) {
|
|
1012
1042
|
for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
|
|
1013
1043
|
const fields = {};
|
|
1014
|
-
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']) {
|
|
1015
1045
|
if (key in ax) fields[key] = ax[key];
|
|
1016
1046
|
else audit.missingFields.push(`axiom.${ax.id || '?'}.${key}`);
|
|
1017
1047
|
}
|
|
@@ -1043,7 +1073,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1043
1073
|
}
|
|
1044
1074
|
// NEW: import stances
|
|
1045
1075
|
for (const s of (Array.isArray(core.stances) ? core.stances : [])) {
|
|
1046
|
-
const text = typeof s === 'string' ? s : s.stance || '';
|
|
1076
|
+
const text = typeof s === 'string' ? s : s.statement || s.stance || '';
|
|
1047
1077
|
importedCards.push(cardApi.createCard('stance', {
|
|
1048
1078
|
statement: text,
|
|
1049
1079
|
applies_when: s.applies_when || [],
|
|
@@ -1095,7 +1125,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1095
1125
|
}
|
|
1096
1126
|
|
|
1097
1127
|
// NEW: import scenarios
|
|
1098
|
-
if (scenarios && scenarios.scenes) {
|
|
1128
|
+
if (scenarios && Array.isArray(scenarios.scenes)) {
|
|
1099
1129
|
for (const scene of scenarios.scenes) {
|
|
1100
1130
|
importedCards.push(cardApi.createCard('scenario', {
|
|
1101
1131
|
name: scene.name || scene.id || '',
|
|
@@ -1105,7 +1135,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1105
1135
|
}
|
|
1106
1136
|
|
|
1107
1137
|
// NEW: import cases
|
|
1108
|
-
if (cases && cases.cases) {
|
|
1138
|
+
if (cases && Array.isArray(cases.cases)) {
|
|
1109
1139
|
for (const c of cases.cases) {
|
|
1110
1140
|
importedCards.push(cardApi.createCard('case', {
|
|
1111
1141
|
title: c.title || c.id || '',
|
|
@@ -1121,7 +1151,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1121
1151
|
// so the cards were silently rewritten as axiom-derived fallback chains
|
|
1122
1152
|
// — looking identical to misunderstanding cards and losing the source's
|
|
1123
1153
|
// identity.
|
|
1124
|
-
if (reasoning && reasoning.reasoning_chains) {
|
|
1154
|
+
if (reasoning && Array.isArray(reasoning.reasoning_chains)) {
|
|
1125
1155
|
for (const rc of reasoning.reasoning_chains) {
|
|
1126
1156
|
importedCards.push(cardApi.createCard('reasoning', {
|
|
1127
1157
|
axiom: rc.axiom || '',
|
|
@@ -1136,7 +1166,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1136
1166
|
}
|
|
1137
1167
|
|
|
1138
1168
|
// NEW: import evolution stages
|
|
1139
|
-
if (evolution && evolution.stages) {
|
|
1169
|
+
if (evolution && Array.isArray(evolution.stages)) {
|
|
1140
1170
|
for (const stage of evolution.stages) {
|
|
1141
1171
|
importedCards.push(cardApi.createCard('evolution_stage', {
|
|
1142
1172
|
name: stage.name || stage.id || '',
|
|
@@ -1180,7 +1210,9 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1180
1210
|
|
|
1181
1211
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
1182
1212
|
writeProject(path.join(projectDir, 'studio.project.json'), project);
|
|
1183
|
-
|
|
1213
|
+
if (!opts.silent) {
|
|
1214
|
+
console.log(JSON.stringify({ audit, imported: importedCards.length, source_mode: 'source_folder' }, null, 2));
|
|
1215
|
+
}
|
|
1184
1216
|
return { project, projectPath: path.join(projectDir, 'studio.project.json'), audit };
|
|
1185
1217
|
}
|
|
1186
1218
|
|
|
@@ -1395,18 +1427,74 @@ const CARD_REQUIRED_FIELDS = {
|
|
|
1395
1427
|
pattern: ['type', 'name', 'one_sentence', 'what_it_looks_like', 'how_to_fix', 'failure_risk'],
|
|
1396
1428
|
};
|
|
1397
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
|
+
|
|
1398
1471
|
function checkRequiredFields(type, fields) {
|
|
1399
1472
|
const required = CARD_REQUIRED_FIELDS[type];
|
|
1400
1473
|
if (!required) return [];
|
|
1401
|
-
const
|
|
1474
|
+
const issues = [];
|
|
1402
1475
|
for (const f of required) {
|
|
1403
|
-
const
|
|
1404
|
-
if (
|
|
1405
|
-
|
|
1406
|
-
|
|
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)}`);
|
|
1407
1495
|
}
|
|
1408
1496
|
}
|
|
1409
|
-
return
|
|
1497
|
+
return issues;
|
|
1410
1498
|
}
|
|
1411
1499
|
|
|
1412
1500
|
function cardStrictTemplate(type, fields) {
|
|
@@ -1490,7 +1578,11 @@ function cmdCard(args) {
|
|
|
1490
1578
|
project.stages.judgment_cards.status = 'in_progress';
|
|
1491
1579
|
}
|
|
1492
1580
|
writeProject(projectPath, project);
|
|
1493
|
-
|
|
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
|
+
}
|
|
1494
1586
|
return;
|
|
1495
1587
|
}
|
|
1496
1588
|
if (sub === 'update') {
|
|
@@ -1733,7 +1825,7 @@ function cmdMigrate(args) {
|
|
|
1733
1825
|
if (!requestedName && project.name) name = project.name;
|
|
1734
1826
|
} else {
|
|
1735
1827
|
tmpDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-')));
|
|
1736
|
-
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity);
|
|
1828
|
+
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity, { silent: true });
|
|
1737
1829
|
project = imported.project;
|
|
1738
1830
|
projectPath = path.join(tmpDir, 'studio.project.json');
|
|
1739
1831
|
}
|
|
@@ -1745,17 +1837,8 @@ function cmdMigrate(args) {
|
|
|
1745
1837
|
// always failing.
|
|
1746
1838
|
const allowIncompleteEarly = args.includes('--allow-incomplete');
|
|
1747
1839
|
|
|
1748
|
-
// Reject if critical judgment fields are missing
|
|
1749
|
-
const criticalMissing =
|
|
1750
|
-
for (const card of (Array.isArray(project.cards) ? project.cards : [])) {
|
|
1751
|
-
if (card.type !== 'axiom') continue;
|
|
1752
|
-
for (const field of ['applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
1753
|
-
const val = card.fields && card.fields[field];
|
|
1754
|
-
if (!val || (Array.isArray(val) && val.length === 0) || val === '') {
|
|
1755
|
-
criticalMissing.push(`${card.id}: missing ${field}`);
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1840
|
+
// Reject if critical judgment fields are missing or malformed on axioms.
|
|
1841
|
+
const criticalMissing = criticalAxiomIssues(project);
|
|
1759
1842
|
if (criticalMissing.length > 0) {
|
|
1760
1843
|
if (allowIncompleteEarly) {
|
|
1761
1844
|
console.warn(
|
|
@@ -1796,9 +1879,8 @@ function cmdMigrate(args) {
|
|
|
1796
1879
|
if (!req) continue;
|
|
1797
1880
|
const missing = [];
|
|
1798
1881
|
for (const f of req) {
|
|
1799
|
-
const
|
|
1800
|
-
if (
|
|
1801
|
-
(Array.isArray(v) && v.length === 0)) missing.push(f);
|
|
1882
|
+
const issue = fieldIssue(card.fields || {}, f, CARD_FIELD_RULES[card.type]?.[f] || {});
|
|
1883
|
+
if (issue) missing.push(formatFieldIssue(issue));
|
|
1802
1884
|
}
|
|
1803
1885
|
if (missing.length > 0) {
|
|
1804
1886
|
if (!summary.by_type[card.type]) summary.by_type[card.type] = [];
|
|
@@ -1905,6 +1987,25 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
1905
1987
|
if (!exportRuntime || typeof exportRuntime.exportRuntimeAsset !== 'function') {
|
|
1906
1988
|
fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for v1 export.', 2);
|
|
1907
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
|
+
}
|
|
1908
2009
|
const gate = projectApi.checkHumanLockGate(project);
|
|
1909
2010
|
if (gate.blocked) {
|
|
1910
2011
|
if (opts.allowIncomplete) {
|
|
@@ -2082,7 +2183,7 @@ function manifestForSigning(manifest) {
|
|
|
2082
2183
|
|
|
2083
2184
|
function canonicalPayload(files) {
|
|
2084
2185
|
return ['mimetype', ...Object.keys(files).filter((k) => k !== 'mimetype').sort()]
|
|
2085
|
-
.filter((name) => name !== 'signature.json' && name !== '.DS_Store')
|
|
2186
|
+
.filter((name) => name !== 'signature.json' && name !== '.DS_Store' && name !== 'build-receipt.json' && !name.startsWith('reports/'))
|
|
2086
2187
|
.map((name) => {
|
|
2087
2188
|
let content = name === 'mimetype' ? 'application/vnd.aikdna.kdna+zip' : files[name];
|
|
2088
2189
|
if (name.endsWith('.json')) {
|
|
@@ -2179,6 +2280,25 @@ function cmdExport(args) {
|
|
|
2179
2280
|
return;
|
|
2180
2281
|
}
|
|
2181
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
|
+
}
|
|
2182
2302
|
const files = { ...result.files };
|
|
2183
2303
|
files['README.md'] = compileApi.generateReadme(project);
|
|
2184
2304
|
files.LICENSE = project.license?.type || 'UNSPECIFIED';
|
|
@@ -2253,15 +2373,16 @@ function cmdStudioInstall(args) {
|
|
|
2253
2373
|
function cmdStudioUpdate(args) {
|
|
2254
2374
|
const target = args[0];
|
|
2255
2375
|
if (!target) fail('Usage: kdna-studio update <@scope/name>');
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
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);
|
|
2265
2386
|
}
|
|
2266
2387
|
|
|
2267
2388
|
function cmdLlm(args) {
|
|
@@ -2379,9 +2500,11 @@ function cmdAuditLocks(args) {
|
|
|
2379
2500
|
one_sentence: { min: 5, type: 'string' },
|
|
2380
2501
|
full_statement: { min: 20, type: 'string' },
|
|
2381
2502
|
why: { min: 20, type: 'string' },
|
|
2503
|
+
applies_when: { type: 'array', minLength: 1 },
|
|
2504
|
+
does_not_apply_when: { type: 'array', minLength: 1 },
|
|
2382
2505
|
failure_risk: { min: 1, type: 'string' },
|
|
2383
2506
|
confidence: { allowed: ['low', 'medium', 'high'] },
|
|
2384
|
-
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle'] },
|
|
2507
|
+
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle', 'practice'] },
|
|
2385
2508
|
},
|
|
2386
2509
|
risk: {
|
|
2387
2510
|
name: { min: 1, type: 'string' },
|
|
@@ -2408,6 +2531,9 @@ function cmdAuditLocks(args) {
|
|
|
2408
2531
|
if (rule.type === 'array' && !Array.isArray(value)) {
|
|
2409
2532
|
return { field: name, issue: 'wrong_type', have_type: typeof value, need_type: 'array' };
|
|
2410
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
|
+
}
|
|
2411
2537
|
return null;
|
|
2412
2538
|
}
|
|
2413
2539
|
|
|
@@ -2719,6 +2845,7 @@ async function cmdFeynman(args) {
|
|
|
2719
2845
|
const projectInput = args[0];
|
|
2720
2846
|
const cardId = args[1];
|
|
2721
2847
|
if (!projectInput || !cardId) fail('Usage: kdna-studio feynman <project> <card-id>');
|
|
2848
|
+
const useJson = args.includes('--json');
|
|
2722
2849
|
const { projectPath, project } = readProject(projectInput);
|
|
2723
2850
|
const cardIdx = (project.cards || []).findIndex(c => c.id === cardId);
|
|
2724
2851
|
if (cardIdx < 0) fail(`Card not found: ${cardId}`);
|
|
@@ -2754,7 +2881,7 @@ async function cmdFeynman(args) {
|
|
|
2754
2881
|
console.warn(`No feynman_restatement on ${cardId}; using synthesised:\n "${synthesised}"`);
|
|
2755
2882
|
}
|
|
2756
2883
|
|
|
2757
|
-
console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2884
|
+
if (!useJson) console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2758
2885
|
|
|
2759
2886
|
// Bug (#1 UX follow-up): if no LLM is configured and the caller
|
|
2760
2887
|
// did not pass --no-llm, route through cmdNeedsLlm which either
|
|
@@ -2783,14 +2910,26 @@ async function cmdFeynman(args) {
|
|
|
2783
2910
|
const score = result.score || 0;
|
|
2784
2911
|
const passed = score >= 4;
|
|
2785
2912
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
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
|
+
}
|
|
2794
2933
|
}
|
|
2795
2934
|
|
|
2796
2935
|
card.feynman_evaluation = { score, criteria: result.criteria, suggestions: result.suggestions, evaluated_at: new Date().toISOString() };
|
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) {
|