@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
package/package.json
CHANGED
package/src/compile/index.js
CHANGED
|
@@ -3,32 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Only locked cards enter compilation output.
|
|
5
5
|
* Draft/Revised cards are silently excluded.
|
|
6
|
+
* Supports full 6-file output: Core / Patterns / Scenarios / Cases / Reasoning / Evolution.
|
|
6
7
|
*/
|
|
8
|
+
const provenance = require('../provenance');
|
|
7
9
|
|
|
8
10
|
function compileCore(cards) {
|
|
9
|
-
const axioms = cards.filter(c => c.type === 'axiom' && c.locked).map(c => ({
|
|
10
|
-
|
|
11
|
-
...c.fields,
|
|
12
|
-
}));
|
|
13
|
-
const ontology = cards.filter(c => c.type === 'ontology' && c.locked).map(c => ({
|
|
14
|
-
id: c.id,
|
|
15
|
-
...c.fields,
|
|
16
|
-
}));
|
|
11
|
+
const axioms = cards.filter(c => c.type === 'axiom' && c.locked).map(c => ({ id: c.id, ...c.fields }));
|
|
12
|
+
const ontology = cards.filter(c => c.type === 'ontology' && c.locked).map(c => ({ id: c.id, ...c.fields }));
|
|
17
13
|
const frameworks = [];
|
|
18
14
|
const stances = [];
|
|
19
15
|
const boundaries = cards.filter(c => c.type === 'boundary' && c.locked).map(c => ({
|
|
20
16
|
id: c.id,
|
|
21
|
-
scope: c.fields
|
|
22
|
-
out_of_scope: c.fields
|
|
23
|
-
acceptable_exceptions: c.fields
|
|
24
|
-
}));
|
|
25
|
-
|
|
26
|
-
const risks = cards.filter(c => c.type === 'risk' && c.locked).map(c => ({
|
|
27
|
-
id: c.id,
|
|
28
|
-
failure_mode: c.fields.failure_mode,
|
|
29
|
-
likelihood: c.fields.likelihood,
|
|
30
|
-
mitigation: c.fields.mitigation,
|
|
17
|
+
scope: c.fields?.scope || '',
|
|
18
|
+
out_of_scope: c.fields?.out_of_scope || '',
|
|
19
|
+
acceptable_exceptions: c.fields?.acceptable_exceptions || [],
|
|
31
20
|
}));
|
|
21
|
+
const risks = cards.filter(c => c.type === 'risk' && c.locked).map(c => ({ id: c.id, ...c.fields }));
|
|
32
22
|
|
|
33
23
|
return { axioms, ontology, frameworks, stances, boundaries, risks };
|
|
34
24
|
}
|
|
@@ -36,52 +26,98 @@ function compileCore(cards) {
|
|
|
36
26
|
function compilePatterns(cards) {
|
|
37
27
|
const misunderstandings = cards.filter(c => c.type === 'misunderstanding' && c.locked).map(c => ({
|
|
38
28
|
id: c.id,
|
|
39
|
-
wrong: c.fields
|
|
40
|
-
correct: c.fields
|
|
41
|
-
key_distinction: c.fields
|
|
42
|
-
failure_risk: c.fields
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const bannedTerms = [];
|
|
46
|
-
const aesthetics = cards.filter(c => c.type === 'aesthetic' && c.locked).map(c => ({
|
|
47
|
-
id: c.id,
|
|
48
|
-
preference: c.fields.preference,
|
|
49
|
-
rationale: c.fields.rationale,
|
|
29
|
+
wrong: c.fields?.wrong || '',
|
|
30
|
+
correct: c.fields?.correct || '',
|
|
31
|
+
key_distinction: c.fields?.key_distinction || '',
|
|
32
|
+
failure_risk: c.fields?.failure_risk || null,
|
|
33
|
+
applies_when: c.fields?.applies_when || [],
|
|
34
|
+
does_not_apply_when: c.fields?.does_not_apply_when || [],
|
|
50
35
|
}));
|
|
36
|
+
const selfCheckQuestions = cards.filter(c => c.type === 'self_check' && c.locked).map(c => c.fields?.question || '');
|
|
37
|
+
const aesthetics = cards.filter(c => c.type === 'aesthetic' && c.locked).map(c => ({ id: c.id, ...c.fields }));
|
|
51
38
|
|
|
52
39
|
const terminology = {
|
|
53
40
|
standard_terms: [],
|
|
54
|
-
banned_terms:
|
|
41
|
+
banned_terms: [],
|
|
55
42
|
};
|
|
56
43
|
|
|
57
|
-
return { terminology, misunderstandings, self_check:
|
|
44
|
+
return { terminology, misunderstandings, self_check: selfCheckQuestions, aesthetics };
|
|
58
45
|
}
|
|
59
46
|
|
|
60
47
|
function compileScenarios(cards) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}));
|
|
48
|
+
const locked = cards.filter(c => c.type === 'scenario' && c.locked);
|
|
49
|
+
if (locked.length === 0) return [];
|
|
50
|
+
return locked.map(c => ({ id: c.id, ...c.fields }));
|
|
65
51
|
}
|
|
66
52
|
|
|
67
53
|
function compileCases(cards) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
const locked = cards.filter(c => c.type === 'case' && c.locked);
|
|
55
|
+
if (locked.length === 0) return [];
|
|
56
|
+
return locked.map(c => ({ id: c.id, ...c.fields }));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function compileReasoning(cards) {
|
|
60
|
+
// Reasoning chains from axiom implications
|
|
61
|
+
const lockedAxioms = cards.filter(c => c.type === 'axiom' && c.locked);
|
|
62
|
+
if (lockedAxioms.length === 0) return [];
|
|
63
|
+
return lockedAxioms.map(ax => ({
|
|
64
|
+
id: `chain_${ax.id}`,
|
|
65
|
+
from: ax.fields?.one_sentence || '',
|
|
66
|
+
logic: [ax.fields?.full_statement || ''],
|
|
67
|
+
so_what: ax.fields?.why || 'Agent judgment changes when this axiom is loaded.',
|
|
71
68
|
}));
|
|
72
69
|
}
|
|
73
70
|
|
|
71
|
+
function compileEvolution(cards) {
|
|
72
|
+
const lockedCards = cards.filter(c => c.locked);
|
|
73
|
+
if (lockedCards.length === 0) return { stages: [], capability_layers: [], measurements: [] };
|
|
74
|
+
|
|
75
|
+
// Build evolution from audit logs
|
|
76
|
+
const stages = [];
|
|
77
|
+
const seenAxioms = new Set();
|
|
78
|
+
for (const card of lockedCards) {
|
|
79
|
+
if (seenAxioms.has(card.id)) continue;
|
|
80
|
+
seenAxioms.add(card.id);
|
|
81
|
+
for (const entry of (card.audit_log || [])) {
|
|
82
|
+
if (entry.event === 'locked' || entry.event === 'published') {
|
|
83
|
+
stages.push({
|
|
84
|
+
card_id: card.id,
|
|
85
|
+
event: entry.event,
|
|
86
|
+
at: entry.at,
|
|
87
|
+
by: entry.by,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
stages: stages.sort((a, b) => a.at.localeCompare(b.at)),
|
|
95
|
+
capability_layers: [
|
|
96
|
+
{ layer: 1, name: 'Foundation', description: 'Core axioms and patterns established.' },
|
|
97
|
+
],
|
|
98
|
+
measurements: [
|
|
99
|
+
{ metric: 'locked_axioms', value: lockedCards.filter(c => c.type === 'axiom').length },
|
|
100
|
+
{ metric: 'locked_misunderstandings', value: lockedCards.filter(c => c.type === 'misunderstanding').length },
|
|
101
|
+
{ metric: 'self_checks', value: lockedCards.filter(c => c.type === 'self_check').length },
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
74
106
|
function compileManifest(project) {
|
|
75
|
-
const lockedCount = project.cards.filter(c => c.locked).length;
|
|
107
|
+
const lockedCount = (project.cards || []).filter(c => c.locked).length;
|
|
108
|
+
const cards = project.cards || [];
|
|
109
|
+
const hasScenarios = cards.some(c => c.type === 'scenario' && c.locked);
|
|
110
|
+
const hasCases = cards.some(c => c.type === 'case' && c.locked);
|
|
111
|
+
const fileCount = 2 + (hasScenarios ? 1 : 0) + (hasCases ? 1 : 0) + 2; // Core+Patterns+Reasoning+Evolution (+ Scenarios + Cases)
|
|
76
112
|
return {
|
|
77
113
|
kdna_spec: '1.0-rc',
|
|
78
114
|
name: project.name,
|
|
79
|
-
version: project.release
|
|
115
|
+
version: (project.release && project.release.version) || '0.1.0',
|
|
80
116
|
status: 'experimental',
|
|
81
|
-
access: project.release
|
|
82
|
-
author: project.author,
|
|
83
|
-
description: project.
|
|
84
|
-
file_count: 2,
|
|
117
|
+
access: (project.release && project.release.access) || 'open',
|
|
118
|
+
author: project.author || { name: '', id: '' },
|
|
119
|
+
description: project.name,
|
|
120
|
+
file_count: 2,
|
|
85
121
|
created: project.created,
|
|
86
122
|
updated: project.updated,
|
|
87
123
|
};
|
|
@@ -93,26 +129,129 @@ function compileDomain(project) {
|
|
|
93
129
|
const patterns = compilePatterns(cards);
|
|
94
130
|
const scenarios = compileScenarios(cards);
|
|
95
131
|
const cases = compileCases(cards);
|
|
132
|
+
const reasoning = compileReasoning(cards);
|
|
133
|
+
const evolution = compileEvolution(cards);
|
|
96
134
|
const manifest = compileManifest(project);
|
|
97
135
|
|
|
98
136
|
const files = {};
|
|
99
137
|
files['KDNA_Core.json'] = JSON.stringify(core, null, 2);
|
|
100
138
|
files['KDNA_Patterns.json'] = JSON.stringify(patterns, null, 2);
|
|
101
|
-
if (scenarios.length > 0) files['KDNA_Scenarios.json'] = JSON.stringify(scenarios, null, 2);
|
|
102
|
-
if (cases.length > 0) files['KDNA_Cases.json'] = JSON.stringify(cases, null, 2);
|
|
139
|
+
if (scenarios.length > 0) files['KDNA_Scenarios.json'] = JSON.stringify({ scenes: scenarios }, null, 2);
|
|
140
|
+
if (cases.length > 0) files['KDNA_Cases.json'] = JSON.stringify({ cases }, null, 2);
|
|
141
|
+
if (reasoning.length > 0) files['KDNA_Reasoning.json'] = JSON.stringify({ chains: reasoning }, null, 2);
|
|
142
|
+
if (evolution.stages && evolution.stages.length > 0) files['KDNA_Evolution.json'] = JSON.stringify(evolution, null, 2);
|
|
103
143
|
files['kdna.json'] = JSON.stringify(manifest, null, 2);
|
|
104
144
|
|
|
105
|
-
const excludedCount =
|
|
145
|
+
const excludedCount = cards.filter(c => !c.locked && !['deprecated'].includes(c.status)).length;
|
|
106
146
|
|
|
107
147
|
return {
|
|
108
148
|
files,
|
|
109
149
|
stats: {
|
|
110
|
-
total_cards:
|
|
111
|
-
locked_cards:
|
|
150
|
+
total_cards: cards.length,
|
|
151
|
+
locked_cards: cards.filter(c => c.locked).length,
|
|
112
152
|
excluded_cards: excludedCount,
|
|
113
|
-
deprecated_cards:
|
|
153
|
+
deprecated_cards: cards.filter(c => c.status === 'deprecated').length,
|
|
154
|
+
files_output: Object.keys(files).length,
|
|
114
155
|
},
|
|
115
156
|
};
|
|
116
157
|
}
|
|
117
158
|
|
|
118
|
-
|
|
159
|
+
function generateReadme(project, options = {}) {
|
|
160
|
+
const cards = project.cards || [];
|
|
161
|
+
const locked = cards.filter(c => c.locked);
|
|
162
|
+
const lockedAxioms = locked.filter(c => c.type === 'axiom');
|
|
163
|
+
const lockedMisunderstandings = locked.filter(c => c.type === 'misunderstanding');
|
|
164
|
+
const lockedSelfChecks = locked.filter(c => c.type === 'self_check');
|
|
165
|
+
const lockedBoundaries = locked.filter(c => c.type === 'boundary');
|
|
166
|
+
const tests = project.tests || [];
|
|
167
|
+
|
|
168
|
+
const lines = [];
|
|
169
|
+
lines.push(`# ${project.name}`);
|
|
170
|
+
lines.push('');
|
|
171
|
+
if (options.description) {
|
|
172
|
+
lines.push(options.description);
|
|
173
|
+
lines.push('');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Four Questions
|
|
177
|
+
lines.push('## Where it comes from');
|
|
178
|
+
lines.push('');
|
|
179
|
+
lines.push(options.origin || `Domain expertise encoded into ${locked.length} judgment cards through structured interview and human lock.`);
|
|
180
|
+
lines.push('');
|
|
181
|
+
|
|
182
|
+
lines.push('## Where it applies');
|
|
183
|
+
lines.push('');
|
|
184
|
+
const appliesWhen = [...new Set(lockedAxioms.flatMap(ax => ax.fields?.applies_when || []))];
|
|
185
|
+
if (appliesWhen.length > 0) {
|
|
186
|
+
appliesWhen.forEach(w => lines.push(`- ${w}`));
|
|
187
|
+
} else {
|
|
188
|
+
lines.push('- As declared in each axiom\'s applies_when field.');
|
|
189
|
+
}
|
|
190
|
+
lines.push('');
|
|
191
|
+
|
|
192
|
+
lines.push('## How it is verified');
|
|
193
|
+
lines.push('');
|
|
194
|
+
lines.push(`- ${tests.length} eval cases (${tests.filter(t => t.result).length} rated)`);
|
|
195
|
+
lines.push(`- ${lockedAxioms.length} locked axioms with applies_when / does_not_apply_when / failure_risk`);
|
|
196
|
+
lines.push(`- ${lockedSelfChecks.length} self-check questions`);
|
|
197
|
+
lines.push(`- ${lockedMisunderstandings.length} misunderstanding patterns`);
|
|
198
|
+
lines.push('');
|
|
199
|
+
|
|
200
|
+
lines.push('## When it does NOT apply');
|
|
201
|
+
lines.push('');
|
|
202
|
+
const notApply = [...new Set(lockedAxioms.flatMap(ax => ax.fields?.does_not_apply_when || []))];
|
|
203
|
+
if (notApply.length > 0) {
|
|
204
|
+
notApply.forEach(w => lines.push(`- ${w}`));
|
|
205
|
+
}
|
|
206
|
+
const outOfScope = lockedBoundaries.flatMap(b => [b.fields?.out_of_scope || '']).filter(Boolean);
|
|
207
|
+
for (const oos of outOfScope) {
|
|
208
|
+
if (!notApply.includes(oos)) lines.push(`- ${oos}`);
|
|
209
|
+
}
|
|
210
|
+
lines.push('');
|
|
211
|
+
|
|
212
|
+
// Top Axioms
|
|
213
|
+
if (lockedAxioms.length > 0) {
|
|
214
|
+
lines.push('## Top Axioms');
|
|
215
|
+
lines.push('');
|
|
216
|
+
lockedAxioms.forEach(ax => {
|
|
217
|
+
lines.push(`- **${ax.fields?.one_sentence || ax.id}**`);
|
|
218
|
+
if (ax.fields?.failure_risk) lines.push(` - Failure risk: ${ax.fields.failure_risk}`);
|
|
219
|
+
});
|
|
220
|
+
lines.push('');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Top Misunderstandings
|
|
224
|
+
if (lockedMisunderstandings.length > 0) {
|
|
225
|
+
lines.push('## Top Misunderstandings');
|
|
226
|
+
lines.push('');
|
|
227
|
+
lockedMisunderstandings.forEach(ms => {
|
|
228
|
+
lines.push(`- WRONG: ${ms.fields?.wrong}`);
|
|
229
|
+
lines.push(` CORRECT: ${ms.fields?.correct}`);
|
|
230
|
+
});
|
|
231
|
+
lines.push('');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Self-checks
|
|
235
|
+
if (lockedSelfChecks.length > 0) {
|
|
236
|
+
lines.push('## Eval Score');
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push(`- quality_badge: ${tests.filter(t => t.result === 'with_kdna_better').length >= 5 ? 'tested' : 'untested'}`);
|
|
239
|
+
lines.push(`- eval cases: ${tests.length}`);
|
|
240
|
+
lines.push('');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Files
|
|
244
|
+
lines.push('## Files');
|
|
245
|
+
lines.push('');
|
|
246
|
+
const fileCount = 2
|
|
247
|
+
+ (cards.filter(c => c.type === 'scenario' && c.locked).length > 0 ? 1 : 0)
|
|
248
|
+
+ (cards.filter(c => c.type === 'case' && c.locked).length > 0 ? 1 : 0)
|
|
249
|
+
+ (lockedAxioms.length > 0 ? 1 : 0)
|
|
250
|
+
+ (cards.filter(c => c.status === 'locked' || c.status === 'tested').length > 0 ? 1 : 0);
|
|
251
|
+
lines.push(`${fileCount} KDNA JSON files + evals/ + demo/`);
|
|
252
|
+
lines.push('');
|
|
253
|
+
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = { compileDomain, compileCore, compilePatterns, compileScenarios, compileCases, compileReasoning, compileEvolution, compileManifest, generateReadme };
|
package/src/index.js
CHANGED
|
@@ -28,6 +28,8 @@ const testlab = require('./testlab');
|
|
|
28
28
|
const versioning = require('./versioning');
|
|
29
29
|
const feynman = require('./cards/feynman');
|
|
30
30
|
const contradiction = require('./quality/contradiction');
|
|
31
|
+
const validateCards = require('./quality/validate-cards');
|
|
32
|
+
const delta = require('./testlab/delta');
|
|
31
33
|
|
|
32
34
|
module.exports = {
|
|
33
35
|
cards,
|
|
@@ -41,4 +43,6 @@ module.exports = {
|
|
|
41
43
|
versioning,
|
|
42
44
|
feynman,
|
|
43
45
|
contradiction,
|
|
46
|
+
validateCards,
|
|
47
|
+
delta,
|
|
44
48
|
};
|
package/src/quality/index.js
CHANGED
|
@@ -1,65 +1,175 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Quality
|
|
2
|
+
* Enhanced Quality Gates — 4-grade readiness scoring with detailed rules.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* draft_grade
|
|
6
|
-
* human_controlled
|
|
7
|
-
* tested_grade
|
|
8
|
-
* publishable_grade
|
|
4
|
+
* Grades:
|
|
5
|
+
* draft_grade — Core+Patterns exist, ≥3 human-reviewed cards
|
|
6
|
+
* human_controlled — All core axioms locked, each with applies_when/does_not_apply_when/failure_risk
|
|
7
|
+
* tested_grade — ≥5 eval cases, ≥3 comparison tests
|
|
8
|
+
* publishable_grade — ≥10 evals, README complete, known limitations, kdna verify passes
|
|
9
9
|
*/
|
|
10
|
+
const contradiction = require('./contradiction');
|
|
11
|
+
|
|
10
12
|
function computeReadiness(project) {
|
|
11
13
|
const cards = project.cards || [];
|
|
12
|
-
const locked = cards.filter(c => c.locked);
|
|
13
14
|
const tests = project.tests || [];
|
|
15
|
+
const locked = cards.filter(c => c.locked);
|
|
16
|
+
const lockedAxioms = locked.filter(c => c.type === 'axiom');
|
|
17
|
+
const lockedSelfChecks = locked.filter(c => c.type === 'self_check');
|
|
18
|
+
const ratedTests = tests.filter(t => t.result);
|
|
14
19
|
|
|
15
20
|
const blocking = [];
|
|
16
21
|
const warnings = [];
|
|
17
22
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
// ── Minimum Structure ──────────────────────────────────────────
|
|
24
|
+
if (project.cards.length === 0) {
|
|
25
|
+
blocking.push('Project has no cards');
|
|
26
|
+
return buildResult('draft_grade', blocking, warnings, project);
|
|
27
|
+
}
|
|
28
|
+
if (locked.length === 0) {
|
|
29
|
+
blocking.push('No locked cards — nothing to compile');
|
|
30
|
+
return buildResult('draft_grade', blocking, warnings, project);
|
|
31
|
+
}
|
|
22
32
|
|
|
23
|
-
//
|
|
33
|
+
// ── Axiom Checks ──────────────────────────────────────────────
|
|
24
34
|
for (const ax of lockedAxioms) {
|
|
25
|
-
if (!ax.fields?.
|
|
35
|
+
if (!ax.fields?.one_sentence || ax.fields.one_sentence.length < 10) {
|
|
36
|
+
blocking.push(`${ax.id}: one_sentence too short or missing`);
|
|
37
|
+
}
|
|
38
|
+
if (!ax.fields?.full_statement || ax.fields.full_statement.length < 30) {
|
|
39
|
+
warnings.push(`${ax.id}: full_statement too short — may be vague`);
|
|
40
|
+
}
|
|
41
|
+
if (!ax.fields?.why || ax.fields.why.length < 10) {
|
|
42
|
+
warnings.push(`${ax.id}: missing "why" — explains what the agent gets wrong without this`);
|
|
43
|
+
}
|
|
44
|
+
if (!ax.fields?.applies_when || ax.fields.applies_when.length === 0) {
|
|
45
|
+
blocking.push(`${ax.id}: missing applies_when`);
|
|
46
|
+
}
|
|
47
|
+
if (!ax.fields?.does_not_apply_when || ax.fields.does_not_apply_when.length === 0) {
|
|
26
48
|
blocking.push(`${ax.id}: missing does_not_apply_when`);
|
|
27
49
|
}
|
|
28
50
|
if (!ax.fields?.failure_risk) {
|
|
29
|
-
|
|
51
|
+
blocking.push(`${ax.id}: missing failure_risk`);
|
|
30
52
|
}
|
|
31
53
|
if (!ax.human_lock) {
|
|
32
|
-
blocking.push(`${ax.id}:
|
|
54
|
+
blocking.push(`${ax.id}: not locked — must be locked before compile`);
|
|
33
55
|
}
|
|
34
56
|
if (!ax.feynman_restatement) {
|
|
35
57
|
warnings.push(`${ax.id}: missing Feynman restatement`);
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
|
|
39
|
-
//
|
|
40
|
-
const lockedSelfChecks = locked.filter(c => c.type === 'self_check');
|
|
61
|
+
// ── Self-check Checks ──────────────────────────────────────────
|
|
41
62
|
for (const sc of lockedSelfChecks) {
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
const q = sc.fields?.question || '';
|
|
64
|
+
if (!q.endsWith('?')) {
|
|
65
|
+
blocking.push(`${sc.id}: self_check must be a question ending with ?`);
|
|
66
|
+
}
|
|
67
|
+
if (q.length < 15) {
|
|
68
|
+
warnings.push(`${sc.id}: self_check question too short — may be too vague`);
|
|
69
|
+
}
|
|
70
|
+
if (/\b(is this good|is this correct|is this helpful|is this clear|good enough)\b/i.test(q)) {
|
|
71
|
+
warnings.push(`${sc.id}: self_check is generic — should be domain-specific`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Misunderstanding Checks ────────────────────────────────────
|
|
76
|
+
const lockedMisunderstandings = locked.filter(c => c.type === 'misunderstanding');
|
|
77
|
+
for (const ms of lockedMisunderstandings) {
|
|
78
|
+
if (!ms.fields?.key_distinction || ms.fields.key_distinction.length < 20) {
|
|
79
|
+
blocking.push(`${ms.id}: key_distinction missing or too short`);
|
|
80
|
+
}
|
|
81
|
+
if (!ms.fields?.wrong || ms.fields.wrong.length < 10) {
|
|
82
|
+
warnings.push(`${ms.id}: wrong belief very short — may be a straw man`);
|
|
44
83
|
}
|
|
84
|
+
if (!ms.fields?.correct || ms.fields.correct.length < 10) {
|
|
85
|
+
warnings.push(`${ms.id}: correct belief very short`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Boundary Checks ────────────────────────────────────────────
|
|
90
|
+
const lockedBoundaries = locked.filter(c => c.type === 'boundary');
|
|
91
|
+
for (const bd of lockedBoundaries) {
|
|
92
|
+
if (bd.fields?.acceptable_exceptions && bd.fields.acceptable_exceptions.length === 0) {
|
|
93
|
+
warnings.push(`${bd.id}: no acceptable_exceptions — every boundary has justified exceptions`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Contradiction Check ────────────────────────────────────────
|
|
98
|
+
const contradictions = contradiction.detectContradictions(cards);
|
|
99
|
+
for (const c of contradictions) {
|
|
100
|
+
if (c.severity === 'blocking') blocking.push(c.message);
|
|
101
|
+
else warnings.push(c.message);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Test Count Checks ──────────────────────────────────────────
|
|
105
|
+
if (ratedTests.length === 0 && locked.length >= 3) {
|
|
106
|
+
warnings.push('No rated tests — domain may not actually change agent behavior');
|
|
45
107
|
}
|
|
108
|
+
if (ratedTests.length < 3 && ratedTests.length > 0) {
|
|
109
|
+
warnings.push(`Only ${ratedTests.length} rated tests — recommend at least 3 for confidence`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Determine Grade ────────────────────────────────────────────
|
|
113
|
+
const axiomsComplete = lockedAxioms.length >= 1 &&
|
|
114
|
+
lockedAxioms.every(ax =>
|
|
115
|
+
ax.fields?.applies_when?.length &&
|
|
116
|
+
ax.fields?.does_not_apply_when?.length &&
|
|
117
|
+
ax.fields?.failure_risk &&
|
|
118
|
+
ax.human_lock
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const boundariesComplete = lockedBoundaries.length === 0 ||
|
|
122
|
+
lockedBoundaries.every(b => b.fields?.scope && b.fields?.out_of_scope);
|
|
46
123
|
|
|
47
|
-
// Determine grade
|
|
48
124
|
let grade = 'draft_grade';
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
125
|
+
if (locked.length >= 3 && axiomsComplete) {
|
|
126
|
+
grade = 'human_controlled';
|
|
127
|
+
}
|
|
128
|
+
if (grade === 'human_controlled' && ratedTests.length >= 5 && lockedSelfChecks.length >= 3 && boundariesComplete) {
|
|
129
|
+
grade = 'tested_grade';
|
|
130
|
+
}
|
|
131
|
+
if (grade === 'tested_grade' &&
|
|
132
|
+
ratedTests.length >= 10 &&
|
|
133
|
+
lockedAxioms.length >= 3 &&
|
|
134
|
+
lockedSelfChecks.length >= 5 &&
|
|
135
|
+
blocking.length === 0) {
|
|
136
|
+
grade = 'publishable_grade';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return buildResult(grade, blocking, warnings, project);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildResult(grade, blocking, warnings, project) {
|
|
143
|
+
const lockedCount = (project.cards || []).filter(c => c.locked).length;
|
|
144
|
+
const ratedTests = (project.tests || []).filter(t => t.result).length;
|
|
55
145
|
|
|
56
146
|
return {
|
|
57
147
|
grade,
|
|
58
148
|
publishable: grade === 'publishable_grade' && blocking.length === 0,
|
|
59
149
|
blocking,
|
|
60
150
|
warnings,
|
|
61
|
-
score: Math.max(0, 100 - blocking.length * 15 - warnings.length *
|
|
151
|
+
score: Math.max(0, 100 - blocking.length * 15 - warnings.length * 3),
|
|
152
|
+
stats: {
|
|
153
|
+
total_cards: (project.cards || []).length,
|
|
154
|
+
locked_cards: lockedCount,
|
|
155
|
+
locked_axioms: (project.cards || []).filter(c => c.type === 'axiom' && c.locked).length,
|
|
156
|
+
locked_self_checks: (project.cards || []).filter(c => c.type === 'self_check' && c.locked).length,
|
|
157
|
+
total_tests: (project.tests || []).length,
|
|
158
|
+
rated_tests: ratedTests,
|
|
159
|
+
},
|
|
160
|
+
next_step: grade === 'draft_grade'
|
|
161
|
+
? 'Lock at least 3 axioms with applies_when, does_not_apply_when, and failure_risk.'
|
|
162
|
+
: grade === 'human_controlled'
|
|
163
|
+
? 'Add 5+ eval cases and run kdna compare to reach tested grade.'
|
|
164
|
+
: grade === 'tested_grade'
|
|
165
|
+
? 'Add 10+ evals, 3+ axioms, 5+ self-checks, and pass kdna verify --judgment to reach publishable.'
|
|
166
|
+
: 'Ready to publish. Run kdna pack and kdna publish.',
|
|
62
167
|
};
|
|
63
168
|
}
|
|
64
169
|
|
|
65
|
-
|
|
170
|
+
function getBlockingIssues(project) {
|
|
171
|
+
const result = computeReadiness(project);
|
|
172
|
+
return result.blocking;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { computeReadiness, getBlockingIssues };
|