@aikdna/kdna-studio-cli 0.8.3 → 0.8.4
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 +87 -22
- package/package.json +2 -2
- package/src/ai/feynman.js +14 -2
package/bin/kdna-studio.js
CHANGED
|
@@ -357,10 +357,13 @@ function cardsFromV1Payload(payload) {
|
|
|
357
357
|
}
|
|
358
358
|
// Accept both `self_check` (canonical) and `self_checks` (legacy schema
|
|
359
359
|
// name) so we round-trip assets written by either version of the
|
|
360
|
-
// payload-profile schema.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
360
|
+
// payload-profile schema. Bug (#53): prior version used
|
|
361
|
+
// `arr || []`, which would crash with "object is not iterable" if
|
|
362
|
+
// the field was a truthy non-array (e.g. a single `{question: ...}`
|
|
363
|
+
// object instead of an array). The fix guards with Array.isArray.
|
|
364
|
+
const rawSelfCheck = payload.reasoning?.self_check
|
|
365
|
+
|| payload.reasoning?.self_checks;
|
|
366
|
+
const selfCheckArr = Array.isArray(rawSelfCheck) ? rawSelfCheck : [];
|
|
364
367
|
for (const selfCheck of selfCheckArr) {
|
|
365
368
|
const fields = typeof selfCheck === 'string' ? { question: selfCheck } : selfCheck;
|
|
366
369
|
pushImportedCard(cards, 'self_check', fields, fields.id || null);
|
|
@@ -440,7 +443,7 @@ function cardsFromLegacyPayload(payload) {
|
|
|
440
443
|
failure_risk: ms.failure_risk || '',
|
|
441
444
|
}, ms.id || null);
|
|
442
445
|
}
|
|
443
|
-
for (const sc of (patterns.self_check
|
|
446
|
+
for (const sc of (Array.isArray(patterns.self_check) ? patterns.self_check : [])) {
|
|
444
447
|
const question = typeof sc === 'string' ? sc : sc.question || '';
|
|
445
448
|
pushImportedCard(cards, 'self_check', { question }, sc.id || null);
|
|
446
449
|
}
|
|
@@ -680,7 +683,7 @@ function importFromKdna(kdnaPath) {
|
|
|
680
683
|
key_distinction: ms.key_distinction || '', why: ms.why || '',
|
|
681
684
|
}));
|
|
682
685
|
}
|
|
683
|
-
for (const sc of (pat.self_check
|
|
686
|
+
for (const sc of (Array.isArray(pat.self_check) ? pat.self_check : [])) {
|
|
684
687
|
const q = typeof sc === 'string' ? sc : sc.question || '';
|
|
685
688
|
importedCards.push(cardApi.createCard('self_check', { question: q }));
|
|
686
689
|
}
|
|
@@ -833,7 +836,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
|
|
|
833
836
|
key_distinction: ms.key_distinction || '', why: ms.why || '',
|
|
834
837
|
}));
|
|
835
838
|
}
|
|
836
|
-
for (const sc of (patterns.self_check
|
|
839
|
+
for (const sc of (Array.isArray(patterns.self_check) ? patterns.self_check : [])) {
|
|
837
840
|
const q = typeof sc === 'string' ? sc : sc.question || '';
|
|
838
841
|
importedCards.push(cardApi.createCard('self_check', { question: q }));
|
|
839
842
|
}
|
|
@@ -1059,13 +1062,23 @@ function cmdImport(args) {
|
|
|
1059
1062
|
return;
|
|
1060
1063
|
}
|
|
1061
1064
|
let content;
|
|
1065
|
+
let fd = null;
|
|
1062
1066
|
try {
|
|
1063
|
-
|
|
1067
|
+
fd = fs.openSync(filePath, 'r');
|
|
1064
1068
|
const buf = Buffer.alloc(120000);
|
|
1065
1069
|
const bytesRead = fs.readSync(fd, buf, 0, 120000, 0);
|
|
1066
|
-
fs.closeSync(fd);
|
|
1067
1070
|
content = buf.toString('utf8', 0, bytesRead);
|
|
1068
1071
|
} catch { console.warn(`Cannot read: ${name}`); skipped++; return; }
|
|
1072
|
+
finally {
|
|
1073
|
+
// Bug (#51): prior version called fs.closeSync only on the
|
|
1074
|
+
// success path, so any readSync failure left the file descriptor
|
|
1075
|
+
// open. Under bulk import (300 files), a single error mid-loop
|
|
1076
|
+
// could exhaust the per-process fd limit and silently break
|
|
1077
|
+
// later imports.
|
|
1078
|
+
if (fd !== null) {
|
|
1079
|
+
try { fs.closeSync(fd); } catch { /* best effort */ }
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1069
1082
|
|
|
1070
1083
|
const evidence = evidenceApi.createEvidenceEntry('text', name, content, filePath);
|
|
1071
1084
|
evidenceApi.addEvidence(project, evidence);
|
|
@@ -1409,6 +1422,13 @@ function cmdMigrate(args) {
|
|
|
1409
1422
|
projectPath = path.join(tmpDir, 'studio.project.json');
|
|
1410
1423
|
}
|
|
1411
1424
|
|
|
1425
|
+
// Bug (#54): --allow-incomplete used to be read AFTER the critical
|
|
1426
|
+
// fields check, so a caller asking to bypass the gate was rejected
|
|
1427
|
+
// for the wrong reason. Resolve the flag here so the critical-missing
|
|
1428
|
+
// check honours it (and reports the bypass correctly) instead of
|
|
1429
|
+
// always failing.
|
|
1430
|
+
const allowIncompleteEarly = args.includes('--allow-incomplete');
|
|
1431
|
+
|
|
1412
1432
|
// Reject if critical judgment fields are missing from axioms
|
|
1413
1433
|
const criticalMissing = [];
|
|
1414
1434
|
for (const card of (project.cards || [])) {
|
|
@@ -1421,13 +1441,21 @@ function cmdMigrate(args) {
|
|
|
1421
1441
|
}
|
|
1422
1442
|
}
|
|
1423
1443
|
if (criticalMissing.length > 0) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1444
|
+
if (allowIncompleteEarly) {
|
|
1445
|
+
console.warn(
|
|
1446
|
+
`--allow-incomplete: bypassing ${criticalMissing.length} critical axiom-field check(s). ` +
|
|
1447
|
+
`The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
|
|
1448
|
+
);
|
|
1449
|
+
} else {
|
|
1450
|
+
fail(
|
|
1451
|
+
`Cannot migrate: ${criticalMissing.length} critical fields missing from axioms.\n` +
|
|
1452
|
+
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
1453
|
+
` decide when to load this domain). Add them to your source files first,\n` +
|
|
1454
|
+
` or pass --allow-incomplete to bypass.\n` +
|
|
1455
|
+
` Missing:\n ` + criticalMissing.slice(0, 10).join('\n ') +
|
|
1456
|
+
(criticalMissing.length > 10 ? `\n ... and ${criticalMissing.length - 10} more` : '')
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1431
1459
|
}
|
|
1432
1460
|
|
|
1433
1461
|
// Step 2: approve and Human Lock all cards
|
|
@@ -1782,7 +1810,15 @@ function cmdExport(args) {
|
|
|
1782
1810
|
fail(`Could not read password from stdin: ${e.message}`);
|
|
1783
1811
|
}
|
|
1784
1812
|
} else {
|
|
1785
|
-
|
|
1813
|
+
// Bug (#55): prior version only consulted --password / --passphrase
|
|
1814
|
+
// here, so a caller who set KDNA_PASSPHRASE for the signing path
|
|
1815
|
+
// (works via resolvePassphrase) found that the v1 encryption
|
|
1816
|
+
// password was silently null. Resolve from the same sources as
|
|
1817
|
+
// the signing path so the two stay in sync.
|
|
1818
|
+
password = process.env.KDNA_PASSPHRASE
|
|
1819
|
+
|| process.env.KDNA_PASSWORD
|
|
1820
|
+
|| option(args, '--password')
|
|
1821
|
+
|| option(args, '--passphrase');
|
|
1786
1822
|
}
|
|
1787
1823
|
exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out), {
|
|
1788
1824
|
allowIncomplete: args.includes('--allow-incomplete'),
|
|
@@ -2231,12 +2267,41 @@ async function cmdTest(args) {
|
|
|
2231
2267
|
const domainName = project.name || 'Untitled Project';
|
|
2232
2268
|
const domainPrompt = `Domain: ${domainName}\n${project.description || ''}`;
|
|
2233
2269
|
|
|
2270
|
+
// Bug (#50): prior version hand-rolled the prompt for the 3 preset
|
|
2271
|
+
// names and bypassed ai.testlab.testPreset entirely. The exported
|
|
2272
|
+
// testPreset function was dead code, and the help text advertised
|
|
2273
|
+
// names (baseline / edge / contradiction) that did not match the
|
|
2274
|
+
// actual preset keys in testlab.js (baseline / edge_case /
|
|
2275
|
+
// contradiction). Result: --preset edge silently ran the
|
|
2276
|
+
// contradiction branch, and any caller that asked for "edge_case"
|
|
2277
|
+
// fell through to the contradiction branch too.
|
|
2278
|
+
//
|
|
2279
|
+
// The fix routes the call through testPreset when a known preset is
|
|
2280
|
+
// given, and accepts both help-text names (baseline / edge /
|
|
2281
|
+
// contradiction) and the canonical testlab names (baseline / edge_case
|
|
2282
|
+
// / contradiction).
|
|
2283
|
+
const PRESET_ALIASES = {
|
|
2284
|
+
baseline: 'baseline',
|
|
2285
|
+
edge: 'edge_case',
|
|
2286
|
+
edge_case: 'edge_case',
|
|
2287
|
+
contradiction: 'contradiction',
|
|
2288
|
+
};
|
|
2289
|
+
const canonical = PRESET_ALIASES[preset] || 'baseline';
|
|
2234
2290
|
console.log(`Testing: ${domainName} [${preset}]`);
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2291
|
+
|
|
2292
|
+
if (canonical === 'baseline') {
|
|
2293
|
+
// Baseline is a single comparison: keep the prior single-result
|
|
2294
|
+
// shape so existing parsers / consumers see the same output.
|
|
2295
|
+
const prompt = `Test the core judgment: ${input}`;
|
|
2296
|
+
const result = await ai.testlab.compare(llm.config(), domainName, prompt, domainPrompt, {});
|
|
2297
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
// For non-baseline presets, run the preset + the baseline so the user
|
|
2301
|
+
// can see both side by side. The preset prompt is the one in
|
|
2302
|
+
// ai/testlab.js — that's the code path that was dead before.
|
|
2303
|
+
const result = await ai.testlab.testPreset(llm.config(), domainName, input, domainPrompt, {});
|
|
2304
|
+
console.log(JSON.stringify(result[canonical] || result, null, 2));
|
|
2240
2305
|
}
|
|
2241
2306
|
|
|
2242
2307
|
const args = process.argv.slice(2);
|
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.4",
|
|
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.3",
|
|
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
|
@@ -31,8 +31,20 @@ Return ONLY valid JSON:
|
|
|
31
31
|
}`;
|
|
32
32
|
|
|
33
33
|
async function evaluate(config, card, options = {}) {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
// Bug: prior version read `card.feynman_text` (a field nothing in the
|
|
35
|
+
// codebase ever wrote) and flattened `card.one_sentence` /
|
|
36
|
+
// `card.full_statement` — the schema actually nests those under
|
|
37
|
+
// `card.fields`. The AI evaluator therefore always received an empty
|
|
38
|
+
// restatement and a blank axiom, no matter what was in the project.
|
|
39
|
+
// The fix reads the canonical schema field names and the nested
|
|
40
|
+
// `fields` object, with a fallback for callers that pass the flattened
|
|
41
|
+
// shape directly.
|
|
42
|
+
const fields = (card && card.fields) || card || {};
|
|
43
|
+
const axiomText = fields.one_sentence || fields.full_statement || fields.question || '';
|
|
44
|
+
const restatement = card.feynman_restatement
|
|
45
|
+
|| fields.feynman_restatement
|
|
46
|
+
|| fields.feynman_text
|
|
47
|
+
|| '';
|
|
36
48
|
|
|
37
49
|
if (!restatement) return { score: 0, criteria: {}, explanations: { notJustRepeat: 'No Feynman restatement provided.' }, suggestions: ['Write a Feynman restatement before evaluation.'] };
|
|
38
50
|
|