@aikdna/kdna-studio-cli 0.5.2 → 0.5.3
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/README.md +7 -5
- package/bin/kdna-studio.js +70 -26
- package/package.json +1 -1
- package/tests/cli.test.js +39 -0
package/README.md
CHANGED
|
@@ -58,12 +58,14 @@ Candidate promotion is scope-gated: only candidates with `status == accepted` an
|
|
|
58
58
|
After export, use the runtime CLI:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
kdna
|
|
62
|
-
kdna
|
|
61
|
+
kdna validate dist/my_domain.kdna
|
|
62
|
+
kdna load dist/my_domain.kdna --profile=compact --as=prompt
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
`kdna-studio` is the CLI entry for
|
|
66
|
-
control plane for
|
|
65
|
+
`kdna-studio` is the CLI entry for confirmed KDNA authoring. `kdna` is the
|
|
66
|
+
runtime control plane for inspecting, validating, packing, unpacking, and
|
|
67
|
+
loading existing `.kdna` assets. Signature, encryption, registry publishing,
|
|
68
|
+
and private assets are future/gated phases, not the current Core v1 baseline.
|
|
67
69
|
|
|
68
70
|
## Identity
|
|
69
71
|
|
|
@@ -85,6 +87,6 @@ kdna-studio create migrated --from-folder ./old-domain-json --name @scope/migrat
|
|
|
85
87
|
## Related
|
|
86
88
|
|
|
87
89
|
- [KDNA Core](https://github.com/aikdna/kdna) — Official format specification
|
|
88
|
-
- [kdna-cli](https://github.com/aikdna/kdna-cli) — runtime CLI for
|
|
90
|
+
- [kdna-cli](https://github.com/aikdna/kdna-cli) — runtime CLI for inspect, validate, pack, unpack, and load
|
|
89
91
|
- [kdna-studio-core](https://github.com/aikdna/kdna-studio-core) — authoring SDK used by this CLI
|
|
90
92
|
- [kdna-studio-swift](https://github.com/aikdna/kdna-studio-swift) — native Swift counterpart
|
package/bin/kdna-studio.js
CHANGED
|
@@ -39,7 +39,7 @@ Create (three entry paths):
|
|
|
39
39
|
kdna-studio create <project-dir> --from-folder <source-dir> --name <@scope/name>
|
|
40
40
|
|
|
41
41
|
Migrate (dev source → trusted .kdna in one command):
|
|
42
|
-
kdna-studio migrate <source-dir> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--sign]
|
|
42
|
+
kdna-studio migrate <source-dir|project> --format v1 --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--sign]
|
|
43
43
|
|
|
44
44
|
Authoring:
|
|
45
45
|
kdna-studio import <project> <source-file-or-dir> # import evidence (txt/md/json/yaml/csv/log/srt/vtt/html)
|
|
@@ -50,7 +50,8 @@ Authoring:
|
|
|
50
50
|
kdna-studio card approve <project> <card-id> --by <id> --statement <text> [--sign] [--passphrase <pass>]
|
|
51
51
|
kdna-studio lock <project>
|
|
52
52
|
kdna-studio compile <project> --out <dir>
|
|
53
|
-
kdna-studio export <project> --out <file.kdna>
|
|
53
|
+
kdna-studio export <project> --format v1 --out <file.kdna>
|
|
54
|
+
kdna-studio export <project> --out <file.kdna> [--sign] # legacy v2
|
|
54
55
|
|
|
55
56
|
AI Authoring (requires LLM config: kdna-studio llm config):
|
|
56
57
|
kdna-studio distill <project> --ai # AI-driven candidate extraction from evidence
|
|
@@ -1125,16 +1126,33 @@ function cmdMigrate(args) {
|
|
|
1125
1126
|
const out = option(args, '--out');
|
|
1126
1127
|
const by = option(args, '--by');
|
|
1127
1128
|
const statement = option(args, '--statement');
|
|
1128
|
-
const
|
|
1129
|
+
const requestedName = option(args, '--name');
|
|
1130
|
+
let name = requestedName || path.basename(path.resolve(sourceDir || '.'));
|
|
1129
1131
|
if (!sourceDir || !out || !by || !statement) {
|
|
1130
1132
|
fail('Usage: kdna-studio migrate <source-dir> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--sign] [--passphrase <pass>]');
|
|
1131
1133
|
}
|
|
1132
1134
|
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
+
const sourcePath = path.resolve(sourceDir);
|
|
1136
|
+
const format = option(args, '--format');
|
|
1137
|
+
const isStudioProject = fs.existsSync(path.join(sourcePath, 'studio.project.json'));
|
|
1138
|
+
let tmpDir = null;
|
|
1139
|
+
let projectPath = null;
|
|
1140
|
+
let project = null;
|
|
1141
|
+
|
|
1142
|
+
// Step 1: import dev source, or use an existing Studio project directly.
|
|
1135
1143
|
let creatorIdentity = null;
|
|
1136
1144
|
try { creatorIdentity = creatorApi.loadIdentity(); } catch { /* proceed without */ }
|
|
1137
|
-
|
|
1145
|
+
if (format === 'v1' && isStudioProject) {
|
|
1146
|
+
const loaded = readProject(sourcePath);
|
|
1147
|
+
project = loaded.project;
|
|
1148
|
+
projectPath = loaded.projectPath;
|
|
1149
|
+
if (!requestedName && project.name) name = project.name;
|
|
1150
|
+
} else {
|
|
1151
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-'));
|
|
1152
|
+
const imported = importFromFolder(sourceDir, tmpDir, name, creatorIdentity);
|
|
1153
|
+
project = imported.project;
|
|
1154
|
+
projectPath = path.join(tmpDir, 'studio.project.json');
|
|
1155
|
+
}
|
|
1138
1156
|
|
|
1139
1157
|
// Reject if critical judgment fields are missing from axioms
|
|
1140
1158
|
const criticalMissing = [];
|
|
@@ -1188,7 +1206,7 @@ function cmdMigrate(args) {
|
|
|
1188
1206
|
project.stages.judgment_cards.locked = locked;
|
|
1189
1207
|
project.stages.judgment_cards.total = project.cards.length;
|
|
1190
1208
|
}
|
|
1191
|
-
writeProject(
|
|
1209
|
+
writeProject(projectPath, project);
|
|
1192
1210
|
console.log(`Approved and Human Locked: ${locked} cards`);
|
|
1193
1211
|
|
|
1194
1212
|
// Step 3: verify Human Lock gate
|
|
@@ -1200,24 +1218,11 @@ function cmdMigrate(args) {
|
|
|
1200
1218
|
|
|
1201
1219
|
const absOut = path.resolve(out);
|
|
1202
1220
|
// Step 4: v1 export or v2 compile + export
|
|
1203
|
-
if (
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1221
|
+
if (format === 'v1') {
|
|
1222
|
+
exportProjectV1(project, name, absOut);
|
|
1223
|
+
if (tmpDir) {
|
|
1224
|
+
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
|
|
1207
1225
|
}
|
|
1208
|
-
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
1209
|
-
const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
|
|
1210
|
-
fs.writeFileSync(path.join(v1Dir, 'mimetype'), 'application/vnd.kdna.asset');
|
|
1211
|
-
fs.writeFileSync(path.join(v1Dir, 'kdna.json'), JSON.stringify(buildV1Manifest(project, name), null, 2));
|
|
1212
|
-
fs.writeFileSync(path.join(v1Dir, 'payload.kdnab'), JSON.stringify(buildV1Payload(project), null, 2));
|
|
1213
|
-
fs.writeFileSync(path.join(v1Dir, 'checksums.json'), JSON.stringify(buildChecksumsV1(v1Dir), null, 2));
|
|
1214
|
-
core.pack(v1Dir, absOut);
|
|
1215
|
-
const vr = core.validate(absOut);
|
|
1216
|
-
if (!vr.overall_valid) fail('v1 export validation failed: ' + (vr.problems || []).join('; '));
|
|
1217
|
-
console.log('Exported (v1): ' + absOut);
|
|
1218
|
-
console.log(' Name: ' + name + ' Cards: ' + lockedCards.length + ' Validated: all gates pass');
|
|
1219
|
-
try { fs.rmSync(v1Dir, { recursive: true }); } catch { /* cleanup */ }
|
|
1220
|
-
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
|
|
1221
1226
|
return;
|
|
1222
1227
|
}
|
|
1223
1228
|
const { result } = compileProject(tmpDir);
|
|
@@ -1240,7 +1245,37 @@ function cmdMigrate(args) {
|
|
|
1240
1245
|
console.log(` Build ID: ${result.identity.build_id}`);
|
|
1241
1246
|
|
|
1242
1247
|
// Cleanup temp
|
|
1243
|
-
|
|
1248
|
+
if (tmpDir) {
|
|
1249
|
+
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* best effort */ }
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function exportProjectV1(project, name, outPath) {
|
|
1254
|
+
let core;
|
|
1255
|
+
try { core = require('@aikdna/kdna-core'); } catch (e) {
|
|
1256
|
+
fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
|
|
1257
|
+
}
|
|
1258
|
+
const gate = projectApi.checkHumanLockGate(project);
|
|
1259
|
+
if (gate.blocked) {
|
|
1260
|
+
const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
|
|
1261
|
+
fail(`Human Lock Gate blocked v1 export:\n - ${reasons}`, EXIT.HUMAN_LOCK_REQUIRED);
|
|
1262
|
+
}
|
|
1263
|
+
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
1264
|
+
const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
|
|
1265
|
+
try {
|
|
1266
|
+
fs.writeFileSync(path.join(v1Dir, 'mimetype'), 'application/vnd.kdna.asset');
|
|
1267
|
+
fs.writeFileSync(path.join(v1Dir, 'kdna.json'), JSON.stringify(buildV1Manifest(project, name), null, 2));
|
|
1268
|
+
fs.writeFileSync(path.join(v1Dir, 'payload.kdnab'), JSON.stringify(buildV1Payload(project), null, 2));
|
|
1269
|
+
fs.writeFileSync(path.join(v1Dir, 'checksums.json'), JSON.stringify(buildChecksumsV1(v1Dir), null, 2));
|
|
1270
|
+
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
1271
|
+
core.pack(v1Dir, outPath);
|
|
1272
|
+
const vr = core.validate(outPath);
|
|
1273
|
+
if (!vr.overall_valid) fail('v1 export validation failed: ' + (vr.problems || []).join('; '));
|
|
1274
|
+
console.log('Exported (v1): ' + outPath);
|
|
1275
|
+
console.log(' Name: ' + name + ' Cards: ' + lockedCards.length + ' Validated: all gates pass');
|
|
1276
|
+
} finally {
|
|
1277
|
+
try { fs.rmSync(v1Dir, { recursive: true }); } catch { /* cleanup */ }
|
|
1278
|
+
}
|
|
1244
1279
|
}
|
|
1245
1280
|
|
|
1246
1281
|
function compileProject(projectInput) {
|
|
@@ -1417,7 +1452,12 @@ function applySignature(files, passphrase = null) {
|
|
|
1417
1452
|
function cmdExport(args) {
|
|
1418
1453
|
const projectInput = args[0];
|
|
1419
1454
|
const out = option(args, '--out');
|
|
1420
|
-
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--sign]');
|
|
1455
|
+
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--format v1] [--sign]');
|
|
1456
|
+
if (option(args, '--format') === 'v1') {
|
|
1457
|
+
const { project } = readProject(projectInput);
|
|
1458
|
+
exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out));
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1421
1461
|
const { project, result } = compileProject(projectInput);
|
|
1422
1462
|
const files = { ...result.files };
|
|
1423
1463
|
files['README.md'] = compileApi.generateReadme(project);
|
|
@@ -1843,6 +1883,10 @@ if (!cmd || cmd === '--help' || cmd === '-h') {
|
|
|
1843
1883
|
usage();
|
|
1844
1884
|
process.exit(EXIT.OK);
|
|
1845
1885
|
}
|
|
1886
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
1887
|
+
console.log(require('../package.json').version);
|
|
1888
|
+
process.exit(EXIT.OK);
|
|
1889
|
+
}
|
|
1846
1890
|
|
|
1847
1891
|
(async () => {
|
|
1848
1892
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-studio-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Official KDNA Studio command-line authoring entry for creating, locking, compiling, and exporting .kdna judgment assets through the KDNA toolchain.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
package/tests/cli.test.js
CHANGED
|
@@ -397,6 +397,45 @@ test('migrate --format v1 maps scoped name, writes checksums, and preserves sour
|
|
|
397
397
|
assert.equal(JSON.stringify(full).includes('registry'), false);
|
|
398
398
|
});
|
|
399
399
|
|
|
400
|
+
test('export --format v1 exports a Studio project as a valid non-empty v1 asset', (t) => {
|
|
401
|
+
assert.ok(kdnaCore, '@aikdna/kdna-core is required for v1 export verification');
|
|
402
|
+
const { tmp, projectDir } = createLockedProject(t);
|
|
403
|
+
const outFile = path.join(tmp, 'project-v1.kdna');
|
|
404
|
+
|
|
405
|
+
const result = run(['export', projectDir, '--format', 'v1', '--out', outFile], { tmp });
|
|
406
|
+
assert.equal(result.status, 0, result.stderr);
|
|
407
|
+
assert.match(result.stdout, /Exported \(v1\)/);
|
|
408
|
+
|
|
409
|
+
const validation = kdnaCore.validate(outFile);
|
|
410
|
+
assert.equal(validation.overall_valid, true, JSON.stringify(validation.problems));
|
|
411
|
+
const loaded = kdnaCore.loadV1(outFile, { profile: 'full', as: 'json' }).content;
|
|
412
|
+
assert.equal(loaded.payload.core.axioms.length, 1);
|
|
413
|
+
assert.equal(loaded.payload.source_cards.length, 1);
|
|
414
|
+
assert.ok(loaded.payload.core.boundaries.some((b) => b.type === 'axiom_applicability'));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('migrate --format v1 accepts an existing Studio project without dropping cards', (t) => {
|
|
418
|
+
assert.ok(kdnaCore, '@aikdna/kdna-core is required for v1 export verification');
|
|
419
|
+
const { tmp, projectDir } = createLockedProject(t);
|
|
420
|
+
const outFile = path.join(tmp, 'project-migrated-v1.kdna');
|
|
421
|
+
|
|
422
|
+
const result = run([
|
|
423
|
+
'migrate', projectDir,
|
|
424
|
+
'--format', 'v1',
|
|
425
|
+
'--out', outFile,
|
|
426
|
+
'--by', 'tester',
|
|
427
|
+
'--statement', 'I confirm this project v1 migration.',
|
|
428
|
+
], { tmp });
|
|
429
|
+
assert.equal(result.status, 0, result.stderr);
|
|
430
|
+
assert.match(result.stdout, /Cards: 1/);
|
|
431
|
+
|
|
432
|
+
const validation = kdnaCore.validate(outFile);
|
|
433
|
+
assert.equal(validation.overall_valid, true, JSON.stringify(validation.problems));
|
|
434
|
+
const loaded = kdnaCore.loadV1(outFile, { profile: 'full', as: 'json' }).content;
|
|
435
|
+
assert.equal(loaded.payload.core.axioms.length, 1);
|
|
436
|
+
assert.equal(loaded.payload.source_cards.length, 1);
|
|
437
|
+
});
|
|
438
|
+
|
|
400
439
|
// ── Create from KDNA ────────────────────────────────────────────────
|
|
401
440
|
|
|
402
441
|
test('create --from-kdna forks an existing .kdna asset', (t) => {
|