@aikdna/kdna-studio-cli 0.8.8 → 0.8.9
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 +222 -6
- package/package.json +1 -1
package/bin/kdna-studio.js
CHANGED
|
@@ -83,6 +83,8 @@ Create (three entry paths):
|
|
|
83
83
|
|
|
84
84
|
Migrate (dev source or Studio project → canonical .kdna in one command):
|
|
85
85
|
kdna-studio migrate <source-dir|project> --format v1 --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--allow-incomplete] [--sign] [--passphrase <pw>|--passphrase-stdin]
|
|
86
|
+
kdna-studio migrate <source-dir> --check --name <@scope/name> # pre-flight: report which fields would block the export without writing the .kdna
|
|
87
|
+
kdna-studio audit-locks <project> [--type axiom|risk|stance|...] [--json] # list cards with missing Human Lock fields (per-card-type, per-field)
|
|
86
88
|
|
|
87
89
|
Authoring:
|
|
88
90
|
kdna-studio import <project> <source-file-or-dir> # import evidence (txt/md/json/yaml/csv/log/srt/vtt/html)
|
|
@@ -114,6 +116,7 @@ Distillation:
|
|
|
114
116
|
kdna-studio candidate override <project> <candidate-id> # override scope gate
|
|
115
117
|
kdna-studio candidate promote <project> # promote accepted+scope_fit → cards
|
|
116
118
|
kdna-studio report <project>
|
|
119
|
+
kdna-studio audit-locks <project> # list all axioms/risk/stance missing Human Lock fields
|
|
117
120
|
|
|
118
121
|
Project may be a directory containing studio.project.json or a project JSON file.`);
|
|
119
122
|
}
|
|
@@ -1493,15 +1496,44 @@ function cmdCard(args) {
|
|
|
1493
1496
|
if (sub === 'update') {
|
|
1494
1497
|
const projectInput = args[1];
|
|
1495
1498
|
const cardId = args[2];
|
|
1496
|
-
if (!projectInput || !cardId) fail('Usage: kdna-studio card update <project> <card-id> --field key=value');
|
|
1499
|
+
if (!projectInput || !cardId) fail('Usage: kdna-studio card update <project> <card-id> --field key=value OR kdna-studio card update <project> <card-id> --from-file <path.json>');
|
|
1497
1500
|
const { projectPath, project } = readProject(projectInput);
|
|
1498
1501
|
const idx = (project.cards || []).findIndex((c) => c.id === cardId);
|
|
1499
1502
|
if (idx < 0) fail(`Card not found: ${cardId}`);
|
|
1500
1503
|
const card = project.cards[idx];
|
|
1501
1504
|
if (card.locked) fail(`Cannot update locked card: ${cardId}. Unlock first with card unlock.`);
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
+
// Bug (UX pro-20 migration): prior version only accepted
|
|
1506
|
+
// --field key=value, so an author migrating 20 source-tree
|
|
1507
|
+
// assets had to run one CLI invocation per field. With
|
|
1508
|
+
// confidence + evidence_type as 8th / 9th required fields on
|
|
1509
|
+
// axiom, a 99-axiom asset became 99 * 2 = 198 invocations.
|
|
1510
|
+
// The fix accepts --from-file <path.json> so an author can
|
|
1511
|
+
// write a single JSON file per card (or per project) and
|
|
1512
|
+
// apply it in one call. JSON shape: {field: value, ...}.
|
|
1513
|
+
// kdna-studio card update . ax_xxx --from-file ./fills/ax_xxx.json
|
|
1514
|
+
// For batched updates across many cards, see the
|
|
1515
|
+
// --from-file-projects variant below, which reads a list
|
|
1516
|
+
// of {id, fields} records.
|
|
1517
|
+
const fromFileIdx = args.indexOf('--from-file');
|
|
1518
|
+
if (fromFileIdx >= 0) {
|
|
1519
|
+
const filePath = args[fromFileIdx + 1];
|
|
1520
|
+
if (!filePath) fail('--from-file requires a path');
|
|
1521
|
+
if (!fs.existsSync(filePath)) fail(`--from-file: file not found: ${filePath}`);
|
|
1522
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1523
|
+
if (typeof data !== 'object' || Array.isArray(data) || data === null) {
|
|
1524
|
+
fail(`--from-file: expected a JSON object mapping field names to values`);
|
|
1525
|
+
}
|
|
1526
|
+
for (const [k, v] of Object.entries(data)) {
|
|
1527
|
+
if (k === 'id' || k === 'type' || k === 'status' || k === 'locked') {
|
|
1528
|
+
fail(`--from-file: cannot edit reserved field "${k}"`);
|
|
1529
|
+
}
|
|
1530
|
+
if (card.fields) card.fields[k] = v;
|
|
1531
|
+
}
|
|
1532
|
+
} else {
|
|
1533
|
+
const updates = parseFields(args.slice(3));
|
|
1534
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
1535
|
+
if (card.fields) card.fields[k] = v;
|
|
1536
|
+
}
|
|
1505
1537
|
}
|
|
1506
1538
|
project.cards[idx] = card;
|
|
1507
1539
|
writeProject(projectPath, project);
|
|
@@ -1650,14 +1682,32 @@ function cmdLock(args) {
|
|
|
1650
1682
|
}
|
|
1651
1683
|
|
|
1652
1684
|
function cmdMigrate(args) {
|
|
1685
|
+
// Bug (UX pro-20 migration): prior version of `migrate` always
|
|
1686
|
+
// attempted the full pipeline — read source folder, import to
|
|
1687
|
+
// Studio project, lock all cards, then export. An author migrating
|
|
1688
|
+
// 20 source-tree assets had no way to know in advance whether
|
|
1689
|
+
// the assets would pass the v1.7.2+ Human Lock gate. The fix
|
|
1690
|
+
// adds `--check` which runs the import + the critical-missing
|
|
1691
|
+
// check + the lock-gate check, then prints a report and exits
|
|
1692
|
+
// without writing the .kdna file. Pair with `audit-locks` for
|
|
1693
|
+
// the full pre-migration report.
|
|
1694
|
+
//
|
|
1695
|
+
// Usage:
|
|
1696
|
+
// kdna-studio migrate <source-dir> --check --name <@scope/name>
|
|
1697
|
+
//
|
|
1698
|
+
// Exit codes:
|
|
1699
|
+
// 0 — would succeed
|
|
1700
|
+
// 2 — INPUT_ERROR (missing args)
|
|
1701
|
+
// 4 — would fail (Human Lock gate or critical axiom field)
|
|
1702
|
+
const checkOnly = args.includes('--check');
|
|
1653
1703
|
const sourceDir = args[0];
|
|
1654
1704
|
const out = option(args, '--out');
|
|
1655
1705
|
const by = option(args, '--by');
|
|
1656
1706
|
const statement = option(args, '--statement');
|
|
1657
1707
|
const requestedName = option(args, '--name');
|
|
1658
1708
|
let name = requestedName || path.basename(path.resolve(sourceDir || '.'));
|
|
1659
|
-
if (!sourceDir || !out || !by || !statement) {
|
|
1660
|
-
fail('Usage: kdna-studio migrate <source-dir> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--sign] [--passphrase <pw>|--passphrase-stdin]');
|
|
1709
|
+
if (!sourceDir || (!out && !checkOnly) || (!by && !checkOnly) || (!statement && !checkOnly)) {
|
|
1710
|
+
fail('Usage: kdna-studio migrate <source-dir> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--check] [--sign] [--passphrase <pw>|--passphrase-stdin]');
|
|
1661
1711
|
}
|
|
1662
1712
|
|
|
1663
1713
|
const sourcePath = path.resolve(sourceDir);
|
|
@@ -1724,6 +1774,42 @@ function cmdMigrate(args) {
|
|
|
1724
1774
|
}
|
|
1725
1775
|
}
|
|
1726
1776
|
|
|
1777
|
+
// --check branch: print a pre-migration report and exit. The
|
|
1778
|
+
// project object already has every card from the source tree
|
|
1779
|
+
// imported, so we can run the same audit-locks logic here
|
|
1780
|
+
// without re-reading the source. This is the single-call
|
|
1781
|
+
// equivalent of `kdna-studio create --from-folder <src> &&
|
|
1782
|
+
// kdna-studio audit-locks .` — but it requires no
|
|
1783
|
+
// intermediate write.
|
|
1784
|
+
if (checkOnly) {
|
|
1785
|
+
const REQUIRED = {
|
|
1786
|
+
axiom: ['confidence', 'evidence_type', 'one_sentence', 'full_statement', 'why',
|
|
1787
|
+
'applies_when', 'does_not_apply_when', 'failure_risk'],
|
|
1788
|
+
risk: ['name', 'description', 'mitigation'],
|
|
1789
|
+
stance: ['statement', 'applies_when'],
|
|
1790
|
+
misunderstanding: ['wrong', 'correct', 'key_distinction', 'why'],
|
|
1791
|
+
boundary: ['scope', 'out_of_scope'],
|
|
1792
|
+
};
|
|
1793
|
+
const summary = { project: name, source: sourceDir, total: project.cards.length, by_type: {}, would_block: false };
|
|
1794
|
+
for (const card of (project.cards || [])) {
|
|
1795
|
+
const req = REQUIRED[card.type];
|
|
1796
|
+
if (!req) continue;
|
|
1797
|
+
const missing = [];
|
|
1798
|
+
for (const f of req) {
|
|
1799
|
+
const v = card.fields?.[f];
|
|
1800
|
+
if (v === undefined || v === null || v === '' ||
|
|
1801
|
+
(Array.isArray(v) && v.length === 0)) missing.push(f);
|
|
1802
|
+
}
|
|
1803
|
+
if (missing.length > 0) {
|
|
1804
|
+
if (!summary.by_type[card.type]) summary.by_type[card.type] = [];
|
|
1805
|
+
summary.by_type[card.type].push({ id: card.id, missing });
|
|
1806
|
+
summary.would_block = true;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
1810
|
+
process.exit(summary.would_block ? EXIT.HUMAN_LOCK_REQUIRED : EXIT.OK);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1727
1813
|
// Step 2: approve and Human Lock all cards
|
|
1728
1814
|
let locked = 0;
|
|
1729
1815
|
for (let i = 0; i < (project.cards || []).length; i++) {
|
|
@@ -2250,6 +2336,135 @@ function cmdReport(args) {
|
|
|
2250
2336
|
process.exit(gate.blocked ? EXIT.HUMAN_LOCK_REQUIRED : EXIT.OK);
|
|
2251
2337
|
}
|
|
2252
2338
|
|
|
2339
|
+
// Audit-locks: list every card that would fail the v1.7.2+ Human Lock
|
|
2340
|
+
// gate because of missing or empty fields. Used during the
|
|
2341
|
+
// pre-migration review of source-tree assets (e.g. pro-20) so an
|
|
2342
|
+
// author can fill in the missing confidence / evidence_type / risk
|
|
2343
|
+
// description / stance statement BEFORE running `migrate --format v1`.
|
|
2344
|
+
//
|
|
2345
|
+
// Bug (UX pro-20 migration): prior version of `kdna-studio report`
|
|
2346
|
+
// returned a flat human_lock_gate.issues list, which mixed card
|
|
2347
|
+
// types and field names. An author migrating 20 source-tree
|
|
2348
|
+
// assets with 99 axioms + 140 risk cards + 70 stance cards had no
|
|
2349
|
+
// way to see, for a single asset, "which axioms are missing
|
|
2350
|
+
// confidence?" — they had to grep the project JSON. The fix adds
|
|
2351
|
+
// a per-type, per-field breakdown.
|
|
2352
|
+
function cmdAuditLocks(args) {
|
|
2353
|
+
const { project } = readProject(args[0]);
|
|
2354
|
+
const useJson = args.includes('--json');
|
|
2355
|
+
const onlyType = option(args, '--type'); // 'axiom' | 'risk' | 'stance' | 'misunderstanding' | null
|
|
2356
|
+
|
|
2357
|
+
const cards = project.cards || [];
|
|
2358
|
+
const findings = {
|
|
2359
|
+
project: project.name,
|
|
2360
|
+
card_count: cards.length,
|
|
2361
|
+
locked_count: cards.filter(c => c.locked).length,
|
|
2362
|
+
by_type: {},
|
|
2363
|
+
};
|
|
2364
|
+
|
|
2365
|
+
// Per-card-type required field lists. Mirrors the v1.7.2+ lockCard
|
|
2366
|
+
// gate (kdna-studio-core/src/cards/index.js) so the output is
|
|
2367
|
+
// exactly the field set the gate will reject.
|
|
2368
|
+
const REQUIRED = {
|
|
2369
|
+
axiom: ['one_sentence', 'full_statement', 'why', 'applies_when',
|
|
2370
|
+
'does_not_apply_when', 'failure_risk', 'confidence', 'evidence_type'],
|
|
2371
|
+
risk: ['name', 'description', 'mitigation'],
|
|
2372
|
+
stance: ['statement', 'applies_when'],
|
|
2373
|
+
misunderstanding: ['wrong', 'correct', 'key_distinction', 'why'],
|
|
2374
|
+
boundary: ['scope', 'out_of_scope'],
|
|
2375
|
+
self_check: ['question'],
|
|
2376
|
+
};
|
|
2377
|
+
const THRESHOLDS = {
|
|
2378
|
+
axiom: {
|
|
2379
|
+
one_sentence: { min: 5, type: 'string' },
|
|
2380
|
+
full_statement: { min: 20, type: 'string' },
|
|
2381
|
+
why: { min: 20, type: 'string' },
|
|
2382
|
+
failure_risk: { min: 1, type: 'string' },
|
|
2383
|
+
confidence: { allowed: ['low', 'medium', 'high'] },
|
|
2384
|
+
evidence_type: { allowed: ['case_observation', 'theoretical', 'empirical', 'analogy', 'principle'] },
|
|
2385
|
+
},
|
|
2386
|
+
risk: {
|
|
2387
|
+
name: { min: 1, type: 'string' },
|
|
2388
|
+
description: { min: 20, type: 'string' },
|
|
2389
|
+
mitigation: { min: 1, type: 'string' },
|
|
2390
|
+
},
|
|
2391
|
+
stance: {
|
|
2392
|
+
statement: { min: 1, type: 'string' },
|
|
2393
|
+
applies_when: { type: 'array', minLength: 0 },
|
|
2394
|
+
},
|
|
2395
|
+
};
|
|
2396
|
+
|
|
2397
|
+
function checkField(type, fields, name, rule) {
|
|
2398
|
+
const value = fields[name];
|
|
2399
|
+
if (value === undefined || value === null || value === '') {
|
|
2400
|
+
return { field: name, issue: 'missing' };
|
|
2401
|
+
}
|
|
2402
|
+
if (rule.type === 'string' && rule.min && String(value).length < rule.min) {
|
|
2403
|
+
return { field: name, issue: 'too_short', have_length: String(value).length, need_min: rule.min };
|
|
2404
|
+
}
|
|
2405
|
+
if (rule.allowed && !rule.allowed.includes(value)) {
|
|
2406
|
+
return { field: name, issue: 'invalid_value', have: value, allowed: rule.allowed };
|
|
2407
|
+
}
|
|
2408
|
+
if (rule.type === 'array' && !Array.isArray(value)) {
|
|
2409
|
+
return { field: name, issue: 'wrong_type', have_type: typeof value, need_type: 'array' };
|
|
2410
|
+
}
|
|
2411
|
+
return null;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
for (const card of cards) {
|
|
2415
|
+
const req = REQUIRED[card.type];
|
|
2416
|
+
if (!req) continue;
|
|
2417
|
+
if (onlyType && card.type !== onlyType) continue;
|
|
2418
|
+
const rule = THRESHOLDS[card.type] || {};
|
|
2419
|
+
const missing = [];
|
|
2420
|
+
for (const name of req) {
|
|
2421
|
+
const issue = checkField(card.type, card.fields || {}, name, rule[name] || {});
|
|
2422
|
+
if (issue) missing.push(issue);
|
|
2423
|
+
}
|
|
2424
|
+
if (missing.length > 0) {
|
|
2425
|
+
if (!findings.by_type[card.type]) {
|
|
2426
|
+
findings.by_type[card.type] = { missing_count: 0, cards: [] };
|
|
2427
|
+
}
|
|
2428
|
+
findings.by_type[card.type].missing_count += 1;
|
|
2429
|
+
findings.by_type[card.type].cards.push({
|
|
2430
|
+
id: card.id,
|
|
2431
|
+
one_sentence: card.fields?.one_sentence || card.fields?.statement || card.fields?.question || '',
|
|
2432
|
+
missing,
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
if (useJson) {
|
|
2438
|
+
console.log(JSON.stringify(findings, null, 2));
|
|
2439
|
+
} else {
|
|
2440
|
+
console.log(`Project: ${findings.project}`);
|
|
2441
|
+
console.log(`Cards: ${findings.card_count} (locked: ${findings.locked_count})`);
|
|
2442
|
+
const types = Object.keys(findings.by_type);
|
|
2443
|
+
if (types.length === 0) {
|
|
2444
|
+
console.log('\nAll cards pass the v1.7.2+ Human Lock gate. Ready to migrate.');
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
for (const t of types) {
|
|
2448
|
+
const block = findings.by_type[t];
|
|
2449
|
+
console.log(`\n${t}: ${block.missing_count} card(s) with missing fields`);
|
|
2450
|
+
for (const card of block.cards) {
|
|
2451
|
+
console.log(` ${card.id} ${(card.one_sentence || '').slice(0, 60)}`);
|
|
2452
|
+
for (const m of card.missing) {
|
|
2453
|
+
if (m.issue === 'missing') {
|
|
2454
|
+
console.log(` - ${m.field}: missing`);
|
|
2455
|
+
} else if (m.issue === 'too_short') {
|
|
2456
|
+
console.log(` - ${m.field}: too short (have ${m.have_length}, need ≥ ${m.need_min})`);
|
|
2457
|
+
} else if (m.issue === 'invalid_value') {
|
|
2458
|
+
console.log(` - ${m.field}: invalid value "${m.have}", allowed: ${m.allowed.join('|')}`);
|
|
2459
|
+
} else {
|
|
2460
|
+
console.log(` - ${m.field}: ${m.issue}`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2253
2468
|
// ─── Distillation Commands ──────────────────────────────────────────
|
|
2254
2469
|
|
|
2255
2470
|
function cmdTarget(args) {
|
|
@@ -2677,6 +2892,7 @@ try {
|
|
|
2677
2892
|
else if (cmd === 'llm') cmdLlm(args.slice(1));
|
|
2678
2893
|
else if (cmd === 'identity') cmdIdentity(args.slice(1));
|
|
2679
2894
|
else if (cmd === 'report') cmdReport(args.slice(1));
|
|
2895
|
+
else if (cmd === 'audit-locks') cmdAuditLocks(args.slice(1));
|
|
2680
2896
|
else if (cmd === 'install') cmdStudioInstall(args.slice(1));
|
|
2681
2897
|
else if (cmd === 'update') cmdStudioUpdate(args.slice(1));
|
|
2682
2898
|
else {
|
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.9",
|
|
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": {
|