@aikdna/kdna-studio-cli 0.5.1 → 0.5.2

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.
@@ -4,6 +4,7 @@ const os = require('os');
4
4
  const path = require('path');
5
5
  const crypto = require('crypto');
6
6
  const zlib = require('zlib');
7
+ const cbor = require('cbor-x');
7
8
 
8
9
  const {
9
10
  project: projectApi,
@@ -14,6 +15,7 @@ const {
14
15
  creator: creatorApi,
15
16
  distillation: distillationApi,
16
17
  } = require('@aikdna/kdna-studio-core');
18
+ const { buildChecksumsV1 } = require('@aikdna/kdna-core/v1');
17
19
 
18
20
  const llm = require('../src/llm');
19
21
  const ai = require('../src/ai');
@@ -149,6 +151,391 @@ function parseFields(args) {
149
151
  return fields;
150
152
  }
151
153
 
154
+ function arrayValue(value) {
155
+ if (Array.isArray(value)) return value;
156
+ if (value === undefined || value === null || value === '') return [];
157
+ return [value];
158
+ }
159
+
160
+ function semverValue(value, fallback = '1.0.0') {
161
+ const raw = String(value || '').trim();
162
+ if (/^[0-9]+\.[0-9]+\.[0-9]+([+-].+)?$/.test(raw)) return raw;
163
+ const twoPart = raw.match(/^([0-9]+)\.([0-9]+)$/);
164
+ if (twoPart) return `${twoPart[1]}.${twoPart[2]}.0`;
165
+ return fallback;
166
+ }
167
+
168
+ function isoDateTime(value) {
169
+ if (!value) return new Date().toISOString();
170
+ const raw = String(value);
171
+ if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) return `${raw}T00:00:00.000Z`;
172
+ const d = new Date(raw);
173
+ return Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString();
174
+ }
175
+
176
+ function slugSegment(value, fallback = 'asset') {
177
+ const s = String(value || fallback)
178
+ .trim()
179
+ .replace(/^@/, '')
180
+ .replace(/[^a-zA-Z0-9_.-]+/g, '_')
181
+ .replace(/^_+|_+$/g, '');
182
+ return s || fallback;
183
+ }
184
+
185
+ function assetIdFromName(name) {
186
+ const raw = String(name || '').trim();
187
+ if (/^[a-zA-Z][a-zA-Z0-9_-]*(:[a-zA-Z0-9_.-]+)+$/.test(raw)) return raw;
188
+ const scoped = raw.match(/^@?([^/:\s]+)\/([^/:\s]+)$/);
189
+ if (scoped) return `kdna:${slugSegment(scoped[1], 'scope')}:${slugSegment(scoped[2], 'asset')}`;
190
+ const parts = raw.split(':').filter(Boolean);
191
+ if (parts.length >= 2) return `kdna:${parts.map((p) => slugSegment(p)).join(':')}`;
192
+ return `kdna:studio:${slugSegment(raw, 'asset')}`;
193
+ }
194
+
195
+ function titleFromName(name, assetId) {
196
+ const raw = String(name || '').trim();
197
+ if (raw) return raw.replace(/^@/, '').replace('/', ' / ');
198
+ return assetId;
199
+ }
200
+
201
+ function publicManifestMetadata(raw = {}) {
202
+ const copy = { ...raw };
203
+ delete copy.registry;
204
+ delete copy.quality_badge;
205
+ delete copy.signature;
206
+ delete copy.media_type;
207
+ return copy;
208
+ }
209
+
210
+ function cardPayload(card) {
211
+ return { id: card.id, ...(card.fields || {}) };
212
+ }
213
+
214
+ function cleanFields(fields = {}) {
215
+ const cleaned = {};
216
+ for (const [key, value] of Object.entries(fields || {})) {
217
+ if (value !== undefined && value !== null) cleaned[key] = value;
218
+ }
219
+ return cleaned;
220
+ }
221
+
222
+ function importedCard(type, fields = {}, id = null) {
223
+ try {
224
+ return cardApi.createCard(type, cleanFields(fields), id || null);
225
+ } catch (_) {
226
+ return null;
227
+ }
228
+ }
229
+
230
+ function pushImportedCard(cards, type, fields = {}, id = null) {
231
+ const card = importedCard(type, fields, id);
232
+ if (card) cards.push(card);
233
+ }
234
+
235
+ function decodePayload(bytes, manifest = {}) {
236
+ const raw = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
237
+ const encoding = manifest.payload?.encoding || manifest.container?.payload_encoding || null;
238
+ if (encoding === 'json' || raw[0] === 0x7b || raw[0] === 0x5b) {
239
+ return JSON.parse(raw.toString('utf8'));
240
+ }
241
+ return cbor.decode(raw);
242
+ }
243
+
244
+ function cardsFromV1Payload(payload) {
245
+ const cards = [];
246
+ for (const sourceCard of (payload.source_cards || [])) {
247
+ pushImportedCard(cards, sourceCard.type, sourceCard.fields || {}, sourceCard.id || null);
248
+ }
249
+ if (cards.length > 0) return cards;
250
+
251
+ for (const ax of (payload.core?.axioms || [])) {
252
+ pushImportedCard(cards, 'axiom', {
253
+ one_sentence: ax.one_sentence || '',
254
+ full_statement: ax.full_statement || '',
255
+ why: ax.why || '',
256
+ applies_when: ax.applies_when || [],
257
+ does_not_apply_when: ax.does_not_apply_when || [],
258
+ failure_risk: ax.failure_risk || '',
259
+ }, ax.id || null);
260
+ }
261
+ for (const boundary of (payload.core?.boundaries || [])) {
262
+ pushImportedCard(cards, 'boundary', boundary, boundary.id || null);
263
+ }
264
+ for (const risk of (payload.core?.risk_model?.risks || [])) {
265
+ pushImportedCard(cards, 'risk', risk, risk.id || null);
266
+ }
267
+ for (const pattern of (payload.patterns || [])) {
268
+ if (!pattern.type) continue;
269
+ const { type, ...fields } = pattern;
270
+ pushImportedCard(cards, type, fields, pattern.id || null);
271
+ }
272
+ for (const scenario of (payload.scenarios || [])) {
273
+ pushImportedCard(cards, 'scenario', scenario, scenario.id || null);
274
+ }
275
+ for (const item of (payload.cases || [])) {
276
+ pushImportedCard(cards, 'case', item, item.id || null);
277
+ }
278
+ for (const selfCheck of (payload.reasoning?.self_checks || [])) {
279
+ const fields = typeof selfCheck === 'string' ? { question: selfCheck } : selfCheck;
280
+ pushImportedCard(cards, 'self_check', fields, fields.id || null);
281
+ }
282
+ for (const failureMode of (payload.reasoning?.failure_modes || [])) {
283
+ pushImportedCard(cards, 'misunderstanding', {
284
+ wrong: failureMode.mode || failureMode.wrong || '',
285
+ correct: failureMode.correct || '',
286
+ key_distinction: failureMode.key_distinction || '',
287
+ why: failureMode.why || '',
288
+ }, failureMode.id || null);
289
+ }
290
+ for (const chain of (payload.reasoning?.reasoning_chains || [])) {
291
+ pushImportedCard(cards, 'reasoning', chain, chain.id || null);
292
+ }
293
+ for (const stage of (payload.evolution?.stages || [])) {
294
+ pushImportedCard(cards, 'evolution_stage', stage, stage.id || null);
295
+ }
296
+ return cards;
297
+ }
298
+
299
+ function cardsFromLegacyPayload(payload) {
300
+ const cards = [];
301
+ const judgment = payload.judgment || {};
302
+ const core = judgment.core || {};
303
+ const patterns = judgment.patterns || {};
304
+
305
+ for (const ax of (core.axioms || [])) {
306
+ pushImportedCard(cards, 'axiom', {
307
+ one_sentence: ax.one_sentence || '',
308
+ full_statement: ax.full_statement || '',
309
+ why: ax.why || '',
310
+ applies_when: ax.applies_when || [],
311
+ does_not_apply_when: ax.does_not_apply_when || [],
312
+ failure_risk: ax.failure_risk || '',
313
+ }, ax.id || null);
314
+ }
315
+ for (const ont of (core.ontology || [])) {
316
+ pushImportedCard(cards, 'ontology', {
317
+ one_sentence: ont.one_sentence || ont.essence || '',
318
+ essence: ont.essence || '',
319
+ boundary: ont.boundary || '',
320
+ trigger_signal: ont.trigger_signal || '',
321
+ }, ont.id || null);
322
+ }
323
+ for (const boundary of (core.boundaries || [])) {
324
+ pushImportedCard(cards, 'boundary', {
325
+ scope: boundary.scope || '',
326
+ out_of_scope: boundary.out_of_scope || '',
327
+ acceptable_exceptions: boundary.acceptable_exceptions || [],
328
+ }, boundary.id || null);
329
+ }
330
+ for (const risk of (core.risks || [])) {
331
+ pushImportedCard(cards, 'risk', risk, risk.id || null);
332
+ }
333
+ for (const term of (patterns.terminology?.standard_terms || [])) {
334
+ pushImportedCard(cards, 'term', term, term.id || null);
335
+ }
336
+ for (const banned of (patterns.terminology?.banned_terms || [])) {
337
+ pushImportedCard(cards, 'banned_term', banned, banned.id || null);
338
+ }
339
+ for (const ms of (patterns.misunderstandings || [])) {
340
+ pushImportedCard(cards, 'misunderstanding', {
341
+ wrong: ms.wrong || '',
342
+ correct: ms.correct || '',
343
+ key_distinction: ms.key_distinction || '',
344
+ why: ms.why || '',
345
+ applies_when: ms.applies_when || [],
346
+ does_not_apply_when: ms.does_not_apply_when || [],
347
+ failure_risk: ms.failure_risk || '',
348
+ }, ms.id || null);
349
+ }
350
+ for (const sc of (patterns.self_check || [])) {
351
+ const question = typeof sc === 'string' ? sc : sc.question || '';
352
+ pushImportedCard(cards, 'self_check', { question }, sc.id || null);
353
+ }
354
+ for (const aesthetic of (patterns.aesthetics || [])) {
355
+ pushImportedCard(cards, 'aesthetic', aesthetic, aesthetic.id || null);
356
+ }
357
+ for (const scene of (judgment.scenarios?.scenes || [])) {
358
+ pushImportedCard(cards, 'scenario', scene, scene.id || null);
359
+ }
360
+ for (const item of (judgment.cases?.cases || [])) {
361
+ pushImportedCard(cards, 'case', item, item.id || null);
362
+ }
363
+ for (const chain of (judgment.reasoning?.reasoning_chains || [])) {
364
+ pushImportedCard(cards, 'reasoning', chain, chain.id || null);
365
+ }
366
+ for (const stage of (judgment.evolution?.stages || [])) {
367
+ pushImportedCard(cards, 'evolution_stage', stage, stage.id || null);
368
+ }
369
+ return cards;
370
+ }
371
+
372
+ function cardsFromPayload(payload) {
373
+ if (!payload || typeof payload !== 'object') return [];
374
+ if (payload.profile === 'judgment-profile-v1' || payload.core || payload.source_cards) {
375
+ return cardsFromV1Payload(payload);
376
+ }
377
+ if (payload.kind === 'kdna.payload' || payload.judgment) {
378
+ return cardsFromLegacyPayload(payload);
379
+ }
380
+ return [];
381
+ }
382
+
383
+ function buildV1Payload(project) {
384
+ const locked = (project.cards || []).filter((c) => c.locked);
385
+ const byType = (type) => locked.filter((c) => c.type === type);
386
+ const source = project.source_manifest || {};
387
+
388
+ const axioms = byType('axiom').map((c) => ({
389
+ id: c.id,
390
+ one_sentence: c.fields?.one_sentence || '',
391
+ full_statement: c.fields?.full_statement || '',
392
+ why: c.fields?.why || '',
393
+ applies_when: arrayValue(c.fields?.applies_when),
394
+ does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
395
+ failure_risk: c.fields?.failure_risk || '',
396
+ status: c.status || null,
397
+ human_lock: c.human_lock || null,
398
+ }));
399
+
400
+ const boundaries = [
401
+ ...byType('boundary').map((c) => ({ type: 'explicit_boundary', ...cardPayload(c) })),
402
+ ...byType('axiom').map((c) => ({
403
+ id: `${c.id}_applicability`,
404
+ type: 'axiom_applicability',
405
+ axiom_id: c.id,
406
+ one_sentence: c.fields?.one_sentence || '',
407
+ applies_when: arrayValue(c.fields?.applies_when),
408
+ does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
409
+ failure_risk: c.fields?.failure_risk || '',
410
+ })),
411
+ ...byType('ontology')
412
+ .filter((c) => c.fields?.boundary || c.fields?.applies_when || c.fields?.does_not_apply_when)
413
+ .map((c) => ({
414
+ id: `${c.id}_boundary`,
415
+ type: 'ontology_boundary',
416
+ concept_id: c.id,
417
+ one_sentence: c.fields?.one_sentence || '',
418
+ boundary: c.fields?.boundary || '',
419
+ applies_when: arrayValue(c.fields?.applies_when),
420
+ does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
421
+ trigger_signal: c.fields?.trigger_signal || '',
422
+ })),
423
+ ...byType('stance')
424
+ .filter((c) => c.fields?.applies_when || c.fields?.does_not_apply_when)
425
+ .map((c) => ({
426
+ id: `${c.id}_boundary`,
427
+ type: 'stance_boundary',
428
+ stance_id: c.id,
429
+ stance: c.fields?.stance || c.fields?.statement || c.fields?.one_sentence || '',
430
+ applies_when: arrayValue(c.fields?.applies_when),
431
+ does_not_apply_when: arrayValue(c.fields?.does_not_apply_when),
432
+ })),
433
+ ];
434
+
435
+ const patterns = [
436
+ ...byType('ontology').map((c) => ({ type: 'ontology', ...cardPayload(c) })),
437
+ ...byType('misunderstanding').map((c) => ({ type: 'misunderstanding', ...cardPayload(c) })),
438
+ ...byType('term').map((c) => ({ type: 'term', ...cardPayload(c) })),
439
+ ...byType('banned_term').map((c) => ({ type: 'banned_term', ...cardPayload(c) })),
440
+ ...byType('framework').map((c) => ({ type: 'framework', ...cardPayload(c) })),
441
+ ...byType('stance').map((c) => ({ type: 'stance', ...cardPayload(c) })),
442
+ ];
443
+
444
+ const failureModes = byType('misunderstanding').map((c) => ({
445
+ id: c.id,
446
+ mode: c.fields?.wrong || c.fields?.key_distinction || c.id,
447
+ correct: c.fields?.correct || '',
448
+ key_distinction: c.fields?.key_distinction || '',
449
+ why: c.fields?.why || '',
450
+ }));
451
+
452
+ return {
453
+ profile: 'judgment-profile-v1',
454
+ core: {
455
+ highest_question: source.core_insight || project.release?.description || '',
456
+ axioms,
457
+ boundaries,
458
+ risk_model: {
459
+ risks: byType('risk').map((c) => cardPayload(c)),
460
+ source_risk_level: source.risk_level || null,
461
+ fitness_for_purpose: source.fitness_for_purpose || null,
462
+ },
463
+ },
464
+ patterns,
465
+ scenarios: byType('scenario').map((c) => cardPayload(c)),
466
+ cases: byType('case').map((c) => cardPayload(c)),
467
+ reasoning: {
468
+ self_checks: byType('self_check').map((c) => c.fields?.question || c.fields?.one_sentence || c.id),
469
+ failure_modes: failureModes,
470
+ reasoning_chains: byType('reasoning').map((c) => cardPayload(c)),
471
+ },
472
+ evolution: {
473
+ stages: byType('evolution_stage').map((c) => cardPayload(c)),
474
+ changelog: locked.map((c) => ({
475
+ id: `locked_${c.id}`,
476
+ card_id: c.id,
477
+ type: c.type,
478
+ event: 'locked',
479
+ at: c.human_lock?.at || null,
480
+ })),
481
+ version_notes: source.judgment_version ? [`Migrated from source judgment_version ${source.judgment_version}`] : [],
482
+ },
483
+ source_cards: locked.map((c) => ({
484
+ id: c.id,
485
+ type: c.type,
486
+ status: c.status,
487
+ fields: c.fields || {},
488
+ human_lock: c.human_lock || null,
489
+ })),
490
+ };
491
+ }
492
+
493
+ function buildV1Manifest(project, name) {
494
+ const source = project.source_manifest || {};
495
+ const assetId = assetIdFromName(name || source.name || project.name);
496
+ const author = source.author || project.author || {};
497
+ return {
498
+ kdna_version: '1.0',
499
+ asset_id: assetId,
500
+ asset_uid: project.asset_uid || 'urn:uuid:' + crypto.randomUUID(),
501
+ asset_type: project.type === 'cluster' ? 'cluster' : 'domain',
502
+ source_asset_type: source.asset_type || null,
503
+ title: titleFromName(source.name || name || project.name, assetId),
504
+ version: semverValue(project.release?.version || source.version, '1.0.0'),
505
+ judgment_version: semverValue(project.release?.judgment_version || source.judgment_version || source.version, '1.0.0'),
506
+ created_at: isoDateTime(source.created || project.created),
507
+ updated_at: isoDateTime(source.updated || project.updated),
508
+ creator: {
509
+ name: author.name || project.author?.name || 'Studio Export',
510
+ id: author.id || project.author?.id || undefined,
511
+ },
512
+ compatibility: { min_loader_version: '1.0.0', profile: 'judgment-profile-v1' },
513
+ payload: { path: 'payload.kdnab', encoding: 'json', encrypted: false },
514
+ summary: source.core_insight || project.release?.description || source.description || '',
515
+ description: source.description || project.release?.description || '',
516
+ language: source.default_language || project.default_language || (Array.isArray(source.languages) ? source.languages[0] : undefined),
517
+ languages: source.languages || project.languages || undefined,
518
+ license: source.license || project.license || undefined,
519
+ keywords: source.keywords || [],
520
+ lineage: {
521
+ type: project.lineage?.type === 'migrated' ? 'adaptation' : (project.lineage?.type || 'original'),
522
+ fork_of: null,
523
+ derived_from: source.name || project.lineage?.parent_name || null,
524
+ source_lineage_type: project.lineage?.type || null,
525
+ },
526
+ load_contract: {
527
+ default_profile: 'compact',
528
+ profiles: {
529
+ index: { requires_decryption: false, max_tokens_hint: 500, selection: 'manifest metadata', intended_for: ['discovery'] },
530
+ compact: { requires_decryption: false, max_tokens_hint: 2000, selection: 'core judgment summary', intended_for: ['agent prompt'] },
531
+ scenario: { requires_decryption: false, max_tokens_hint: 3000, selection: 'scenario cards', intended_for: ['situational loading'] },
532
+ full: { requires_decryption: false, max_tokens_hint: 12000, selection: 'full manifest and payload', intended_for: ['audit', 'migration'] },
533
+ },
534
+ },
535
+ source_manifest: publicManifestMetadata(source),
536
+ };
537
+ }
538
+
152
539
  function cmdCreate(args) {
153
540
  const dir = args[0];
154
541
  if (!dir) fail('Usage: kdna-studio create <project-dir> --name <name> [--from-kdna <file> | --from-folder <dir>]');
@@ -171,13 +558,30 @@ function cmdCreate(args) {
171
558
  const kdnaData = importFromKdna(fromKdna);
172
559
  lineage = kdnaData.lineage;
173
560
  if (kdnaData.cards) {
561
+ const sourceAuthor = kdnaData.source_manifest?.author || {};
174
562
  const project = projectApi.createProject(name, 'domain', {
175
- author: { name: option(args, '--author-name', ''), id: option(args, '--author-id', '') },
563
+ author: {
564
+ name: option(args, '--author-name', sourceAuthor.name || ''),
565
+ id: option(args, '--author-id', sourceAuthor.id || ''),
566
+ },
176
567
  sourceMode,
177
568
  creatorIdentity,
178
569
  lineage,
179
570
  });
180
571
  project.cards = kdnaData.cards;
572
+ if (kdnaData.source_manifest) project.source_manifest = publicManifestMetadata(kdnaData.source_manifest);
573
+ if (kdnaData.source_manifest?.version) {
574
+ if (!project.release) project.release = {};
575
+ project.release.version = kdnaData.source_manifest.version;
576
+ }
577
+ if (kdnaData.source_manifest?.judgment_version) {
578
+ if (!project.release) project.release = {};
579
+ project.release.judgment_version = kdnaData.source_manifest.judgment_version;
580
+ }
581
+ if (kdnaData.source_manifest?.description) {
582
+ if (!project.release) project.release = {};
583
+ project.release.description = kdnaData.source_manifest.description;
584
+ }
181
585
  if (project.stages?.judgment_cards) {
182
586
  project.stages.judgment_cards.total = kdnaData.cards.length;
183
587
  project.stages.judgment_cards.status = 'in_progress';
@@ -232,10 +636,10 @@ function importFromKdna(kdnaPath) {
232
636
  const manifest = JSON.parse(entries.get('kdna.json').toString());
233
637
  const lineage = {
234
638
  type: 'fork',
235
- parent_name: manifest.name || null,
639
+ parent_name: manifest.name || manifest.title || manifest.asset_id || null,
236
640
  parent_asset_uid: manifest.asset_uid || null,
237
641
  parent_version: manifest.version || null,
238
- parent_asset_digest: manifest.content_digest || null,
642
+ parent_asset_digest: manifest.content_digest || manifest.asset_digest || null,
239
643
  };
240
644
 
241
645
  // Extract cards from KDNA files
@@ -277,7 +681,16 @@ function importFromKdna(kdnaPath) {
277
681
  }
278
682
  }
279
683
 
280
- return { lineage, cards: importedCards };
684
+ if (importedCards.length === 0 && entries.has('payload.kdnab')) {
685
+ try {
686
+ const payload = decodePayload(entries.get('payload.kdnab'), manifest);
687
+ importedCards.push(...cardsFromPayload(payload));
688
+ } catch (e) {
689
+ fail(`Could not import cards from payload.kdnab: ${e.message}`);
690
+ }
691
+ }
692
+
693
+ return { lineage, cards: importedCards, source_manifest: manifest };
281
694
  }
282
695
 
283
696
  /**
@@ -305,12 +718,22 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
305
718
  // Load manifest for version/languages/description
306
719
  const manifest = loadJson('kdna.json');
307
720
  if (manifest) {
721
+ manifestData.raw = manifest;
308
722
  if (manifest.version) manifestData.version = manifest.version;
309
723
  if (manifest.judgment_version) manifestData.judgment_version = manifest.judgment_version;
310
724
  if (manifest.languages) manifestData.languages = manifest.languages;
311
725
  if (manifest.default_language) manifestData.default_language = manifest.default_language;
312
726
  if (manifest.description) manifestData.description = manifest.description;
313
727
  if (manifest.name) manifestData.name = manifest.name;
728
+ if (manifest.keywords) manifestData.keywords = manifest.keywords;
729
+ if (manifest.license) manifestData.license = manifest.license;
730
+ if (manifest.author) manifestData.author = manifest.author;
731
+ if (manifest.asset_type) manifestData.asset_type = manifest.asset_type;
732
+ if (manifest.core_insight) manifestData.core_insight = manifest.core_insight;
733
+ if (manifest.created) manifestData.created = manifest.created;
734
+ if (manifest.updated) manifestData.updated = manifest.updated;
735
+ if (manifest.risk_level) manifestData.risk_level = manifest.risk_level;
736
+ if (manifest.fitness_for_purpose) manifestData.fitness_for_purpose = manifest.fitness_for_purpose;
314
737
  }
315
738
 
316
739
  const core = loadJson('KDNA_Core.json');
@@ -450,8 +873,11 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity) {
450
873
  if (!project.release) project.release = {};
451
874
  project.release.description = manifestData.description;
452
875
  }
876
+ if (manifestData.license) project.license = manifestData.license;
877
+ if (manifestData.author) project.author = manifestData.author;
453
878
  if (manifestData.languages) project.languages = manifestData.languages;
454
879
  if (manifestData.default_language) project.default_language = manifestData.default_language;
880
+ if (manifestData.raw) project.source_manifest = publicManifestMetadata(manifestData.raw);
455
881
 
456
882
  project.cards = importedCards;
457
883
  if (project.stages?.judgment_cards) {
@@ -779,30 +1205,17 @@ function cmdMigrate(args) {
779
1205
  try { core = require('@aikdna/kdna-core'); } catch (e) {
780
1206
  fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
781
1207
  }
782
- const lockedAxioms = (project.cards || []).filter(c => c.type === 'axiom' && c.locked);
1208
+ const lockedCards = (project.cards || []).filter(c => c.locked);
783
1209
  const v1Dir = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-'));
784
1210
  fs.writeFileSync(path.join(v1Dir, 'mimetype'), 'application/vnd.kdna.asset');
785
- fs.writeFileSync(path.join(v1Dir, 'kdna.json'), JSON.stringify({
786
- kdna_version: '1.0', asset_id: name || 'studio:v1-export',
787
- asset_uid: 'urn:uuid:' + crypto.randomUUID(), asset_type: 'domain',
788
- title: name || 'Studio v1 Export', version: '1.0.0',
789
- judgment_version: '1.0.0',
790
- created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
791
- creator: { name: 'Studio Export' },
792
- compatibility: { min_loader_version: '1.0.0', profile: 'judgment-profile-v1' },
793
- payload: { path: 'payload.kdnab', encoding: 'json', encrypted: false },
794
- lineage: { type: 'original', fork_of: null, derived_from: null },
795
- }, null, 2));
796
- fs.writeFileSync(path.join(v1Dir, 'payload.kdnab'), JSON.stringify({
797
- profile: 'judgment-profile-v1',
798
- core: { highest_question: '', axioms: lockedAxioms.map(c => ({ one_sentence: c.fields?.one_sentence || c.summary || '' })), boundaries: [], risk_model: {} },
799
- patterns: [], scenarios: [], cases: [], reasoning: { self_checks: [], failure_modes: [] },
800
- }, null, 2));
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));
801
1214
  core.pack(v1Dir, absOut);
802
1215
  const vr = core.validate(absOut);
803
1216
  if (!vr.overall_valid) fail('v1 export validation failed: ' + (vr.problems || []).join('; '));
804
1217
  console.log('Exported (v1): ' + absOut);
805
- console.log(' Name: ' + name + ' Axioms: ' + lockedAxioms.length + ' Validated: all gates pass');
1218
+ console.log(' Name: ' + name + ' Cards: ' + lockedCards.length + ' Validated: all gates pass');
806
1219
  try { fs.rmSync(v1Dir, { recursive: true }); } catch { /* cleanup */ }
807
1220
  try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
808
1221
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-studio-cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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": {
@@ -38,10 +38,13 @@
38
38
  "node": ">=18"
39
39
  },
40
40
  "dependencies": {
41
- "@aikdna/kdna-studio-core": "^1.5.0",
42
- "@aikdna/kdna-core": "^0.11.0"
41
+ "@aikdna/kdna-core": "^0.11.1",
42
+ "@aikdna/kdna-studio-core": "^1.5.3",
43
+ "ajv": "^8.20.0",
44
+ "ajv-formats": "^3.0.1",
45
+ "cbor-x": "^1.6.4"
43
46
  },
44
47
  "devDependencies": {
45
- "@aikdna/kdna-core": "^0.11.0"
48
+ "@aikdna/kdna-core": "^0.11.1"
46
49
  }
47
50
  }
package/tests/cli.test.js CHANGED
@@ -309,6 +309,94 @@ test('create --from-folder imports legacy JSON source, outputs audit', (t) => {
309
309
  assert.equal(project.lineage.type, 'migrated');
310
310
  });
311
311
 
312
+ test('migrate --format v1 maps scoped name, writes checksums, and preserves source cards', (t) => {
313
+ assert.ok(kdnaCore, '@aikdna/kdna-core is required for v1 export verification');
314
+ const tmp = tmpDir();
315
+ t.after(() => fs.rmSync(tmp, { recursive: true, force: true }));
316
+
317
+ const sourceDir = path.join(tmp, 'legacy-source');
318
+ fs.mkdirSync(sourceDir, { recursive: true });
319
+ fs.writeFileSync(path.join(sourceDir, 'kdna.json'), JSON.stringify({
320
+ name: '@test/legacy-import',
321
+ version: '0.7.3',
322
+ judgment_version: '2026.05',
323
+ asset_type: 'domain_judgment',
324
+ description: 'Legacy import fixture.',
325
+ core_insight: 'Preserve real judgment structure.',
326
+ author: { name: 'Test Author', id: 'test-author' },
327
+ license: { type: 'CC-BY-4.0' },
328
+ keywords: ['legacy', 'v1'],
329
+ languages: ['en'],
330
+ default_language: 'en',
331
+ quality_badge: 'tested',
332
+ registry: { repo: 'https://example.invalid/legacy' },
333
+ }));
334
+ fs.writeFileSync(path.join(sourceDir, 'KDNA_Core.json'), JSON.stringify({
335
+ axioms: [{
336
+ id: 'ax_legacy',
337
+ one_sentence: 'Prefer specific evidence over broad claims.',
338
+ full_statement: 'When evaluating a claim the agent must preserve specific evidence and not collapse it into generic advice.',
339
+ why: 'Without this judgment the agent produces plausible but weak summaries.',
340
+ applies_when: ['reviewing content'],
341
+ does_not_apply_when: ['pure formatting'],
342
+ failure_risk: 'May over-diagnose simple copy edits.',
343
+ }],
344
+ ontology: [{ id: 'on_legacy', essence: 'Specificity anchor', boundary: 'Not generic polish', trigger_signal: 'abstract claim' }],
345
+ frameworks: [{ id: 'fw_legacy', name: 'Diagnose before rewrite', when_to_use: 'reviewing drafts', steps: ['diagnose', 'rewrite'] }],
346
+ stances: [{ id: 'st_legacy', stance: 'Structure first', applies_when: ['draft review'], does_not_apply_when: ['grammar only'] }],
347
+ }));
348
+ fs.writeFileSync(path.join(sourceDir, 'KDNA_Patterns.json'), JSON.stringify({
349
+ terminology: {
350
+ standard_terms: [{ term: 'specificity anchor', definition: 'A concrete detail.' }],
351
+ banned_terms: [{ term: 'make it better', why: 'Non-diagnostic.', replace_with: 'name the structural issue' }],
352
+ },
353
+ misunderstandings: [{ wrong: 'Smooth means good.', correct: 'Smooth can hide weak thinking.', key_distinction: 'surface vs structure', why: 'Prevents false praise.' }],
354
+ self_check: ['Did I preserve the structural judgment?'],
355
+ }));
356
+ fs.writeFileSync(path.join(sourceDir, 'KDNA_Scenarios.json'), JSON.stringify({ scenes: [{ id: 'sc_legacy', name: 'Draft review', trigger: 'user asks for review', action: 'diagnose first' }] }));
357
+ fs.writeFileSync(path.join(sourceDir, 'KDNA_Cases.json'), JSON.stringify({ cases: [{ id: 'ca_legacy', title: 'Smooth but empty', scenario: 'draft reads well', expected: 'flag missing claim' }] }));
358
+ fs.writeFileSync(path.join(sourceDir, 'KDNA_Reasoning.json'), JSON.stringify({ reasoning_chains: [{ id: 're_legacy', name: 'structure-first', conclusion: 'diagnose structure first', so_what: 'avoid surface polish' }] }));
359
+ fs.writeFileSync(path.join(sourceDir, 'KDNA_Evolution.json'), JSON.stringify({ stages: [{ id: 'ev_legacy', name: 'Structural reviewer', description: 'Moves beyond surface editing.' }] }));
360
+
361
+ const outFile = path.join(tmp, 'legacy.kdna');
362
+ const result = run([
363
+ 'migrate', sourceDir,
364
+ '--out', outFile,
365
+ '--name', '@test/legacy-import',
366
+ '--by', 'tester',
367
+ '--statement', 'I confirm this v1 migration fixture.',
368
+ '--format', 'v1',
369
+ ], { tmp });
370
+ assert.equal(result.status, 0, result.stderr);
371
+
372
+ const validation = kdnaCore.validate(outFile);
373
+ assert.equal(validation.overall_valid, true, JSON.stringify(validation.problems));
374
+ const inspected = kdnaCore.inspect(outFile);
375
+ assert.equal(inspected.asset_id, 'kdna:test:legacy-import');
376
+ assert.equal(inspected.checksums_present, true);
377
+
378
+ const full = kdnaCore.loadV1(outFile, { profile: 'full', as: 'json' }).content;
379
+ assert.equal(full.manifest.creator.name, 'Test Author');
380
+ assert.equal(full.manifest.version, '0.7.3');
381
+ assert.equal(full.manifest.judgment_version, '2026.05.0');
382
+ assert.equal(full.payload.core.axioms.length, 1);
383
+ assert.equal(full.payload.core.boundaries.length, 3);
384
+ assert.ok(full.payload.core.boundaries.some((b) => b.type === 'axiom_applicability'));
385
+ assert.ok(full.payload.core.boundaries.some((b) => b.type === 'ontology_boundary'));
386
+ assert.ok(full.payload.core.boundaries.some((b) => b.type === 'stance_boundary'));
387
+ assert.equal(full.payload.patterns.filter((p) => p.type === 'term').length, 1);
388
+ assert.equal(full.payload.patterns.filter((p) => p.type === 'banned_term').length, 1);
389
+ assert.equal(full.payload.scenarios.length, 1);
390
+ assert.equal(full.payload.cases.length, 1);
391
+ assert.equal(full.payload.reasoning.self_checks.length, 1);
392
+ assert.equal(full.payload.reasoning.failure_modes.length, 1);
393
+ assert.equal(full.payload.reasoning.reasoning_chains.length, 1);
394
+ assert.equal(full.payload.evolution.stages.length, 1);
395
+ assert.equal(full.payload.source_cards.length, 12);
396
+ assert.equal(JSON.stringify(full).includes('quality_badge'), false);
397
+ assert.equal(JSON.stringify(full).includes('registry'), false);
398
+ });
399
+
312
400
  // ── Create from KDNA ────────────────────────────────────────────────
313
401
 
314
402
  test('create --from-kdna forks an existing .kdna asset', (t) => {