@aikdna/kdna-studio-cli 0.8.13 → 0.8.15

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
@@ -50,13 +50,13 @@ kdna-studio card add my_domain axiom \
50
50
  --field does_not_apply_when='["pure formatting"]' \
51
51
  --field failure_risk="generic advice"
52
52
  kdna-studio card approve my_domain --all --by expert --statement "I confirm this judgment."
53
- kdna-studio export my_domain --format v1 --out dist/my_domain.kdna
53
+ kdna-studio export my_domain --out dist/my_domain.kdna
54
54
  ```
55
55
 
56
56
  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.
57
57
 
58
58
  The current Studio CLI export workflow uses approved cards as release evidence.
59
- This is Studio project policy, not a KDNA Core v1 format-validity rule. Human
59
+ This is Studio project policy, not a KDNA format-validity rule. Human
60
60
  Lock and other provenance records are optional review evidence, not validity
61
61
  requirements.
62
62
 
@@ -74,7 +74,7 @@ loading existing `.kdna` assets.
74
74
 
75
75
  ## 5-minute first asset
76
76
 
77
- If you just installed and want to ship a v1 asset without an LLM:
77
+ If you just installed and want to ship a KDNA asset without an LLM:
78
78
 
79
79
  ```bash
80
80
  # 1. Init identity (required for the export signature)
@@ -97,8 +97,8 @@ kdna-studio card add . axiom \
97
97
  # 4. Approve and lock
98
98
  kdna-studio card approve . --all --by me --statement "i confirm"
99
99
 
100
- # 5. Export the v1 asset
101
- kdna-studio export . --format v1 --out my_domain.kdna
100
+ # 5. Export the asset
101
+ kdna-studio export . --out my_domain.kdna
102
102
 
103
103
  # 6. Verify with the runtime CLI
104
104
  kdna load my_domain.kdna --profile=compact
@@ -115,11 +115,11 @@ kdna-studio llm config --provider openai --model gpt-4 --key <your-key>
115
115
 
116
116
  ## Runtime Export Contract
117
117
 
118
- `kdna-studio export --format v1` is the canonical runtime export path. It uses
119
- `@aikdna/kdna-studio-core` to compile the Studio project into a KDNA Core v1
118
+ `kdna-studio export` is the canonical runtime export path. It uses
119
+ `@aikdna/kdna-studio-core` to compile the Studio project into the current KDNA
120
120
  runtime asset and then packs it with `@aikdna/kdna-core`.
121
121
 
122
- A KDNA Core v1 runtime export contains only these top-level entries:
122
+ A KDNA runtime export contains only these top-level entries:
123
123
 
124
124
  - `mimetype`
125
125
  - `kdna.json`
@@ -82,7 +82,7 @@ Create (three entry paths):
82
82
  kdna-studio create <project-dir> --from-folder <source-dir> --name <@scope/name>
83
83
 
84
84
  Migrate (dev source or Studio project → canonical .kdna in one command):
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]
85
+ kdna-studio migrate <source-dir|project> --out <file.kdna> --name <@scope/name> --by <id> --statement <text> [--allow-incomplete] [--sign] [--passphrase <pw>|--passphrase-stdin]
86
86
  kdna-studio migrate <source-dir> --check --name <@scope/name> # pre-flight: report which fields would block the export without writing the .kdna
87
87
  kdna-studio audit-locks <project> [--type axiom|risk|stance|...] [--json] # list cards with missing Human Lock fields (per-card-type, per-field)
88
88
 
@@ -97,7 +97,7 @@ Authoring:
97
97
  kdna-studio card approve <project> <card-id|--all> --by <id> --statement <text> [--sign] [--passphrase <pass>]
98
98
  kdna-studio card unlock <project> <card-id> --by <id> --statement <text>
99
99
  kdna-studio compile <project> --out <dir>
100
- kdna-studio export <project> --format v1 --out <file.kdna> [--allow-incomplete] [--password <pw>|--password-stdin]
100
+ kdna-studio export <project> --out <file.kdna> [--allow-incomplete] [--password <pw>|--password-stdin]
101
101
  kdna-studio export-route-card <domain-id> [--out=<path>] # Export route-card sidecar skeleton
102
102
  kdna-studio export-consumer-index [--entries=<domain-ids>] [--out=<path>] # Export consumer-index skeleton
103
103
 
@@ -341,6 +341,13 @@ function titleFromName(name, assetId) {
341
341
  return assetId;
342
342
  }
343
343
 
344
+ function packageNameFromManifest(manifest = {}) {
345
+ if (manifest.name) return manifest.name;
346
+ const match = String(manifest.asset_id || '').match(/^kdna:([^:]+):(.+)$/);
347
+ if (match) return `@${match[1]}/${match[2]}`;
348
+ return manifest.title || manifest.asset_id || null;
349
+ }
350
+
344
351
  function publicManifestMetadata(raw = {}) {
345
352
  const copy = { ...raw };
346
353
  delete copy.registry;
@@ -367,8 +374,8 @@ function importedCard(type, fields = {}, id = null) {
367
374
  }
368
375
 
369
376
  // Bug (#3 + #4 UX): payload.kdnab intentionally carries the same
370
- // misunderstanding card in two places — `patterns[]` (the v1 schema's
371
- // primary card list) and `reasoning.failure_modes[]` (the v1 schema's
377
+ // misunderstanding card in two places — `patterns[]` (the runtime schema's
378
+ // primary card list) and `reasoning.failure_modes[]` (the runtime schema's
372
379
  // "what bad judgment looks like" view). Both share the same `id`.
373
380
  // Prior version imported both, doubling every misunderstanding on
374
381
  // re-import. The dedup is by id: when a card with the same id is
@@ -409,15 +416,15 @@ function decodePayload(bytes, manifest = {}) {
409
416
  return cbor.decode(raw);
410
417
  }
411
418
 
412
- // V1 path: trust the producer (export-runtime) to have already shaped
413
- // each card's fields. The v1 producer normalises everything into a
419
+ // Runtime path: trust the producer (export-runtime) to have already shaped
420
+ // each card's fields. The producer normalises everything into a
414
421
  // canonical shape, so we just round-trip the raw object. Bug (#57):
415
422
  // the legacy path below explicitly constructs each field. The two
416
- // paths cannot be unified without losing either v1's per-field
423
+ // paths cannot be unified without losing either runtime per-field
417
424
  // guarantees or legacy's defensive defaults — the asymmetry is
418
425
  // intentional. Documented here so a future reader does not "fix" it
419
426
  // by collapsing one path into the other.
420
- function cardsFromV1Payload(payload) {
427
+ function cardsFromRuntimePayload(payload) {
421
428
  resetImportedIds();
422
429
  const cards = [];
423
430
  for (const sourceCard of (Array.isArray(payload.source_cards) ? payload.source_cards : [])) {
@@ -462,7 +469,7 @@ function cardsFromV1Payload(payload) {
462
469
  }
463
470
  for (const pattern of (Array.isArray(payload.patterns) ? payload.patterns : [])) {
464
471
  // Bug (#146): prior version dropped any payload.patterns entry
465
- // that lacked an explicit `type` field. The v1 schema lists
472
+ // that lacked an explicit `type` field. The runtime schema lists
466
473
  // misunderstandings in patterns[] without a `type` discriminator
467
474
  // (their shape is `{wrong, correct, key_distinction, why}`),
468
475
  // and several exporters omit `type` to keep the on-the-wire
@@ -518,8 +525,8 @@ function cardsFromV1Payload(payload) {
518
525
  for (const failureMode of (Array.isArray(payload.reasoning?.failure_modes) ? payload.reasoning?.failure_modes : [])) {
519
526
  // Bug (#145 follow-up): the prior version dropped three
520
527
  // judgment fields (failure_risk, applies_when, does_not_apply_when)
521
- // when reading failure_modes back out of a v1 asset. A Studio
522
- // project that round-tripped through `migrate --format v1` lost
528
+ // when reading failure_modes back out of an asset. A Studio
529
+ // project that round-tripped through `migrate` lost
523
530
  // these fields every time. The fix reads them through, matching
524
531
  // the export-side map in kdna-studio-core's buildPayload.
525
532
  pushImportedCard(cards, 'misunderstanding', {
@@ -640,7 +647,7 @@ function cardsFromLegacyPayload(payload) {
640
647
  function cardsFromPayload(payload) {
641
648
  if (!payload || typeof payload !== 'object') return [];
642
649
  if (payload.profile === 'judgment-profile-v1' || payload.core || payload.source_cards) {
643
- return cardsFromV1Payload(payload);
650
+ return cardsFromRuntimePayload(payload);
644
651
  }
645
652
  if (payload.kind === 'kdna.payload' || payload.judgment) {
646
653
  return cardsFromLegacyPayload(payload);
@@ -648,13 +655,13 @@ function cardsFromPayload(payload) {
648
655
  return [];
649
656
  }
650
657
 
651
- function buildV1Manifest(project, name) {
658
+ function buildManifest(project, name) {
652
659
  // Bug (#58): prior version was a hand-rolled manifest builder that
653
660
  // shared zero logic with the canonical buildManifest in
654
661
  // @aikdna/kdna-studio-core/src/export-runtime. The two paths
655
662
  // diverged on every field added since 1.0 (lineage, load_contract,
656
663
  // authoring block, etc.). The fix delegates the manifest shape to
657
- // kdna-studio-core's buildManifest so the v1 path is the single
664
+ // kdna-studio-core's buildManifest so runtime export has a single
658
665
  // source of truth.
659
666
  //
660
667
  // We still need a default manifest here (the exportRuntimeAsset
@@ -821,19 +828,19 @@ function importFromKdna(kdnaPath) {
821
828
  const manifest = JSON.parse(entries.get('kdna.json').toString());
822
829
  const lineage = {
823
830
  type: 'fork',
824
- parent_name: manifest.name || manifest.title || manifest.asset_id || null,
831
+ parent_name: packageNameFromManifest(manifest),
825
832
  parent_asset_uid: manifest.asset_uid || null,
826
833
  parent_version: manifest.version || null,
827
834
  parent_asset_digest: manifest.content_digest || manifest.asset_digest || null,
828
835
  };
829
836
 
830
- // Extract cards. Modern v1 packages carry `kdna.json` + `payload.kdnab`
837
+ // Extract cards. Current packages carry `kdna.json` + `payload.kdnab`
831
838
  // and use the unified judgment profile. Legacy packages split content
832
839
  // across KDNA_*.json files. Prefer payload.kdnab when present — it
833
840
  // covers all 8+ card types (axiom / boundary / risk / aesthetic /
834
841
  // ontology / misunderstanding / self_check / scenario / case /
835
842
  // reasoning / evolution_stage / stance / framework / term / banned_term)
836
- // via cardsFromPayload / cardsFromV1Payload.
843
+ // via cardsFromRuntimePayload / cardsFromLegacyPayload.
837
844
  const importedCards = [];
838
845
  if (entries.has('payload.kdnab')) {
839
846
  try {
@@ -843,7 +850,7 @@ function importFromKdna(kdnaPath) {
843
850
  fail(`Could not import cards from payload.kdnab: ${e.message}`);
844
851
  }
845
852
  } else if (entries.has('KDNA_Core.json') || entries.has('KDNA_Patterns.json')) {
846
- // Legacy fallback — used when the v1 asset has no payload.kdnab
853
+ // Source-tree fallback — used when an imported source has no payload.kdnab
847
854
  // (i.e. the asset predates the 0.7 split). Bug (#67): prior
848
855
  // version only handled axiom / ontology / boundary / misunderstanding
849
856
  // / self_check — 5 of the 14 card types — so every other type
@@ -942,7 +949,7 @@ function importFromKdna(kdnaPath) {
942
949
  }
943
950
 
944
951
  if (importedCards.length === 0) {
945
- fail(`No cards could be extracted from ${absKdna}. Expected payload.kdnab (v1) or KDNA_Core.json + KDNA_Patterns.json (legacy).`);
952
+ fail(`No cards could be extracted from ${absKdna}. Expected payload.kdnab or an importable KDNA_Core.json + KDNA_Patterns.json source tree.`);
946
953
  }
947
954
 
948
955
  // Bug #15 / #28 follow-up: surface the source's KDNA_Patterns /
@@ -1116,7 +1123,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
1116
1123
  importedCards.push(cardApi.createCard('self_check', { question: q }));
1117
1124
  }
1118
1125
  // Bug (aesthetic round-trip): prior version dropped
1119
- // `patterns.aesthetics` on import. `cardsFromV1Payload` and
1126
+ // `patterns.aesthetics` on import. `cardsFromRuntimePayload` and
1120
1127
  // `cardsFromLegacyPayload` both read the field, so a domain
1121
1128
  // that authored aesthetic cards in either format would lose
1122
1129
  // them when imported through `create --from-folder`. The fix
@@ -1781,7 +1788,7 @@ function cmdMigrate(args) {
1781
1788
  // attempted the full pipeline — read source folder, import to
1782
1789
  // Studio project, lock all cards, then export. An author migrating
1783
1790
  // 20 source-tree assets had no way to know in advance whether
1784
- // the assets would pass the v1.7.2+ Human Lock gate. The fix
1791
+ // the assets would pass the current Human Lock gate. The fix
1785
1792
  // adds `--check` which runs the import + the critical-missing
1786
1793
  // check + the lock-gate check, then prints a report and exits
1787
1794
  // without writing the .kdna file. Pair with `audit-locks` for
@@ -1806,7 +1813,9 @@ function cmdMigrate(args) {
1806
1813
  }
1807
1814
 
1808
1815
  const sourcePath = path.resolve(sourceDir);
1809
- const format = option(args, '--format');
1816
+ if (option(args, '--format')) {
1817
+ fail('The --format option is not supported. KDNA has one current asset format.');
1818
+ }
1810
1819
  const isStudioProject = fs.existsSync(path.join(sourcePath, 'studio.project.json'));
1811
1820
  let tmpDir = null;
1812
1821
  let projectPath = null;
@@ -1814,14 +1823,14 @@ function cmdMigrate(args) {
1814
1823
 
1815
1824
  // Wrap everything in try / finally so a mid-migrate failure (e.g. missing
1816
1825
  // critical fields, gate blocked, signing error) still cleans up tmpDir.
1817
- // Bug: prior code only cleaned up on the v1 and v2 success paths; any
1818
- // non-v1 export path that threw leaked `/tmp/kdna-migrate-*` entries
1826
+ // Bug: prior code only cleaned up on its success paths; any
1827
+ // older export path that threw leaked `/tmp/kdna-migrate-*` entries
1819
1828
  // indefinitely.
1820
1829
  try {
1821
1830
  // Step 1: import dev source, or use an existing Studio project directly.
1822
1831
  let creatorIdentity = null;
1823
1832
  try { creatorIdentity = creatorApi.loadIdentity(); } catch { /* proceed without */ }
1824
- if (format === 'v1' && isStudioProject) {
1833
+ if (isStudioProject) {
1825
1834
  const loaded = readProject(sourcePath);
1826
1835
  project = loaded.project;
1827
1836
  projectPath = loaded.projectPath;
@@ -1938,34 +1947,8 @@ function cmdMigrate(args) {
1938
1947
 
1939
1948
  const absOut = path.resolve(out);
1940
1949
  const allowIncomplete = args.includes('--allow-incomplete');
1941
- // Step 4: v1 export or v2 compile + export
1942
- if (format === 'v1') {
1943
- exportProjectV1(project, name, absOut, { allowIncomplete });
1944
- if (tmpDir) {
1945
- try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
1946
- }
1947
- return;
1948
- }
1949
- const { result } = compileProject(tmpDir);
1950
- const files = { ...result.files };
1951
- files['README.md'] = compileApi.generateReadme(project);
1952
- files.LICENSE = project.license?.type || 'UNSPECIFIED';
1953
- files.mimetype = 'application/vnd.aikdna.kdna+zip';
1954
-
1955
- const entries = [['mimetype', files.mimetype]];
1956
- for (const name of Object.keys(files).filter(k => k !== 'mimetype').sort()) {
1957
- entries.push([name, files[name]]);
1958
- }
1959
- const zip = buildZip(entries);
1960
- fs.mkdirSync(path.dirname(absOut), { recursive: true });
1961
- fs.writeFileSync(absOut, zip);
1962
- console.log(`Exported: ${absOut}`);
1963
- console.log(` Name: ${name}`);
1964
- console.log(` Cards: ${project.cards.length} (${locked} locked)`);
1965
- console.log(` Files: ${Object.keys(files).length}`);
1966
- console.log(` Build ID: ${result.identity.build_id}`);
1967
-
1968
- // Cleanup temp
1950
+ // Step 4: export the canonical runtime asset.
1951
+ exportProject(project, name, absOut, { allowIncomplete });
1969
1952
  if (tmpDir) {
1970
1953
  try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* best effort */ }
1971
1954
  tmpDir = null;
@@ -1982,13 +1965,13 @@ function cmdMigrate(args) {
1982
1965
  }
1983
1966
  }
1984
1967
 
1985
- function exportProjectV1(project, name, outPath, opts = {}) {
1968
+ function exportProject(project, name, outPath, opts = {}) {
1986
1969
  let core;
1987
1970
  try { core = require('@aikdna/kdna-core'); } catch (e) {
1988
- fail('@aikdna/kdna-core@0.11.0+ is required for v1 export.', 2);
1971
+ fail('@aikdna/kdna-core is required for runtime export.', 2);
1989
1972
  }
1990
1973
  if (!exportRuntime || typeof exportRuntime.exportRuntimeAsset !== 'function') {
1991
- fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for v1 export.', 2);
1974
+ fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for runtime export.', 2);
1992
1975
  }
1993
1976
  const criticalIssues = criticalAxiomIssues(project);
1994
1977
  if (criticalIssues.length > 0) {
@@ -1999,7 +1982,7 @@ function exportProjectV1(project, name, outPath, opts = {}) {
1999
1982
  );
2000
1983
  } else {
2001
1984
  fail(
2002
- `Cannot export v1: ${criticalIssues.length} critical axiom field issue(s).\n` +
1985
+ `Cannot export: ${criticalIssues.length} critical axiom field issue(s).\n` +
2003
1986
  ` These fields are required for domain routing (kdna-loader uses them to\n` +
2004
1987
  ` decide when to load this domain). Fix them first, or pass\n` +
2005
1988
  ` --allow-incomplete to bypass.\n` +
@@ -2016,13 +1999,13 @@ function exportProjectV1(project, name, outPath, opts = {}) {
2016
1999
  for (const issue of gate.issues) console.warn(` - ${issue.cardId}: ${issue.reason}`);
2017
2000
  } else {
2018
2001
  const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
2019
- fail(`Human Lock Gate blocked v1 export:\n - ${reasons}\nUse --allow-incomplete to bypass.`, EXIT.HUMAN_LOCK_REQUIRED);
2002
+ fail(`Human Lock Gate blocked export:\n - ${reasons}\nUse --allow-incomplete to bypass.`, EXIT.HUMAN_LOCK_REQUIRED);
2020
2003
  }
2021
2004
  }
2022
2005
  const lockedCards = (project.cards || []).filter(c => c.locked);
2023
- const v1Dir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-v1-')));
2006
+ const runtimeDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-runtime-')));
2024
2007
  try {
2025
- const manifestDefaults = buildV1Manifest(project, name);
2008
+ const manifestDefaults = buildManifest(project, name);
2026
2009
  const runtimeAsset = exportRuntime.exportRuntimeAsset(project, {
2027
2010
  asset_id: manifestDefaults.asset_id,
2028
2011
  asset_uid: manifestDefaults.asset_uid,
@@ -2032,15 +2015,15 @@ function exportProjectV1(project, name, outPath, opts = {}) {
2032
2015
  access: project.release?.access || project.source_manifest?.access || 'public',
2033
2016
  password: opts.password || undefined,
2034
2017
  });
2035
- writeFiles(v1Dir, runtimeAsset.files);
2018
+ writeFiles(runtimeDir, runtimeAsset.files);
2036
2019
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
2037
- core.pack(v1Dir, outPath);
2020
+ core.pack(runtimeDir, outPath);
2038
2021
  const vr = core.validate(outPath);
2039
- if (!vr.overall_valid) fail('v1 export validation failed: ' + (vr.problems || []).join('; '));
2040
- console.log('Exported (v1): ' + outPath);
2022
+ if (!vr.overall_valid) fail('runtime export validation failed: ' + (vr.problems || []).join('; '));
2023
+ console.log('Exported: ' + outPath);
2041
2024
  console.log(' Name: ' + name + ' Cards: ' + lockedCards.length + ' Validated: all gates pass');
2042
2025
  } finally {
2043
- try { fs.rmSync(v1Dir, { recursive: true }); } catch { /* cleanup */ }
2026
+ try { fs.rmSync(runtimeDir, { recursive: true }); } catch { /* cleanup */ }
2044
2027
  }
2045
2028
  }
2046
2029
 
@@ -2064,178 +2047,17 @@ function cmdCompile(args) {
2064
2047
  console.log(`Build ID: ${result.identity.build_id}`);
2065
2048
  }
2066
2049
 
2067
- function crc32(data) {
2068
- let crc = ~0;
2069
- for (const byte of data) {
2070
- crc ^= byte;
2071
- for (let k = 0; k < 8; k++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
2072
- }
2073
- return (~crc) >>> 0;
2074
- }
2075
-
2076
- function u16(parts, n) {
2077
- const b = Buffer.alloc(2);
2078
- b.writeUInt16LE(n);
2079
- parts.push(b);
2080
- }
2081
-
2082
- function u32(parts, n) {
2083
- const b = Buffer.alloc(4);
2084
- b.writeUInt32LE(n >>> 0);
2085
- parts.push(b);
2086
- }
2087
-
2088
- function buildZip(entries) {
2089
- const chunks = [];
2090
- const central = [];
2091
- let offset = 0;
2092
- for (const [name, content] of entries) {
2093
- const nameBuf = Buffer.from(name);
2094
- const raw = Buffer.isBuffer(content) ? content : Buffer.from(String(content));
2095
- const compressed = name === 'mimetype' ? raw : zlib.deflateRawSync(raw);
2096
- const method = name === 'mimetype' ? 0 : 8;
2097
- const crc = crc32(raw);
2098
- const local = [];
2099
- u32(local, 0x04034b50);
2100
- u16(local, 20);
2101
- u16(local, 0x0800);
2102
- u16(local, method);
2103
- u16(local, 0);
2104
- u16(local, 0);
2105
- u32(local, crc);
2106
- u32(local, compressed.length);
2107
- u32(local, raw.length);
2108
- u16(local, nameBuf.length);
2109
- u16(local, 0);
2110
- local.push(nameBuf, compressed);
2111
- const localBuf = Buffer.concat(local);
2112
- chunks.push(localBuf);
2113
- central.push({ nameBuf, method, crc, compressedSize: compressed.length, size: raw.length, offset });
2114
- offset += localBuf.length;
2115
- }
2116
- const centralStart = offset;
2117
- for (const entry of central) {
2118
- const cd = [];
2119
- u32(cd, 0x02014b50);
2120
- u16(cd, 20);
2121
- u16(cd, 20);
2122
- u16(cd, 0x0800);
2123
- u16(cd, entry.method);
2124
- u16(cd, 0);
2125
- u16(cd, 0);
2126
- u32(cd, entry.crc);
2127
- u32(cd, entry.compressedSize);
2128
- u32(cd, entry.size);
2129
- u16(cd, entry.nameBuf.length);
2130
- u16(cd, 0);
2131
- u16(cd, 0);
2132
- u16(cd, 0);
2133
- u16(cd, 0);
2134
- u32(cd, 0);
2135
- u32(cd, entry.offset);
2136
- cd.push(entry.nameBuf);
2137
- const cdBuf = Buffer.concat(cd);
2138
- chunks.push(cdBuf);
2139
- offset += cdBuf.length;
2140
- }
2141
- const eocd = [];
2142
- u32(eocd, 0x06054b50);
2143
- u16(eocd, 0);
2144
- u16(eocd, 0);
2145
- u16(eocd, central.length);
2146
- u16(eocd, central.length);
2147
- u32(eocd, offset - centralStart);
2148
- u32(eocd, centralStart);
2149
- u16(eocd, 0);
2150
- chunks.push(Buffer.concat(eocd));
2151
- return Buffer.concat(chunks);
2152
- }
2153
-
2154
- function stableStringify(value) {
2155
- if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
2156
- if (value && typeof value === 'object') {
2157
- return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`;
2158
- }
2159
- return JSON.stringify(value);
2160
- }
2161
-
2162
- function manifestForSigning(manifest) {
2163
- // Mirrors the kdna-core implementation in
2164
- // packages/kdna-core/src/asset-reader.js#manifestForSignature /
2165
- // manifestForDigest. Anything not stripped here will end up in the
2166
- // signing payload, and a mismatch between producer (this CLI) and
2167
- // verifier (kdna-core) will cause the signature check to fail.
2168
- //
2169
- // Bug: prior version did not strip `_source` (only set by eval/loader
2170
- // helpers) and did not recursively strip `authoring.content_digest`,
2171
- // so a payload with either field would diverge from kdna-core's
2172
- // canonicalisation and report a false-positive verification failure.
2173
- const copy = { ...(manifest || {}) };
2174
- delete copy.signature;
2175
- delete copy.asset_digest;
2176
- delete copy.container_sha256;
2177
- delete copy.content_digest;
2178
- delete copy._source;
2179
- if (copy.authoring && typeof copy.authoring === 'object') {
2180
- const auth = { ...copy.authoring };
2181
- delete auth.content_digest;
2182
- copy.authoring = auth;
2183
- }
2184
- return copy;
2185
- }
2186
-
2187
- function canonicalPayload(files) {
2188
- return ['mimetype', ...Object.keys(files).filter((k) => k !== 'mimetype').sort()]
2189
- .filter((name) => name !== 'signature.json' && name !== '.DS_Store' && name !== 'build-receipt.json' && !name.startsWith('reports/'))
2190
- .map((name) => {
2191
- let content = name === 'mimetype' ? 'application/vnd.aikdna.kdna+zip' : files[name];
2192
- if (name.endsWith('.json')) {
2193
- const json = JSON.parse(content);
2194
- content = stableStringify(name === 'kdna.json' ? manifestForSigning(json) : json);
2195
- }
2196
- return `${name}:${crypto.createHash('sha256').update(Buffer.from(content)).digest('hex')}`;
2197
- })
2198
- .join('\n');
2199
- }
2200
-
2201
- function identityPaths() {
2202
- const dir = process.env.KDNA_IDENTITY_DIR || path.join(os.homedir(), '.kdna', 'identity');
2203
- return { privateKey: path.join(dir, 'kdna.key'), publicKey: path.join(dir, 'kdna.pub') };
2204
- }
2205
-
2206
- function publicKeyFingerprint(publicKeyPem) {
2207
- return `ed25519:${crypto.createHash('sha256').update(publicKeyPem).digest('hex')}`;
2208
- }
2209
-
2210
- function applySignature(files, passphrase = null) {
2211
- const paths = identityPaths();
2212
- if (!fs.existsSync(paths.privateKey) || !fs.existsSync(paths.publicKey)) {
2213
- fail('Signing requires KDNA identity keys. Run: kdna-studio identity init', EXIT.TRUST_FAILED);
2214
- }
2215
- const manifest = JSON.parse(files['kdna.json']);
2216
- const publicKeyPem = fs.readFileSync(paths.publicKey, 'utf8');
2217
- manifest.author = manifest.author || {};
2218
- manifest.author.pubkey = publicKeyFingerprint(publicKeyPem);
2219
- manifest.author.public_key_pem = publicKeyPem;
2220
- files['kdna.json'] = JSON.stringify(manifest, null, 2);
2221
-
2222
- const payload = canonicalPayload(files);
2223
- let privateKeyPem = fs.readFileSync(paths.privateKey, 'utf8');
2224
- if (creatorApi.isEncryptedKey(privateKeyPem)) {
2225
- if (!passphrase) {
2226
- fail('Private key is encrypted. Export with: kdna-studio export ... --sign --passphrase <your-passphrase>', EXIT.TRUST_FAILED);
2227
- }
2228
- privateKeyPem = creatorApi.decryptPrivateKey(privateKeyPem, passphrase);
2229
- }
2230
- manifest.signature = `ed25519:${crypto.sign(null, Buffer.from(payload), privateKeyPem).toString('hex')}`;
2231
- files['kdna.json'] = JSON.stringify(manifest, null, 2);
2232
- }
2233
-
2234
2050
  function cmdExport(args) {
2235
2051
  const projectInput = args[0];
2236
2052
  const out = option(args, '--out');
2237
- if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--format v1] [--sign] [--allow-incomplete]');
2238
- if (option(args, '--format') === 'v1') {
2053
+ if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--allow-incomplete]');
2054
+ if (option(args, '--format')) {
2055
+ fail('The --format option is not supported. KDNA has one current asset format.');
2056
+ }
2057
+ if (args.includes('--sign')) {
2058
+ fail('Asset signing is a separate runtime step. Export first, then run: kdna sign <asset.kdna>');
2059
+ }
2060
+ {
2239
2061
  const { project } = readProject(projectInput);
2240
2062
  // BUG-11 (2026-06-27): previous logic required --password to be set
2241
2063
  // before --password-stdin would take effect. That meant callers using
@@ -2256,7 +2078,7 @@ function cmdExport(args) {
2256
2078
  if (process.stdin.isTTY) {
2257
2079
  fail(
2258
2080
  '--password-stdin requires the password to be piped in on stdin.\n' +
2259
- 'Example: echo "$KDNA_PASSWORD" | kdna-studio export <project> --format v1 --out <file.kdna> --password-stdin\n' +
2081
+ 'Example: echo "$KDNA_PASSWORD" | kdna-studio export <project> --out <file.kdna> --password-stdin\n' +
2260
2082
  'If you are running interactively, omit --password-stdin and you will be prompted.'
2261
2083
  );
2262
2084
  }
@@ -2268,7 +2090,7 @@ function cmdExport(args) {
2268
2090
  } else {
2269
2091
  // Bug (#55): prior version only consulted --password / --passphrase
2270
2092
  // here, so a caller who set KDNA_PASSPHRASE for the signing path
2271
- // (works via resolvePassphrase) found that the v1 encryption
2093
+ // (works via resolvePassphrase) found that the runtime encryption
2272
2094
  // password was silently null. Resolve from the same sources as
2273
2095
  // the signing path so the two stay in sync.
2274
2096
  password = process.env.KDNA_PASSPHRASE
@@ -2276,84 +2098,12 @@ function cmdExport(args) {
2276
2098
  || option(args, '--password')
2277
2099
  || option(args, '--passphrase');
2278
2100
  }
2279
- exportProjectV1(project, option(args, '--name') || project.name, path.resolve(out), {
2101
+ exportProject(project, option(args, '--name') || project.name, path.resolve(out), {
2280
2102
  allowIncomplete: args.includes('--allow-incomplete'),
2281
2103
  password,
2282
2104
  });
2283
2105
  return;
2284
2106
  }
2285
- const { project, result } = compileProject(projectInput);
2286
- const criticalIssues = criticalAxiomIssues(project);
2287
- if (criticalIssues.length > 0) {
2288
- if (args.includes('--allow-incomplete')) {
2289
- console.warn(
2290
- `--allow-incomplete: bypassing ${criticalIssues.length} critical axiom-field check(s). ` +
2291
- `The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
2292
- );
2293
- } else {
2294
- fail(
2295
- `Cannot export: ${criticalIssues.length} critical axiom field issue(s).\n` +
2296
- ` These fields are required for domain routing (kdna-loader uses them to\n` +
2297
- ` decide when to load this domain). Fix them first, or pass\n` +
2298
- ` --allow-incomplete to bypass.\n` +
2299
- ` Issues:\n ` + criticalIssues.slice(0, 10).join('\n ') +
2300
- (criticalIssues.length > 10 ? `\n ... and ${criticalIssues.length - 10} more` : ''),
2301
- EXIT.HUMAN_LOCK_REQUIRED,
2302
- );
2303
- }
2304
- }
2305
- const files = { ...result.files };
2306
- files['README.md'] = compileApi.generateReadme(project);
2307
- files.LICENSE = project.license?.type || 'UNSPECIFIED';
2308
- files.mimetype = 'application/vnd.aikdna.kdna+zip';
2309
-
2310
- // Recompute content_digest after README.md / LICENSE / mimetype are added
2311
- // so that manifest, receipt, and provenance report all agree with the final ZIP contents.
2312
- const finalDigest = compileApi.computeContentDigest(files);
2313
- result.identity.content_digest = finalDigest;
2314
-
2315
- // Update kdna.json manifest with final digest
2316
- const manifest = JSON.parse(files['kdna.json']);
2317
- manifest.content_digest = finalDigest;
2318
- if (manifest.authoring) manifest.authoring.content_digest = finalDigest;
2319
- files['kdna.json'] = JSON.stringify(manifest, null, 2);
2320
-
2321
- // Update build-receipt.json
2322
- let receiptData = JSON.parse(files['build-receipt.json']);
2323
- receiptData.content_digest = finalDigest;
2324
- files['build-receipt.json'] = JSON.stringify(receiptData, null, 2);
2325
-
2326
- // Update provenance report
2327
- const provenance = JSON.parse(files['reports/provenance-report.json']);
2328
- provenance.content_digest = finalDigest;
2329
- provenance.content_fingerprint = finalDigest;
2330
- files['reports/provenance-report.json'] = JSON.stringify(provenance, null, 2);
2331
-
2332
- if (args.includes('--sign')) applySignature(files, resolvePassphrase(args));
2333
-
2334
- const entries = [['mimetype', files.mimetype]];
2335
- for (const name of Object.keys(files).filter(k => k !== 'mimetype').sort()) {
2336
- entries.push([name, files[name]]);
2337
- }
2338
- const zip = buildZip(entries);
2339
- const outPath = path.resolve(out);
2340
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
2341
- fs.writeFileSync(outPath, zip);
2342
-
2343
- const assetDigest = `sha256:${crypto.createHash('sha256').update(zip).digest('hex')}`;
2344
- receiptData = JSON.parse(files['build-receipt.json']);
2345
- receiptData.asset_path = outPath;
2346
- receiptData.asset_digest = assetDigest;
2347
- receiptData.signature_status = args.includes('--sign') ? 'signed' : 'unsigned';
2348
- fs.writeFileSync(path.join(path.dirname(outPath), 'build-receipt.json'), JSON.stringify(receiptData, null, 2));
2349
- fs.writeFileSync(path.join(path.dirname(outPath), 'kdna.json'), files['kdna.json']);
2350
- fs.writeFileSync(path.join(path.dirname(outPath), 'provenance-report.json'), files['reports/provenance-report.json']);
2351
- fs.writeFileSync(path.join(path.dirname(outPath), 'quality-gate-report.json'), files['reports/quality-gate-report.json']);
2352
- fs.writeFileSync(path.join(path.dirname(outPath), 'human-lock-report.json'), files['reports/human-lock-report.json']);
2353
- fs.writeFileSync(path.join(path.dirname(outPath), 'eval-report.json'), files['reports/eval-report.json']);
2354
- console.log(`Exported canonical .kdna asset: ${outPath}`);
2355
- console.log(`Asset digest: ${assetDigest}`);
2356
- console.log(`Build ID: ${result.identity.build_id}`);
2357
2107
  }
2358
2108
 
2359
2109
  function cmdStudioInstall(args) {
@@ -2460,11 +2210,11 @@ function cmdReport(args) {
2460
2210
  process.exit(gate.blocked ? EXIT.HUMAN_LOCK_REQUIRED : EXIT.OK);
2461
2211
  }
2462
2212
 
2463
- // Audit-locks: list every card that would fail the v1.7.2+ Human Lock
2213
+ // Audit-locks: list every card that would fail the current Human Lock
2464
2214
  // gate because of missing or empty fields. Used during the
2465
2215
  // pre-migration review of source-tree assets (e.g. pro-20) so an
2466
2216
  // author can fill in the missing confidence / evidence_type / risk
2467
- // description / stance statement BEFORE running `migrate --format v1`.
2217
+ // description / stance statement BEFORE running `migrate`.
2468
2218
  //
2469
2219
  // Bug (UX pro-20 migration): prior version of `kdna-studio report`
2470
2220
  // returned a flat human_lock_gate.issues list, which mixed card
@@ -2486,7 +2236,7 @@ function cmdAuditLocks(args) {
2486
2236
  by_type: {},
2487
2237
  };
2488
2238
 
2489
- // Per-card-type required field lists. Mirrors the v1.7.2+ lockCard
2239
+ // Per-card-type required field lists. Mirrors the current lockCard
2490
2240
  // gate (kdna-studio-core/src/cards/index.js) so the output is
2491
2241
  // exactly the field set the gate will reject.
2492
2242
  const REQUIRED = {
@@ -2570,7 +2320,7 @@ function cmdAuditLocks(args) {
2570
2320
  console.log(`Cards: ${findings.card_count} (locked: ${findings.locked_count})`);
2571
2321
  const types = Object.keys(findings.by_type);
2572
2322
  if (types.length === 0) {
2573
- console.log('\nAll cards pass the v1.7.2+ Human Lock gate. Ready to migrate.');
2323
+ console.log('\nAll cards pass the current Human Lock gate. Ready to migrate.');
2574
2324
  return;
2575
2325
  }
2576
2326
  for (const t of types) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-studio-cli",
3
- "version": "0.8.13",
3
+ "version": "0.8.15",
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,12 +41,15 @@
41
41
  "node": ">=18"
42
42
  },
43
43
  "dependencies": {
44
- "@aikdna/kdna-studio-core": "^1.7.7",
44
+ "@aikdna/kdna-core": "0.15.12",
45
+ "@aikdna/kdna-studio-core": "1.7.12",
45
46
  "ajv": "^8.20.0",
46
47
  "ajv-formats": "^3.0.1",
47
48
  "cbor-x": "^1.6.4"
48
49
  },
49
- "devDependencies": {
50
- "@aikdna/kdna-core": "^0.15.9"
51
- }
50
+ "main": "index.js",
51
+ "directories": {
52
+ "test": "tests"
53
+ },
54
+ "author": ""
52
55
  }