@aikdna/kdna-studio-cli 0.5.11 → 0.6.1
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 +177 -25
- package/package.json +1 -1
- package/tests/cli.test.js +18 -0
package/bin/kdna-studio.js
CHANGED
|
@@ -44,22 +44,25 @@ Identity:
|
|
|
44
44
|
kdna-studio identity show
|
|
45
45
|
|
|
46
46
|
Create (three entry paths):
|
|
47
|
-
kdna-studio create <project-dir> --name <@scope/name>
|
|
47
|
+
kdna-studio create <project-dir> --name <@scope/name> [--author <name>] # blank
|
|
48
48
|
kdna-studio create <project-dir> --from-kdna <file.kdna> --name <@scope/name>
|
|
49
49
|
kdna-studio create <project-dir> --from-folder <source-dir> --name <@scope/name>
|
|
50
50
|
|
|
51
51
|
Migrate (dev source or Studio project → canonical .kdna in one command):
|
|
52
|
-
kdna-studio migrate <source-dir|project> --format v1 --out <file.kdna> --name <@scope/name> --by <id> --statement <text>
|
|
52
|
+
kdna-studio migrate <source-dir|project> --format v1 --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--allow-incomplete]
|
|
53
53
|
|
|
54
54
|
Authoring:
|
|
55
55
|
kdna-studio import <project> <source-file-or-dir> # import evidence (txt/md/json/yaml/csv/log/srt/vtt/html)
|
|
56
56
|
kdna-studio filter <project> # check evidence for sensitive content
|
|
57
57
|
kdna-studio source classify <project> # classify evidence against declared target
|
|
58
58
|
kdna-studio card list <project>
|
|
59
|
-
kdna-studio card add <project> <type> --field key=value [--field key=value]
|
|
59
|
+
kdna-studio card add <project> <type> --field key=value [--field key=value] [--template <name>] [--no-strict]
|
|
60
|
+
kdna-studio card update <project> <card-id> --field key=value
|
|
61
|
+
kdna-studio card remove <project> <card-id>
|
|
60
62
|
kdna-studio card approve <project> <card-id|--all> --by <id> --statement <text> [--sign] [--passphrase <pass>]
|
|
63
|
+
kdna-studio card unlock <project> <card-id> --by <id> --statement <text>
|
|
61
64
|
kdna-studio compile <project> --out <dir>
|
|
62
|
-
kdna-studio export <project> --format v1 --out <file.kdna>
|
|
65
|
+
kdna-studio export <project> --format v1 --out <file.kdna> [--allow-incomplete]
|
|
63
66
|
|
|
64
67
|
AI Authoring (requires LLM config: kdna-studio llm config):
|
|
65
68
|
kdna-studio distill <project> --ai # AI-driven candidate extraction from evidence
|
|
@@ -425,11 +428,12 @@ function buildV1Manifest(project, name) {
|
|
|
425
428
|
|
|
426
429
|
function cmdCreate(args) {
|
|
427
430
|
const dir = args[0];
|
|
428
|
-
if (!dir) fail('Usage: kdna-studio create <project-dir> --name <name> [--from-kdna <file> | --from-folder <dir>]');
|
|
431
|
+
if (!dir) fail('Usage: kdna-studio create <project-dir> --name <name> [--author <name>] [--from-kdna <file> | --from-folder <dir>]');
|
|
429
432
|
const abs = path.resolve(dir);
|
|
430
433
|
if (fs.existsSync(abs)) fail(`Directory already exists: ${abs}`);
|
|
431
434
|
|
|
432
435
|
const name = option(args, '--name', path.basename(abs));
|
|
436
|
+
const authorName = option(args, '--author', '');
|
|
433
437
|
const fromKdna = option(args, '--from-kdna');
|
|
434
438
|
const fromFolder = option(args, '--from-folder');
|
|
435
439
|
|
|
@@ -448,7 +452,7 @@ function cmdCreate(args) {
|
|
|
448
452
|
const sourceAuthor = kdnaData.source_manifest?.author || {};
|
|
449
453
|
const project = projectApi.createProject(name, 'domain', {
|
|
450
454
|
author: {
|
|
451
|
-
name: option(args, '--author-name', sourceAuthor.name || ''),
|
|
455
|
+
name: authorName || option(args, '--author-name', sourceAuthor.name || ''),
|
|
452
456
|
id: option(args, '--author-id', sourceAuthor.id || ''),
|
|
453
457
|
},
|
|
454
458
|
sourceMode,
|
|
@@ -485,7 +489,7 @@ function cmdCreate(args) {
|
|
|
485
489
|
}
|
|
486
490
|
|
|
487
491
|
const project = projectApi.createProject(name, 'domain', {
|
|
488
|
-
author: { name: option(args, '--author-name', ''), id: option(args, '--author-id', '') },
|
|
492
|
+
author: { name: authorName || option(args, '--author-name', ''), id: option(args, '--author-id', '') },
|
|
489
493
|
sourceMode,
|
|
490
494
|
creatorIdentity,
|
|
491
495
|
lineage,
|
|
@@ -904,6 +908,49 @@ function cmdFilter(args) {
|
|
|
904
908
|
}
|
|
905
909
|
}
|
|
906
910
|
|
|
911
|
+
const CARD_REQUIRED_FIELDS = {
|
|
912
|
+
axiom: [
|
|
913
|
+
'one_sentence', 'full_statement', 'why',
|
|
914
|
+
'applies_when', 'does_not_apply_when', 'failure_risk',
|
|
915
|
+
'confidence', 'evidence_type',
|
|
916
|
+
],
|
|
917
|
+
boundary: ['scope', 'out_of_scope'],
|
|
918
|
+
misunderstanding: ['wrong', 'correct', 'key_distinction', 'why'],
|
|
919
|
+
self_check: ['question'],
|
|
920
|
+
scenario: ['name', 'trigger', 'action', 'expected'],
|
|
921
|
+
case: ['title', 'scenario', 'input', 'expected'],
|
|
922
|
+
risk: ['name', 'description', 'mitigation'],
|
|
923
|
+
stance: ['statement'],
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
function checkRequiredFields(type, fields) {
|
|
927
|
+
const required = CARD_REQUIRED_FIELDS[type];
|
|
928
|
+
if (!required) return [];
|
|
929
|
+
const missing = [];
|
|
930
|
+
for (const f of required) {
|
|
931
|
+
const val = fields[f];
|
|
932
|
+
if (val === undefined || val === null || val === '' ||
|
|
933
|
+
(Array.isArray(val) && val.length === 0)) {
|
|
934
|
+
missing.push(f);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return missing;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function cardStrictTemplate(type, fields) {
|
|
941
|
+
const required = CARD_REQUIRED_FIELDS[type];
|
|
942
|
+
if (!required) return fields;
|
|
943
|
+
const filled = { ...fields };
|
|
944
|
+
for (const f of required) {
|
|
945
|
+
const val = filled[f];
|
|
946
|
+
if (val === undefined || val === null || val === '' ||
|
|
947
|
+
(Array.isArray(val) && val.length === 0)) {
|
|
948
|
+
filled[f] = `<TBD: ${f}>`;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return filled;
|
|
952
|
+
}
|
|
953
|
+
|
|
907
954
|
function cmdCard(args) {
|
|
908
955
|
const sub = args[0];
|
|
909
956
|
if (sub === 'list') {
|
|
@@ -916,9 +963,28 @@ function cmdCard(args) {
|
|
|
916
963
|
if (sub === 'add') {
|
|
917
964
|
const projectInput = args[1];
|
|
918
965
|
const type = args[2];
|
|
919
|
-
if (!projectInput || !type) fail('Usage: kdna-studio card add <project> <type> --field key=value');
|
|
966
|
+
if (!projectInput || !type) fail('Usage: kdna-studio card add <project> <type> --field key=value [--template <name>] [--no-strict]');
|
|
920
967
|
const { projectPath, project } = readProject(projectInput);
|
|
921
|
-
|
|
968
|
+
let fields = parseFields(args.slice(3));
|
|
969
|
+
|
|
970
|
+
const useTemplate = option(args, '--template');
|
|
971
|
+
if (useTemplate === 'axiom-strict') {
|
|
972
|
+
if (type !== 'axiom') fail('--template axiom-strict only applies to axiom cards');
|
|
973
|
+
fields = cardStrictTemplate('axiom', fields);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const isNoStrict = args.includes('--no-strict');
|
|
977
|
+
if (!isNoStrict) {
|
|
978
|
+
const missing = checkRequiredFields(type, fields);
|
|
979
|
+
if (missing.length > 0) {
|
|
980
|
+
fail(
|
|
981
|
+
`Missing required fields for ${type}: ${missing.join(', ')}\n` +
|
|
982
|
+
`Use --no-strict to add a partial card.`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const card = cardApi.createCard(type, fields);
|
|
922
988
|
project.cards.push(card);
|
|
923
989
|
if (project.stages?.judgment_cards) {
|
|
924
990
|
project.stages.judgment_cards.total = project.cards.length;
|
|
@@ -928,6 +994,40 @@ function cmdCard(args) {
|
|
|
928
994
|
console.log(`Added card: ${card.id}`);
|
|
929
995
|
return;
|
|
930
996
|
}
|
|
997
|
+
if (sub === 'update') {
|
|
998
|
+
const projectInput = args[1];
|
|
999
|
+
const cardId = args[2];
|
|
1000
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card update <project> <card-id> --field key=value');
|
|
1001
|
+
const { projectPath, project } = readProject(projectInput);
|
|
1002
|
+
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1003
|
+
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1004
|
+
const card = project.cards[idx];
|
|
1005
|
+
if (card.locked) fail(`Cannot update locked card: ${cardId}. Unlock first with card unlock.`);
|
|
1006
|
+
const updates = parseFields(args.slice(3));
|
|
1007
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
1008
|
+
if (card.fields) card.fields[k] = v;
|
|
1009
|
+
}
|
|
1010
|
+
project.cards[idx] = card;
|
|
1011
|
+
writeProject(projectPath, project);
|
|
1012
|
+
console.log(`Updated card: ${cardId}`);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (sub === 'remove') {
|
|
1016
|
+
const projectInput = args[1];
|
|
1017
|
+
const cardId = args[2];
|
|
1018
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card remove <project> <card-id>');
|
|
1019
|
+
const { projectPath, project } = readProject(projectInput);
|
|
1020
|
+
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1021
|
+
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1022
|
+
if (project.cards[idx].locked) fail(`Cannot remove locked card: ${cardId}. Unlock first with card unlock.`);
|
|
1023
|
+
project.cards.splice(idx, 1);
|
|
1024
|
+
if (project.stages?.judgment_cards) {
|
|
1025
|
+
project.stages.judgment_cards.total = project.cards.length;
|
|
1026
|
+
}
|
|
1027
|
+
writeProject(projectPath, project);
|
|
1028
|
+
console.log(`Removed card: ${cardId}`);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
931
1031
|
if (sub === 'approve') {
|
|
932
1032
|
const projectInput = args[1];
|
|
933
1033
|
const approveAll = args.includes('--all');
|
|
@@ -939,6 +1039,7 @@ function cmdCard(args) {
|
|
|
939
1039
|
const statement = option(args, '--statement');
|
|
940
1040
|
if (!by || !statement) fail('card approve requires --by and --statement');
|
|
941
1041
|
const { projectPath, project } = readProject(projectInput);
|
|
1042
|
+
const errors = [];
|
|
942
1043
|
|
|
943
1044
|
const lockOneCard = (card) => {
|
|
944
1045
|
if (card.status === 'draft') card = cardApi.transitionCard(card, 'revised', { by });
|
|
@@ -949,42 +1050,57 @@ function cmdCard(args) {
|
|
|
949
1050
|
};
|
|
950
1051
|
if (args.includes('--sign')) {
|
|
951
1052
|
const identity = creatorApi.loadIdentity();
|
|
952
|
-
if (!identity)
|
|
1053
|
+
if (!identity) {
|
|
1054
|
+
errors.push(`${card.id}: No creator identity. Run: kdna-studio identity init`);
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
953
1057
|
lockPayload.creator_id = identity.creator_id;
|
|
954
1058
|
const passphrase = option(args, '--passphrase');
|
|
955
1059
|
try {
|
|
956
1060
|
if (identity.encrypted && !passphrase) {
|
|
957
|
-
|
|
1061
|
+
errors.push(`${card.id}: Private key is encrypted — provide --passphrase`);
|
|
1062
|
+
return null;
|
|
958
1063
|
}
|
|
959
1064
|
lockPayload.signature = creatorApi.signHumanLock(
|
|
960
1065
|
card.id, statement, cardApi.cardJudgmentFingerprint(card), null, passphrase,
|
|
961
1066
|
);
|
|
962
1067
|
} catch (e) {
|
|
963
|
-
|
|
1068
|
+
errors.push(`${card.id}: Signing failed: ${e.message}`);
|
|
1069
|
+
return null;
|
|
964
1070
|
}
|
|
965
1071
|
}
|
|
966
|
-
return
|
|
1072
|
+
return cardApi.lockCard(card, lockPayload);
|
|
967
1073
|
};
|
|
968
1074
|
|
|
969
1075
|
if (cardId) {
|
|
970
1076
|
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
971
1077
|
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
972
|
-
|
|
1078
|
+
if (project.cards[idx].locked) fail(`Card already locked: ${cardId}`);
|
|
1079
|
+
const locked = lockOneCard(project.cards[idx]);
|
|
1080
|
+
if (!locked) fail(errors[0]);
|
|
973
1081
|
project.cards[idx] = locked;
|
|
974
1082
|
const signed = args.includes('--sign') ? ' (signed)' : '';
|
|
975
1083
|
console.log(`Approved and Human Locked: ${cardId}${signed}`);
|
|
976
1084
|
} else {
|
|
977
1085
|
let approved = 0;
|
|
1086
|
+
let skipped = 0;
|
|
978
1087
|
for (let i = 0; i < (project.cards || []).length; i++) {
|
|
979
1088
|
const card = project.cards[i];
|
|
980
|
-
if (card.locked) continue;
|
|
981
|
-
const
|
|
1089
|
+
if (card.locked) { skipped++; continue; }
|
|
1090
|
+
const locked = lockOneCard(card);
|
|
1091
|
+
if (!locked) continue;
|
|
982
1092
|
project.cards[i] = locked;
|
|
983
1093
|
approved++;
|
|
984
1094
|
}
|
|
985
|
-
if (
|
|
1095
|
+
if (errors.length > 0) {
|
|
1096
|
+
fail(`Failed to approve ${errors.length} card(s):\n ${errors.join('\n ')}`);
|
|
1097
|
+
}
|
|
1098
|
+
if (approved === 0) {
|
|
1099
|
+
if (skipped > 0) fail(`No unlocked cards to approve (${skipped} already locked).`);
|
|
1100
|
+
fail('No unlocked cards to approve.');
|
|
1101
|
+
}
|
|
986
1102
|
const signed = args.includes('--sign') ? ' (signed)' : '';
|
|
987
|
-
console.log(`Approved and Human Locked: ${approved} cards${signed}`);
|
|
1103
|
+
console.log(`Approved and Human Locked: ${approved} cards${signed}${skipped > 0 ? ` (${skipped} already locked)` : ''}`);
|
|
988
1104
|
}
|
|
989
1105
|
if (project.stages?.judgment_cards) {
|
|
990
1106
|
project.stages.judgment_cards.locked = project.cards.filter((c) => c.locked).length;
|
|
@@ -993,7 +1109,37 @@ function cmdCard(args) {
|
|
|
993
1109
|
writeProject(projectPath, project);
|
|
994
1110
|
return;
|
|
995
1111
|
}
|
|
996
|
-
|
|
1112
|
+
if (sub === 'unlock') {
|
|
1113
|
+
const projectInput = args[1];
|
|
1114
|
+
const cardId = args[2];
|
|
1115
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card unlock <project> <card-id> --by <id> --statement <text>');
|
|
1116
|
+
const by = option(args, '--by');
|
|
1117
|
+
const statement = option(args, '--statement');
|
|
1118
|
+
if (!by || !statement) fail('card unlock requires --by and --statement');
|
|
1119
|
+
const { projectPath, project } = readProject(projectInput);
|
|
1120
|
+
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1121
|
+
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1122
|
+
const card = project.cards[idx];
|
|
1123
|
+
if (!card.locked) fail(`Card is not locked: ${cardId}`);
|
|
1124
|
+
card.locked = false;
|
|
1125
|
+
card.status = 'draft';
|
|
1126
|
+
delete card.human_lock;
|
|
1127
|
+
if (!card.audit_log) card.audit_log = [];
|
|
1128
|
+
card.audit_log.push({
|
|
1129
|
+
action: 'unlocked',
|
|
1130
|
+
by,
|
|
1131
|
+
statement,
|
|
1132
|
+
timestamp: new Date().toISOString(),
|
|
1133
|
+
});
|
|
1134
|
+
project.cards[idx] = card;
|
|
1135
|
+
if (project.stages?.judgment_cards) {
|
|
1136
|
+
project.stages.judgment_cards.locked = project.cards.filter((c) => c.locked).length;
|
|
1137
|
+
}
|
|
1138
|
+
writeProject(projectPath, project);
|
|
1139
|
+
console.log(`Unlocked card: ${cardId}`);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
fail('Usage: kdna-studio card <list|add|update|remove|approve|unlock> ...');
|
|
997
1143
|
}
|
|
998
1144
|
|
|
999
1145
|
function cmdLock(args) {
|
|
@@ -1103,9 +1249,10 @@ function cmdMigrate(args) {
|
|
|
1103
1249
|
}
|
|
1104
1250
|
|
|
1105
1251
|
const absOut = path.resolve(out);
|
|
1252
|
+
const allowIncomplete = args.includes('--allow-incomplete');
|
|
1106
1253
|
// Step 4: v1 export or v2 compile + export
|
|
1107
1254
|
if (format === 'v1') {
|
|
1108
|
-
exportProjectV1(project, name, absOut);
|
|
1255
|
+
exportProjectV1(project, name, absOut, { allowIncomplete });
|
|
1109
1256
|
if (tmpDir) {
|
|
1110
1257
|
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
|
|
1111
1258
|
}
|
|
@@ -1136,7 +1283,7 @@ function cmdMigrate(args) {
|
|
|
1136
1283
|
}
|
|
1137
1284
|
}
|
|
1138
1285
|
|
|
1139
|
-
function exportProjectV1(project, name, outPath) {
|
|
1286
|
+
function exportProjectV1(project, name, outPath, opts = {}) {
|
|
1140
1287
|
let core;
|
|
1141
1288
|
try { core = require('@aikdna/kdna-core'); } catch (e) {
|
|
1142
1289
|
fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
|
|
@@ -1146,8 +1293,13 @@ function exportProjectV1(project, name, outPath) {
|
|
|
1146
1293
|
}
|
|
1147
1294
|
const gate = projectApi.checkHumanLockGate(project);
|
|
1148
1295
|
if (gate.blocked) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1296
|
+
if (opts.allowIncomplete) {
|
|
1297
|
+
console.warn('Human Lock Gate bypassed (--allow-incomplete):');
|
|
1298
|
+
for (const issue of gate.issues) console.warn(` - ${issue.cardId}: ${issue.reason}`);
|
|
1299
|
+
} else {
|
|
1300
|
+
const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
|
|
1301
|
+
fail(`Human Lock Gate blocked v1 export:\n - ${reasons}\nUse --allow-incomplete to bypass.`, EXIT.HUMAN_LOCK_REQUIRED);
|
|
1302
|
+
}
|
|
1151
1303
|
}
|
|
1152
1304
|
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
1153
1305
|
const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
|
|
@@ -1347,10 +1499,10 @@ function applySignature(files, passphrase = null) {
|
|
|
1347
1499
|
function cmdExport(args) {
|
|
1348
1500
|
const projectInput = args[0];
|
|
1349
1501
|
const out = option(args, '--out');
|
|
1350
|
-
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--format v1] [--sign]');
|
|
1502
|
+
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--format v1] [--sign] [--allow-incomplete]');
|
|
1351
1503
|
if (option(args, '--format') === 'v1') {
|
|
1352
1504
|
const { project } = readProject(projectInput);
|
|
1353
|
-
exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out));
|
|
1505
|
+
exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out), { allowIncomplete: args.includes('--allow-incomplete') });
|
|
1354
1506
|
return;
|
|
1355
1507
|
}
|
|
1356
1508
|
const { project, result } = compileProject(projectInput);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-studio-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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": {
|
package/tests/cli.test.js
CHANGED
|
@@ -65,6 +65,10 @@ function createLockedProject(t) {
|
|
|
65
65
|
'does_not_apply_when=["pure formatting"]',
|
|
66
66
|
'--field',
|
|
67
67
|
'failure_risk=generic advice',
|
|
68
|
+
'--field',
|
|
69
|
+
'confidence=high',
|
|
70
|
+
'--field',
|
|
71
|
+
'evidence_type=practice',
|
|
68
72
|
], { tmp });
|
|
69
73
|
assert.equal(result.status, 0, result.stderr);
|
|
70
74
|
const cardId = result.stdout.match(/Added card: (\S+)/)?.[1];
|
|
@@ -123,6 +127,10 @@ test('card approve --all locks every unlocked card', (t) => {
|
|
|
123
127
|
'does_not_apply_when=["pure formatting"]',
|
|
124
128
|
'--field',
|
|
125
129
|
'failure_risk=generic advice',
|
|
130
|
+
'--field',
|
|
131
|
+
'confidence=high',
|
|
132
|
+
'--field',
|
|
133
|
+
'evidence_type=practice',
|
|
126
134
|
], { tmp });
|
|
127
135
|
assert.equal(result.status, 0, result.stderr);
|
|
128
136
|
|
|
@@ -174,6 +182,7 @@ test('blocks compile and export until judgment cards are Human Locked', (t) => {
|
|
|
174
182
|
'add',
|
|
175
183
|
projectDir,
|
|
176
184
|
'axiom',
|
|
185
|
+
'--no-strict',
|
|
177
186
|
'--field',
|
|
178
187
|
'one_sentence=Prefer specific evidence over broad claims',
|
|
179
188
|
], { tmp });
|
|
@@ -550,6 +559,8 @@ test('card approve --sign binds Human Lock to creator identity', (t) => {
|
|
|
550
559
|
'--field', 'applies_when=["reviewing content"]',
|
|
551
560
|
'--field', 'does_not_apply_when=["pure formatting"]',
|
|
552
561
|
'--field', 'failure_risk=generic advice',
|
|
562
|
+
'--field', 'confidence=high',
|
|
563
|
+
'--field', 'evidence_type=practice',
|
|
553
564
|
], { tmp });
|
|
554
565
|
assert.equal(result.status, 0, result.stderr);
|
|
555
566
|
const cardId = result.stdout.match(/Added card: (\S+)/)?.[1];
|
|
@@ -615,6 +626,8 @@ test('export includes creator and lineage in kdna.json', (t) => {
|
|
|
615
626
|
'--field', 'applies_when=["testing"]',
|
|
616
627
|
'--field', 'does_not_apply_when=["not testing"]',
|
|
617
628
|
'--field', 'failure_risk=test may pass incorrectly',
|
|
629
|
+
'--field', 'confidence=high',
|
|
630
|
+
'--field', 'evidence_type=practice',
|
|
618
631
|
], { tmp });
|
|
619
632
|
let result = run(['card', 'list', projectDir], { tmp });
|
|
620
633
|
const cardId = result.stdout.match(/(\S+)\s+axiom/)?.[1];
|
|
@@ -652,6 +665,8 @@ test('E2E blank: create → approve → lock → export → runtime digest match
|
|
|
652
665
|
'--field', 'applies_when=["e2e testing"]',
|
|
653
666
|
'--field', 'does_not_apply_when=["production"]',
|
|
654
667
|
'--field', 'failure_risk=e2e test may incorrectly pass',
|
|
668
|
+
'--field', 'confidence=high',
|
|
669
|
+
'--field', 'evidence_type=practice',
|
|
655
670
|
], { tmp });
|
|
656
671
|
assert.equal(result.status, 0, result.stderr);
|
|
657
672
|
const cardId = result.stdout.match(/Added card: (\S+)/)?.[1];
|
|
@@ -662,6 +677,7 @@ test('E2E blank: create → approve → lock → export → runtime digest match
|
|
|
662
677
|
'--field', 'wrong=E2E tests are unnecessary.',
|
|
663
678
|
'--field', 'correct=E2E tests catch integration issues not visible in unit tests.',
|
|
664
679
|
'--field', 'key_distinction=E2E tests validate the full pipeline, not individual functions.',
|
|
680
|
+
'--field', 'why=E2E test failures are hard to debug without a full integration test running.',
|
|
665
681
|
], { tmp });
|
|
666
682
|
assert.equal(result.status, 0, result.stderr);
|
|
667
683
|
|
|
@@ -740,6 +756,8 @@ test('E2E fork: parent → fork → approve → export → lineage check', (t) =
|
|
|
740
756
|
'--field', 'applies_when=["fork testing"]',
|
|
741
757
|
'--field', 'does_not_apply_when=["not forking"]',
|
|
742
758
|
'--field', 'failure_risk=forked content may lose lineage',
|
|
759
|
+
'--field', 'confidence=high',
|
|
760
|
+
'--field', 'evidence_type=practice',
|
|
743
761
|
], { tmp });
|
|
744
762
|
const pCards = run(['card', 'list', parentDir], { tmp });
|
|
745
763
|
for (const line of pCards.stdout.trim().split('\n')) {
|