@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,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feynman Restatement — Verify understanding, not just agreement.
|
|
3
|
+
*
|
|
4
|
+
* The Feynman technique: explain a concept in simple terms a non-expert
|
|
5
|
+
* would understand. This proves the expert truly owns the judgment,
|
|
6
|
+
* rather than just nodding at an AI proposal.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function tokenize(text) {
|
|
10
|
+
if (!text) return [];
|
|
11
|
+
try {
|
|
12
|
+
if (typeof Intl !== 'undefined' && Intl.Segmenter) {
|
|
13
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'word' });
|
|
14
|
+
return [...segmenter.segment(text)].filter(s => s.isWordLike && s.segment.length > 3).map(s => s.segment);
|
|
15
|
+
}
|
|
16
|
+
} catch { /* fallback */ }
|
|
17
|
+
return text.split(/\s+/).filter(w => w.length > 3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createFeynmanRestatement(card, text) {
|
|
21
|
+
if (!text || typeof text !== 'string') throw new Error('Feynman restatement text is required');
|
|
22
|
+
if (text.length < 20) throw new Error('Feynman restatement too short (minimum 20 chars)');
|
|
23
|
+
|
|
24
|
+
const restatement = {
|
|
25
|
+
text,
|
|
26
|
+
evaluated_at: new Date().toISOString(),
|
|
27
|
+
score: evaluateRestatementQuality(card, text),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return restatement;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function evaluateRestatementQuality(card, text) {
|
|
34
|
+
const original = card.fields?.one_sentence || card.fields?.essence || card.fields?.wrong || '';
|
|
35
|
+
const originalLower = original.toLowerCase();
|
|
36
|
+
const textLower = text.toLowerCase();
|
|
37
|
+
|
|
38
|
+
// 1. Not just a repeat — check word overlap ratio
|
|
39
|
+
const originalWords = new Set(tokenize(originalLower));
|
|
40
|
+
const textWords = tokenize(textLower);
|
|
41
|
+
const overlapCount = textWords.filter(w => originalWords.has(w)).length;
|
|
42
|
+
const overlapRatio = textWords.length > 0 ? overlapCount / textWords.length : 1;
|
|
43
|
+
const not_just_repeat = overlapRatio < 0.5;
|
|
44
|
+
|
|
45
|
+
// 2. Not too abstract — check for concrete words
|
|
46
|
+
const concreteSignals = ['example', 'instance', 'case', 'scenario', 'when', 'if', 'customer', 'user', 'client', 'team', 'manager', 'project', 'code', 'product', 'meeting', 'email'];
|
|
47
|
+
const hasConcrete = concreteSignals.some(w => textLower.includes(w));
|
|
48
|
+
const not_too_abstract = hasConcrete;
|
|
49
|
+
|
|
50
|
+
// 3. Has concrete example — check for story-like patterns
|
|
51
|
+
const storyPatterns = ['when', 'if', 'because', 'so', 'then', 'example', 'imagine', 'suppose', 'consider'];
|
|
52
|
+
const hasStory = storyPatterns.filter(w => textLower.includes(w)).length >= 2;
|
|
53
|
+
const has_concrete_example = hasStory;
|
|
54
|
+
|
|
55
|
+
// 4. Clarifies boundary — mentions what it's NOT
|
|
56
|
+
const boundaryWords = ['not', "don't", 'does not', 'cannot', 'unless', 'except', 'only if', 'but not', 'however'];
|
|
57
|
+
const clarifies_boundary = boundaryWords.some(w => textLower.includes(w));
|
|
58
|
+
|
|
59
|
+
// 5. Ordinary person understands — Flesch-like readability check
|
|
60
|
+
const sentences = text.split(/[.!?]+/).filter(s => s.trim());
|
|
61
|
+
const avgWordsPerSentence = sentences.length > 0
|
|
62
|
+
? sentences.reduce((s, sent) => s + sent.trim().split(/\s+/).length, 0) / sentences.length
|
|
63
|
+
: 99;
|
|
64
|
+
const ordinary_person_understands = avgWordsPerSentence < 25;
|
|
65
|
+
|
|
66
|
+
const scores = { not_just_repeat, not_too_abstract, has_concrete_example, clarifies_boundary, ordinary_person_understands };
|
|
67
|
+
const totalScore = Object.values(scores).filter(Boolean).length;
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...scores,
|
|
71
|
+
total: totalScore,
|
|
72
|
+
quality: totalScore >= 4 ? 'good' : totalScore >= 3 ? 'acceptable' : 'needs_improvement',
|
|
73
|
+
detail: {
|
|
74
|
+
overlap_ratio: Math.round(overlapRatio * 100) + '%',
|
|
75
|
+
avg_words_per_sentence: Math.round(avgWordsPerSentence),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function attachRestatementToLock(card, restatement) {
|
|
81
|
+
if (!card.human_lock) throw new Error('Card must be locked before attaching Feynman restatement');
|
|
82
|
+
card.feynman_restatement = restatement;
|
|
83
|
+
card.audit_log.push({
|
|
84
|
+
at: new Date().toISOString(),
|
|
85
|
+
event: 'feynman_restatement',
|
|
86
|
+
by: card.human_lock.by,
|
|
87
|
+
});
|
|
88
|
+
return card;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validateRestatementCard(card) {
|
|
92
|
+
const issues = [];
|
|
93
|
+
if (!card.feynman_restatement) {
|
|
94
|
+
issues.push({ type: 'missing_feynman', severity: 'warning', message: `${card.id}: missing Feynman restatement (lock is stronger with it)` });
|
|
95
|
+
return issues;
|
|
96
|
+
}
|
|
97
|
+
const fr = card.feynman_restatement;
|
|
98
|
+
if (!fr.score?.not_just_repeat) issues.push({ type: 'repeat', severity: 'warning', message: `${card.id}: Feynman may just repeat the original text` });
|
|
99
|
+
if (!fr.score?.not_too_abstract) issues.push({ type: 'abstract', severity: 'warning', message: `${card.id}: Feynman may be too abstract` });
|
|
100
|
+
if (!fr.score?.clarifies_boundary) issues.push({ type: 'no_boundary', severity: 'warning', message: `${card.id}: Feynman does not explain when this does NOT apply` });
|
|
101
|
+
if (!fr.score?.ordinary_person_understands) issues.push({ type: 'complex', severity: 'warning', message: `${card.id}: Feynman may be too complex for a non-expert` });
|
|
102
|
+
return issues;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { createFeynmanRestatement, evaluateRestatementQuality, attachRestatementToLock, validateRestatementCard };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judgment Card state machine and lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Card CRUD operations
|
|
6
|
+
* - State machine enforcement (Draft → Revised → Locked → Tested → Published → Deprecated)
|
|
7
|
+
* - Human Lock protocol
|
|
8
|
+
* - Feynman Restatement
|
|
9
|
+
* - Audit trail management
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { cardJudgmentFingerprint } = require('../judgment-fields');
|
|
13
|
+
|
|
14
|
+
const VALID_STATES = ['draft', 'revised', 'locked', 'tested', 'published', 'deprecated'];
|
|
15
|
+
const CARD_TYPES = ['axiom', 'ontology', 'misunderstanding', 'boundary', 'self_check', 'risk', 'aesthetic', 'scenario', 'case'];
|
|
16
|
+
|
|
17
|
+
const TRANSITIONS = {
|
|
18
|
+
draft: ['revised', 'deprecated'],
|
|
19
|
+
revised: ['locked', 'draft', 'deprecated'],
|
|
20
|
+
locked: ['tested', 'revised', 'deprecated'],
|
|
21
|
+
tested: ['published', 'locked', 'deprecated'],
|
|
22
|
+
published: ['deprecated'],
|
|
23
|
+
deprecated: [],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function createCard(type, fields = {}, id = null) {
|
|
27
|
+
if (!CARD_TYPES.includes(type)) throw new Error(`Invalid card type: ${type}`);
|
|
28
|
+
const card = {
|
|
29
|
+
id: id || `${type.slice(0, 2)}_${require('crypto').randomUUID()}`,
|
|
30
|
+
type,
|
|
31
|
+
status: 'draft',
|
|
32
|
+
locked: false,
|
|
33
|
+
fields,
|
|
34
|
+
evidence_refs: [],
|
|
35
|
+
test_refs: [],
|
|
36
|
+
human_lock: null,
|
|
37
|
+
feynman_restatement: null,
|
|
38
|
+
audit_log: [
|
|
39
|
+
{ at: new Date().toISOString(), event: 'created', by: 'ai' }
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
return card;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function transitionCard(card, toState, transitionContext = {}) {
|
|
46
|
+
if (!VALID_STATES.includes(toState)) throw new Error(`Invalid state: ${toState}`);
|
|
47
|
+
if (!TRANSITIONS[card.status].includes(toState)) {
|
|
48
|
+
throw new Error(`Invalid transition: ${card.status} → ${toState}`);
|
|
49
|
+
}
|
|
50
|
+
const newCard = { ...card, fields: { ...card.fields } };
|
|
51
|
+
newCard.status = toState;
|
|
52
|
+
newCard.locked = ['locked', 'tested', 'published'].includes(toState);
|
|
53
|
+
newCard.audit_log = [...(card.audit_log || []), {
|
|
54
|
+
at: new Date().toISOString(),
|
|
55
|
+
event: toState,
|
|
56
|
+
by: transitionContext.by || 'system',
|
|
57
|
+
...(transitionContext.reason && { reason: transitionContext.reason }),
|
|
58
|
+
}];
|
|
59
|
+
return newCard;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function lockCard(card, lockPayload) {
|
|
63
|
+
if (!lockPayload.by) throw new Error('lockPayload.by is required');
|
|
64
|
+
if (!lockPayload.statement) throw new Error('lockPayload.statement is required (expert confirmation in own words)');
|
|
65
|
+
if (!lockPayload.checked?.applies_when) throw new Error('Must confirm applies_when reviewed');
|
|
66
|
+
if (!lockPayload.checked?.does_not_apply_when) throw new Error('Must confirm does_not_apply_when reviewed');
|
|
67
|
+
if (!lockPayload.checked?.failure_risk) throw new Error('Must confirm failure_risk reviewed');
|
|
68
|
+
|
|
69
|
+
const lockedCard = { ...card, fields: { ...card.fields } };
|
|
70
|
+
lockedCard.human_lock = {
|
|
71
|
+
by: lockPayload.by,
|
|
72
|
+
at: new Date().toISOString(),
|
|
73
|
+
statement: lockPayload.statement,
|
|
74
|
+
checked: lockPayload.checked,
|
|
75
|
+
judgment_fingerprint: cardJudgmentFingerprint(lockedCard),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return transitionCard(lockedCard, 'locked', { by: lockPayload.by });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function unlockCard(card, reason, by) {
|
|
82
|
+
if (!reason) throw new Error('Unlock requires a reason');
|
|
83
|
+
const unlockedCard = { ...card, fields: { ...card.fields } };
|
|
84
|
+
unlockedCard.human_lock = null;
|
|
85
|
+
return transitionCard(unlockedCard, 'revised', {
|
|
86
|
+
by,
|
|
87
|
+
reason: `unlocked: ${reason}`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getLockedCards(project) {
|
|
92
|
+
return project.cards.filter(c => ['locked', 'tested', 'published'].includes(c.status));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getPublishableCards(project) {
|
|
96
|
+
return project.cards.filter(c => c.status === 'tested' || c.status === 'locked');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
CARD_TYPES,
|
|
101
|
+
VALID_STATES,
|
|
102
|
+
TRANSITIONS,
|
|
103
|
+
createCard,
|
|
104
|
+
transitionCard,
|
|
105
|
+
lockCard,
|
|
106
|
+
unlockCard,
|
|
107
|
+
getLockedCards,
|
|
108
|
+
getPublishableCards,
|
|
109
|
+
// Feynman restatement (re-exported from feynman.js)
|
|
110
|
+
createFeynmanRestatement: require('./feynman').createFeynmanRestatement,
|
|
111
|
+
evaluateRestatementQuality: require('./feynman').evaluateRestatementQuality,
|
|
112
|
+
attachRestatementToLock: require('./feynman').attachRestatementToLock,
|
|
113
|
+
validateRestatementCard: require('./feynman').validateRestatementCard,
|
|
114
|
+
};
|