@aikdna/studio-core 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/compile/index.js +17 -11
- package/tests/e2e.test.js +276 -0
- package/tests/milestone2.test.js +2 -2
package/package.json
CHANGED
package/src/compile/index.js
CHANGED
|
@@ -51,7 +51,8 @@ function compilePatterns(cards, project) {
|
|
|
51
51
|
wrong: c.fields?.wrong || '',
|
|
52
52
|
correct: c.fields?.correct || '',
|
|
53
53
|
key_distinction: c.fields?.key_distinction || '',
|
|
54
|
-
|
|
54
|
+
why: c.fields?.why || `What bad judgment results from believing "${(c.fields?.wrong || '').slice(0, 40)}"`,
|
|
55
|
+
failure_risk: c.fields?.failure_risk || 'No specific failure risk declared',
|
|
55
56
|
applies_when: c.fields?.applies_when || [],
|
|
56
57
|
does_not_apply_when: c.fields?.does_not_apply_when || [],
|
|
57
58
|
}));
|
|
@@ -95,7 +96,7 @@ function compileReasoning(cards, project) {
|
|
|
95
96
|
meta: makeMeta(project),
|
|
96
97
|
reasoning_chains: lockedAxioms.map(ax => ({
|
|
97
98
|
id: `chain_${ax.id}`,
|
|
98
|
-
|
|
99
|
+
one_sentence: ax.fields?.one_sentence || '',
|
|
99
100
|
logic: [ax.fields?.full_statement || ''],
|
|
100
101
|
so_what: ax.fields?.why || 'Agent judgment changes when this axiom is loaded.',
|
|
101
102
|
})),
|
|
@@ -112,22 +113,27 @@ function compileEvolution(cards, project) {
|
|
|
112
113
|
if (seenAxioms.has(card.id)) continue;
|
|
113
114
|
seenAxioms.add(card.id);
|
|
114
115
|
for (const entry of (card.audit_log || [])) {
|
|
115
|
-
if (entry.event === 'locked'
|
|
116
|
-
stages.push({
|
|
116
|
+
if (entry.event === 'locked') {
|
|
117
|
+
stages.push({
|
|
118
|
+
id: `stage_${card.id}`,
|
|
119
|
+
name: card.fields?.one_sentence || card.fields?.question || card.id,
|
|
120
|
+
description: `Card ${card.id} was locked by ${entry.by} at ${entry.at}. Type: ${card.type}.`,
|
|
121
|
+
indicators: [`${card.type} card locked`, 'Human judgment confirmed'],
|
|
122
|
+
});
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
return {
|
|
122
128
|
meta: makeMeta(project),
|
|
123
|
-
stages: stages.sort((a, b) => a.
|
|
124
|
-
|
|
125
|
-
{
|
|
129
|
+
stages: stages.sort((a, b) => a.id.localeCompare(b.id)),
|
|
130
|
+
evolution_layers: [
|
|
131
|
+
{ id: 'layer_1', name: 'Foundation', capability: 'Core axioms and patterns established.', from_stage: stages[0]?.id || 'none', to_stage: stages[stages.length - 1]?.id || 'none' },
|
|
126
132
|
],
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
{
|
|
130
|
-
{
|
|
133
|
+
measurement: [
|
|
134
|
+
{ id: 'meas_axioms', what: 'locked_axioms', how: 'Count of locked axiom cards', threshold: `${lockedCards.filter(c => c.type === 'axiom').length}` },
|
|
135
|
+
{ id: 'meas_misunderstandings', what: 'locked_misunderstandings', how: 'Count of locked misunderstanding cards', threshold: `${lockedCards.filter(c => c.type === 'misunderstanding').length}` },
|
|
136
|
+
{ id: 'meas_self_checks', what: 'self_checks', how: 'Count of locked self-check cards', threshold: `${lockedCards.filter(c => c.type === 'self_check').length}` },
|
|
131
137
|
],
|
|
132
138
|
};
|
|
133
139
|
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end validation: prove Studio Core output passes KDNA SPEC validation.
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. compile → write files → kdna validate passes
|
|
6
|
+
* 2. compile → kdna pack → kdna inspect returns valid container
|
|
7
|
+
* 3. compile output matches KDNA SPEC reference structure
|
|
8
|
+
* 4. kdna-core schema validation on compiled output
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { test, describe } = require('node:test');
|
|
12
|
+
const assert = require('node:assert/strict');
|
|
13
|
+
const { execFileSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
const { createProject } = require('../src/project');
|
|
19
|
+
const { createCard, lockCard, transitionCard, createFeynmanRestatement, attachRestatementToLock } = require('../src/cards');
|
|
20
|
+
const { compileDomain } = require('../src/compile');
|
|
21
|
+
const { buildProvenance } = require('../src/provenance');
|
|
22
|
+
|
|
23
|
+
function makeLockedCard(type, fields, id) {
|
|
24
|
+
const card = createCard(type, fields, id);
|
|
25
|
+
transitionCard(card, 'revised', { by: 'expert' });
|
|
26
|
+
lockCard(card, { by: 'expert', statement: 'I confirm this judgment.', checked: { applies_when: true, does_not_apply_when: true, failure_risk: true } });
|
|
27
|
+
return card;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createFullProject() {
|
|
31
|
+
const project = createProject('leadership_decisions', 'domain', {
|
|
32
|
+
author: { name: 'Leadership Expert', id: 'expert_001' },
|
|
33
|
+
});
|
|
34
|
+
project.release = { version: '0.1.0', description: 'Leadership decision-making judgment — diagnose whether execution failures are really decision voids.' };
|
|
35
|
+
|
|
36
|
+
// Axioms
|
|
37
|
+
const ax1 = makeLockedCard('axiom', {
|
|
38
|
+
one_sentence: 'Execution failure is often decision failure in disguise.',
|
|
39
|
+
full_statement: 'When a team fails to execute, first check whether a real decision (with named owner, deadline, and criteria) was ever made. Most "execution problems" are decision voids.',
|
|
40
|
+
why: 'Without this axiom, managers address symptoms (motivation, process) while missing the root cause.',
|
|
41
|
+
applies_when: ['Team reports being stuck', 'Deadline was missed', 'Project not progressing'],
|
|
42
|
+
does_not_apply_when: ['Clear decision exists with owner', 'External blocker (vendor, regulation)'],
|
|
43
|
+
failure_risk: 'May cause over-scrutiny of decision quality when issue is resource availability.',
|
|
44
|
+
}, 'ax_001');
|
|
45
|
+
project.cards.push(ax1);
|
|
46
|
+
|
|
47
|
+
const ax2 = makeLockedCard('axiom', {
|
|
48
|
+
one_sentence: 'Broad agreement is not commitment. Only named ownership is commitment.',
|
|
49
|
+
full_statement: 'A decision without a single named owner with a deadline has no commitment. Multiple owners = no owner.',
|
|
50
|
+
why: 'Teams confuse "everyone nodding" with actual commitment. Without a named owner, no one wakes up responsible.',
|
|
51
|
+
applies_when: ['Meeting ends with "sounds good"', 'Decision without deadline', 'Multiple people assigned'],
|
|
52
|
+
does_not_apply_when: ['Solo work with clear self-accountability', 'Informal team alignment'],
|
|
53
|
+
failure_risk: 'May create unnecessary formality for trivial decisions.',
|
|
54
|
+
}, 'ax_002');
|
|
55
|
+
project.cards.push(ax2);
|
|
56
|
+
|
|
57
|
+
const ax3 = makeLockedCard('axiom', {
|
|
58
|
+
one_sentence: 'The cost of a slow decision is usually higher than the cost of an imperfect decision.',
|
|
59
|
+
full_statement: 'In leadership, decision speed compounds. A two-week delay for a perfect decision often costs more than an 80% decision made today.',
|
|
60
|
+
why: 'Leaders who wait for perfect information create organizational bottlenecks.',
|
|
61
|
+
applies_when: ['Decision is reversible', 'Stakes are below team/org level', 'More information unlikely to change outcome'],
|
|
62
|
+
does_not_apply_when: ['Safety-critical decisions', 'Irreversible resource commitments', 'Legal/compliance required decisions'],
|
|
63
|
+
failure_risk: 'May encourage premature decisions in high-stakes, irreversible situations.',
|
|
64
|
+
}, 'ax_003');
|
|
65
|
+
project.cards.push(ax3);
|
|
66
|
+
|
|
67
|
+
// Misunderstandings
|
|
68
|
+
const ms1 = makeLockedCard('misunderstanding', {
|
|
69
|
+
wrong: 'If the team is not executing, they lack motivation or skills.',
|
|
70
|
+
correct: 'If the team is not executing, first check whether a real decision was ever made with owner, deadline, and criteria.',
|
|
71
|
+
key_distinction: 'Motivation gaps produce gradual decline over weeks. Decision voids produce sudden stalls within days. The pattern is fundamentally different.',
|
|
72
|
+
}, 'ms_001');
|
|
73
|
+
project.cards.push(ms1);
|
|
74
|
+
|
|
75
|
+
const ms2 = makeLockedCard('misunderstanding', {
|
|
76
|
+
wrong: 'Consensus means everyone agrees with the decision.',
|
|
77
|
+
correct: 'Consensus means everyone understands the decision, knows their role in executing it, and commits to not blocking it — even if they disagree.',
|
|
78
|
+
key_distinction: 'Agreement is an emotional state. Commitment to execute is a behavioral contract. You need the latter, not the former.',
|
|
79
|
+
}, 'ms_002');
|
|
80
|
+
project.cards.push(ms2);
|
|
81
|
+
|
|
82
|
+
// Self-checks
|
|
83
|
+
for (let i = 0; i < 5; i++) {
|
|
84
|
+
const checks = [
|
|
85
|
+
'Before concluding execution is the problem, did I verify that a named owner with a deadline exists?',
|
|
86
|
+
'Does this decision have exactly one person who will wake up responsible for it tomorrow?',
|
|
87
|
+
'Is this decision reversible enough that speed matters more than perfection?',
|
|
88
|
+
'Did I check whether this is a decision void disguised as an execution gap?',
|
|
89
|
+
'If I asked the team "who owns this?", would everyone point to the same person?',
|
|
90
|
+
];
|
|
91
|
+
const sc = makeLockedCard('self_check', { question: checks[i] }, `sc_00${i + 1}`);
|
|
92
|
+
project.cards.push(sc);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Feynman for axioms
|
|
96
|
+
attachRestatementToLock(ax1, createFeynmanRestatement(ax1,
|
|
97
|
+
'When your team is stuck and nothing is moving forward, do not immediately assume they lack skills or motivation. First ask: was there a clear decision? Meaning: someone specific was named, a date was given, and everyone knows what "done" looks like. If any of these is missing, you have a decision problem pretending to be an execution problem.'));
|
|
98
|
+
attachRestatementToLock(ax2, createFeynmanRestatement(ax2,
|
|
99
|
+
'After a meeting where everyone seems to agree, do not assume commitment happened. If you cannot name one specific person who knows they are accountable by a specific date, you do not have a decision — you have a discussion. Multiple owners means zero owners.'));
|
|
100
|
+
attachRestatementToLock(ax3, createFeynmanRestatement(ax3,
|
|
101
|
+
'Waiting three weeks for the perfect answer is usually worse than making a good-enough decision today. The only exception is when the decision is irreversible — like hiring someone you cannot fire, or spending money you cannot get back. But most daily leadership decisions are reversible, so speed beats perfection.'));
|
|
102
|
+
|
|
103
|
+
return project;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const TMPDIR = fs.mkdtempSync(path.join(os.tmpdir(), 'kdna-studio-e2e-'));
|
|
107
|
+
|
|
108
|
+
// ─── E2E: compile → kdna validate ────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
describe('E2E: compile → validate', () => {
|
|
111
|
+
test('compiled output passes kdna validate', () => {
|
|
112
|
+
const project = createFullProject();
|
|
113
|
+
const result = compileDomain(project);
|
|
114
|
+
const domainDir = path.join(TMPDIR, 'test-domain');
|
|
115
|
+
|
|
116
|
+
fs.mkdirSync(domainDir, { recursive: true });
|
|
117
|
+
for (const [filename, content] of Object.entries(result.files)) {
|
|
118
|
+
fs.writeFileSync(path.join(domainDir, filename), content);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const validateResult = execFileSync('kdna', ['validate', domainDir], {
|
|
123
|
+
encoding: 'utf8', timeout: 30000,
|
|
124
|
+
});
|
|
125
|
+
assert.match(validateResult, /valid|✓/i);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
const stderr = (e.stderr || '').toString();
|
|
128
|
+
// If kdna CLI not installed, skip gracefully
|
|
129
|
+
if (stderr.includes('kdna') || stderr.includes('not found')) {
|
|
130
|
+
console.log(' (kdna CLI not available — skipping validate test)');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
throw e;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('compiled output structure matches KDNA SPEC', () => {
|
|
138
|
+
const project = createFullProject();
|
|
139
|
+
const result = compileDomain(project);
|
|
140
|
+
|
|
141
|
+
// Verify KDNA_Core.json has all SPEC-required fields
|
|
142
|
+
const core = JSON.parse(result.files['KDNA_Core.json']);
|
|
143
|
+
assert.ok(core.meta, 'meta required');
|
|
144
|
+
assert.equal(core.meta.domain, 'leadership_decisions');
|
|
145
|
+
assert.equal(core.meta.version, '0.1.0');
|
|
146
|
+
assert.ok(core.meta.purpose);
|
|
147
|
+
assert.ok(core.meta.load_condition);
|
|
148
|
+
assert.ok(Array.isArray(core.axioms), 'axioms array required');
|
|
149
|
+
assert.equal(core.axioms.length, 3);
|
|
150
|
+
assert.ok(Array.isArray(core.ontology), 'ontology array required');
|
|
151
|
+
assert.ok(Array.isArray(core.frameworks), 'frameworks array required');
|
|
152
|
+
assert.ok(Array.isArray(core.stances), 'stances array required');
|
|
153
|
+
assert.ok(Array.isArray(core.core_structure), 'core_structure array required');
|
|
154
|
+
|
|
155
|
+
// Verify KDNA_Patterns.json
|
|
156
|
+
const patterns = JSON.parse(result.files['KDNA_Patterns.json']);
|
|
157
|
+
assert.ok(patterns.meta, 'Patterns meta required');
|
|
158
|
+
assert.ok(Array.isArray(patterns.misunderstandings));
|
|
159
|
+
assert.equal(patterns.misunderstandings.length, 2);
|
|
160
|
+
assert.ok(Array.isArray(patterns.self_check));
|
|
161
|
+
assert.equal(patterns.self_check.length, 5);
|
|
162
|
+
|
|
163
|
+
// Verify kdna.json manifest
|
|
164
|
+
const manifest = JSON.parse(result.files['kdna.json']);
|
|
165
|
+
assert.equal(manifest.name, 'leadership_decisions');
|
|
166
|
+
assert.equal(manifest.kdna_spec, '1.0-rc');
|
|
167
|
+
assert.ok(manifest.file_count >= 2, `file_count should be >= 2, got ${manifest.file_count}`);
|
|
168
|
+
|
|
169
|
+
// Verify KDNA_Reasoning.json (should exist since we have axioms)
|
|
170
|
+
assert.ok('KDNA_Reasoning.json' in result.files, 'Reasoning file should exist');
|
|
171
|
+
const reasoning = JSON.parse(result.files['KDNA_Reasoning.json']);
|
|
172
|
+
assert.ok(reasoning.meta);
|
|
173
|
+
assert.ok(Array.isArray(reasoning.reasoning_chains));
|
|
174
|
+
assert.ok(reasoning.reasoning_chains.every(c => 'so_what' in c), 'Every chain must have so_what');
|
|
175
|
+
assert.ok(reasoning.reasoning_chains.every(c => 'one_sentence' in c), 'Every chain must have one_sentence');
|
|
176
|
+
|
|
177
|
+
// Verify KDNA_Evolution.json
|
|
178
|
+
assert.ok('KDNA_Evolution.json' in result.files, 'Evolution file should exist');
|
|
179
|
+
const evolution = JSON.parse(result.files['KDNA_Evolution.json']);
|
|
180
|
+
assert.ok(evolution.meta);
|
|
181
|
+
assert.ok(Array.isArray(evolution.measurement));
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('compiled axioms have required fields', () => {
|
|
185
|
+
const project = createFullProject();
|
|
186
|
+
const result = compileDomain(project);
|
|
187
|
+
const core = JSON.parse(result.files['KDNA_Core.json']);
|
|
188
|
+
|
|
189
|
+
for (const ax of core.axioms) {
|
|
190
|
+
assert.ok(ax.one_sentence, `${ax.id}: missing one_sentence`);
|
|
191
|
+
assert.ok(ax.full_statement, `${ax.id}: missing full_statement`);
|
|
192
|
+
assert.ok(ax.why, `${ax.id}: missing why`);
|
|
193
|
+
assert.ok(Array.isArray(ax.applies_when), `${ax.id}: applies_when must be array`);
|
|
194
|
+
assert.ok(Array.isArray(ax.does_not_apply_when), `${ax.id}: does_not_apply_when must be array`);
|
|
195
|
+
assert.ok(ax.failure_risk, `${ax.id}: missing failure_risk`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('compiled misunderstandings have key_distinction', () => {
|
|
200
|
+
const project = createFullProject();
|
|
201
|
+
const result = compileDomain(project);
|
|
202
|
+
const patterns = JSON.parse(result.files['KDNA_Patterns.json']);
|
|
203
|
+
|
|
204
|
+
for (const ms of patterns.misunderstandings) {
|
|
205
|
+
assert.ok(ms.key_distinction, `${ms.id}: missing key_distinction`);
|
|
206
|
+
assert.ok(ms.key_distinction.length >= 20, `${ms.id}: key_distinction too short`);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// ─── E2E: compile → pack → inspect ───────────────────────────────────
|
|
212
|
+
|
|
213
|
+
describe('E2E: compile → pack → inspect', () => {
|
|
214
|
+
test('compiled output survives pack→inspect round-trip', () => {
|
|
215
|
+
const project = createFullProject();
|
|
216
|
+
const result = compileDomain(project);
|
|
217
|
+
const domainDir = path.join(TMPDIR, 'pack-test');
|
|
218
|
+
|
|
219
|
+
fs.mkdirSync(domainDir, { recursive: true });
|
|
220
|
+
for (const [filename, content] of Object.entries(result.files)) {
|
|
221
|
+
fs.writeFileSync(path.join(domainDir, filename), content);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Pack
|
|
226
|
+
const packResult = execFileSync('kdna', ['pack', domainDir, '--output', TMPDIR], {
|
|
227
|
+
encoding: 'utf8', timeout: 60000,
|
|
228
|
+
});
|
|
229
|
+
assert.match(packResult, /Packed|✓/);
|
|
230
|
+
|
|
231
|
+
// Find the .kdna file
|
|
232
|
+
const kdnaFile = fs.readdirSync(TMPDIR).find(f => f.endsWith('.kdna'));
|
|
233
|
+
assert.ok(kdnaFile, 'Should produce a .kdna file');
|
|
234
|
+
|
|
235
|
+
// Inspect
|
|
236
|
+
const inspectResult = execFileSync('kdna', ['inspect', path.join(TMPDIR, kdnaFile), '--json'], {
|
|
237
|
+
encoding: 'utf8', timeout: 15000,
|
|
238
|
+
});
|
|
239
|
+
const inspected = JSON.parse(inspectResult);
|
|
240
|
+
assert.equal(inspected.name, 'leadership_decisions');
|
|
241
|
+
assert.equal(inspected.version, '0.1.0');
|
|
242
|
+
} catch (e) {
|
|
243
|
+
const stderr = (e.stderr || '').toString();
|
|
244
|
+
if (stderr.includes('kdna') || stderr.includes('not found')) {
|
|
245
|
+
console.log(' (kdna CLI not available — skipping pack/inspect test)');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
throw e;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ─── E2E: provenance ─────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
describe('E2E: provenance completeness', () => {
|
|
256
|
+
test('provenance covers all required metadata', () => {
|
|
257
|
+
const project = createFullProject();
|
|
258
|
+
const result = compileDomain(project);
|
|
259
|
+
const prov = buildProvenance(project, result.files);
|
|
260
|
+
|
|
261
|
+
assert.equal(prov.studio_core, 'knowledge-dna/kdna-studio');
|
|
262
|
+
assert.ok(prov.studio_core_version);
|
|
263
|
+
assert.ok(prov.build_id);
|
|
264
|
+
assert.ok(prov.project_id);
|
|
265
|
+
assert.equal(prov.author_id, 'expert_001');
|
|
266
|
+
assert.equal(prov.locked_card_count, 10); // 3 axioms + 2 misunderstandings + 5 self-checks
|
|
267
|
+
assert.equal(prov.test_case_count, 0);
|
|
268
|
+
assert.ok(prov.built_at);
|
|
269
|
+
assert.ok(prov.content_fingerprint.startsWith('sha256:'));
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Cleanup
|
|
274
|
+
process.on('exit', () => {
|
|
275
|
+
try { fs.rmSync(TMPDIR, { recursive: true, force: true }); } catch {}
|
|
276
|
+
});
|
package/tests/milestone2.test.js
CHANGED
|
@@ -221,8 +221,8 @@ describe('Full Compile', () => {
|
|
|
221
221
|
const result = compileDomain(project);
|
|
222
222
|
const evo = JSON.parse(result.files['KDNA_Evolution.json']);
|
|
223
223
|
assert.ok(evo.stages.length > 0);
|
|
224
|
-
assert.equal(evo.
|
|
225
|
-
assert.equal(evo.
|
|
224
|
+
assert.equal(evo.measurement[0].what, 'locked_axioms');
|
|
225
|
+
assert.equal(evo.measurement[0].threshold, '1');
|
|
226
226
|
});
|
|
227
227
|
});
|
|
228
228
|
|