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