@aikdna/kdna-studio-cli 0.5.11 → 0.6.0
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 +164 -25
- package/package.json +1 -1
- package/tests/cli.test.js +17 -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,37 @@ function cmdFilter(args) {
|
|
|
904
908
|
}
|
|
905
909
|
}
|
|
906
910
|
|
|
911
|
+
const AXIOM_REQUIRED_FIELDS = [
|
|
912
|
+
'one_sentence', 'full_statement', 'why',
|
|
913
|
+
'applies_when', 'does_not_apply_when', 'failure_risk',
|
|
914
|
+
'confidence', 'evidence_type',
|
|
915
|
+
];
|
|
916
|
+
|
|
917
|
+
function checkRequiredFields(type, fields) {
|
|
918
|
+
if (type !== 'axiom') return [];
|
|
919
|
+
const missing = [];
|
|
920
|
+
for (const f of AXIOM_REQUIRED_FIELDS) {
|
|
921
|
+
const val = fields[f];
|
|
922
|
+
if (val === undefined || val === null || val === '' ||
|
|
923
|
+
(Array.isArray(val) && val.length === 0)) {
|
|
924
|
+
missing.push(f);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return missing;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function axiomStrictTemplate(fields) {
|
|
931
|
+
const filled = { ...fields };
|
|
932
|
+
for (const f of AXIOM_REQUIRED_FIELDS) {
|
|
933
|
+
const val = filled[f];
|
|
934
|
+
if (val === undefined || val === null || val === '' ||
|
|
935
|
+
(Array.isArray(val) && val.length === 0)) {
|
|
936
|
+
filled[f] = `<TBD: ${f}>`;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return filled;
|
|
940
|
+
}
|
|
941
|
+
|
|
907
942
|
function cmdCard(args) {
|
|
908
943
|
const sub = args[0];
|
|
909
944
|
if (sub === 'list') {
|
|
@@ -916,9 +951,27 @@ function cmdCard(args) {
|
|
|
916
951
|
if (sub === 'add') {
|
|
917
952
|
const projectInput = args[1];
|
|
918
953
|
const type = args[2];
|
|
919
|
-
if (!projectInput || !type) fail('Usage: kdna-studio card add <project> <type> --field key=value');
|
|
954
|
+
if (!projectInput || !type) fail('Usage: kdna-studio card add <project> <type> --field key=value [--template <name>] [--no-strict]');
|
|
920
955
|
const { projectPath, project } = readProject(projectInput);
|
|
921
|
-
|
|
956
|
+
let fields = parseFields(args.slice(3));
|
|
957
|
+
|
|
958
|
+
const useTemplate = option(args, '--template');
|
|
959
|
+
if (useTemplate === 'axiom-strict') {
|
|
960
|
+
fields = axiomStrictTemplate(fields);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const isNoStrict = args.includes('--no-strict');
|
|
964
|
+
if (!isNoStrict && type === 'axiom') {
|
|
965
|
+
const missing = checkRequiredFields(type, fields);
|
|
966
|
+
if (missing.length > 0) {
|
|
967
|
+
fail(
|
|
968
|
+
`Missing required fields for axiom: ${missing.join(', ')}\n` +
|
|
969
|
+
`Use --no-strict to add a partial card, or --template axiom-strict for placeholders.`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const card = cardApi.createCard(type, fields);
|
|
922
975
|
project.cards.push(card);
|
|
923
976
|
if (project.stages?.judgment_cards) {
|
|
924
977
|
project.stages.judgment_cards.total = project.cards.length;
|
|
@@ -928,6 +981,40 @@ function cmdCard(args) {
|
|
|
928
981
|
console.log(`Added card: ${card.id}`);
|
|
929
982
|
return;
|
|
930
983
|
}
|
|
984
|
+
if (sub === 'update') {
|
|
985
|
+
const projectInput = args[1];
|
|
986
|
+
const cardId = args[2];
|
|
987
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card update <project> <card-id> --field key=value');
|
|
988
|
+
const { projectPath, project } = readProject(projectInput);
|
|
989
|
+
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
990
|
+
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
991
|
+
const card = project.cards[idx];
|
|
992
|
+
if (card.locked) fail(`Cannot update locked card: ${cardId}. Unlock first with card unlock.`);
|
|
993
|
+
const updates = parseFields(args.slice(3));
|
|
994
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
995
|
+
if (card.fields) card.fields[k] = v;
|
|
996
|
+
}
|
|
997
|
+
project.cards[idx] = card;
|
|
998
|
+
writeProject(projectPath, project);
|
|
999
|
+
console.log(`Updated card: ${cardId}`);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (sub === 'remove') {
|
|
1003
|
+
const projectInput = args[1];
|
|
1004
|
+
const cardId = args[2];
|
|
1005
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card remove <project> <card-id>');
|
|
1006
|
+
const { projectPath, project } = readProject(projectInput);
|
|
1007
|
+
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1008
|
+
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1009
|
+
if (project.cards[idx].locked) fail(`Cannot remove locked card: ${cardId}. Unlock first with card unlock.`);
|
|
1010
|
+
project.cards.splice(idx, 1);
|
|
1011
|
+
if (project.stages?.judgment_cards) {
|
|
1012
|
+
project.stages.judgment_cards.total = project.cards.length;
|
|
1013
|
+
}
|
|
1014
|
+
writeProject(projectPath, project);
|
|
1015
|
+
console.log(`Removed card: ${cardId}`);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
931
1018
|
if (sub === 'approve') {
|
|
932
1019
|
const projectInput = args[1];
|
|
933
1020
|
const approveAll = args.includes('--all');
|
|
@@ -939,6 +1026,7 @@ function cmdCard(args) {
|
|
|
939
1026
|
const statement = option(args, '--statement');
|
|
940
1027
|
if (!by || !statement) fail('card approve requires --by and --statement');
|
|
941
1028
|
const { projectPath, project } = readProject(projectInput);
|
|
1029
|
+
const errors = [];
|
|
942
1030
|
|
|
943
1031
|
const lockOneCard = (card) => {
|
|
944
1032
|
if (card.status === 'draft') card = cardApi.transitionCard(card, 'revised', { by });
|
|
@@ -949,42 +1037,57 @@ function cmdCard(args) {
|
|
|
949
1037
|
};
|
|
950
1038
|
if (args.includes('--sign')) {
|
|
951
1039
|
const identity = creatorApi.loadIdentity();
|
|
952
|
-
if (!identity)
|
|
1040
|
+
if (!identity) {
|
|
1041
|
+
errors.push(`${card.id}: No creator identity. Run: kdna-studio identity init`);
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
953
1044
|
lockPayload.creator_id = identity.creator_id;
|
|
954
1045
|
const passphrase = option(args, '--passphrase');
|
|
955
1046
|
try {
|
|
956
1047
|
if (identity.encrypted && !passphrase) {
|
|
957
|
-
|
|
1048
|
+
errors.push(`${card.id}: Private key is encrypted — provide --passphrase`);
|
|
1049
|
+
return null;
|
|
958
1050
|
}
|
|
959
1051
|
lockPayload.signature = creatorApi.signHumanLock(
|
|
960
1052
|
card.id, statement, cardApi.cardJudgmentFingerprint(card), null, passphrase,
|
|
961
1053
|
);
|
|
962
1054
|
} catch (e) {
|
|
963
|
-
|
|
1055
|
+
errors.push(`${card.id}: Signing failed: ${e.message}`);
|
|
1056
|
+
return null;
|
|
964
1057
|
}
|
|
965
1058
|
}
|
|
966
|
-
return
|
|
1059
|
+
return cardApi.lockCard(card, lockPayload);
|
|
967
1060
|
};
|
|
968
1061
|
|
|
969
1062
|
if (cardId) {
|
|
970
1063
|
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
971
1064
|
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
972
|
-
|
|
1065
|
+
if (project.cards[idx].locked) fail(`Card already locked: ${cardId}`);
|
|
1066
|
+
const locked = lockOneCard(project.cards[idx]);
|
|
1067
|
+
if (!locked) fail(errors[0]);
|
|
973
1068
|
project.cards[idx] = locked;
|
|
974
1069
|
const signed = args.includes('--sign') ? ' (signed)' : '';
|
|
975
1070
|
console.log(`Approved and Human Locked: ${cardId}${signed}`);
|
|
976
1071
|
} else {
|
|
977
1072
|
let approved = 0;
|
|
1073
|
+
let skipped = 0;
|
|
978
1074
|
for (let i = 0; i < (project.cards || []).length; i++) {
|
|
979
1075
|
const card = project.cards[i];
|
|
980
|
-
if (card.locked) continue;
|
|
981
|
-
const
|
|
1076
|
+
if (card.locked) { skipped++; continue; }
|
|
1077
|
+
const locked = lockOneCard(card);
|
|
1078
|
+
if (!locked) continue;
|
|
982
1079
|
project.cards[i] = locked;
|
|
983
1080
|
approved++;
|
|
984
1081
|
}
|
|
985
|
-
if (
|
|
1082
|
+
if (errors.length > 0) {
|
|
1083
|
+
fail(`Failed to approve ${errors.length} card(s):\n ${errors.join('\n ')}`);
|
|
1084
|
+
}
|
|
1085
|
+
if (approved === 0) {
|
|
1086
|
+
if (skipped > 0) fail(`No unlocked cards to approve (${skipped} already locked).`);
|
|
1087
|
+
fail('No unlocked cards to approve.');
|
|
1088
|
+
}
|
|
986
1089
|
const signed = args.includes('--sign') ? ' (signed)' : '';
|
|
987
|
-
console.log(`Approved and Human Locked: ${approved} cards${signed}`);
|
|
1090
|
+
console.log(`Approved and Human Locked: ${approved} cards${signed}${skipped > 0 ? ` (${skipped} already locked)` : ''}`);
|
|
988
1091
|
}
|
|
989
1092
|
if (project.stages?.judgment_cards) {
|
|
990
1093
|
project.stages.judgment_cards.locked = project.cards.filter((c) => c.locked).length;
|
|
@@ -993,7 +1096,37 @@ function cmdCard(args) {
|
|
|
993
1096
|
writeProject(projectPath, project);
|
|
994
1097
|
return;
|
|
995
1098
|
}
|
|
996
|
-
|
|
1099
|
+
if (sub === 'unlock') {
|
|
1100
|
+
const projectInput = args[1];
|
|
1101
|
+
const cardId = args[2];
|
|
1102
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card unlock <project> <card-id> --by <id> --statement <text>');
|
|
1103
|
+
const by = option(args, '--by');
|
|
1104
|
+
const statement = option(args, '--statement');
|
|
1105
|
+
if (!by || !statement) fail('card unlock requires --by and --statement');
|
|
1106
|
+
const { projectPath, project } = readProject(projectInput);
|
|
1107
|
+
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1108
|
+
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1109
|
+
const card = project.cards[idx];
|
|
1110
|
+
if (!card.locked) fail(`Card is not locked: ${cardId}`);
|
|
1111
|
+
card.locked = false;
|
|
1112
|
+
card.status = 'draft';
|
|
1113
|
+
delete card.human_lock;
|
|
1114
|
+
if (!card.audit_log) card.audit_log = [];
|
|
1115
|
+
card.audit_log.push({
|
|
1116
|
+
action: 'unlocked',
|
|
1117
|
+
by,
|
|
1118
|
+
statement,
|
|
1119
|
+
timestamp: new Date().toISOString(),
|
|
1120
|
+
});
|
|
1121
|
+
project.cards[idx] = card;
|
|
1122
|
+
if (project.stages?.judgment_cards) {
|
|
1123
|
+
project.stages.judgment_cards.locked = project.cards.filter((c) => c.locked).length;
|
|
1124
|
+
}
|
|
1125
|
+
writeProject(projectPath, project);
|
|
1126
|
+
console.log(`Unlocked card: ${cardId}`);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
fail('Usage: kdna-studio card <list|add|update|remove|approve|unlock> ...');
|
|
997
1130
|
}
|
|
998
1131
|
|
|
999
1132
|
function cmdLock(args) {
|
|
@@ -1103,9 +1236,10 @@ function cmdMigrate(args) {
|
|
|
1103
1236
|
}
|
|
1104
1237
|
|
|
1105
1238
|
const absOut = path.resolve(out);
|
|
1239
|
+
const allowIncomplete = args.includes('--allow-incomplete');
|
|
1106
1240
|
// Step 4: v1 export or v2 compile + export
|
|
1107
1241
|
if (format === 'v1') {
|
|
1108
|
-
exportProjectV1(project, name, absOut);
|
|
1242
|
+
exportProjectV1(project, name, absOut, { allowIncomplete });
|
|
1109
1243
|
if (tmpDir) {
|
|
1110
1244
|
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
|
|
1111
1245
|
}
|
|
@@ -1136,7 +1270,7 @@ function cmdMigrate(args) {
|
|
|
1136
1270
|
}
|
|
1137
1271
|
}
|
|
1138
1272
|
|
|
1139
|
-
function exportProjectV1(project, name, outPath) {
|
|
1273
|
+
function exportProjectV1(project, name, outPath, opts = {}) {
|
|
1140
1274
|
let core;
|
|
1141
1275
|
try { core = require('@aikdna/kdna-core'); } catch (e) {
|
|
1142
1276
|
fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
|
|
@@ -1146,8 +1280,13 @@ function exportProjectV1(project, name, outPath) {
|
|
|
1146
1280
|
}
|
|
1147
1281
|
const gate = projectApi.checkHumanLockGate(project);
|
|
1148
1282
|
if (gate.blocked) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1283
|
+
if (opts.allowIncomplete) {
|
|
1284
|
+
console.warn('Human Lock Gate bypassed (--allow-incomplete):');
|
|
1285
|
+
for (const issue of gate.issues) console.warn(` - ${issue.cardId}: ${issue.reason}`);
|
|
1286
|
+
} else {
|
|
1287
|
+
const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
|
|
1288
|
+
fail(`Human Lock Gate blocked v1 export:\n - ${reasons}\nUse --allow-incomplete to bypass.`, EXIT.HUMAN_LOCK_REQUIRED);
|
|
1289
|
+
}
|
|
1151
1290
|
}
|
|
1152
1291
|
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
1153
1292
|
const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
|
|
@@ -1347,10 +1486,10 @@ function applySignature(files, passphrase = null) {
|
|
|
1347
1486
|
function cmdExport(args) {
|
|
1348
1487
|
const projectInput = args[0];
|
|
1349
1488
|
const out = option(args, '--out');
|
|
1350
|
-
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--format v1] [--sign]');
|
|
1489
|
+
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--format v1] [--sign] [--allow-incomplete]');
|
|
1351
1490
|
if (option(args, '--format') === 'v1') {
|
|
1352
1491
|
const { project } = readProject(projectInput);
|
|
1353
|
-
exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out));
|
|
1492
|
+
exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out), { allowIncomplete: args.includes('--allow-incomplete') });
|
|
1354
1493
|
return;
|
|
1355
1494
|
}
|
|
1356
1495
|
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.0",
|
|
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];
|
|
@@ -740,6 +755,8 @@ test('E2E fork: parent → fork → approve → export → lineage check', (t) =
|
|
|
740
755
|
'--field', 'applies_when=["fork testing"]',
|
|
741
756
|
'--field', 'does_not_apply_when=["not forking"]',
|
|
742
757
|
'--field', 'failure_risk=forked content may lose lineage',
|
|
758
|
+
'--field', 'confidence=high',
|
|
759
|
+
'--field', 'evidence_type=practice',
|
|
743
760
|
], { tmp });
|
|
744
761
|
const pCards = run(['card', 'list', parentDir], { tmp });
|
|
745
762
|
for (const line of pCards.stdout.trim().split('\n')) {
|