@aikdna/kdna-studio-core 1.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/LICENSE +202 -0
- package/README.md +168 -0
- package/package.json +27 -0
- package/schemas/studio.project.schema.json +194 -0
- package/src/cards/feynman.js +105 -0
- package/src/cards/index.js +114 -0
- package/src/cli-bridge/index.js +2 -0
- package/src/compile/index.js +511 -0
- package/src/evidence/index.js +81 -0
- package/src/governance/index.js +140 -0
- package/src/i18n/index.js +145 -0
- package/src/index.js +59 -0
- package/src/judgment-fields.js +28 -0
- package/src/packaging/index.js +88 -0
- package/src/pipeline.js +101 -0
- package/src/project/index.js +359 -0
- package/src/provenance/index.js +44 -0
- package/src/quality/contradiction.js +183 -0
- package/src/quality/index.js +161 -0
- package/src/quality/validate-cards.js +164 -0
- package/src/testlab/delta.js +193 -0
- package/src/testlab/index.js +116 -0
- package/src/versioning/index.js +155 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Versioning — Judgment-aware semver with refined bump rules (v0.3.3).
|
|
3
|
+
*
|
|
4
|
+
* PATCH: typo, description, Feynman restatement, evidence_refs, examples
|
|
5
|
+
* MINOR: new axiom/misunderstanding/self_check, narrowed applies_when, new does_not_apply_when, new evals
|
|
6
|
+
* MAJOR: removed axiom, changed core meaning, expanded applies_when, removed does_not_apply_when, scope change, access change
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function diffProjects(oldProject, newProject) {
|
|
10
|
+
const oldCards = oldProject.cards || [];
|
|
11
|
+
const newCards = newProject.cards || [];
|
|
12
|
+
const oldById = new Map(oldCards.map(c => [c.id, c]));
|
|
13
|
+
const newById = new Map(newCards.map(c => [c.id, c]));
|
|
14
|
+
|
|
15
|
+
const added = []; const removed = []; const changed = [];
|
|
16
|
+
for (const [id, nc] of newById) {
|
|
17
|
+
if (!oldById.has(id)) { added.push(cardSummary(nc)); }
|
|
18
|
+
else {
|
|
19
|
+
const oc = oldById.get(id);
|
|
20
|
+
const fieldChanges = diffFields(oc.fields || {}, nc.fields || {});
|
|
21
|
+
if (Object.keys(fieldChanges).length > 0) {
|
|
22
|
+
changed.push(cardSummary(nc, fieldChanges));
|
|
23
|
+
} else if (oc.status !== nc.status) {
|
|
24
|
+
changed.push({ ...cardSummary(nc), status_change: { from: oc.status, to: nc.status } });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const [id, oc] of oldById) {
|
|
29
|
+
if (!newById.has(id)) removed.push(cardSummary(oc));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { added, removed, changed, unchanged: oldCards.length - removed.length - changed.length,
|
|
33
|
+
summary: { added_count: added.length, removed_count: removed.length, changed_count: changed.length } };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cardSummary(card, changes) {
|
|
37
|
+
return { id: card.id, type: card.type, one_sentence: card.fields?.one_sentence || card.fields?.question || '',
|
|
38
|
+
changes: changes || null };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function stableStringify(obj) {
|
|
42
|
+
if (typeof obj !== 'object' || obj === null) return JSON.stringify(obj);
|
|
43
|
+
if (Array.isArray(obj)) return '[' + obj.map(stableStringify).join(',') + ']';
|
|
44
|
+
const keys = Object.keys(obj).sort();
|
|
45
|
+
return '{' + keys.map(k => JSON.stringify(k) + ':' + stableStringify(obj[k])).join(',') + '}';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function diffFields(oldFields, newFields) {
|
|
49
|
+
const changes = {};
|
|
50
|
+
for (const key of new Set([...Object.keys(oldFields), ...Object.keys(newFields)])) {
|
|
51
|
+
const ov = stableStringify(oldFields[key] || null), nv = stableStringify(newFields[key] || null);
|
|
52
|
+
if (ov !== nv) changes[key] = { before: oldFields[key] || null, after: newFields[key] || null };
|
|
53
|
+
}
|
|
54
|
+
return changes;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function recommendVersionBump(diff) {
|
|
58
|
+
const { added, removed, changed } = diff;
|
|
59
|
+
const removedAxioms = removed.filter(c => c.type === 'axiom');
|
|
60
|
+
const removedMisunderstandings = removed.filter(c => c.type === 'misunderstanding');
|
|
61
|
+
|
|
62
|
+
// MAJOR checks
|
|
63
|
+
if (removedAxioms.length > 0 || removedMisunderstandings.length > 0) return 'major';
|
|
64
|
+
for (const c of changed) {
|
|
65
|
+
if (!c.changes) continue;
|
|
66
|
+
// Core meaning change on axiom → major
|
|
67
|
+
if (c.type === 'axiom' && ('one_sentence' in c.changes || 'full_statement' in c.changes)) return 'major';
|
|
68
|
+
// Expanded scope → major
|
|
69
|
+
if ('applies_when' in c.changes) {
|
|
70
|
+
const bef = c.changes.applies_when.before || [], aft = c.changes.applies_when.after || [];
|
|
71
|
+
if (aft.length > bef.length) return 'major';
|
|
72
|
+
}
|
|
73
|
+
// Removed boundary → major
|
|
74
|
+
if ('does_not_apply_when' in c.changes) {
|
|
75
|
+
const bef = c.changes.does_not_apply_when.before || [], aft = c.changes.does_not_apply_when.after || [];
|
|
76
|
+
if (aft.length < bef.length) return 'major';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// MINOR checks
|
|
81
|
+
const addedAxioms = added.filter(c => c.type === 'axiom');
|
|
82
|
+
const addedMisunderstandings = added.filter(c => c.type === 'misunderstanding');
|
|
83
|
+
const addedSelfChecks = added.filter(c => c.type === 'self_check');
|
|
84
|
+
if (addedAxioms.length > 0 || addedMisunderstandings.length > 0 || addedSelfChecks.length > 0) return 'minor';
|
|
85
|
+
for (const c of changed) {
|
|
86
|
+
if (!c.changes) continue;
|
|
87
|
+
// Narrowed scope → minor
|
|
88
|
+
if ('does_not_apply_when' in c.changes) {
|
|
89
|
+
const bef = c.changes.does_not_apply_when.before || [], aft = c.changes.does_not_apply_when.after || [];
|
|
90
|
+
if (aft.length > bef.length) return 'minor';
|
|
91
|
+
}
|
|
92
|
+
// Changed why/key_distinction → minor
|
|
93
|
+
if (c.type === 'axiom' && 'why' in c.changes) return 'minor';
|
|
94
|
+
if (c.type === 'misunderstanding' && 'key_distinction' in c.changes) return 'minor';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// PATCH: wording-only changes
|
|
98
|
+
if (added.length > 0 || changed.length > 0) return 'patch';
|
|
99
|
+
return 'none';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function generateChangelog(diff, oldVersion, newVersion, options = {}) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
const bump = recommendVersionBump(diff);
|
|
105
|
+
lines.push(`# ${options.domain || 'domain'} v${newVersion}`);
|
|
106
|
+
lines.push('');
|
|
107
|
+
lines.push(`**Previous:** v${oldVersion} **Bump:** ${bump.toUpperCase()}`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
|
|
110
|
+
for (const [label, items] of [['Added', diff.added], ['Removed', diff.removed], ['Changed', diff.changed]]) {
|
|
111
|
+
if (items.length === 0) continue;
|
|
112
|
+
lines.push(`## ${label}`); lines.push('');
|
|
113
|
+
for (const c of items) {
|
|
114
|
+
lines.push(`- **${c.type}** \`${c.id}\`: ${c.one_sentence}`);
|
|
115
|
+
if (c.status_change) lines.push(` - Status: ${c.status_change.from} → ${c.status_change.to}`);
|
|
116
|
+
if (c.changes) for (const [f, v] of Object.entries(c.changes)) {
|
|
117
|
+
lines.push(` - ${f}: "${String(v.before || '').slice(0, 60)}" → "${String(v.after || '').slice(0, 60)}"`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
lines.push('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0) {
|
|
124
|
+
lines.push('No judgment changes detected.\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function bumpVersion(currentVersion, bumpType) {
|
|
131
|
+
const [maj, min, pat] = currentVersion.split('.').map(Number);
|
|
132
|
+
if (bumpType === 'major') return `${maj + 1}.0.0`;
|
|
133
|
+
if (bumpType === 'minor') return `${maj}.${min + 1}.0`;
|
|
134
|
+
if (bumpType === 'patch') return `${maj}.${min}.${pat + 1}`;
|
|
135
|
+
return currentVersion;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function markBreakingChange(diff) {
|
|
139
|
+
const recommended = recommendVersionBump(diff);
|
|
140
|
+
const removedAxioms = diff.removed.filter(c => c.type === 'axiom');
|
|
141
|
+
const scopeWidening = diff.changed.filter(c => c.changes && 'applies_when' in c.changes &&
|
|
142
|
+
(c.changes.applies_when.after || []).length > (c.changes.applies_when.before || []).length);
|
|
143
|
+
const coreMeaningChanges = diff.changed.filter(c => c.changes &&
|
|
144
|
+
('one_sentence' in c.changes || 'full_statement' in c.changes));
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
breaking: recommended === 'major',
|
|
148
|
+
reason: removedAxioms.length > 0 ? `${removedAxioms.length} axiom(s) removed — breaking change` :
|
|
149
|
+
coreMeaningChanges.length > 0 ? `${coreMeaningChanges.length} core meaning change(s) — breaking change` :
|
|
150
|
+
scopeWidening.length > 0 ? `${scopeWidening.length} scope widening(s) — may affect existing behavior` : null,
|
|
151
|
+
recommended_bump: recommended,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { diffProjects, recommendVersionBump, generateChangelog, bumpVersion, markBreakingChange };
|