@aikdna/kdna-studio-cli 0.8.17 → 0.9.1

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
@@ -8,7 +8,7 @@ Distillation-first authoring is domain-first: declare the target domain, owner s
8
8
 
9
9
  This package provides the `kdna-studio` command. It creates Studio projects,
10
10
  imports evidence, manages judgment cards, records optional review/provenance
11
- signals, compiles reviewed project content, and exports canonical `.kdna`
11
+ signals, compiles project content, and exports canonical `.kdna`
12
12
  assets with build reports.
13
13
 
14
14
  It is intentionally separate from `@aikdna/kdna-cli`:
@@ -37,7 +37,7 @@ kdna-studio target declare my_domain \
37
37
  --task "longform article review" \
38
38
  --include "argument structure,tone,revision" \
39
39
  --exclude "life habits,food preference"
40
- kdna-studio source classify my_domain
40
+ kdna-studio source my_domain
41
41
  kdna-studio distill my_domain --candidates candidates.json
42
42
  kdna-studio candidate list my_domain
43
43
  kdna-studio candidate accept my_domain <candidate-id>
@@ -51,16 +51,16 @@ kdna-studio card add my_domain axiom \
51
51
  --field failure_risk="generic advice" \
52
52
  --field confidence='high' \
53
53
  --field evidence_type='practice'
54
+ # Optional: record review provenance
54
55
  kdna-studio card approve my_domain --all --by expert --statement "I confirm this judgment."
55
56
  kdna-studio export my_domain --out dist/my_domain.kdna
56
57
  ```
57
58
 
58
59
  Candidate promotion is scope-gated: only candidates with `status == accepted` and `scope_fit == true` are promoted to cards by default. Use `kdna-studio candidate override <project> <candidate-id>` only when a human intentionally overrides the scope gate.
59
60
 
60
- The current Studio CLI export workflow uses approved cards as release evidence.
61
- This is Studio project policy, not a KDNA format-validity rule. Human
62
- Lock and other provenance records are optional review evidence, not validity
63
- requirements.
61
+ The Studio CLI exports complete, non-deprecated cards. Human Lock and other
62
+ provenance records are optional review evidence, not creation permission or
63
+ format-validity requirements.
64
64
 
65
65
  After export, use the runtime CLI:
66
66
 
@@ -79,14 +79,11 @@ loading existing `.kdna` assets.
79
79
  If you just installed and want to ship a KDNA asset without an LLM:
80
80
 
81
81
  ```bash
82
- # 1. Init identity (required for the export signature)
83
- kdna-studio identity init --name "Your Name"
84
-
85
- # 2. Create a project
82
+ # 1. Create a project
86
83
  kdna-studio create my_domain --name @yourscope/my_domain
87
84
 
88
- # 3. Add at least one judgment card
89
- kdna-studio card add . axiom \
85
+ # 2. Add at least one judgment card
86
+ kdna-studio card add my_domain axiom \
90
87
  --field one_sentence='specific evidence outranks broad claims' \
91
88
  --field full_statement='Always cite the specific source that supports a judgment; broad claims without evidence are the most common cause of bad agent advice.' \
92
89
  --field why='because vague advice fails in production' \
@@ -96,13 +93,13 @@ kdna-studio card add . axiom \
96
93
  --field confidence='high' \
97
94
  --field evidence_type='practice'
98
95
 
99
- # 4. Approve and lock
100
- kdna-studio card approve . --all --by me --statement "i confirm"
96
+ # 3. Optional: approve and record Human Lock provenance
97
+ kdna-studio card approve my_domain --all --by me --statement "i confirm"
101
98
 
102
- # 5. Export the asset
103
- kdna-studio export . --out my_domain.kdna
99
+ # 4. Export the asset
100
+ kdna-studio export my_domain --out my_domain.kdna
104
101
 
105
- # 6. Verify with the runtime CLI
102
+ # 5. Verify with the runtime CLI
106
103
  kdna load my_domain.kdna --profile=compact
107
104
  ```
108
105
 
@@ -157,7 +154,7 @@ kdna-studio identity show
157
154
  ## Import from existing KDNA or legacy folders
158
155
 
159
156
  ```bash
160
- # Fork an existing .kdna asset (cards imported as draft — review before Studio export)
157
+ # Fork an existing canonical .kdna asset (cards are imported as draft)
161
158
  kdna-studio create forked --from-kdna ./parent.kdna --name @scope/forked
162
159
 
163
160
  # Migrate a legacy JSON source folder
@@ -410,10 +410,14 @@ function resetImportedIds() { _importedIds.clear(); }
410
410
  function decodePayload(bytes, manifest = {}) {
411
411
  const raw = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
412
412
  const encoding = manifest.payload?.encoding || manifest.container?.payload_encoding || null;
413
- if (encoding === 'json' || raw[0] === 0x7b || raw[0] === 0x5b) {
414
- return JSON.parse(raw.toString('utf8'));
413
+ if (encoding && encoding !== 'cbor') {
414
+ throw new Error(`unsupported payload encoding "${encoding}"; current .kdna assets require CBOR`);
415
+ }
416
+ try {
417
+ return cbor.decode(raw);
418
+ } catch (error) {
419
+ throw new Error(`payload.kdnab is not valid CBOR: ${error.message}`);
415
420
  }
416
- return cbor.decode(raw);
417
421
  }
418
422
 
419
423
  // Runtime path: trust the producer (export-runtime) to have already shaped
@@ -561,97 +565,11 @@ function cardsFromRuntimePayload(payload) {
561
565
  return cards;
562
566
  }
563
567
 
564
- function cardsFromLegacyPayload(payload) {
565
- resetImportedIds();
566
- const cards = [];
567
- const judgment = payload.judgment || {};
568
- const core = judgment.core || {};
569
- const patterns = judgment.patterns || {};
570
-
571
- for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
572
- pushImportedCard(cards, 'axiom', {
573
- one_sentence: ax.one_sentence || '',
574
- full_statement: ax.full_statement || '',
575
- why: ax.why || '',
576
- applies_when: ax.applies_when || [],
577
- does_not_apply_when: ax.does_not_apply_when || [],
578
- failure_risk: ax.failure_risk || '',
579
- }, ax.id || null);
580
- }
581
- for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
582
- pushImportedCard(cards, 'ontology', {
583
- one_sentence: ont.one_sentence || ont.essence || '',
584
- essence: ont.essence || '',
585
- boundary: ont.boundary || '',
586
- trigger_signal: ont.trigger_signal || '',
587
- }, ont.id || null);
588
- }
589
- for (const boundary of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
590
- pushImportedCard(cards, 'boundary', {
591
- scope: boundary.scope || '',
592
- out_of_scope: boundary.out_of_scope || '',
593
- acceptable_exceptions: boundary.acceptable_exceptions || [],
594
- }, boundary.id || null);
595
- }
596
- for (const risk of (Array.isArray(core.risks) ? core.risks : [])) {
597
- pushImportedCard(cards, 'risk', risk, risk.id || null);
598
- }
599
- // Bug (#63): prior version skipped `core.stances` and
600
- // `core.frameworks`, so a legacy asset that authored either type
601
- // round-tripped out as zero cards. The fix adds both loops.
602
- for (const stance of (Array.isArray(core.stances) ? core.stances : [])) {
603
- pushImportedCard(cards, 'stance', stance, stance.id || null);
604
- }
605
- for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
606
- pushImportedCard(cards, 'framework', fw, fw.id || null);
607
- }
608
- for (const term of (Array.isArray(patterns.terminology?.standard_terms) ? patterns.terminology?.standard_terms : [])) {
609
- pushImportedCard(cards, 'term', term, term.id || null);
610
- }
611
- for (const banned of (Array.isArray(patterns.terminology?.banned_terms) ? patterns.terminology?.banned_terms : [])) {
612
- pushImportedCard(cards, 'banned_term', banned, banned.id || null);
613
- }
614
- for (const ms of (Array.isArray(patterns.misunderstandings) ? patterns.misunderstandings : [])) {
615
- pushImportedCard(cards, 'misunderstanding', {
616
- wrong: ms.wrong || '',
617
- correct: ms.correct || '',
618
- key_distinction: ms.key_distinction || '',
619
- why: ms.why || '',
620
- applies_when: ms.applies_when || [],
621
- does_not_apply_when: ms.does_not_apply_when || [],
622
- failure_risk: ms.failure_risk || '',
623
- }, ms.id || null);
624
- }
625
- for (const sc of (Array.isArray(patterns.self_check) ? patterns.self_check : [])) {
626
- const question = typeof sc === 'string' ? sc : sc.question || '';
627
- pushImportedCard(cards, 'self_check', { question }, sc.id || null);
628
- }
629
- for (const aesthetic of (Array.isArray(patterns.aesthetics) ? patterns.aesthetics : [])) {
630
- pushImportedCard(cards, 'aesthetic', aesthetic, aesthetic.id || null);
631
- }
632
- for (const scene of (Array.isArray(judgment.scenarios?.scenes) ? judgment.scenarios?.scenes : [])) {
633
- pushImportedCard(cards, 'scenario', scene, scene.id || null);
634
- }
635
- for (const item of (Array.isArray(judgment.cases?.cases) ? judgment.cases?.cases : [])) {
636
- pushImportedCard(cards, 'case', item, item.id || null);
637
- }
638
- for (const chain of (Array.isArray(judgment.reasoning?.reasoning_chains) ? judgment.reasoning?.reasoning_chains : [])) {
639
- pushImportedCard(cards, 'reasoning', chain, chain.id || null);
640
- }
641
- for (const stage of (Array.isArray(judgment.evolution?.stages) ? judgment.evolution?.stages : [])) {
642
- pushImportedCard(cards, 'evolution_stage', stage, stage.id || null);
643
- }
644
- return cards;
645
- }
646
-
647
568
  function cardsFromPayload(payload) {
648
569
  if (!payload || typeof payload !== 'object') return [];
649
570
  if (payload.profile === 'judgment-profile-v1' || payload.core || payload.source_cards) {
650
571
  return cardsFromRuntimePayload(payload);
651
572
  }
652
- if (payload.kind === 'kdna.payload' || payload.judgment) {
653
- return cardsFromLegacyPayload(payload);
654
- }
655
573
  return [];
656
574
  }
657
575
 
@@ -675,20 +593,20 @@ function buildManifest(project, name) {
675
593
  return {
676
594
  kdna_version: '1.0',
677
595
  asset_id: assetId,
678
- asset_uid: project.asset_uid || 'urn:uuid:' + crypto.randomUUID(),
596
+ asset_uid: project.asset_uid || source.asset_uid || 'urn:uuid:' + crypto.randomUUID(),
679
597
  asset_type: project.type === 'cluster' ? 'cluster' : 'domain',
680
598
  source_asset_type: source.asset_type || null,
681
599
  title: titleFromName(source.name || name || project.name, assetId),
682
600
  version: semverValue(project.release?.version || source.version, '1.0.0'),
683
601
  judgment_version: semverValue(project.release?.judgment_version || source.judgment_version || source.version, '1.0.0'),
684
- created_at: isoDateTime(source.created || project.created),
685
- updated_at: isoDateTime(source.updated || project.updated),
602
+ created_at: isoDateTime(source.created_at || source.created || project.created),
603
+ updated_at: isoDateTime(source.updated_at || source.updated || project.updated),
686
604
  creator: {
687
605
  name: author.name || project.author?.name || 'Studio Export',
688
606
  id: author.id || project.author?.id || undefined,
689
607
  },
690
608
  compatibility: { min_loader_version: '1.0.0', profile: 'judgment-profile-v1' },
691
- payload: { path: 'payload.kdnab', encoding: 'json', encrypted: false },
609
+ payload: { path: 'payload.kdnab', encoding: 'cbor', encrypted: false },
692
610
  summary: source.core_insight || project.release?.description || source.description || '',
693
611
  description: source.description || project.release?.description || '',
694
612
  language: source.default_language || project.default_language || (Array.isArray(source.languages) ? source.languages[0] : undefined),
@@ -821,6 +739,14 @@ function importFromKdna(kdnaPath) {
821
739
  if (stats.size > 50 * 1024 * 1024) {
822
740
  fail(`KDNA file too large (${(stats.size / (1024 * 1024)).toFixed(1)} MiB, max 50 MiB): ${absKdna}`);
823
741
  }
742
+ let runtimeCore;
743
+ try { runtimeCore = require('@aikdna/kdna-core'); } catch {
744
+ fail('@aikdna/kdna-core is required to validate --from-kdna imports.');
745
+ }
746
+ const validation = runtimeCore.validate(absKdna);
747
+ if (!validation.overall_valid) {
748
+ fail(`Not a current .kdna asset: ${(validation.problems || []).join('; ')}`);
749
+ }
824
750
  const zipBuf = fs.readFileSync(absKdna);
825
751
  const entries = readZipEntries(zipBuf);
826
752
  if (!entries.has('kdna.json')) fail('Not a valid .kdna asset: missing kdna.json');
@@ -834,122 +760,21 @@ function importFromKdna(kdnaPath) {
834
760
  parent_asset_digest: manifest.content_digest || manifest.asset_digest || null,
835
761
  };
836
762
 
837
- // Extract cards. Current packages carry `kdna.json` + `payload.kdnab`
838
- // and use the unified judgment profile. Legacy packages split content
839
- // across KDNA_*.json files. Prefer payload.kdnab when present — it
840
- // covers all 8+ card types (axiom / boundary / risk / aesthetic /
841
- // ontology / misunderstanding / self_check / scenario / case /
842
- // reasoning / evolution_stage / stance / framework / term / banned_term)
843
- // via cardsFromRuntimePayload / cardsFromLegacyPayload.
763
+ // Current assets carry strict-CBOR `payload.kdnab`. Legacy source folders
764
+ // are migrated explicitly with --from-folder; they are not runtime assets.
844
765
  const importedCards = [];
845
- if (entries.has('payload.kdnab')) {
846
- try {
847
- const payload = decodePayload(entries.get('payload.kdnab'), manifest);
848
- importedCards.push(...cardsFromPayload(payload));
849
- } catch (e) {
850
- fail(`Could not import cards from payload.kdnab: ${e.message}`);
851
- }
852
- } else if (entries.has('KDNA_Core.json') || entries.has('KDNA_Patterns.json')) {
853
- // Source-tree fallback — used when an imported source has no payload.kdnab
854
- // (i.e. the asset predates the 0.7 split). Bug (#67): prior
855
- // version only handled axiom / ontology / boundary / misunderstanding
856
- // / self_check — 5 of the 14 card types — so every other type
857
- // (risk / stance / framework / term / banned_term / aesthetic /
858
- // scenario / case / reasoning / evolution_stage) round-tripped
859
- // out as zero cards. The fix threads every type this CLI can
860
- // import through, and prefers the same field-shape rules as
861
- // cardsFromLegacyPayload so the round-trip is symmetric.
862
- if (entries.has('KDNA_Core.json')) {
863
- const core = JSON.parse(entries.get('KDNA_Core.json').toString());
864
- for (const ax of (Array.isArray(core.axioms) ? core.axioms : [])) {
865
- const fields = {};
866
- for (const k of ['one_sentence','full_statement','why','applies_when','does_not_apply_when','failure_risk','confidence','evidence_type']) {
867
- if (k in ax) fields[k] = ax[k];
868
- }
869
- importedCards.push(cardApi.createCard('axiom', fields));
870
- }
871
- for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
872
- importedCards.push(cardApi.createCard('ontology', {
873
- one_sentence: ont.one_sentence || ont.essence || '',
874
- essence: ont.essence || '', boundary: ont.boundary || '',
875
- trigger_signal: ont.trigger_signal || '',
876
- }));
877
- }
878
- for (const b of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
879
- importedCards.push(cardApi.createCard('boundary', {
880
- scope: b.scope || '', out_of_scope: b.out_of_scope || '',
881
- acceptable_exceptions: b.acceptable_exceptions || [],
882
- }));
883
- }
884
- // Accept risks as a top-level array (`risks`) or wrapped in `risk_model`
885
- // (either as `{risks: [...]}` or as an array itself). Uses the same
886
- // Array.isArray ladder pattern as the safe path at lines 1033-1040.
887
- let risksSource = core.risks;
888
- if (!Array.isArray(risksSource)) {
889
- if (Array.isArray(core.risk_model)) risksSource = core.risk_model;
890
- else if (Array.isArray(core.risk_model?.risks)) risksSource = core.risk_model.risks;
891
- else risksSource = [];
892
- }
893
- for (const risk of risksSource) {
894
- importedCards.push(cardApi.createCard('risk', risk.fields || risk));
895
- }
896
- for (const stance of (Array.isArray(core.stances) ? core.stances : [])) {
897
- importedCards.push(cardApi.createCard('stance', stance.fields || stance));
898
- }
899
- for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
900
- importedCards.push(cardApi.createCard('framework', fw.fields || fw));
901
- }
902
- }
903
- if (entries.has('KDNA_Patterns.json')) {
904
- const pat = JSON.parse(entries.get('KDNA_Patterns.json').toString());
905
- for (const ms of (Array.isArray(pat.misunderstandings) ? pat.misunderstandings : [])) {
906
- importedCards.push(cardApi.createCard('misunderstanding', {
907
- wrong: ms.wrong || '', correct: ms.correct || '',
908
- key_distinction: ms.key_distinction || '', why: ms.why || '',
909
- }));
910
- }
911
- for (const sc of (Array.isArray(pat.self_check) ? pat.self_check : [])) {
912
- const q = typeof sc === 'string' ? sc : sc.question || '';
913
- importedCards.push(cardApi.createCard('self_check', { question: q }));
914
- }
915
- for (const aesthetic of (Array.isArray(pat.aesthetics) ? pat.aesthetics : [])) {
916
- importedCards.push(cardApi.createCard('aesthetic', aesthetic.fields || aesthetic));
917
- }
918
- for (const term of (Array.isArray(pat.terminology?.standard_terms) ? pat.terminology?.standard_terms : [])) {
919
- importedCards.push(cardApi.createCard('term', term));
920
- }
921
- for (const banned of (Array.isArray(pat.terminology?.banned_terms) ? pat.terminology?.banned_terms : [])) {
922
- importedCards.push(cardApi.createCard('banned_term', banned));
923
- }
924
- }
925
- if (entries.has('KDNA_Scenarios.json')) {
926
- const scen = JSON.parse(entries.get('KDNA_Scenarios.json').toString());
927
- for (const s of (Array.isArray(scen.scenes) ? scen.scenes : [])) {
928
- importedCards.push(cardApi.createCard('scenario', s));
929
- }
930
- }
931
- if (entries.has('KDNA_Cases.json')) {
932
- const cases = JSON.parse(entries.get('KDNA_Cases.json').toString());
933
- for (const c of (Array.isArray(cases.cases) ? cases.cases : [])) {
934
- importedCards.push(cardApi.createCard('case', c));
935
- }
936
- }
937
- if (entries.has('KDNA_Reasoning.json')) {
938
- const reasoning = JSON.parse(entries.get('KDNA_Reasoning.json').toString());
939
- for (const chain of (Array.isArray(reasoning.reasoning_chains) ? reasoning.reasoning_chains : [])) {
940
- importedCards.push(cardApi.createCard('reasoning', chain));
941
- }
942
- }
943
- if (entries.has('KDNA_Evolution.json')) {
944
- const evolution = JSON.parse(entries.get('KDNA_Evolution.json').toString());
945
- for (const stage of (Array.isArray(evolution.stages) ? evolution.stages : [])) {
946
- importedCards.push(cardApi.createCard('evolution_stage', stage));
947
- }
948
- }
766
+ if (!entries.has('payload.kdnab')) {
767
+ fail('Not a current .kdna asset: missing payload.kdnab. Use --from-folder for legacy JSON source migration.');
768
+ }
769
+ try {
770
+ const payload = decodePayload(entries.get('payload.kdnab'), manifest);
771
+ importedCards.push(...cardsFromPayload(payload));
772
+ } catch (e) {
773
+ fail(`Could not import cards from payload.kdnab: ${e.message}`);
949
774
  }
950
775
 
951
776
  if (importedCards.length === 0) {
952
- fail(`No cards could be extracted from ${absKdna}. Expected payload.kdnab or an importable KDNA_Core.json + KDNA_Patterns.json source tree.`);
777
+ fail(`No current judgment cards could be extracted from ${absKdna}.`);
953
778
  }
954
779
 
955
780
  // Bug #15 / #28 follow-up: surface the source's KDNA_Patterns /
@@ -958,23 +783,13 @@ function importFromKdna(kdnaPath) {
958
783
  // compileDomain via exportRuntimeAsset, which preserves
959
784
  // changelog / version_notes / reasoning_chains / banned_terms
960
785
  // through the round-trip.
961
- const sourcePatterns = entries.has('KDNA_Patterns.json')
962
- ? JSON.parse(entries.get('KDNA_Patterns.json').toString())
963
- : null;
964
- const sourceReasoning = entries.has('KDNA_Reasoning.json')
965
- ? JSON.parse(entries.get('KDNA_Reasoning.json').toString())
966
- : null;
967
- const sourceEvolution = entries.has('KDNA_Evolution.json')
968
- ? JSON.parse(entries.get('KDNA_Evolution.json').toString())
969
- : null;
970
-
971
786
  return {
972
787
  lineage,
973
788
  cards: importedCards,
974
789
  source_manifest: manifest,
975
- source_patterns: sourcePatterns,
976
- source_reasoning: sourceReasoning,
977
- source_evolution: sourceEvolution,
790
+ source_patterns: null,
791
+ source_reasoning: null,
792
+ source_evolution: null,
978
793
  };
979
794
  }
980
795
 
@@ -1006,6 +821,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1006
821
  if (manifest) {
1007
822
  manifestData.raw = manifest;
1008
823
  if (manifest.version) manifestData.version = manifest.version;
824
+ if (manifest.asset_uid) manifestData.asset_uid = manifest.asset_uid;
1009
825
  if (manifest.judgment_version) manifestData.judgment_version = manifest.judgment_version;
1010
826
  if (manifest.languages) manifestData.languages = manifest.languages;
1011
827
  if (manifest.default_language) manifestData.default_language = manifest.default_language;
@@ -1016,8 +832,8 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1016
832
  if (manifest.author) manifestData.author = manifest.author;
1017
833
  if (manifest.asset_type) manifestData.asset_type = manifest.asset_type;
1018
834
  if (manifest.core_insight) manifestData.core_insight = manifest.core_insight;
1019
- if (manifest.created) manifestData.created = manifest.created;
1020
- if (manifest.updated) manifestData.updated = manifest.updated;
835
+ if (manifest.created_at || manifest.created) manifestData.created = manifest.created_at || manifest.created;
836
+ if (manifest.updated_at || manifest.updated) manifestData.updated = manifest.updated_at || manifest.updated;
1021
837
  if (manifest.risk_level) manifestData.risk_level = manifest.risk_level;
1022
838
  if (manifest.fitness_for_purpose) manifestData.fitness_for_purpose = manifest.fitness_for_purpose;
1023
839
  }
@@ -1054,20 +870,20 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1054
870
  if (key in ax) fields[key] = ax[key];
1055
871
  else audit.missingFields.push(`axiom.${ax.id || '?'}.${key}`);
1056
872
  }
1057
- importedCards.push(cardApi.createCard('axiom', fields));
873
+ importedCards.push(cardApi.createCard('axiom', fields, ax.id || null));
1058
874
  }
1059
875
  for (const ont of (Array.isArray(core.ontology) ? core.ontology : [])) {
1060
876
  importedCards.push(cardApi.createCard('ontology', {
1061
877
  one_sentence: ont.one_sentence || ont.essence || '',
1062
878
  essence: ont.essence || '', boundary: ont.boundary || '',
1063
879
  trigger_signal: ont.trigger_signal || '',
1064
- }));
880
+ }, ont.id || null));
1065
881
  }
1066
882
  for (const b of (Array.isArray(core.boundaries) ? core.boundaries : [])) {
1067
883
  importedCards.push(cardApi.createCard('boundary', {
1068
884
  scope: b.scope || '', out_of_scope: b.out_of_scope || '',
1069
885
  acceptable_exceptions: b.acceptable_exceptions || [],
1070
- }));
886
+ }, b.id || null));
1071
887
  }
1072
888
  // Accept risks as a top-level array (`risks`) or wrapped in `risk_model`
1073
889
  // (either as `{risks: [...]}` or as an array itself).
@@ -1078,7 +894,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1078
894
  else risksSource = [];
1079
895
  }
1080
896
  for (const r of risksSource) {
1081
- importedCards.push(cardApi.createCard('risk', r.fields || r));
897
+ importedCards.push(cardApi.createCard('risk', r.fields || r, r.id || null));
1082
898
  }
1083
899
  // NEW: import stances
1084
900
  for (const s of (Array.isArray(core.stances) ? core.stances : [])) {
@@ -1087,14 +903,14 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1087
903
  statement: text,
1088
904
  applies_when: s.applies_when || [],
1089
905
  does_not_apply_when: s.does_not_apply_when || [],
1090
- }));
906
+ }, typeof s === 'object' ? s.id || null : null));
1091
907
  }
1092
908
  // NEW: import frameworks
1093
909
  for (const fw of (Array.isArray(core.frameworks) ? core.frameworks : [])) {
1094
910
  importedCards.push(cardApi.createCard('framework', {
1095
911
  name: fw.name || '', when_to_use: fw.when_to_use || '',
1096
912
  steps: fw.steps || [],
1097
- }));
913
+ }, fw.id || null));
1098
914
  }
1099
915
  }
1100
916
 
@@ -1104,23 +920,27 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1104
920
  for (const t of (Array.isArray(patterns.terminology.standard_terms) ? patterns.terminology.standard_terms : [])) {
1105
921
  importedCards.push(cardApi.createCard('term', {
1106
922
  term: t.term || '', definition: t.definition || '',
1107
- }));
923
+ }, t.id || null));
1108
924
  }
1109
925
  for (const bt of (Array.isArray(patterns.terminology.banned_terms) ? patterns.terminology.banned_terms : [])) {
1110
926
  importedCards.push(cardApi.createCard('banned_term', {
1111
927
  term: bt.term || '', why: bt.why || '', replace_with: bt.replace_with || '',
1112
- }));
928
+ }, bt.id || null));
1113
929
  }
1114
930
  }
1115
931
  for (const ms of (Array.isArray(patterns.misunderstandings) ? patterns.misunderstandings : [])) {
1116
932
  importedCards.push(cardApi.createCard('misunderstanding', {
1117
933
  wrong: ms.wrong || '', correct: ms.correct || '',
1118
934
  key_distinction: ms.key_distinction || '', why: ms.why || '',
1119
- }));
935
+ }, ms.id || null));
1120
936
  }
1121
937
  for (const sc of (Array.isArray(patterns.self_check) ? patterns.self_check : [])) {
1122
938
  const q = typeof sc === 'string' ? sc : sc.question || '';
1123
- importedCards.push(cardApi.createCard('self_check', { question: q }));
939
+ importedCards.push(cardApi.createCard(
940
+ 'self_check',
941
+ { question: q },
942
+ typeof sc === 'object' ? sc.id || null : null,
943
+ ));
1124
944
  }
1125
945
  // Bug (aesthetic round-trip): prior version dropped
1126
946
  // `patterns.aesthetics` on import. `cardsFromRuntimePayload` and
@@ -1129,7 +949,11 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1129
949
  // them when imported through `create --from-folder`. The fix
1130
950
  // adds the missing loop here.
1131
951
  for (const aesthetic of (Array.isArray(patterns.aesthetics) ? patterns.aesthetics : [])) {
1132
- importedCards.push(cardApi.createCard('aesthetic', aesthetic.fields || aesthetic));
952
+ importedCards.push(cardApi.createCard(
953
+ 'aesthetic',
954
+ aesthetic.fields || aesthetic,
955
+ aesthetic.id || null,
956
+ ));
1133
957
  }
1134
958
  }
1135
959
 
@@ -1139,7 +963,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1139
963
  importedCards.push(cardApi.createCard('scenario', {
1140
964
  name: scene.name || scene.id || '',
1141
965
  trigger: scene.trigger || '', action: scene.action || '',
1142
- }));
966
+ }, scene.id || null));
1143
967
  }
1144
968
  }
1145
969
 
@@ -1149,7 +973,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1149
973
  importedCards.push(cardApi.createCard('case', {
1150
974
  title: c.title || c.id || '',
1151
975
  scenario: c.scenario || '', expected: c.expected || '',
1152
- }));
976
+ }, c.id || null));
1153
977
  }
1154
978
  }
1155
979
 
@@ -1170,7 +994,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1170
994
  concrete_action: rc.concrete_action || rc.so_what || '',
1171
995
  so_what: rc.so_what || rc.concrete_action || '',
1172
996
  conclusion: rc.conclusion || '',
1173
- }));
997
+ }, rc.id || null));
1174
998
  }
1175
999
  }
1176
1000
 
@@ -1194,6 +1018,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1194
1018
  lineage: { type: 'migrated', parent_name: manifestData.name || null, parent_asset_uid: null, parent_version: manifestData.version || null, parent_asset_digest: null },
1195
1019
  });
1196
1020
  // Preserve manifest metadata
1021
+ if (manifestData.asset_uid) project.asset_uid = manifestData.asset_uid;
1197
1022
  if (manifestData.version) {
1198
1023
  if (!project.release) project.release = {};
1199
1024
  project.release.version = manifestData.version;
@@ -1992,17 +1817,7 @@ function exportProject(project, name, outPath, opts = {}) {
1992
1817
  );
1993
1818
  }
1994
1819
  }
1995
- const gate = projectApi.checkHumanLockGate(project);
1996
- if (gate.blocked) {
1997
- if (opts.allowIncomplete) {
1998
- console.warn('Human Lock Gate bypassed (--allow-incomplete):');
1999
- for (const issue of gate.issues) console.warn(` - ${issue.cardId}: ${issue.reason}`);
2000
- } else {
2001
- const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
2002
- fail(`Human Lock Gate blocked export:\n - ${reasons}\nUse --allow-incomplete to bypass.`, EXIT.HUMAN_LOCK_REQUIRED);
2003
- }
2004
- }
2005
- const lockedCards = (project.cards || []).filter(c => c.locked);
1820
+ const exportedCards = (project.cards || []).filter(c => c.status !== 'deprecated');
2006
1821
  const runtimeDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-runtime-')));
2007
1822
  try {
2008
1823
  const manifestDefaults = buildManifest(project, name);
@@ -2021,7 +1836,7 @@ function exportProject(project, name, outPath, opts = {}) {
2021
1836
  const vr = core.validate(outPath);
2022
1837
  if (!vr.overall_valid) fail('runtime export validation failed: ' + (vr.problems || []).join('; '));
2023
1838
  console.log('Exported: ' + outPath);
2024
- console.log(' Name: ' + name + ' Cards: ' + lockedCards.length + ' Validated: all gates pass');
1839
+ console.log(' Name: ' + name + ' Cards: ' + exportedCards.length + ' Validated: runtime contract passed');
2025
1840
  } finally {
2026
1841
  try { fs.rmSync(runtimeDir, { recursive: true }); } catch { /* cleanup */ }
2027
1842
  }
@@ -2029,11 +1844,6 @@ function exportProject(project, name, outPath, opts = {}) {
2029
1844
 
2030
1845
  function compileProject(projectInput) {
2031
1846
  const { project } = readProject(projectInput);
2032
- const gate = projectApi.checkHumanLockGate(project);
2033
- if (gate.blocked) {
2034
- const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
2035
- fail(`Human Lock Gate blocked compile:\n - ${reasons}`, EXIT.HUMAN_LOCK_REQUIRED);
2036
- }
2037
1847
  return { project, result: compileApi.compileDomain(project) };
2038
1848
  }
2039
1849
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-studio-cli",
3
- "version": "0.8.17",
3
+ "version": "0.9.1",
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": {
@@ -41,8 +41,8 @@
41
41
  "node": ">=18"
42
42
  },
43
43
  "dependencies": {
44
- "@aikdna/kdna-core": "0.15.12",
45
- "@aikdna/kdna-studio-core": "1.7.13",
44
+ "@aikdna/kdna-core": "0.16.0",
45
+ "@aikdna/kdna-studio-core": "1.8.0",
46
46
  "ajv": "^8.20.0",
47
47
  "ajv-formats": "^3.0.1",
48
48
  "cbor-x": "^1.6.4"