@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 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 verify dist/my_domain.kdna --judgment
62
- kdna publish dist/my_domain.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 trusted KDNA creation. `kdna` is the runtime
66
- control plane for existing `.kdna` assets.
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 verify, install, load, publish
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
@@ -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> [--sign]
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 name = option(args, '--name') || path.basename(path.resolve(sourceDir || '.'));
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
- // Step 1: create temp project and import everything from dev source
1134
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-migrate-'));
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
- const { project } = importFromFolder(sourceDir, tmpDir, name, creatorIdentity);
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(path.join(tmpDir, 'studio.project.json'), project);
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 (args.includes('--format') && option(args, '--format') === 'v1') {
1204
- let core;
1205
- try { core = require('@aikdna/kdna-core'); } catch (e) {
1206
- fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
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
- try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* best effort */ }
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.2",
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) => {