@aikdna/kdna-studio-cli 0.8.9 → 0.8.11
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 +201 -61
- 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,12 +1166,13 @@ 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
|
-
importedCards
|
|
1171
|
+
pushImportedCard(importedCards, 'evolution_stage', {
|
|
1142
1172
|
name: stage.name || stage.id || '',
|
|
1143
|
-
level: stage.level
|
|
1144
|
-
|
|
1173
|
+
level: stage.level != null ? stage.level : '',
|
|
1174
|
+
description: stage.description || '',
|
|
1175
|
+
}, stage.id || null);
|
|
1145
1176
|
}
|
|
1146
1177
|
}
|
|
1147
1178
|
|
|
@@ -1180,7 +1211,9 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
1180
1211
|
|
|
1181
1212
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
1182
1213
|
writeProject(path.join(projectDir, 'studio.project.json'), project);
|
|
1183
|
-
|
|
1214
|
+
if (!opts.silent) {
|
|
1215
|
+
console.log(JSON.stringify({ audit, imported: importedCards.length, source_mode: 'source_folder' }, null, 2));
|
|
1216
|
+
}
|
|
1184
1217
|
return { project, projectPath: path.join(projectDir, 'studio.project.json'), audit };
|
|
1185
1218
|
}
|
|
1186
1219
|
|
|
@@ -1395,18 +1428,74 @@ const CARD_REQUIRED_FIELDS = {
|
|
|
1395
1428
|
pattern: ['type', 'name', 'one_sentence', 'what_it_looks_like', 'how_to_fix', 'failure_risk'],
|
|
1396
1429
|
};
|
|
1397
1430
|
|
|
1431
|
+
const CARD_FIELD_RULES = {
|
|
1432
|
+
axiom: {
|
|
1433
|
+
applies_when: { type: 'array' },
|
|
1434
|
+
does_not_apply_when: { type: 'array' },
|
|
1435
|
+
confidence: { allowed: ['low', 'medium', 'high'] },
|
|
1436
|
+
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle', 'practice'] },
|
|
1437
|
+
},
|
|
1438
|
+
boundary: {
|
|
1439
|
+
acceptable_exceptions: { type: 'array', optional: true },
|
|
1440
|
+
},
|
|
1441
|
+
stance: {
|
|
1442
|
+
applies_when: { type: 'array', optional: true },
|
|
1443
|
+
},
|
|
1444
|
+
misunderstanding: {
|
|
1445
|
+
applies_when: { type: 'array', optional: true },
|
|
1446
|
+
does_not_apply_when: { type: 'array', optional: true },
|
|
1447
|
+
},
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
function fieldIssue(fields, name, rule = {}) {
|
|
1451
|
+
const val = fields[name];
|
|
1452
|
+
if (val === undefined || val === null || val === '' ||
|
|
1453
|
+
(Array.isArray(val) && val.length === 0)) {
|
|
1454
|
+
return rule.optional ? null : { field: name, issue: 'missing' };
|
|
1455
|
+
}
|
|
1456
|
+
if (rule.type === 'array' && !Array.isArray(val)) {
|
|
1457
|
+
return { field: name, issue: 'wrong_type', have_type: typeof val, need_type: 'array' };
|
|
1458
|
+
}
|
|
1459
|
+
if (rule.allowed && !rule.allowed.includes(val)) {
|
|
1460
|
+
return { field: name, issue: 'invalid_value', have: val, allowed: rule.allowed };
|
|
1461
|
+
}
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
function formatFieldIssue(issue) {
|
|
1466
|
+
if (issue.issue === 'missing') return issue.field;
|
|
1467
|
+
if (issue.issue === 'wrong_type') return `${issue.field} (must be ${issue.need_type})`;
|
|
1468
|
+
if (issue.issue === 'invalid_value') return `${issue.field} (must be one of: ${issue.allowed.join('|')})`;
|
|
1469
|
+
return `${issue.field} (${issue.issue})`;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1398
1472
|
function checkRequiredFields(type, fields) {
|
|
1399
1473
|
const required = CARD_REQUIRED_FIELDS[type];
|
|
1400
1474
|
if (!required) return [];
|
|
1401
|
-
const
|
|
1475
|
+
const issues = [];
|
|
1402
1476
|
for (const f of required) {
|
|
1403
|
-
const
|
|
1404
|
-
if (
|
|
1405
|
-
|
|
1406
|
-
|
|
1477
|
+
const issue = fieldIssue(fields, f, CARD_FIELD_RULES[type]?.[f] || {});
|
|
1478
|
+
if (issue) issues.push(formatFieldIssue(issue));
|
|
1479
|
+
}
|
|
1480
|
+
for (const [name, rule] of Object.entries(CARD_FIELD_RULES[type] || {})) {
|
|
1481
|
+
if (required.includes(name)) continue;
|
|
1482
|
+
const issue = fieldIssue(fields, name, rule);
|
|
1483
|
+
if (issue) issues.push(formatFieldIssue(issue));
|
|
1484
|
+
}
|
|
1485
|
+
return issues;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function criticalAxiomIssues(project) {
|
|
1489
|
+
const issues = [];
|
|
1490
|
+
for (const card of (Array.isArray(project.cards) ? project.cards : [])) {
|
|
1491
|
+
if (card.type !== 'axiom') continue;
|
|
1492
|
+
const fields = card.fields || {};
|
|
1493
|
+
for (const field of ['applies_when', 'does_not_apply_when', 'failure_risk']) {
|
|
1494
|
+
const issue = fieldIssue(fields, field, CARD_FIELD_RULES.axiom[field] || {});
|
|
1495
|
+
if (issue) issues.push(`${card.id}: ${formatFieldIssue(issue)}`);
|
|
1407
1496
|
}
|
|
1408
1497
|
}
|
|
1409
|
-
return
|
|
1498
|
+
return issues;
|
|
1410
1499
|
}
|
|
1411
1500
|
|
|
1412
1501
|
function cardStrictTemplate(type, fields) {
|
|
@@ -1490,7 +1579,11 @@ function cmdCard(args) {
|
|
|
1490
1579
|
project.stages.judgment_cards.status = 'in_progress';
|
|
1491
1580
|
}
|
|
1492
1581
|
writeProject(projectPath, project);
|
|
1493
|
-
|
|
1582
|
+
if (args.includes('--json')) {
|
|
1583
|
+
console.log(JSON.stringify({ added: true, card }, null, 2));
|
|
1584
|
+
} else {
|
|
1585
|
+
console.log(`Added card: ${card.id}`);
|
|
1586
|
+
}
|
|
1494
1587
|
return;
|
|
1495
1588
|
}
|
|
1496
1589
|
if (sub === 'update') {
|
|
@@ -1733,7 +1826,7 @@ function cmdMigrate(args) {
|
|
|
1733
1826
|
if (!requestedName && project.name) name = project.name;
|
|
1734
1827
|
} else {
|
|
1735
1828
|
tmpDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-')));
|
|
1736
|
-
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity);
|
|
1829
|
+
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity, { silent: true });
|
|
1737
1830
|
project = imported.project;
|
|
1738
1831
|
projectPath = path.join(tmpDir, 'studio.project.json');
|
|
1739
1832
|
}
|
|
@@ -1745,17 +1838,8 @@ function cmdMigrate(args) {
|
|
|
1745
1838
|
// always failing.
|
|
1746
1839
|
const allowIncompleteEarly = args.includes('--allow-incomplete');
|
|
1747
1840
|
|
|
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
|
-
}
|
|
1841
|
+
// Reject if critical judgment fields are missing or malformed on axioms.
|
|
1842
|
+
const criticalMissing = criticalAxiomIssues(project);
|
|
1759
1843
|
if (criticalMissing.length > 0) {
|
|
1760
1844
|
if (allowIncompleteEarly) {
|
|
1761
1845
|
console.warn(
|
|
@@ -1796,9 +1880,8 @@ function cmdMigrate(args) {
|
|
|
1796
1880
|
if (!req) continue;
|
|
1797
1881
|
const missing = [];
|
|
1798
1882
|
for (const f of req) {
|
|
1799
|
-
const
|
|
1800
|
-
if (
|
|
1801
|
-
(Array.isArray(v) && v.length === 0)) missing.push(f);
|
|
1883
|
+
const issue = fieldIssue(card.fields || {}, f, CARD_FIELD_RULES[card.type]?.[f] || {});
|
|
1884
|
+
if (issue) missing.push(formatFieldIssue(issue));
|
|
1802
1885
|
}
|
|
1803
1886
|
if (missing.length > 0) {
|
|
1804
1887
|
if (!summary.by_type[card.type]) summary.by_type[card.type] = [];
|
|
@@ -1905,6 +1988,25 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
1905
1988
|
if (!exportRuntime || typeof exportRuntime.exportRuntimeAsset !== 'function') {
|
|
1906
1989
|
fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for v1 export.', 2);
|
|
1907
1990
|
}
|
|
1991
|
+
const criticalIssues = criticalAxiomIssues(project);
|
|
1992
|
+
if (criticalIssues.length > 0) {
|
|
1993
|
+
if (opts.allowIncomplete) {
|
|
1994
|
+
console.warn(
|
|
1995
|
+
`--allow-incomplete: bypassing ${criticalIssues.length} critical axiom-field check(s). ` +
|
|
1996
|
+
`The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
|
|
1997
|
+
);
|
|
1998
|
+
} else {
|
|
1999
|
+
fail(
|
|
2000
|
+
`Cannot export v1: ${criticalIssues.length} critical axiom field issue(s).\n` +
|
|
2001
|
+
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
2002
|
+
` decide when to load this domain). Fix them first, or pass\n` +
|
|
2003
|
+
` --allow-incomplete to bypass.\n` +
|
|
2004
|
+
` Issues:\n ` + criticalIssues.slice(0, 10).join('\n ') +
|
|
2005
|
+
(criticalIssues.length > 10 ? `\n ... and ${criticalIssues.length - 10} more` : ''),
|
|
2006
|
+
EXIT.HUMAN_LOCK_REQUIRED,
|
|
2007
|
+
);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
1908
2010
|
const gate = projectApi.checkHumanLockGate(project);
|
|
1909
2011
|
if (gate.blocked) {
|
|
1910
2012
|
if (opts.allowIncomplete) {
|
|
@@ -2082,7 +2184,7 @@ function manifestForSigning(manifest) {
|
|
|
2082
2184
|
|
|
2083
2185
|
function canonicalPayload(files) {
|
|
2084
2186
|
return ['mimetype', ...Object.keys(files).filter((k) => k !== 'mimetype').sort()]
|
|
2085
|
-
.filter((name) => name !== 'signature.json' && name !== '.DS_Store')
|
|
2187
|
+
.filter((name) => name !== 'signature.json' && name !== '.DS_Store' && name !== 'build-receipt.json' && !name.startsWith('reports/'))
|
|
2086
2188
|
.map((name) => {
|
|
2087
2189
|
let content = name === 'mimetype' ? 'application/vnd.aikdna.kdna+zip' : files[name];
|
|
2088
2190
|
if (name.endsWith('.json')) {
|
|
@@ -2179,6 +2281,25 @@ function cmdExport(args) {
|
|
|
2179
2281
|
return;
|
|
2180
2282
|
}
|
|
2181
2283
|
const { project, result } = compileProject(projectInput);
|
|
2284
|
+
const criticalIssues = criticalAxiomIssues(project);
|
|
2285
|
+
if (criticalIssues.length > 0) {
|
|
2286
|
+
if (args.includes('--allow-incomplete')) {
|
|
2287
|
+
console.warn(
|
|
2288
|
+
`--allow-incomplete: bypassing ${criticalIssues.length} critical axiom-field check(s). ` +
|
|
2289
|
+
`The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
|
|
2290
|
+
);
|
|
2291
|
+
} else {
|
|
2292
|
+
fail(
|
|
2293
|
+
`Cannot export: ${criticalIssues.length} critical axiom field issue(s).\n` +
|
|
2294
|
+
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
2295
|
+
` decide when to load this domain). Fix them first, or pass\n` +
|
|
2296
|
+
` --allow-incomplete to bypass.\n` +
|
|
2297
|
+
` Issues:\n ` + criticalIssues.slice(0, 10).join('\n ') +
|
|
2298
|
+
(criticalIssues.length > 10 ? `\n ... and ${criticalIssues.length - 10} more` : ''),
|
|
2299
|
+
EXIT.HUMAN_LOCK_REQUIRED,
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2182
2303
|
const files = { ...result.files };
|
|
2183
2304
|
files['README.md'] = compileApi.generateReadme(project);
|
|
2184
2305
|
files.LICENSE = project.license?.type || 'UNSPECIFIED';
|
|
@@ -2253,15 +2374,16 @@ function cmdStudioInstall(args) {
|
|
|
2253
2374
|
function cmdStudioUpdate(args) {
|
|
2254
2375
|
const target = args[0];
|
|
2255
2376
|
if (!target) fail('Usage: kdna-studio update <@scope/name>');
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2377
|
+
// kdna update does not exist in kdna-cli v0.28.x. The correct
|
|
2378
|
+
// path is to re-install via npm or use kdna install to pull
|
|
2379
|
+
// the latest published version of the asset.
|
|
2380
|
+
console.log(`To update ${target}, run:`);
|
|
2381
|
+
console.log(` npm install -g @aikdna/kdna-cli@latest`);
|
|
2382
|
+
console.log(` kdna install ${target}`);
|
|
2383
|
+
console.log(``);
|
|
2384
|
+
console.log(`Or for a project-local install:`);
|
|
2385
|
+
console.log(` cd <project-dir> && npm install @aikdna/kdna-cli@latest`);
|
|
2386
|
+
process.exit(0);
|
|
2265
2387
|
}
|
|
2266
2388
|
|
|
2267
2389
|
function cmdLlm(args) {
|
|
@@ -2379,9 +2501,11 @@ function cmdAuditLocks(args) {
|
|
|
2379
2501
|
one_sentence: { min: 5, type: 'string' },
|
|
2380
2502
|
full_statement: { min: 20, type: 'string' },
|
|
2381
2503
|
why: { min: 20, type: 'string' },
|
|
2504
|
+
applies_when: { type: 'array', minLength: 1 },
|
|
2505
|
+
does_not_apply_when: { type: 'array', minLength: 1 },
|
|
2382
2506
|
failure_risk: { min: 1, type: 'string' },
|
|
2383
2507
|
confidence: { allowed: ['low', 'medium', 'high'] },
|
|
2384
|
-
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle'] },
|
|
2508
|
+
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle', 'practice'] },
|
|
2385
2509
|
},
|
|
2386
2510
|
risk: {
|
|
2387
2511
|
name: { min: 1, type: 'string' },
|
|
@@ -2408,6 +2532,9 @@ function cmdAuditLocks(args) {
|
|
|
2408
2532
|
if (rule.type === 'array' && !Array.isArray(value)) {
|
|
2409
2533
|
return { field: name, issue: 'wrong_type', have_type: typeof value, need_type: 'array' };
|
|
2410
2534
|
}
|
|
2535
|
+
if (rule.type === 'array' && rule.minLength && value.length < rule.minLength) {
|
|
2536
|
+
return { field: name, issue: 'too_short', have_length: value.length, need_min: rule.minLength };
|
|
2537
|
+
}
|
|
2411
2538
|
return null;
|
|
2412
2539
|
}
|
|
2413
2540
|
|
|
@@ -2719,6 +2846,7 @@ async function cmdFeynman(args) {
|
|
|
2719
2846
|
const projectInput = args[0];
|
|
2720
2847
|
const cardId = args[1];
|
|
2721
2848
|
if (!projectInput || !cardId) fail('Usage: kdna-studio feynman <project> <card-id>');
|
|
2849
|
+
const useJson = args.includes('--json');
|
|
2722
2850
|
const { projectPath, project } = readProject(projectInput);
|
|
2723
2851
|
const cardIdx = (project.cards || []).findIndex(c => c.id === cardId);
|
|
2724
2852
|
if (cardIdx < 0) fail(`Card not found: ${cardId}`);
|
|
@@ -2754,7 +2882,7 @@ async function cmdFeynman(args) {
|
|
|
2754
2882
|
console.warn(`No feynman_restatement on ${cardId}; using synthesised:\n "${synthesised}"`);
|
|
2755
2883
|
}
|
|
2756
2884
|
|
|
2757
|
-
console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2885
|
+
if (!useJson) console.log(`Evaluating Feynman restatement for card: ${cardId}`);
|
|
2758
2886
|
|
|
2759
2887
|
// Bug (#1 UX follow-up): if no LLM is configured and the caller
|
|
2760
2888
|
// did not pass --no-llm, route through cmdNeedsLlm which either
|
|
@@ -2783,14 +2911,26 @@ async function cmdFeynman(args) {
|
|
|
2783
2911
|
const score = result.score || 0;
|
|
2784
2912
|
const passed = score >= 4;
|
|
2785
2913
|
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2914
|
+
if (useJson) {
|
|
2915
|
+
console.log(JSON.stringify({
|
|
2916
|
+
card_id: cardId,
|
|
2917
|
+
score,
|
|
2918
|
+
passed,
|
|
2919
|
+
no_llm: !!llmGate.noLlm,
|
|
2920
|
+
criteria: result.criteria || {},
|
|
2921
|
+
suggestions: result.suggestions || [],
|
|
2922
|
+
feynman_restatement: card.feynman_restatement,
|
|
2923
|
+
}, null, 2));
|
|
2924
|
+
} else {
|
|
2925
|
+
console.log(`Score: ${score}/5 ${passed ? '✓ publishable' : '✗ below threshold (need 4/5)'}${llmGate.noLlm ? ' (--no-llm: static result)' : ''}`);
|
|
2926
|
+
for (const [criterion, desc] of Object.entries(ai.feynman.CRITERIA)) {
|
|
2927
|
+
const r = (result.criteria || {})[criterion] ? '✓' : '✗';
|
|
2928
|
+
console.log(` ${r} ${criterion}`);
|
|
2929
|
+
}
|
|
2930
|
+
if (result.suggestions && result.suggestions.length > 0) {
|
|
2931
|
+
console.log('\nSuggestions:');
|
|
2932
|
+
result.suggestions.forEach(s => console.log(` - ${s}`));
|
|
2933
|
+
}
|
|
2794
2934
|
}
|
|
2795
2935
|
|
|
2796
2936
|
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.11",
|
|
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) {
|