@aikdna/studio-core 0.1.0 → 0.3.0
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 +190 -51
- package/src/index.js +4 -0
- package/src/quality/index.js +138 -28
- package/src/quality/validate-cards.js +163 -0
- package/src/testlab/delta.js +160 -0
- package/src/versioning/index.js +181 -1
- package/tests/milestone2.test.js +285 -0
- package/tests/milestone3.test.js +156 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
const { test, describe } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
|
|
4
|
+
const { createProject } = require('../src/project');
|
|
5
|
+
const { createCard, lockCard, transitionCard, createFeynmanRestatement, attachRestatementToLock } = require('../src/cards');
|
|
6
|
+
const { computeReadiness } = require('../src/quality');
|
|
7
|
+
const { detectContradictions } = require('../src/quality/contradiction');
|
|
8
|
+
const { validateCard, validateAllCards } = require('../src/quality/validate-cards');
|
|
9
|
+
const { compileDomain, generateReadme } = require('../src/compile');
|
|
10
|
+
const { createTestCase, recordHumanRating } = require('../src/testlab');
|
|
11
|
+
const { buildProvenance } = require('../src/provenance');
|
|
12
|
+
|
|
13
|
+
function makeLockedCard(type, fields = {}, id = null) {
|
|
14
|
+
const card = createCard(type, fields, id);
|
|
15
|
+
transitionCard(card, 'revised', { by: 'tester' });
|
|
16
|
+
lockCard(card, {
|
|
17
|
+
by: 'tester',
|
|
18
|
+
statement: 'I confirm this judgment.',
|
|
19
|
+
checked: { applies_when: true, does_not_apply_when: true, failure_risk: true },
|
|
20
|
+
});
|
|
21
|
+
return card;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Quality Gates (4-grade scoring) ─────────────────────────────────
|
|
25
|
+
|
|
26
|
+
describe('Quality Gates', () => {
|
|
27
|
+
test('draft_grade: empty project', () => {
|
|
28
|
+
const r = computeReadiness({ cards: [], tests: [] });
|
|
29
|
+
assert.equal(r.grade, 'draft_grade');
|
|
30
|
+
assert.equal(r.publishable, false);
|
|
31
|
+
assert.ok(r.blocking.length > 0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('draft_grade: no locked cards → nothing to compile', () => {
|
|
35
|
+
const project = createProject('test');
|
|
36
|
+
project.cards = [createCard('axiom', { one_sentence: 'Draft.' })];
|
|
37
|
+
const r = computeReadiness(project);
|
|
38
|
+
assert.equal(r.grade, 'draft_grade');
|
|
39
|
+
assert.ok(r.blocking.some(b => b.includes('nothing to compile') || b.includes('No locked cards')));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('human_controlled: 3+ locked axioms with boundaries ', () => {
|
|
43
|
+
const project = createProject('test');
|
|
44
|
+
for (let i = 0; i < 3; i++) {
|
|
45
|
+
const ax = makeLockedCard('axiom', {
|
|
46
|
+
one_sentence: `Axiom ${i}: a specific testable judgment principle.`,
|
|
47
|
+
full_statement: `Full statement for axiom ${i} explaining what the agent should do differently.`,
|
|
48
|
+
why: 'Without this, agents would get this specific thing wrong.',
|
|
49
|
+
applies_when: [`situation ${i}a`, `situation ${i}b`],
|
|
50
|
+
does_not_apply_when: [`not when ${i}`],
|
|
51
|
+
failure_risk: `Risk ${i}: misapplying this could cause X.`,
|
|
52
|
+
});
|
|
53
|
+
attachRestatementToLock(ax, createFeynmanRestatement(ax, `Simple explanation for axiom ${i}: when you see this situation, do this instead of that, but only if the conditions are right.`));
|
|
54
|
+
project.cards.push(ax);
|
|
55
|
+
}
|
|
56
|
+
const r = computeReadiness(project);
|
|
57
|
+
assert.equal(r.grade, 'human_controlled');
|
|
58
|
+
assert.equal(r.stats.locked_axioms, 3);
|
|
59
|
+
assert.ok(r.score >= 70);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('tested_grade: 5+ rated tests + 3+ self-checks', () => {
|
|
63
|
+
const project = createProject('test');
|
|
64
|
+
for (let i = 0; i < 3; i++) {
|
|
65
|
+
const ax = makeLockedCard('axiom', {
|
|
66
|
+
one_sentence: `Test axiom ${i}.`,
|
|
67
|
+
full_statement: `Full statement ${i} with enough detail for the agent.`,
|
|
68
|
+
why: 'Because.',
|
|
69
|
+
applies_when: ['when x'],
|
|
70
|
+
does_not_apply_when: ['when y'],
|
|
71
|
+
failure_risk: 'risk',
|
|
72
|
+
});
|
|
73
|
+
project.cards.push(ax);
|
|
74
|
+
}
|
|
75
|
+
for (let i = 0; i < 3; i++) {
|
|
76
|
+
const sc = makeLockedCard('self_check', { question: `Does the response satisfy condition ${i}?` });
|
|
77
|
+
project.cards.push(sc);
|
|
78
|
+
}
|
|
79
|
+
for (let i = 0; i < 6; i++) {
|
|
80
|
+
const tc = createTestCase(`input ${i}`);
|
|
81
|
+
recordHumanRating(tc, i < 4 ? 'with_kdna_better' : 'no_difference', 'tester');
|
|
82
|
+
project.tests.push(tc);
|
|
83
|
+
}
|
|
84
|
+
const r = computeReadiness(project);
|
|
85
|
+
assert.equal(r.grade, 'tested_grade');
|
|
86
|
+
assert.equal(r.stats.rated_tests, 6);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('publishable_grade: 10+ eval cases, 3+ axioms, 5+ self-checks', () => {
|
|
90
|
+
const project = createProject('test');
|
|
91
|
+
for (let i = 0; i < 4; i++) {
|
|
92
|
+
project.cards.push(makeLockedCard('axiom', {
|
|
93
|
+
one_sentence: `Pub axiom ${i} with judgment.`,
|
|
94
|
+
full_statement: `Full statement ${i} with enough detail for the agent to act on.`,
|
|
95
|
+
why: 'Without this, agents default to wrong behavior.',
|
|
96
|
+
applies_when: ['when x'],
|
|
97
|
+
does_not_apply_when: ['when y'],
|
|
98
|
+
failure_risk: 'risk of misapplication',
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
for (let i = 0; i < 5; i++) {
|
|
102
|
+
project.cards.push(makeLockedCard('self_check', { question: `Does the output pass criterion ${i}?` }));
|
|
103
|
+
}
|
|
104
|
+
for (let i = 0; i < 12; i++) {
|
|
105
|
+
const tc = createTestCase(`eval case ${i}`);
|
|
106
|
+
recordHumanRating(tc, 'with_kdna_better', 'tester');
|
|
107
|
+
project.tests.push(tc);
|
|
108
|
+
}
|
|
109
|
+
const r = computeReadiness(project);
|
|
110
|
+
assert.equal(r.grade, 'publishable_grade');
|
|
111
|
+
assert.equal(r.publishable, true);
|
|
112
|
+
assert.equal(r.blocking.length, 0);
|
|
113
|
+
assert.ok(r.score >= 75);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// ─── Anti-Vagueness Validation ──────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
describe('Card Validation', () => {
|
|
120
|
+
test('flags slogan-like axiom', () => {
|
|
121
|
+
const card = createCard('axiom', { one_sentence: 'Trust is important for teams.', full_statement: 'Trust matters.' });
|
|
122
|
+
const issues = validateCard(card);
|
|
123
|
+
assert.ok(issues.some(i => i.type === 'slogan' || i.type === 'too_short'));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('flags SOP-like axiom', () => {
|
|
127
|
+
const card = createCard('axiom', {
|
|
128
|
+
one_sentence: 'First, you should always remember to follow these steps.',
|
|
129
|
+
full_statement: 'The process is to first identify the problem.',
|
|
130
|
+
});
|
|
131
|
+
const issues = validateCard(card);
|
|
132
|
+
assert.ok(issues.some(i => i.type === 'sop'));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('flags straw-man misunderstanding', () => {
|
|
136
|
+
const card = createCard('misunderstanding', {
|
|
137
|
+
wrong: 'Some people say it is commonly thought that quality matters.',
|
|
138
|
+
correct: 'Actually it does matter.',
|
|
139
|
+
key_distinction: 'Quality is about getting things right the first time.',
|
|
140
|
+
});
|
|
141
|
+
const issues = validateCard(card);
|
|
142
|
+
assert.ok(issues.some(i => i.type === 'straw_man'));
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('flags generic self_check', () => {
|
|
146
|
+
const card = createCard('self_check', { question: 'Is this good?' });
|
|
147
|
+
const issues = validateCard(card);
|
|
148
|
+
assert.ok(issues.some(i => i.type === 'generic' || i.type === 'vague'));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('validateAllCards aggregates per-card', () => {
|
|
152
|
+
const project = createProject('test');
|
|
153
|
+
project.cards = [
|
|
154
|
+
createCard('axiom', { one_sentence: 'Trust is key.', full_statement: 'Trust matters in everything.' }),
|
|
155
|
+
createCard('self_check', { question: 'Is it helpful?' }),
|
|
156
|
+
];
|
|
157
|
+
const results = validateAllCards(project);
|
|
158
|
+
assert.equal(results.length, 2);
|
|
159
|
+
for (const r of results) {
|
|
160
|
+
assert.ok(r.card_id);
|
|
161
|
+
assert.ok(r.issues.length > 0);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// ─── Full 6-File Compile ────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
describe('Full Compile', () => {
|
|
169
|
+
test('produces Core + Patterns minimum', () => {
|
|
170
|
+
const project = createProject('test');
|
|
171
|
+
project.cards = [
|
|
172
|
+
makeLockedCard('axiom', { one_sentence: 'Test.', full_statement: 'Test full.', why: 'Because.', applies_when: ['x'], does_not_apply_when: ['y'], failure_risk: 'risk' }),
|
|
173
|
+
];
|
|
174
|
+
const result = compileDomain(project);
|
|
175
|
+
assert.ok('KDNA_Core.json' in result.files);
|
|
176
|
+
assert.ok('KDNA_Patterns.json' in result.files);
|
|
177
|
+
assert.ok('kdna.json' in result.files);
|
|
178
|
+
assert.ok(result.stats.files_output >= 4); // Core + Patterns + Reasoning + Evolution minimum
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('excludes draft cards from output', () => {
|
|
182
|
+
const project = createProject('test');
|
|
183
|
+
project.cards = [
|
|
184
|
+
makeLockedCard('axiom', { one_sentence: 'Locked.', full_statement: 'FS.', why: 'B.', applies_when: ['x'], does_not_apply_when: ['y'], failure_risk: 'r' }),
|
|
185
|
+
createCard('axiom', { one_sentence: 'Draft.' }),
|
|
186
|
+
];
|
|
187
|
+
const result = compileDomain(project);
|
|
188
|
+
assert.equal(result.stats.locked_cards, 1);
|
|
189
|
+
assert.equal(result.stats.excluded_cards, 1);
|
|
190
|
+
const core = JSON.parse(result.files['KDNA_Core.json']);
|
|
191
|
+
assert.equal(core.axioms.length, 1);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('produces Scenarios when scenario cards locked', () => {
|
|
195
|
+
const project = createProject('test');
|
|
196
|
+
project.cards = [
|
|
197
|
+
makeLockedCard('scenario', { id: 'scene_01', name: 'User reports bug', trigger_signal: 'bug report', sub_scenarios: [] }, 'sc_001'),
|
|
198
|
+
];
|
|
199
|
+
const result = compileDomain(project);
|
|
200
|
+
assert.ok('KDNA_Scenarios.json' in result.files);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('produces Reasoning from axiom implications', () => {
|
|
204
|
+
const project = createProject('test');
|
|
205
|
+
project.cards = [
|
|
206
|
+
makeLockedCard('axiom', { one_sentence: 'Price objections are certainty deficits.', full_statement: 'When a buyer says too expensive, first diagnose which type of uncertainty is blocking them.', why: 'Without this axiom, agents default to offering discounts instead of diagnosing.', applies_when: ['price objection'], does_not_apply_when: ['explicit discount request'], failure_risk: 'Agent may seem evasive.' }),
|
|
207
|
+
];
|
|
208
|
+
const result = compileDomain(project);
|
|
209
|
+
assert.ok('KDNA_Reasoning.json' in result.files);
|
|
210
|
+
const reasoning = JSON.parse(result.files['KDNA_Reasoning.json']);
|
|
211
|
+
assert.ok(reasoning.chains.length > 0);
|
|
212
|
+
assert.equal(reasoning.chains[0].so_what, 'Without this axiom, agents default to offering discounts instead of diagnosing.');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('produces Evolution from audit logs', () => {
|
|
216
|
+
const project = createProject('test');
|
|
217
|
+
const card = makeLockedCard('axiom', { one_sentence: 'Evolution test.', full_statement: 'FS.', why: 'B.', applies_when: ['x'], does_not_apply_when: ['y'], failure_risk: 'r' });
|
|
218
|
+
project.cards = [card];
|
|
219
|
+
const result = compileDomain(project);
|
|
220
|
+
const evo = JSON.parse(result.files['KDNA_Evolution.json']);
|
|
221
|
+
assert.ok(evo.stages.length > 0);
|
|
222
|
+
assert.equal(evo.measurements[0].metric, 'locked_axioms');
|
|
223
|
+
assert.equal(evo.measurements[0].value, 1);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ─── README Generation ──────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
describe('README Generation', () => {
|
|
230
|
+
test('generates README with 4 questions', () => {
|
|
231
|
+
const project = createProject('leadership_decisions', 'domain', { author: { name: 'Expert' } });
|
|
232
|
+
project.cards = [
|
|
233
|
+
makeLockedCard('axiom', {
|
|
234
|
+
one_sentence: 'Execution failure is decision failure in disguise.',
|
|
235
|
+
full_statement: 'When a team fails to execute, first check whether a real decision was ever made.',
|
|
236
|
+
why: 'Without this axiom, managers address symptoms while missing the root cause.',
|
|
237
|
+
applies_when: ['Team reports being stuck'],
|
|
238
|
+
does_not_apply_when: ['Clear decision exists'],
|
|
239
|
+
failure_risk: 'May cause over-scrutiny of decision quality when issue is resources.',
|
|
240
|
+
}),
|
|
241
|
+
makeLockedCard('misunderstanding', {
|
|
242
|
+
wrong: 'If the team is not executing, they lack motivation.',
|
|
243
|
+
correct: 'If the team is not executing, first check whether a decision with owner+deadline+criteria was made.',
|
|
244
|
+
key_distinction: 'Motivation gaps produce gradual decline. Decision voids produce sudden stalls.',
|
|
245
|
+
}),
|
|
246
|
+
makeLockedCard('self_check', { question: 'Did I verify that a concrete decision exists before diagnosing the team?' }),
|
|
247
|
+
];
|
|
248
|
+
project.tests = [];
|
|
249
|
+
for (let i = 0; i < 6; i++) {
|
|
250
|
+
const tc = createTestCase(`test ${i}`);
|
|
251
|
+
recordHumanRating(tc, 'with_kdna_better', 'expert');
|
|
252
|
+
project.tests.push(tc);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const readme = generateReadme(project, {
|
|
256
|
+
description: 'Leadership decision-making judgment — diagnose whether execution failures are really decision voids.',
|
|
257
|
+
origin: '15 years of leadership coaching across 200+ teams.',
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
assert.ok(readme.includes('# leadership_decisions'));
|
|
261
|
+
assert.ok(readme.includes('## Where it comes from'));
|
|
262
|
+
assert.ok(readme.includes('## Where it applies'));
|
|
263
|
+
assert.ok(readme.includes('## How it is verified'));
|
|
264
|
+
assert.ok(readme.includes('## When it does NOT apply'));
|
|
265
|
+
assert.ok(readme.includes('15 years'));
|
|
266
|
+
assert.ok(readme.includes('quality_badge: tested'));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ─── Provenance with Compile ────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
describe('Provenance Integration', () => {
|
|
273
|
+
test('buildProvenance captures compile metadata', () => {
|
|
274
|
+
const project = createProject('test', 'domain', { author: { name: 'Author', id: 'auth_001' } });
|
|
275
|
+
project.cards = [
|
|
276
|
+
makeLockedCard('axiom', { one_sentence: 'T.', full_statement: 'FS.', why: 'B.', applies_when: ['x'], does_not_apply_when: ['y'], failure_risk: 'r' }),
|
|
277
|
+
];
|
|
278
|
+
const compiled = compileDomain(project);
|
|
279
|
+
const prov = buildProvenance(project, compiled.files);
|
|
280
|
+
assert.equal(prov.author_id, 'auth_001');
|
|
281
|
+
assert.equal(prov.locked_card_count, 1);
|
|
282
|
+
assert.ok(prov.build_id.startsWith('build_'));
|
|
283
|
+
assert.ok(prov.content_fingerprint.startsWith('sha256:'));
|
|
284
|
+
});
|
|
285
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const { test, describe } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
|
|
4
|
+
const { createProject } = require('../src/project');
|
|
5
|
+
const { createCard, lockCard, transitionCard } = require('../src/cards');
|
|
6
|
+
const { parseCompareOutput, createJudgmentDelta, compareDeltas, formatDeltaMarkdown, scoreDelta } = require('../src/testlab/delta');
|
|
7
|
+
const { diffProjects, recommendVersionBump, generateChangelog, bumpVersion, markBreakingChange } = require('../src/versioning');
|
|
8
|
+
|
|
9
|
+
function makeLockedCard(type, fields, id) {
|
|
10
|
+
const card = createCard(type, fields, id);
|
|
11
|
+
transitionCard(card, 'revised', { by: 'tester' });
|
|
12
|
+
lockCard(card, { by: 'tester', statement: 'ok', checked: { applies_when: true, does_not_apply_when: true, failure_risk: true } });
|
|
13
|
+
return card;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ─── Judgment Delta ──────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
describe('Judgment Delta', () => {
|
|
19
|
+
const DIFF_TEXT = `1. CLASSIFICATION: language_polishing → structural_diagnosis
|
|
20
|
+
2. DIAGNOSIS: The root cause was identified as a missing argument rather than poor wording.
|
|
21
|
+
3. ACTIONS: Suggested deletion more than rewriting — structural fix, not surface polish.
|
|
22
|
+
4. BOUNDARY AWARENESS: SAME
|
|
23
|
+
5. TERMINOLOGY: Used domain-specific terms like judgment_pressure and cognitive_hook.
|
|
24
|
+
VERDICT: trajectory_changed`;
|
|
25
|
+
|
|
26
|
+
test('parseCompareOutput extracts axes', () => {
|
|
27
|
+
const result = parseCompareOutput(DIFF_TEXT);
|
|
28
|
+
assert.equal(result.verdict, 'trajectory_changed');
|
|
29
|
+
assert.ok(result.axes.classification);
|
|
30
|
+
assert.ok(result.axes.diagnosis);
|
|
31
|
+
assert.ok(result.axes.actions);
|
|
32
|
+
assert.ok(!result.axes.boundary_awareness); // SAME → omitted
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('scoreDelta counts changed axes', () => {
|
|
36
|
+
const axes = { classification: 'changed', diagnosis: 'changed', terminology: 'changed' };
|
|
37
|
+
const result = scoreDelta(axes);
|
|
38
|
+
assert.equal(result.score, 8); // 5 + 3
|
|
39
|
+
assert.equal(result.changed.length, 3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('createJudgmentDelta builds full report', () => {
|
|
43
|
+
const delta = createJudgmentDelta('@aikdna/writing', 'Help me improve this post',
|
|
44
|
+
'Generic response...', 'Domain-specific response...', DIFF_TEXT,
|
|
45
|
+
{ model: 'claude-sonnet-4-5', triggeredAxioms: ['ax_001'], selfChecksPassed: 5 }
|
|
46
|
+
);
|
|
47
|
+
assert.equal(delta.meta.domain, '@aikdna/writing');
|
|
48
|
+
assert.equal(delta.verdict, 'trajectory_changed');
|
|
49
|
+
assert.ok(delta.score >= 8);
|
|
50
|
+
assert.ok(delta.summary.includes('dimensions'));
|
|
51
|
+
assert.ok(delta.scoring.D1_diagnostic_depth >= 5);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('createJudgmentDelta handles no-change verdict', () => {
|
|
55
|
+
const noChangeText = '1. CLASSIFICATION: SAME\n2. DIAGNOSIS: SAME\nVERDICT: trajectory_unchanged';
|
|
56
|
+
const delta = createJudgmentDelta('test', 'input', 'a', 'b', noChangeText);
|
|
57
|
+
assert.equal(delta.verdict, 'trajectory_unchanged');
|
|
58
|
+
assert.equal(delta.score, 5);
|
|
59
|
+
assert.ok(delta.summary.includes('did not significantly alter'));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('compareDeltas shows improvement', () => {
|
|
63
|
+
const d1 = createJudgmentDelta('test', 'input', 'a', 'b', DIFF_TEXT);
|
|
64
|
+
const betterText = DIFF_TEXT.replace('SAME', 'Improved boundary awareness with explicit scope limits');
|
|
65
|
+
const d2 = createJudgmentDelta('test', 'input', 'a', 'b', betterText);
|
|
66
|
+
const cmp = compareDeltas(d1, d2);
|
|
67
|
+
assert.ok(cmp.improved);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('formatDeltaMarkdown produces readable report', () => {
|
|
71
|
+
const delta = createJudgmentDelta('@aikdna/writing', 'test input', 'a', 'b', DIFF_TEXT);
|
|
72
|
+
const md = formatDeltaMarkdown(delta);
|
|
73
|
+
assert.ok(md.includes('# KDNA Judgment Comparison Report'));
|
|
74
|
+
assert.ok(md.includes('## Judgment Diff'));
|
|
75
|
+
assert.ok(md.includes('## Scoring'));
|
|
76
|
+
assert.ok(md.includes('Verdict'));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('parseCompareOutput handles legacy format', () => {
|
|
80
|
+
const legacyText = 'classification: changed\nterminology: domain_specific\nVERDICT: trajectory_changed';
|
|
81
|
+
const result = parseCompareOutput(legacyText);
|
|
82
|
+
assert.equal(result.verdict, 'trajectory_changed');
|
|
83
|
+
assert.ok(result.axes.classification);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ─── Versioning ──────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
describe('Versioning', () => {
|
|
90
|
+
test('diffProjects detects added cards', () => {
|
|
91
|
+
const oldProject = createProject('test');
|
|
92
|
+
const newProject = createProject('test');
|
|
93
|
+
newProject.cards = [makeLockedCard('axiom', { one_sentence: 'New axiom.' }, 'ax_001')];
|
|
94
|
+
const diff = diffProjects(oldProject, newProject);
|
|
95
|
+
assert.equal(diff.added.length, 1);
|
|
96
|
+
assert.equal(diff.removed.length, 0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('diffProjects detects removed cards', () => {
|
|
100
|
+
const oldProject = createProject('test');
|
|
101
|
+
oldProject.cards = [makeLockedCard('axiom', { one_sentence: 'Old.' }, 'ax_001')];
|
|
102
|
+
const newProject = createProject('test');
|
|
103
|
+
const diff = diffProjects(oldProject, newProject);
|
|
104
|
+
assert.equal(diff.removed.length, 1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('diffProjects detects changed fields', () => {
|
|
108
|
+
const oldProject = createProject('test');
|
|
109
|
+
oldProject.cards = [makeLockedCard('axiom', { one_sentence: 'Old text.', full_statement: 'Old full.' }, 'ax_001')];
|
|
110
|
+
const newProject = createProject('test');
|
|
111
|
+
newProject.cards = [makeLockedCard('axiom', { one_sentence: 'New text.', full_statement: 'New full.' }, 'ax_001')];
|
|
112
|
+
const diff = diffProjects(oldProject, newProject);
|
|
113
|
+
assert.equal(diff.changed.length, 1);
|
|
114
|
+
assert.ok(diff.changed[0].changes.one_sentence);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('recommendVersionBump: MAJOR for removed axiom', () => {
|
|
118
|
+
const diff = { added: [], removed: [{ type: 'axiom' }], changed: [], summary: { added_count: 0, removed_count: 1, changed_count: 0 } };
|
|
119
|
+
assert.equal(recommendVersionBump(diff), 'major');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('recommendVersionBump: MINOR for added axiom', () => {
|
|
123
|
+
const diff = { added: [{ type: 'axiom' }], removed: [], changed: [], summary: { added_count: 1, removed_count: 0, changed_count: 0 } };
|
|
124
|
+
assert.equal(recommendVersionBump(diff), 'minor');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('recommendVersionBump: PATCH for field changes', () => {
|
|
128
|
+
const diff = { added: [], removed: [], changed: [{}], summary: { added_count: 0, removed_count: 0, changed_count: 1 } };
|
|
129
|
+
assert.equal(recommendVersionBump(diff), 'minor');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('bumpVersion increments correctly', () => {
|
|
133
|
+
assert.equal(bumpVersion('0.1.0', 'patch'), '0.1.1');
|
|
134
|
+
assert.equal(bumpVersion('0.1.0', 'minor'), '0.2.0');
|
|
135
|
+
assert.equal(bumpVersion('0.1.0', 'major'), '1.0.0');
|
|
136
|
+
assert.equal(bumpVersion('2.3.5', 'none'), '2.3.5');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('generateChangelog produces markdown', () => {
|
|
140
|
+
const oldProject = createProject('test');
|
|
141
|
+
const newProject = createProject('test');
|
|
142
|
+
newProject.cards = [makeLockedCard('axiom', { one_sentence: 'New axiom for agent judgment.' }, 'ax_001')];
|
|
143
|
+
const diff = diffProjects(oldProject, newProject);
|
|
144
|
+
const changelog = generateChangelog(diff, '0.1.0', '0.2.0', { domain: 'test' });
|
|
145
|
+
assert.ok(changelog.includes('# test v0.2.0'));
|
|
146
|
+
assert.ok(changelog.includes('MINOR'));
|
|
147
|
+
assert.ok(changelog.includes('ax_001'));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('markBreakingChange detects axiom removal', () => {
|
|
151
|
+
const diff = { added: [], removed: [{ type: 'axiom' }], changed: [], summary: { added_count: 0, removed_count: 1, changed_count: 0 } };
|
|
152
|
+
const result = markBreakingChange(diff);
|
|
153
|
+
assert.equal(result.breaking, true);
|
|
154
|
+
assert.ok(result.reason.includes('breaking change'));
|
|
155
|
+
});
|
|
156
|
+
});
|