@aikdna/kdna-studio-cli 0.8.12 → 0.8.14
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 +18 -8
- package/bin/kdna-studio.js +133 -314
- package/package.json +9 -5
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 --
|
|
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
|
|
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
|
|
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
|
|
101
|
-
kdna-studio export . --
|
|
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
|
|
119
|
-
`@aikdna/kdna-studio-core` to compile the Studio project into
|
|
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
|
|
122
|
+
A KDNA runtime export contains only these top-level entries:
|
|
123
123
|
|
|
124
124
|
- `mimetype`
|
|
125
125
|
- `kdna.json`
|
|
@@ -135,6 +135,16 @@ Being part of the official toolchain means this package is maintained by the
|
|
|
135
135
|
KDNA project. It does not make any specific asset endorsed or suitable for
|
|
136
136
|
every use case.
|
|
137
137
|
|
|
138
|
+
## Consumption sidecars
|
|
139
|
+
|
|
140
|
+
Studio can export route-card and consumer-index drafts for applications that
|
|
141
|
+
use the consumption runtime. These files are separate from the `.kdna` export
|
|
142
|
+
and are disabled by default. They are review inputs, not claims that an asset
|
|
143
|
+
is automatically suitable for a task or ready for a production runtime.
|
|
144
|
+
|
|
145
|
+
Use the KDNA CLI review and evaluation commands before an application enables
|
|
146
|
+
a sidecar entry.
|
|
147
|
+
|
|
138
148
|
## Identity
|
|
139
149
|
|
|
140
150
|
```bash
|
package/bin/kdna-studio.js
CHANGED
|
@@ -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> --
|
|
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,9 @@ 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> --
|
|
100
|
+
kdna-studio export <project> --out <file.kdna> [--allow-incomplete] [--password <pw>|--password-stdin]
|
|
101
|
+
kdna-studio export-route-card <domain-id> [--out=<path>] # Export route-card sidecar skeleton
|
|
102
|
+
kdna-studio export-consumer-index [--entries=<domain-ids>] [--out=<path>] # Export consumer-index skeleton
|
|
101
103
|
|
|
102
104
|
AI Authoring (requires LLM config: kdna-studio llm config):
|
|
103
105
|
kdna-studio distill <project> --ai # AI-driven candidate extraction from evidence
|
|
@@ -339,6 +341,13 @@ function titleFromName(name, assetId) {
|
|
|
339
341
|
return assetId;
|
|
340
342
|
}
|
|
341
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
|
+
|
|
342
351
|
function publicManifestMetadata(raw = {}) {
|
|
343
352
|
const copy = { ...raw };
|
|
344
353
|
delete copy.registry;
|
|
@@ -365,8 +374,8 @@ function importedCard(type, fields = {}, id = null) {
|
|
|
365
374
|
}
|
|
366
375
|
|
|
367
376
|
// Bug (#3 + #4 UX): payload.kdnab intentionally carries the same
|
|
368
|
-
// misunderstanding card in two places — `patterns[]` (the
|
|
369
|
-
// primary card list) and `reasoning.failure_modes[]` (the
|
|
377
|
+
// misunderstanding card in two places — `patterns[]` (the runtime schema's
|
|
378
|
+
// primary card list) and `reasoning.failure_modes[]` (the runtime schema's
|
|
370
379
|
// "what bad judgment looks like" view). Both share the same `id`.
|
|
371
380
|
// Prior version imported both, doubling every misunderstanding on
|
|
372
381
|
// re-import. The dedup is by id: when a card with the same id is
|
|
@@ -407,15 +416,15 @@ function decodePayload(bytes, manifest = {}) {
|
|
|
407
416
|
return cbor.decode(raw);
|
|
408
417
|
}
|
|
409
418
|
|
|
410
|
-
//
|
|
411
|
-
// each card's fields. The
|
|
419
|
+
// Runtime path: trust the producer (export-runtime) to have already shaped
|
|
420
|
+
// each card's fields. The producer normalises everything into a
|
|
412
421
|
// canonical shape, so we just round-trip the raw object. Bug (#57):
|
|
413
422
|
// the legacy path below explicitly constructs each field. The two
|
|
414
|
-
// paths cannot be unified without losing either
|
|
423
|
+
// paths cannot be unified without losing either runtime per-field
|
|
415
424
|
// guarantees or legacy's defensive defaults — the asymmetry is
|
|
416
425
|
// intentional. Documented here so a future reader does not "fix" it
|
|
417
426
|
// by collapsing one path into the other.
|
|
418
|
-
function
|
|
427
|
+
function cardsFromRuntimePayload(payload) {
|
|
419
428
|
resetImportedIds();
|
|
420
429
|
const cards = [];
|
|
421
430
|
for (const sourceCard of (Array.isArray(payload.source_cards) ? payload.source_cards : [])) {
|
|
@@ -460,7 +469,7 @@ function cardsFromV1Payload(payload) {
|
|
|
460
469
|
}
|
|
461
470
|
for (const pattern of (Array.isArray(payload.patterns) ? payload.patterns : [])) {
|
|
462
471
|
// Bug (#146): prior version dropped any payload.patterns entry
|
|
463
|
-
// that lacked an explicit `type` field. The
|
|
472
|
+
// that lacked an explicit `type` field. The runtime schema lists
|
|
464
473
|
// misunderstandings in patterns[] without a `type` discriminator
|
|
465
474
|
// (their shape is `{wrong, correct, key_distinction, why}`),
|
|
466
475
|
// and several exporters omit `type` to keep the on-the-wire
|
|
@@ -516,8 +525,8 @@ function cardsFromV1Payload(payload) {
|
|
|
516
525
|
for (const failureMode of (Array.isArray(payload.reasoning?.failure_modes) ? payload.reasoning?.failure_modes : [])) {
|
|
517
526
|
// Bug (#145 follow-up): the prior version dropped three
|
|
518
527
|
// judgment fields (failure_risk, applies_when, does_not_apply_when)
|
|
519
|
-
// when reading failure_modes back out of
|
|
520
|
-
// project that round-tripped through `migrate
|
|
528
|
+
// when reading failure_modes back out of an asset. A Studio
|
|
529
|
+
// project that round-tripped through `migrate` lost
|
|
521
530
|
// these fields every time. The fix reads them through, matching
|
|
522
531
|
// the export-side map in kdna-studio-core's buildPayload.
|
|
523
532
|
pushImportedCard(cards, 'misunderstanding', {
|
|
@@ -638,7 +647,7 @@ function cardsFromLegacyPayload(payload) {
|
|
|
638
647
|
function cardsFromPayload(payload) {
|
|
639
648
|
if (!payload || typeof payload !== 'object') return [];
|
|
640
649
|
if (payload.profile === 'judgment-profile-v1' || payload.core || payload.source_cards) {
|
|
641
|
-
return
|
|
650
|
+
return cardsFromRuntimePayload(payload);
|
|
642
651
|
}
|
|
643
652
|
if (payload.kind === 'kdna.payload' || payload.judgment) {
|
|
644
653
|
return cardsFromLegacyPayload(payload);
|
|
@@ -646,13 +655,13 @@ function cardsFromPayload(payload) {
|
|
|
646
655
|
return [];
|
|
647
656
|
}
|
|
648
657
|
|
|
649
|
-
function
|
|
658
|
+
function buildManifest(project, name) {
|
|
650
659
|
// Bug (#58): prior version was a hand-rolled manifest builder that
|
|
651
660
|
// shared zero logic with the canonical buildManifest in
|
|
652
661
|
// @aikdna/kdna-studio-core/src/export-runtime. The two paths
|
|
653
662
|
// diverged on every field added since 1.0 (lineage, load_contract,
|
|
654
663
|
// authoring block, etc.). The fix delegates the manifest shape to
|
|
655
|
-
// kdna-studio-core's buildManifest so
|
|
664
|
+
// kdna-studio-core's buildManifest so runtime export has a single
|
|
656
665
|
// source of truth.
|
|
657
666
|
//
|
|
658
667
|
// We still need a default manifest here (the exportRuntimeAsset
|
|
@@ -819,19 +828,19 @@ function importFromKdna(kdnaPath) {
|
|
|
819
828
|
const manifest = JSON.parse(entries.get('kdna.json').toString());
|
|
820
829
|
const lineage = {
|
|
821
830
|
type: 'fork',
|
|
822
|
-
parent_name: manifest
|
|
831
|
+
parent_name: packageNameFromManifest(manifest),
|
|
823
832
|
parent_asset_uid: manifest.asset_uid || null,
|
|
824
833
|
parent_version: manifest.version || null,
|
|
825
834
|
parent_asset_digest: manifest.content_digest || manifest.asset_digest || null,
|
|
826
835
|
};
|
|
827
836
|
|
|
828
|
-
// Extract cards.
|
|
837
|
+
// Extract cards. Current packages carry `kdna.json` + `payload.kdnab`
|
|
829
838
|
// and use the unified judgment profile. Legacy packages split content
|
|
830
839
|
// across KDNA_*.json files. Prefer payload.kdnab when present — it
|
|
831
840
|
// covers all 8+ card types (axiom / boundary / risk / aesthetic /
|
|
832
841
|
// ontology / misunderstanding / self_check / scenario / case /
|
|
833
842
|
// reasoning / evolution_stage / stance / framework / term / banned_term)
|
|
834
|
-
// via
|
|
843
|
+
// via cardsFromRuntimePayload / cardsFromLegacyPayload.
|
|
835
844
|
const importedCards = [];
|
|
836
845
|
if (entries.has('payload.kdnab')) {
|
|
837
846
|
try {
|
|
@@ -841,7 +850,7 @@ function importFromKdna(kdnaPath) {
|
|
|
841
850
|
fail(`Could not import cards from payload.kdnab: ${e.message}`);
|
|
842
851
|
}
|
|
843
852
|
} else if (entries.has('KDNA_Core.json') || entries.has('KDNA_Patterns.json')) {
|
|
844
|
-
//
|
|
853
|
+
// Source-tree fallback — used when an imported source has no payload.kdnab
|
|
845
854
|
// (i.e. the asset predates the 0.7 split). Bug (#67): prior
|
|
846
855
|
// version only handled axiom / ontology / boundary / misunderstanding
|
|
847
856
|
// / self_check — 5 of the 14 card types — so every other type
|
|
@@ -940,7 +949,7 @@ function importFromKdna(kdnaPath) {
|
|
|
940
949
|
}
|
|
941
950
|
|
|
942
951
|
if (importedCards.length === 0) {
|
|
943
|
-
fail(`No cards could be extracted from ${absKdna}. Expected payload.kdnab
|
|
952
|
+
fail(`No cards could be extracted from ${absKdna}. Expected payload.kdnab or an importable KDNA_Core.json + KDNA_Patterns.json source tree.`);
|
|
944
953
|
}
|
|
945
954
|
|
|
946
955
|
// Bug #15 / #28 follow-up: surface the source's KDNA_Patterns /
|
|
@@ -1114,7 +1123,7 @@ function importFromFolder(sourceDir, projectDir, projectName, creatorIdentity, o
|
|
|
1114
1123
|
importedCards.push(cardApi.createCard('self_check', { question: q }));
|
|
1115
1124
|
}
|
|
1116
1125
|
// Bug (aesthetic round-trip): prior version dropped
|
|
1117
|
-
// `patterns.aesthetics` on import. `
|
|
1126
|
+
// `patterns.aesthetics` on import. `cardsFromRuntimePayload` and
|
|
1118
1127
|
// `cardsFromLegacyPayload` both read the field, so a domain
|
|
1119
1128
|
// that authored aesthetic cards in either format would lose
|
|
1120
1129
|
// them when imported through `create --from-folder`. The fix
|
|
@@ -1779,7 +1788,7 @@ function cmdMigrate(args) {
|
|
|
1779
1788
|
// attempted the full pipeline — read source folder, import to
|
|
1780
1789
|
// Studio project, lock all cards, then export. An author migrating
|
|
1781
1790
|
// 20 source-tree assets had no way to know in advance whether
|
|
1782
|
-
// the assets would pass the
|
|
1791
|
+
// the assets would pass the current Human Lock gate. The fix
|
|
1783
1792
|
// adds `--check` which runs the import + the critical-missing
|
|
1784
1793
|
// check + the lock-gate check, then prints a report and exits
|
|
1785
1794
|
// without writing the .kdna file. Pair with `audit-locks` for
|
|
@@ -1804,7 +1813,9 @@ function cmdMigrate(args) {
|
|
|
1804
1813
|
}
|
|
1805
1814
|
|
|
1806
1815
|
const sourcePath = path.resolve(sourceDir);
|
|
1807
|
-
|
|
1816
|
+
if (option(args, '--format')) {
|
|
1817
|
+
fail('The --format option is not supported. KDNA has one current asset format.');
|
|
1818
|
+
}
|
|
1808
1819
|
const isStudioProject = fs.existsSync(path.join(sourcePath, 'studio.project.json'));
|
|
1809
1820
|
let tmpDir = null;
|
|
1810
1821
|
let projectPath = null;
|
|
@@ -1812,14 +1823,14 @@ function cmdMigrate(args) {
|
|
|
1812
1823
|
|
|
1813
1824
|
// Wrap everything in try / finally so a mid-migrate failure (e.g. missing
|
|
1814
1825
|
// critical fields, gate blocked, signing error) still cleans up tmpDir.
|
|
1815
|
-
// Bug: prior code only cleaned up on
|
|
1816
|
-
//
|
|
1826
|
+
// Bug: prior code only cleaned up on its success paths; any
|
|
1827
|
+
// older export path that threw leaked `/tmp/kdna-migrate-*` entries
|
|
1817
1828
|
// indefinitely.
|
|
1818
1829
|
try {
|
|
1819
1830
|
// Step 1: import dev source, or use an existing Studio project directly.
|
|
1820
1831
|
let creatorIdentity = null;
|
|
1821
1832
|
try { creatorIdentity = creatorApi.loadIdentity(); } catch { /* proceed without */ }
|
|
1822
|
-
if (
|
|
1833
|
+
if (isStudioProject) {
|
|
1823
1834
|
const loaded = readProject(sourcePath);
|
|
1824
1835
|
project = loaded.project;
|
|
1825
1836
|
projectPath = loaded.projectPath;
|
|
@@ -1936,34 +1947,8 @@ function cmdMigrate(args) {
|
|
|
1936
1947
|
|
|
1937
1948
|
const absOut = path.resolve(out);
|
|
1938
1949
|
const allowIncomplete = args.includes('--allow-incomplete');
|
|
1939
|
-
// Step 4:
|
|
1940
|
-
|
|
1941
|
-
exportProjectV1(project, name, absOut, { allowIncomplete });
|
|
1942
|
-
if (tmpDir) {
|
|
1943
|
-
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* cleanup */ }
|
|
1944
|
-
}
|
|
1945
|
-
return;
|
|
1946
|
-
}
|
|
1947
|
-
const { result } = compileProject(tmpDir);
|
|
1948
|
-
const files = { ...result.files };
|
|
1949
|
-
files['README.md'] = compileApi.generateReadme(project);
|
|
1950
|
-
files.LICENSE = project.license?.type || 'UNSPECIFIED';
|
|
1951
|
-
files.mimetype = 'application/vnd.aikdna.kdna+zip';
|
|
1952
|
-
|
|
1953
|
-
const entries = [['mimetype', files.mimetype]];
|
|
1954
|
-
for (const name of Object.keys(files).filter(k => k !== 'mimetype').sort()) {
|
|
1955
|
-
entries.push([name, files[name]]);
|
|
1956
|
-
}
|
|
1957
|
-
const zip = buildZip(entries);
|
|
1958
|
-
fs.mkdirSync(path.dirname(absOut), { recursive: true });
|
|
1959
|
-
fs.writeFileSync(absOut, zip);
|
|
1960
|
-
console.log(`Exported: ${absOut}`);
|
|
1961
|
-
console.log(` Name: ${name}`);
|
|
1962
|
-
console.log(` Cards: ${project.cards.length} (${locked} locked)`);
|
|
1963
|
-
console.log(` Files: ${Object.keys(files).length}`);
|
|
1964
|
-
console.log(` Build ID: ${result.identity.build_id}`);
|
|
1965
|
-
|
|
1966
|
-
// Cleanup temp
|
|
1950
|
+
// Step 4: export the canonical runtime asset.
|
|
1951
|
+
exportProject(project, name, absOut, { allowIncomplete });
|
|
1967
1952
|
if (tmpDir) {
|
|
1968
1953
|
try { fs.rmSync(tmpDir, { recursive: true }); } catch { /* best effort */ }
|
|
1969
1954
|
tmpDir = null;
|
|
@@ -1980,13 +1965,13 @@ function cmdMigrate(args) {
|
|
|
1980
1965
|
}
|
|
1981
1966
|
}
|
|
1982
1967
|
|
|
1983
|
-
function
|
|
1968
|
+
function exportProject(project, name, outPath, opts = {}) {
|
|
1984
1969
|
let core;
|
|
1985
1970
|
try { core = require('@aikdna/kdna-core'); } catch (e) {
|
|
1986
|
-
fail('@aikdna/kdna-core
|
|
1971
|
+
fail('@aikdna/kdna-core is required for runtime export.', 2);
|
|
1987
1972
|
}
|
|
1988
1973
|
if (!exportRuntime || typeof exportRuntime.exportRuntimeAsset !== 'function') {
|
|
1989
|
-
fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for
|
|
1974
|
+
fail('@aikdna/kdna-studio-core with exportRuntime.exportRuntimeAsset is required for runtime export.', 2);
|
|
1990
1975
|
}
|
|
1991
1976
|
const criticalIssues = criticalAxiomIssues(project);
|
|
1992
1977
|
if (criticalIssues.length > 0) {
|
|
@@ -1997,7 +1982,7 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
1997
1982
|
);
|
|
1998
1983
|
} else {
|
|
1999
1984
|
fail(
|
|
2000
|
-
`Cannot export
|
|
1985
|
+
`Cannot export: ${criticalIssues.length} critical axiom field issue(s).\n` +
|
|
2001
1986
|
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
2002
1987
|
` decide when to load this domain). Fix them first, or pass\n` +
|
|
2003
1988
|
` --allow-incomplete to bypass.\n` +
|
|
@@ -2014,13 +1999,13 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
2014
1999
|
for (const issue of gate.issues) console.warn(` - ${issue.cardId}: ${issue.reason}`);
|
|
2015
2000
|
} else {
|
|
2016
2001
|
const reasons = gate.issues.map((i) => `${i.cardId}: ${i.reason}`).join('\n - ');
|
|
2017
|
-
fail(`Human Lock Gate blocked
|
|
2002
|
+
fail(`Human Lock Gate blocked export:\n - ${reasons}\nUse --allow-incomplete to bypass.`, EXIT.HUMAN_LOCK_REQUIRED);
|
|
2018
2003
|
}
|
|
2019
2004
|
}
|
|
2020
2005
|
const lockedCards = (project.cards || []).filter(c => c.locked);
|
|
2021
|
-
const
|
|
2006
|
+
const runtimeDir = trackTempDir(fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-runtime-')));
|
|
2022
2007
|
try {
|
|
2023
|
-
const manifestDefaults =
|
|
2008
|
+
const manifestDefaults = buildManifest(project, name);
|
|
2024
2009
|
const runtimeAsset = exportRuntime.exportRuntimeAsset(project, {
|
|
2025
2010
|
asset_id: manifestDefaults.asset_id,
|
|
2026
2011
|
asset_uid: manifestDefaults.asset_uid,
|
|
@@ -2030,15 +2015,15 @@ function exportProjectV1(project, name, outPath, opts = {}) {
|
|
|
2030
2015
|
access: project.release?.access || project.source_manifest?.access || 'public',
|
|
2031
2016
|
password: opts.password || undefined,
|
|
2032
2017
|
});
|
|
2033
|
-
writeFiles(
|
|
2018
|
+
writeFiles(runtimeDir, runtimeAsset.files);
|
|
2034
2019
|
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
2035
|
-
core.pack(
|
|
2020
|
+
core.pack(runtimeDir, outPath);
|
|
2036
2021
|
const vr = core.validate(outPath);
|
|
2037
|
-
if (!vr.overall_valid) fail('
|
|
2038
|
-
console.log('Exported
|
|
2022
|
+
if (!vr.overall_valid) fail('runtime export validation failed: ' + (vr.problems || []).join('; '));
|
|
2023
|
+
console.log('Exported: ' + outPath);
|
|
2039
2024
|
console.log(' Name: ' + name + ' Cards: ' + lockedCards.length + ' Validated: all gates pass');
|
|
2040
2025
|
} finally {
|
|
2041
|
-
try { fs.rmSync(
|
|
2026
|
+
try { fs.rmSync(runtimeDir, { recursive: true }); } catch { /* cleanup */ }
|
|
2042
2027
|
}
|
|
2043
2028
|
}
|
|
2044
2029
|
|
|
@@ -2062,178 +2047,17 @@ function cmdCompile(args) {
|
|
|
2062
2047
|
console.log(`Build ID: ${result.identity.build_id}`);
|
|
2063
2048
|
}
|
|
2064
2049
|
|
|
2065
|
-
function crc32(data) {
|
|
2066
|
-
let crc = ~0;
|
|
2067
|
-
for (const byte of data) {
|
|
2068
|
-
crc ^= byte;
|
|
2069
|
-
for (let k = 0; k < 8; k++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
2070
|
-
}
|
|
2071
|
-
return (~crc) >>> 0;
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
function u16(parts, n) {
|
|
2075
|
-
const b = Buffer.alloc(2);
|
|
2076
|
-
b.writeUInt16LE(n);
|
|
2077
|
-
parts.push(b);
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
function u32(parts, n) {
|
|
2081
|
-
const b = Buffer.alloc(4);
|
|
2082
|
-
b.writeUInt32LE(n >>> 0);
|
|
2083
|
-
parts.push(b);
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
function buildZip(entries) {
|
|
2087
|
-
const chunks = [];
|
|
2088
|
-
const central = [];
|
|
2089
|
-
let offset = 0;
|
|
2090
|
-
for (const [name, content] of entries) {
|
|
2091
|
-
const nameBuf = Buffer.from(name);
|
|
2092
|
-
const raw = Buffer.isBuffer(content) ? content : Buffer.from(String(content));
|
|
2093
|
-
const compressed = name === 'mimetype' ? raw : zlib.deflateRawSync(raw);
|
|
2094
|
-
const method = name === 'mimetype' ? 0 : 8;
|
|
2095
|
-
const crc = crc32(raw);
|
|
2096
|
-
const local = [];
|
|
2097
|
-
u32(local, 0x04034b50);
|
|
2098
|
-
u16(local, 20);
|
|
2099
|
-
u16(local, 0x0800);
|
|
2100
|
-
u16(local, method);
|
|
2101
|
-
u16(local, 0);
|
|
2102
|
-
u16(local, 0);
|
|
2103
|
-
u32(local, crc);
|
|
2104
|
-
u32(local, compressed.length);
|
|
2105
|
-
u32(local, raw.length);
|
|
2106
|
-
u16(local, nameBuf.length);
|
|
2107
|
-
u16(local, 0);
|
|
2108
|
-
local.push(nameBuf, compressed);
|
|
2109
|
-
const localBuf = Buffer.concat(local);
|
|
2110
|
-
chunks.push(localBuf);
|
|
2111
|
-
central.push({ nameBuf, method, crc, compressedSize: compressed.length, size: raw.length, offset });
|
|
2112
|
-
offset += localBuf.length;
|
|
2113
|
-
}
|
|
2114
|
-
const centralStart = offset;
|
|
2115
|
-
for (const entry of central) {
|
|
2116
|
-
const cd = [];
|
|
2117
|
-
u32(cd, 0x02014b50);
|
|
2118
|
-
u16(cd, 20);
|
|
2119
|
-
u16(cd, 20);
|
|
2120
|
-
u16(cd, 0x0800);
|
|
2121
|
-
u16(cd, entry.method);
|
|
2122
|
-
u16(cd, 0);
|
|
2123
|
-
u16(cd, 0);
|
|
2124
|
-
u32(cd, entry.crc);
|
|
2125
|
-
u32(cd, entry.compressedSize);
|
|
2126
|
-
u32(cd, entry.size);
|
|
2127
|
-
u16(cd, entry.nameBuf.length);
|
|
2128
|
-
u16(cd, 0);
|
|
2129
|
-
u16(cd, 0);
|
|
2130
|
-
u16(cd, 0);
|
|
2131
|
-
u16(cd, 0);
|
|
2132
|
-
u32(cd, 0);
|
|
2133
|
-
u32(cd, entry.offset);
|
|
2134
|
-
cd.push(entry.nameBuf);
|
|
2135
|
-
const cdBuf = Buffer.concat(cd);
|
|
2136
|
-
chunks.push(cdBuf);
|
|
2137
|
-
offset += cdBuf.length;
|
|
2138
|
-
}
|
|
2139
|
-
const eocd = [];
|
|
2140
|
-
u32(eocd, 0x06054b50);
|
|
2141
|
-
u16(eocd, 0);
|
|
2142
|
-
u16(eocd, 0);
|
|
2143
|
-
u16(eocd, central.length);
|
|
2144
|
-
u16(eocd, central.length);
|
|
2145
|
-
u32(eocd, offset - centralStart);
|
|
2146
|
-
u32(eocd, centralStart);
|
|
2147
|
-
u16(eocd, 0);
|
|
2148
|
-
chunks.push(Buffer.concat(eocd));
|
|
2149
|
-
return Buffer.concat(chunks);
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
function stableStringify(value) {
|
|
2153
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
|
|
2154
|
-
if (value && typeof value === 'object') {
|
|
2155
|
-
return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(',')}}`;
|
|
2156
|
-
}
|
|
2157
|
-
return JSON.stringify(value);
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
function manifestForSigning(manifest) {
|
|
2161
|
-
// Mirrors the kdna-core implementation in
|
|
2162
|
-
// packages/kdna-core/src/asset-reader.js#manifestForSignature /
|
|
2163
|
-
// manifestForDigest. Anything not stripped here will end up in the
|
|
2164
|
-
// signing payload, and a mismatch between producer (this CLI) and
|
|
2165
|
-
// verifier (kdna-core) will cause the signature check to fail.
|
|
2166
|
-
//
|
|
2167
|
-
// Bug: prior version did not strip `_source` (only set by eval/loader
|
|
2168
|
-
// helpers) and did not recursively strip `authoring.content_digest`,
|
|
2169
|
-
// so a payload with either field would diverge from kdna-core's
|
|
2170
|
-
// canonicalisation and report a false-positive verification failure.
|
|
2171
|
-
const copy = { ...(manifest || {}) };
|
|
2172
|
-
delete copy.signature;
|
|
2173
|
-
delete copy.asset_digest;
|
|
2174
|
-
delete copy.container_sha256;
|
|
2175
|
-
delete copy.content_digest;
|
|
2176
|
-
delete copy._source;
|
|
2177
|
-
if (copy.authoring && typeof copy.authoring === 'object') {
|
|
2178
|
-
const auth = { ...copy.authoring };
|
|
2179
|
-
delete auth.content_digest;
|
|
2180
|
-
copy.authoring = auth;
|
|
2181
|
-
}
|
|
2182
|
-
return copy;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
function canonicalPayload(files) {
|
|
2186
|
-
return ['mimetype', ...Object.keys(files).filter((k) => k !== 'mimetype').sort()]
|
|
2187
|
-
.filter((name) => name !== 'signature.json' && name !== '.DS_Store' && name !== 'build-receipt.json' && !name.startsWith('reports/'))
|
|
2188
|
-
.map((name) => {
|
|
2189
|
-
let content = name === 'mimetype' ? 'application/vnd.aikdna.kdna+zip' : files[name];
|
|
2190
|
-
if (name.endsWith('.json')) {
|
|
2191
|
-
const json = JSON.parse(content);
|
|
2192
|
-
content = stableStringify(name === 'kdna.json' ? manifestForSigning(json) : json);
|
|
2193
|
-
}
|
|
2194
|
-
return `${name}:${crypto.createHash('sha256').update(Buffer.from(content)).digest('hex')}`;
|
|
2195
|
-
})
|
|
2196
|
-
.join('\n');
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
function identityPaths() {
|
|
2200
|
-
const dir = process.env.KDNA_IDENTITY_DIR || path.join(os.homedir(), '.kdna', 'identity');
|
|
2201
|
-
return { privateKey: path.join(dir, 'kdna.key'), publicKey: path.join(dir, 'kdna.pub') };
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
function publicKeyFingerprint(publicKeyPem) {
|
|
2205
|
-
return `ed25519:${crypto.createHash('sha256').update(publicKeyPem).digest('hex')}`;
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
function applySignature(files, passphrase = null) {
|
|
2209
|
-
const paths = identityPaths();
|
|
2210
|
-
if (!fs.existsSync(paths.privateKey) || !fs.existsSync(paths.publicKey)) {
|
|
2211
|
-
fail('Signing requires KDNA identity keys. Run: kdna-studio identity init', EXIT.TRUST_FAILED);
|
|
2212
|
-
}
|
|
2213
|
-
const manifest = JSON.parse(files['kdna.json']);
|
|
2214
|
-
const publicKeyPem = fs.readFileSync(paths.publicKey, 'utf8');
|
|
2215
|
-
manifest.author = manifest.author || {};
|
|
2216
|
-
manifest.author.pubkey = publicKeyFingerprint(publicKeyPem);
|
|
2217
|
-
manifest.author.public_key_pem = publicKeyPem;
|
|
2218
|
-
files['kdna.json'] = JSON.stringify(manifest, null, 2);
|
|
2219
|
-
|
|
2220
|
-
const payload = canonicalPayload(files);
|
|
2221
|
-
let privateKeyPem = fs.readFileSync(paths.privateKey, 'utf8');
|
|
2222
|
-
if (creatorApi.isEncryptedKey(privateKeyPem)) {
|
|
2223
|
-
if (!passphrase) {
|
|
2224
|
-
fail('Private key is encrypted. Export with: kdna-studio export ... --sign --passphrase <your-passphrase>', EXIT.TRUST_FAILED);
|
|
2225
|
-
}
|
|
2226
|
-
privateKeyPem = creatorApi.decryptPrivateKey(privateKeyPem, passphrase);
|
|
2227
|
-
}
|
|
2228
|
-
manifest.signature = `ed25519:${crypto.sign(null, Buffer.from(payload), privateKeyPem).toString('hex')}`;
|
|
2229
|
-
files['kdna.json'] = JSON.stringify(manifest, null, 2);
|
|
2230
|
-
}
|
|
2231
|
-
|
|
2232
2050
|
function cmdExport(args) {
|
|
2233
2051
|
const projectInput = args[0];
|
|
2234
2052
|
const out = option(args, '--out');
|
|
2235
|
-
if (!projectInput || !out) fail('Usage: kdna-studio export <project> --out <file.kdna> [--
|
|
2236
|
-
if (option(args, '--format')
|
|
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
|
+
{
|
|
2237
2061
|
const { project } = readProject(projectInput);
|
|
2238
2062
|
// BUG-11 (2026-06-27): previous logic required --password to be set
|
|
2239
2063
|
// before --password-stdin would take effect. That meant callers using
|
|
@@ -2254,7 +2078,7 @@ function cmdExport(args) {
|
|
|
2254
2078
|
if (process.stdin.isTTY) {
|
|
2255
2079
|
fail(
|
|
2256
2080
|
'--password-stdin requires the password to be piped in on stdin.\n' +
|
|
2257
|
-
'Example: echo "$KDNA_PASSWORD" | kdna-studio export <project> --
|
|
2081
|
+
'Example: echo "$KDNA_PASSWORD" | kdna-studio export <project> --out <file.kdna> --password-stdin\n' +
|
|
2258
2082
|
'If you are running interactively, omit --password-stdin and you will be prompted.'
|
|
2259
2083
|
);
|
|
2260
2084
|
}
|
|
@@ -2266,7 +2090,7 @@ function cmdExport(args) {
|
|
|
2266
2090
|
} else {
|
|
2267
2091
|
// Bug (#55): prior version only consulted --password / --passphrase
|
|
2268
2092
|
// here, so a caller who set KDNA_PASSPHRASE for the signing path
|
|
2269
|
-
// (works via resolvePassphrase) found that the
|
|
2093
|
+
// (works via resolvePassphrase) found that the runtime encryption
|
|
2270
2094
|
// password was silently null. Resolve from the same sources as
|
|
2271
2095
|
// the signing path so the two stay in sync.
|
|
2272
2096
|
password = process.env.KDNA_PASSPHRASE
|
|
@@ -2274,84 +2098,12 @@ function cmdExport(args) {
|
|
|
2274
2098
|
|| option(args, '--password')
|
|
2275
2099
|
|| option(args, '--passphrase');
|
|
2276
2100
|
}
|
|
2277
|
-
|
|
2101
|
+
exportProject(project, option(args, '--name') || project.name, path.resolve(out), {
|
|
2278
2102
|
allowIncomplete: args.includes('--allow-incomplete'),
|
|
2279
2103
|
password,
|
|
2280
2104
|
});
|
|
2281
2105
|
return;
|
|
2282
2106
|
}
|
|
2283
|
-
const { project, result } = compileProject(projectInput);
|
|
2284
|
-
const criticalIssues = criticalAxiomIssues(project);
|
|
2285
|
-
if (criticalIssues.length > 0) {
|
|
2286
|
-
if (args.includes('--allow-incomplete')) {
|
|
2287
|
-
console.warn(
|
|
2288
|
-
`--allow-incomplete: bypassing ${criticalIssues.length} critical axiom-field check(s). ` +
|
|
2289
|
-
`The exported .kdna will load but kdna-loader may not match this domain on those signals.`,
|
|
2290
|
-
);
|
|
2291
|
-
} else {
|
|
2292
|
-
fail(
|
|
2293
|
-
`Cannot export: ${criticalIssues.length} critical axiom field issue(s).\n` +
|
|
2294
|
-
` These fields are required for domain routing (kdna-loader uses them to\n` +
|
|
2295
|
-
` decide when to load this domain). Fix them first, or pass\n` +
|
|
2296
|
-
` --allow-incomplete to bypass.\n` +
|
|
2297
|
-
` Issues:\n ` + criticalIssues.slice(0, 10).join('\n ') +
|
|
2298
|
-
(criticalIssues.length > 10 ? `\n ... and ${criticalIssues.length - 10} more` : ''),
|
|
2299
|
-
EXIT.HUMAN_LOCK_REQUIRED,
|
|
2300
|
-
);
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2303
|
-
const files = { ...result.files };
|
|
2304
|
-
files['README.md'] = compileApi.generateReadme(project);
|
|
2305
|
-
files.LICENSE = project.license?.type || 'UNSPECIFIED';
|
|
2306
|
-
files.mimetype = 'application/vnd.aikdna.kdna+zip';
|
|
2307
|
-
|
|
2308
|
-
// Recompute content_digest after README.md / LICENSE / mimetype are added
|
|
2309
|
-
// so that manifest, receipt, and provenance report all agree with the final ZIP contents.
|
|
2310
|
-
const finalDigest = compileApi.computeContentDigest(files);
|
|
2311
|
-
result.identity.content_digest = finalDigest;
|
|
2312
|
-
|
|
2313
|
-
// Update kdna.json manifest with final digest
|
|
2314
|
-
const manifest = JSON.parse(files['kdna.json']);
|
|
2315
|
-
manifest.content_digest = finalDigest;
|
|
2316
|
-
if (manifest.authoring) manifest.authoring.content_digest = finalDigest;
|
|
2317
|
-
files['kdna.json'] = JSON.stringify(manifest, null, 2);
|
|
2318
|
-
|
|
2319
|
-
// Update build-receipt.json
|
|
2320
|
-
let receiptData = JSON.parse(files['build-receipt.json']);
|
|
2321
|
-
receiptData.content_digest = finalDigest;
|
|
2322
|
-
files['build-receipt.json'] = JSON.stringify(receiptData, null, 2);
|
|
2323
|
-
|
|
2324
|
-
// Update provenance report
|
|
2325
|
-
const provenance = JSON.parse(files['reports/provenance-report.json']);
|
|
2326
|
-
provenance.content_digest = finalDigest;
|
|
2327
|
-
provenance.content_fingerprint = finalDigest;
|
|
2328
|
-
files['reports/provenance-report.json'] = JSON.stringify(provenance, null, 2);
|
|
2329
|
-
|
|
2330
|
-
if (args.includes('--sign')) applySignature(files, resolvePassphrase(args));
|
|
2331
|
-
|
|
2332
|
-
const entries = [['mimetype', files.mimetype]];
|
|
2333
|
-
for (const name of Object.keys(files).filter(k => k !== 'mimetype').sort()) {
|
|
2334
|
-
entries.push([name, files[name]]);
|
|
2335
|
-
}
|
|
2336
|
-
const zip = buildZip(entries);
|
|
2337
|
-
const outPath = path.resolve(out);
|
|
2338
|
-
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
|
2339
|
-
fs.writeFileSync(outPath, zip);
|
|
2340
|
-
|
|
2341
|
-
const assetDigest = `sha256:${crypto.createHash('sha256').update(zip).digest('hex')}`;
|
|
2342
|
-
receiptData = JSON.parse(files['build-receipt.json']);
|
|
2343
|
-
receiptData.asset_path = outPath;
|
|
2344
|
-
receiptData.asset_digest = assetDigest;
|
|
2345
|
-
receiptData.signature_status = args.includes('--sign') ? 'signed' : 'unsigned';
|
|
2346
|
-
fs.writeFileSync(path.join(path.dirname(outPath), 'build-receipt.json'), JSON.stringify(receiptData, null, 2));
|
|
2347
|
-
fs.writeFileSync(path.join(path.dirname(outPath), 'kdna.json'), files['kdna.json']);
|
|
2348
|
-
fs.writeFileSync(path.join(path.dirname(outPath), 'provenance-report.json'), files['reports/provenance-report.json']);
|
|
2349
|
-
fs.writeFileSync(path.join(path.dirname(outPath), 'quality-gate-report.json'), files['reports/quality-gate-report.json']);
|
|
2350
|
-
fs.writeFileSync(path.join(path.dirname(outPath), 'human-lock-report.json'), files['reports/human-lock-report.json']);
|
|
2351
|
-
fs.writeFileSync(path.join(path.dirname(outPath), 'eval-report.json'), files['reports/eval-report.json']);
|
|
2352
|
-
console.log(`Exported canonical .kdna asset: ${outPath}`);
|
|
2353
|
-
console.log(`Asset digest: ${assetDigest}`);
|
|
2354
|
-
console.log(`Build ID: ${result.identity.build_id}`);
|
|
2355
2107
|
}
|
|
2356
2108
|
|
|
2357
2109
|
function cmdStudioInstall(args) {
|
|
@@ -2393,7 +2145,7 @@ function cmdLlm(args) {
|
|
|
2393
2145
|
console.log(`Provider : ${cfg.provider || '(not set)'}`);
|
|
2394
2146
|
console.log(`Model : ${cfg.model || '(not set)'}`);
|
|
2395
2147
|
console.log(`Base URL : ${cfg.baseURL || '(not set)'}`);
|
|
2396
|
-
console.log(`API Key : ${cfg.apiKey ? '
|
|
2148
|
+
console.log(`API Key : ${cfg.apiKey ? '(set)' : '(not set)'}`);
|
|
2397
2149
|
return;
|
|
2398
2150
|
}
|
|
2399
2151
|
if (sub === 'config') {
|
|
@@ -2458,11 +2210,11 @@ function cmdReport(args) {
|
|
|
2458
2210
|
process.exit(gate.blocked ? EXIT.HUMAN_LOCK_REQUIRED : EXIT.OK);
|
|
2459
2211
|
}
|
|
2460
2212
|
|
|
2461
|
-
// Audit-locks: list every card that would fail the
|
|
2213
|
+
// Audit-locks: list every card that would fail the current Human Lock
|
|
2462
2214
|
// gate because of missing or empty fields. Used during the
|
|
2463
2215
|
// pre-migration review of source-tree assets (e.g. pro-20) so an
|
|
2464
2216
|
// author can fill in the missing confidence / evidence_type / risk
|
|
2465
|
-
// description / stance statement BEFORE running `migrate
|
|
2217
|
+
// description / stance statement BEFORE running `migrate`.
|
|
2466
2218
|
//
|
|
2467
2219
|
// Bug (UX pro-20 migration): prior version of `kdna-studio report`
|
|
2468
2220
|
// returned a flat human_lock_gate.issues list, which mixed card
|
|
@@ -2484,7 +2236,7 @@ function cmdAuditLocks(args) {
|
|
|
2484
2236
|
by_type: {},
|
|
2485
2237
|
};
|
|
2486
2238
|
|
|
2487
|
-
// Per-card-type required field lists. Mirrors the
|
|
2239
|
+
// Per-card-type required field lists. Mirrors the current lockCard
|
|
2488
2240
|
// gate (kdna-studio-core/src/cards/index.js) so the output is
|
|
2489
2241
|
// exactly the field set the gate will reject.
|
|
2490
2242
|
const REQUIRED = {
|
|
@@ -2568,7 +2320,7 @@ function cmdAuditLocks(args) {
|
|
|
2568
2320
|
console.log(`Cards: ${findings.card_count} (locked: ${findings.locked_count})`);
|
|
2569
2321
|
const types = Object.keys(findings.by_type);
|
|
2570
2322
|
if (types.length === 0) {
|
|
2571
|
-
console.log('\nAll cards pass the
|
|
2323
|
+
console.log('\nAll cards pass the current Human Lock gate. Ready to migrate.');
|
|
2572
2324
|
return;
|
|
2573
2325
|
}
|
|
2574
2326
|
for (const t of types) {
|
|
@@ -3035,6 +2787,8 @@ try {
|
|
|
3035
2787
|
else if (cmd === 'audit-locks') cmdAuditLocks(args.slice(1));
|
|
3036
2788
|
else if (cmd === 'install') cmdStudioInstall(args.slice(1));
|
|
3037
2789
|
else if (cmd === 'update') cmdStudioUpdate(args.slice(1));
|
|
2790
|
+
else if (cmd === 'export-route-card') cmdExportRouteCard(args.slice(1));
|
|
2791
|
+
else if (cmd === 'export-consumer-index') cmdExportConsumerIndex(args.slice(1));
|
|
3038
2792
|
else {
|
|
3039
2793
|
usage();
|
|
3040
2794
|
fail(`Unknown command: ${cmd}`);
|
|
@@ -3043,3 +2797,68 @@ try {
|
|
|
3043
2797
|
fail(err.message || String(err));
|
|
3044
2798
|
}
|
|
3045
2799
|
})();
|
|
2800
|
+
|
|
2801
|
+
function cmdExportRouteCard(args) {
|
|
2802
|
+
const domainId = args[0];
|
|
2803
|
+
if (!domainId) fail('Usage: kdna-studio export-route-card <domain-id> [--out=<path>]');
|
|
2804
|
+
const outArg = option(args, '--out');
|
|
2805
|
+
const outPath = outArg || `${slugSegment(domainId, 'domain')}.route-card.json`;
|
|
2806
|
+
|
|
2807
|
+
const card = {
|
|
2808
|
+
route_card: "0.1.0",
|
|
2809
|
+
domain_id: domainId,
|
|
2810
|
+
role: "primary",
|
|
2811
|
+
boundaries: {
|
|
2812
|
+
applies_when: [],
|
|
2813
|
+
does_not_apply_when: [],
|
|
2814
|
+
},
|
|
2815
|
+
neighbors: [],
|
|
2816
|
+
advisor_edges: [],
|
|
2817
|
+
provenance: {
|
|
2818
|
+
generated_by: "kdna-studio",
|
|
2819
|
+
generated_at: new Date().toISOString(),
|
|
2820
|
+
input_hash: crypto.createHash("sha256").update(domainId).digest("hex").slice(0, 12),
|
|
2821
|
+
review_status: "draft_generated",
|
|
2822
|
+
},
|
|
2823
|
+
};
|
|
2824
|
+
|
|
2825
|
+
const absOut = path.resolve(outPath);
|
|
2826
|
+
fs.mkdirSync(path.dirname(absOut), { recursive: true });
|
|
2827
|
+
fs.writeFileSync(absOut, JSON.stringify(card, null, 2) + "\n");
|
|
2828
|
+
console.log(`Route card skeleton written: ${absOut}`);
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
function cmdExportConsumerIndex(args) {
|
|
2832
|
+
const entriesArg = option(args, '--entries');
|
|
2833
|
+
const outArg = option(args, '--out');
|
|
2834
|
+
const outPath = outArg || 'consumer-index.json';
|
|
2835
|
+
|
|
2836
|
+
const domainIds = entriesArg
|
|
2837
|
+
? entriesArg.split(",").map((s) => s.trim()).filter(Boolean)
|
|
2838
|
+
: [];
|
|
2839
|
+
|
|
2840
|
+
const index = {
|
|
2841
|
+
consumer_index: "0.1.0",
|
|
2842
|
+
entries: domainIds.map((domainId) => ({
|
|
2843
|
+
domain_id: domainId,
|
|
2844
|
+
status: "draft_generated",
|
|
2845
|
+
enabled: false,
|
|
2846
|
+
route_preference: {
|
|
2847
|
+
primary_for: [],
|
|
2848
|
+
advisor_for: [],
|
|
2849
|
+
never_for: [],
|
|
2850
|
+
},
|
|
2851
|
+
provenance: {
|
|
2852
|
+
generated_by: "kdna-studio",
|
|
2853
|
+
generated_at: new Date().toISOString(),
|
|
2854
|
+
input_hash: crypto.createHash("sha256").update(domainId).digest("hex").slice(0, 12),
|
|
2855
|
+
source: "studio-export",
|
|
2856
|
+
},
|
|
2857
|
+
})),
|
|
2858
|
+
};
|
|
2859
|
+
|
|
2860
|
+
const absOut = path.resolve(outPath);
|
|
2861
|
+
fs.mkdirSync(path.dirname(absOut), { recursive: true });
|
|
2862
|
+
fs.writeFileSync(absOut, JSON.stringify(index, null, 2) + "\n");
|
|
2863
|
+
console.log(`Consumer index skeleton written: ${absOut}`);
|
|
2864
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikdna/kdna-studio-cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.14",
|
|
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,16 @@
|
|
|
41
41
|
"node": ">=18"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@aikdna/kdna-
|
|
44
|
+
"@aikdna/kdna-core": "0.15.12",
|
|
45
|
+
"@aikdna/kdna-studio-core": "1.7.11",
|
|
45
46
|
"ajv": "^8.20.0",
|
|
46
47
|
"ajv-formats": "^3.0.1",
|
|
47
48
|
"cbor-x": "^1.6.4"
|
|
48
49
|
},
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
"main": "index.js",
|
|
51
|
+
"directories": {
|
|
52
|
+
"test": "tests"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {},
|
|
55
|
+
"author": ""
|
|
52
56
|
}
|