@aikdna/kdna-cli 0.9.0 → 0.12.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/README.md +109 -31
- package/SECURITY.md +41 -0
- package/package.json +3 -2
- package/src/agent.js +430 -2
- package/src/cli.js +280 -38
- package/src/cmds/_common.js +68 -3
- package/src/cmds/badge.js +244 -0
- package/src/cmds/changelog.js +226 -0
- package/src/cmds/cluster.js +214 -3
- package/src/cmds/doctor.js +233 -0
- package/src/cmds/domain.js +110 -33
- package/src/cmds/governance.js +471 -0
- package/src/cmds/identity.js +5 -4
- package/src/cmds/quality.js +34 -9
- package/src/cmds/registry.js +62 -22
- package/src/cmds/studio.js +577 -0
- package/src/cmds/test.js +177 -0
- package/src/cmds/trace.js +225 -0
- package/src/compare.js +46 -32
- package/src/diff.js +136 -38
- package/src/identity.js +20 -4
- package/src/install.js +181 -91
- package/src/publish.js +39 -3
- package/src/search.js +33 -2
- package/src/verify.js +98 -24
- package/src/version.js +110 -3
package/src/cmds/registry.js
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { CANONICAL_REGISTRY_URL, REGISTRY_CACHE, fetchRegistry } = require('../registry');
|
|
4
|
-
const { error, readJson, loadRegistry, INSTALL_DIR } = require('./_common');
|
|
4
|
+
const { error, readJson, loadRegistry, INSTALL_DIR, EXIT } = require('./_common');
|
|
5
5
|
|
|
6
|
-
function cmdList(showAvailable) {
|
|
6
|
+
function cmdList(showAvailable, jsonMode = false) {
|
|
7
7
|
if (showAvailable) {
|
|
8
8
|
const domains = loadRegistry({ allowNetwork: true });
|
|
9
9
|
if (!domains || !domains.length) {
|
|
10
|
+
if (jsonMode) {
|
|
11
|
+
console.log(JSON.stringify([]));
|
|
12
|
+
process.exit(EXIT.OK);
|
|
13
|
+
}
|
|
10
14
|
error('No registry found.');
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
if (jsonMode) {
|
|
18
|
+
const result = domains.map((d) => ({
|
|
19
|
+
name: d.name || d.id || null,
|
|
20
|
+
version: d.version || null,
|
|
21
|
+
type: d.type || 'domain',
|
|
22
|
+
status: d.status || null,
|
|
23
|
+
description: d.description || null,
|
|
24
|
+
yanked: d.yanked || false,
|
|
25
|
+
deprecated: d.deprecated || false,
|
|
26
|
+
}));
|
|
27
|
+
console.log(JSON.stringify(result));
|
|
28
|
+
process.exit(EXIT.OK);
|
|
29
|
+
}
|
|
30
|
+
|
|
13
31
|
console.log('Available KDNA domains:');
|
|
14
32
|
console.log(`Registry: ${REGISTRY_CACHE}`);
|
|
15
33
|
console.log('');
|
|
@@ -30,6 +48,10 @@ function cmdList(showAvailable) {
|
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
if (!fs.existsSync(INSTALL_DIR)) {
|
|
51
|
+
if (jsonMode) {
|
|
52
|
+
console.log(JSON.stringify([]));
|
|
53
|
+
process.exit(EXIT.OK);
|
|
54
|
+
}
|
|
33
55
|
console.log('No domains installed.');
|
|
34
56
|
console.log(`Installation directory: ${INSTALL_DIR}`);
|
|
35
57
|
return;
|
|
@@ -61,38 +83,56 @@ function cmdList(showAvailable) {
|
|
|
61
83
|
}
|
|
62
84
|
|
|
63
85
|
// Detect and warn about legacy (un-scoped) installs
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
if (!jsonMode) {
|
|
87
|
+
const legacy = fs.readdirSync(INSTALL_DIR).filter((d) => {
|
|
88
|
+
if (d.startsWith('@') || d.startsWith('.')) return false;
|
|
89
|
+
try {
|
|
90
|
+
return fs.statSync(path.join(INSTALL_DIR, d)).isDirectory();
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (legacy.length) {
|
|
96
|
+
console.log('⚠ Legacy (un-scoped) directories detected — please remove + re-install:');
|
|
97
|
+
legacy.forEach((d) => console.log(` ~/.kdna/domains/${d}/`));
|
|
98
|
+
console.log('');
|
|
70
99
|
}
|
|
71
|
-
});
|
|
72
|
-
if (legacy.length) {
|
|
73
|
-
console.log('⚠ Legacy (un-scoped) directories detected — please remove + re-install:');
|
|
74
|
-
legacy.forEach((d) => console.log(` ~/.kdna/domains/${d}/`));
|
|
75
|
-
console.log('');
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
if (!installed.length) {
|
|
103
|
+
if (jsonMode) {
|
|
104
|
+
console.log(JSON.stringify([]));
|
|
105
|
+
process.exit(EXIT.OK);
|
|
106
|
+
}
|
|
79
107
|
console.log('No v0.7 domains installed.');
|
|
80
108
|
console.log(`Run: kdna install <name> # e.g. kdna install writing`);
|
|
81
109
|
return;
|
|
82
110
|
}
|
|
83
111
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
for (const { scope, ident, full } of installed) {
|
|
112
|
+
// Build structured data for installed domains
|
|
113
|
+
const domains = installed.map(({ scope, ident, full }) => {
|
|
87
114
|
const core = readJson(path.join(full, 'KDNA_Core.json'));
|
|
88
115
|
const manifest = readJson(path.join(full, 'kdna.json'));
|
|
89
116
|
const cluster = readJson(path.join(full, 'cluster.json'));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
117
|
+
return {
|
|
118
|
+
name: `${scope}/${ident}`,
|
|
119
|
+
version: manifest?.version || manifest?._source?.version || core?.meta?.version || '?',
|
|
120
|
+
type: cluster ? 'cluster' : 'domain',
|
|
121
|
+
description: manifest?.description || core?.meta?.purpose || '',
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (jsonMode) {
|
|
126
|
+
console.log(JSON.stringify(domains));
|
|
127
|
+
process.exit(EXIT.OK);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log('Installed KDNA domains:');
|
|
131
|
+
console.log('');
|
|
132
|
+
for (const d of domains) {
|
|
133
|
+
const kind = d.type === 'cluster' ? '[cluster]' : '';
|
|
134
|
+
console.log(` ${d.name.padEnd(36)} v${d.version} ${kind}`);
|
|
135
|
+
if (d.description) console.log(` ${d.description}`);
|
|
96
136
|
}
|
|
97
137
|
console.log('');
|
|
98
138
|
console.log(`Location: ${INSTALL_DIR}`);
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KDNA Studio CLI commands — Phase 1: Studio Beta 底层支撑.
|
|
3
|
+
*
|
|
4
|
+
* kdna studio scaffold <name> Create Studio project skeleton
|
|
5
|
+
* kdna cards validate <project.json> Validate Judgment Cards
|
|
6
|
+
* kdna lock verify <project.json> Verify Human Lock status
|
|
7
|
+
* kdna studio compile <project.json> Compile locked cards into KDNA domain
|
|
8
|
+
* kdna studio readiness <project.json> Generate Domain Readiness Card
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { error, readJson, writeJson, EXIT } = require('./_common');
|
|
14
|
+
|
|
15
|
+
// ─── Scaffold ─────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
const CARD_TEMPLATES = {
|
|
18
|
+
axioms: [
|
|
19
|
+
{
|
|
20
|
+
id: 'ax_001',
|
|
21
|
+
one_sentence: '[TODO: One-line judgment principle]',
|
|
22
|
+
full_statement: '[TODO: Full statement — what the agent should do differently]',
|
|
23
|
+
why: '[TODO: What the agent would get wrong without this axiom]',
|
|
24
|
+
applies_when: [],
|
|
25
|
+
does_not_apply_when: [],
|
|
26
|
+
failure_risk: null,
|
|
27
|
+
confidence: null,
|
|
28
|
+
evidence_type: [],
|
|
29
|
+
locked: false,
|
|
30
|
+
human_lock: null,
|
|
31
|
+
feynman_restatement: null,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
ontology: [
|
|
35
|
+
{
|
|
36
|
+
id: 'ont_001',
|
|
37
|
+
concept: '[TODO: Core concept name]',
|
|
38
|
+
essence: '[TODO: Operational essence — what the agent checks]',
|
|
39
|
+
boundary: '[TODO: What this is not, or is often confused with]',
|
|
40
|
+
trigger_signal: '[TODO: Observable words/patterns the agent detects]',
|
|
41
|
+
locked: false,
|
|
42
|
+
human_lock: null,
|
|
43
|
+
feynman_restatement: null,
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
misunderstandings: [
|
|
47
|
+
{
|
|
48
|
+
id: 'ms_001',
|
|
49
|
+
wrong: '[TODO: A belief a real agent might hold]',
|
|
50
|
+
correct: '[TODO: The correct judgment]',
|
|
51
|
+
key_distinction: '[TODO: Conceptual boundary between wrong and correct]',
|
|
52
|
+
failure_risk: null,
|
|
53
|
+
locked: false,
|
|
54
|
+
human_lock: null,
|
|
55
|
+
feynman_restatement: null,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
boundaries: [
|
|
59
|
+
{
|
|
60
|
+
id: 'bd_001',
|
|
61
|
+
scope: '[TODO: What this domain covers]',
|
|
62
|
+
out_of_scope: '[TODO: What this domain explicitly does not cover]',
|
|
63
|
+
acceptable_exceptions: [],
|
|
64
|
+
locked: false,
|
|
65
|
+
human_lock: null,
|
|
66
|
+
feynman_restatement: null,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
self_checks: [
|
|
70
|
+
{
|
|
71
|
+
id: 'sc_001',
|
|
72
|
+
question: '[TODO: A yes/no question the agent asks before final output?]',
|
|
73
|
+
applies_to: [],
|
|
74
|
+
locked: false,
|
|
75
|
+
human_lock: null,
|
|
76
|
+
feynman_restatement: null,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function cmdStudioScaffold(name, args = []) {
|
|
82
|
+
if (!name) error('Usage: kdna studio scaffold <name> [--type=domain|cluster] [--minimal]', EXIT.INPUT_ERROR);
|
|
83
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) {
|
|
84
|
+
error(`Invalid name "${name}". Use lowercase letters, numbers, hyphens, underscores. Start with letter.`, EXIT.INPUT_ERROR);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const type = args.includes('--type=cluster') ? 'cluster' : 'domain';
|
|
88
|
+
const minimal = args.includes('--minimal');
|
|
89
|
+
const targetDir = path.resolve(name);
|
|
90
|
+
|
|
91
|
+
if (fs.existsSync(targetDir)) {
|
|
92
|
+
error(`Directory already exists: ${targetDir}`, EXIT.INPUT_ERROR);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
96
|
+
|
|
97
|
+
// Create directory structure
|
|
98
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
99
|
+
fs.mkdirSync(path.join(targetDir, 'cards'), { recursive: true });
|
|
100
|
+
fs.mkdirSync(path.join(targetDir, 'exports'), { recursive: true });
|
|
101
|
+
|
|
102
|
+
// Write studio.project.json
|
|
103
|
+
const project = {
|
|
104
|
+
kdna_studio: '1.0-beta',
|
|
105
|
+
name,
|
|
106
|
+
type,
|
|
107
|
+
created: today,
|
|
108
|
+
updated: today,
|
|
109
|
+
cards: {
|
|
110
|
+
axioms: 'cards/axioms.json',
|
|
111
|
+
ontology: 'cards/ontology.json',
|
|
112
|
+
misunderstandings: 'cards/misunderstandings.json',
|
|
113
|
+
boundaries: 'cards/boundaries.json',
|
|
114
|
+
self_checks: 'cards/self_checks.json',
|
|
115
|
+
},
|
|
116
|
+
exports: {
|
|
117
|
+
dir: 'exports/',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
writeJson(path.join(targetDir, 'studio.project.json'), project);
|
|
121
|
+
|
|
122
|
+
// Write card templates (skip ontology/boundaries in minimal mode)
|
|
123
|
+
const cardTypes = minimal
|
|
124
|
+
? ['axioms', 'self_checks']
|
|
125
|
+
: ['axioms', 'ontology', 'misunderstandings', 'boundaries', 'self_checks'];
|
|
126
|
+
|
|
127
|
+
for (const type of cardTypes) {
|
|
128
|
+
const file = project.cards[type];
|
|
129
|
+
writeJson(path.join(targetDir, file), CARD_TEMPLATES[type]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Write exports/README.md
|
|
133
|
+
fs.writeFileSync(
|
|
134
|
+
path.join(targetDir, 'exports', 'README.md'),
|
|
135
|
+
`# ${name}\n\nCompiled KDNA domain files will appear here after \`kdna studio compile\`.\n`,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
console.log(`✓ Studio project created: ${targetDir}/`);
|
|
139
|
+
console.log(` Type: ${type}${minimal ? ' (minimal)' : ''}`);
|
|
140
|
+
console.log(` Cards: ${cardTypes.join(', ')}`);
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log('Next steps:');
|
|
143
|
+
console.log(' 1. Edit cards/ — replace all [TODO] placeholders');
|
|
144
|
+
console.log(' 2. Run: kdna cards validate studio.project.json');
|
|
145
|
+
console.log(' 3. Run: kdna lock verify studio.project.json');
|
|
146
|
+
console.log(' 4. Run: kdna studio compile studio.project.json');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Cards Validate ───────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
function cmdCardsValidate(projectPath, args = []) {
|
|
152
|
+
const jsonMode = args.includes('--json');
|
|
153
|
+
const abs = path.resolve(projectPath);
|
|
154
|
+
|
|
155
|
+
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
156
|
+
const project = readJson(abs);
|
|
157
|
+
if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
158
|
+
|
|
159
|
+
const errors = [];
|
|
160
|
+
const warnings = [];
|
|
161
|
+
const passed = [];
|
|
162
|
+
|
|
163
|
+
function fail(msg) { errors.push(msg); }
|
|
164
|
+
function warn(msg) { warnings.push(msg); }
|
|
165
|
+
function ok(msg) { passed.push(msg); }
|
|
166
|
+
|
|
167
|
+
// Validate each card set
|
|
168
|
+
for (const [cardType, cardFile] of Object.entries(project.cards || {})) {
|
|
169
|
+
const cardPath = path.join(path.dirname(abs), cardFile);
|
|
170
|
+
if (!fs.existsSync(cardPath)) {
|
|
171
|
+
warn(`Card file not found: ${cardFile}`);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const cards = readJson(cardPath);
|
|
176
|
+
if (!cards || !Array.isArray(cards)) {
|
|
177
|
+
fail(`Invalid card file (not a JSON array): ${cardFile}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
switch (cardType) {
|
|
182
|
+
case 'axioms':
|
|
183
|
+
for (const ax of cards) {
|
|
184
|
+
const label = ax.id || '?';
|
|
185
|
+
if (!ax.one_sentence || ax.one_sentence.includes('[TODO]')) {
|
|
186
|
+
warn(`axiom ${label}: one_sentence is placeholder`);
|
|
187
|
+
} else {
|
|
188
|
+
ok(`axiom ${label}: one_sentence OK`);
|
|
189
|
+
}
|
|
190
|
+
if (!ax.applies_when || !Array.isArray(ax.applies_when) || ax.applies_when.length === 0) {
|
|
191
|
+
fail(`axiom ${label}: missing applies_when`);
|
|
192
|
+
} else {
|
|
193
|
+
ok(`axiom ${label}: applies_when has ${ax.applies_when.length} entries`);
|
|
194
|
+
}
|
|
195
|
+
if (!ax.does_not_apply_when || !Array.isArray(ax.does_not_apply_when) || ax.does_not_apply_when.length === 0) {
|
|
196
|
+
fail(`axiom ${label}: missing does_not_apply_when`);
|
|
197
|
+
} else {
|
|
198
|
+
ok(`axiom ${label}: does_not_apply_when has ${ax.does_not_apply_when.length} entries`);
|
|
199
|
+
}
|
|
200
|
+
if (!ax.failure_risk) {
|
|
201
|
+
fail(`axiom ${label}: missing failure_risk`);
|
|
202
|
+
} else {
|
|
203
|
+
ok(`axiom ${label}: failure_risk declared`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case 'misunderstandings':
|
|
209
|
+
for (const ms of cards) {
|
|
210
|
+
const label = ms.id || '?';
|
|
211
|
+
if (!ms.wrong || ms.wrong.includes('[TODO]')) warn(`misunderstanding ${label}: wrong is placeholder`);
|
|
212
|
+
else ok(`misunderstanding ${label}: wrong OK`);
|
|
213
|
+
if (!ms.correct || ms.correct.includes('[TODO]')) warn(`misunderstanding ${label}: correct is placeholder`);
|
|
214
|
+
else ok(`misunderstanding ${label}: correct OK`);
|
|
215
|
+
if (!ms.key_distinction || ms.key_distinction.length < 15) {
|
|
216
|
+
fail(`misunderstanding ${label}: key_distinction missing or too short`);
|
|
217
|
+
} else {
|
|
218
|
+
ok(`misunderstanding ${label}: key_distinction OK`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
|
|
223
|
+
case 'boundaries':
|
|
224
|
+
for (const bd of cards) {
|
|
225
|
+
const label = bd.id || '?';
|
|
226
|
+
if (!bd.scope || bd.scope.includes('[TODO]')) warn(`boundary ${label}: scope is placeholder`);
|
|
227
|
+
else ok(`boundary ${label}: scope OK`);
|
|
228
|
+
if (!bd.out_of_scope || bd.out_of_scope.includes('[TODO]')) warn(`boundary ${label}: out_of_scope is placeholder`);
|
|
229
|
+
else ok(`boundary ${label}: out_of_scope OK`);
|
|
230
|
+
if (!bd.acceptable_exceptions || !Array.isArray(bd.acceptable_exceptions)) {
|
|
231
|
+
warn(`boundary ${label}: acceptable_exceptions not declared`);
|
|
232
|
+
} else {
|
|
233
|
+
ok(`boundary ${label}: acceptable_exceptions has ${bd.acceptable_exceptions.length} entries`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case 'self_checks':
|
|
239
|
+
for (const sc of cards) {
|
|
240
|
+
const label = sc.id || '?';
|
|
241
|
+
if (!sc.question || sc.question.includes('[TODO]')) {
|
|
242
|
+
warn(`self_check ${label}: question is placeholder`);
|
|
243
|
+
} else if (!sc.question.trim().endsWith('?')) {
|
|
244
|
+
fail(`self_check ${label}: question should end with "?"`);
|
|
245
|
+
} else {
|
|
246
|
+
ok(`self_check ${label}: question OK`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'ontology':
|
|
252
|
+
for (const ont of cards) {
|
|
253
|
+
const label = ont.id || '?';
|
|
254
|
+
if (!ont.essence || ont.essence.includes('[TODO]')) warn(`ontology ${label}: essence is placeholder`);
|
|
255
|
+
else ok(`ontology ${label}: essence OK`);
|
|
256
|
+
if (!ont.boundary || ont.boundary.includes('[TODO]')) warn(`ontology ${label}: boundary is placeholder`);
|
|
257
|
+
else ok(`ontology ${label}: boundary OK`);
|
|
258
|
+
if (!ont.trigger_signal || ont.trigger_signal.includes('[TODO]')) warn(`ontology ${label}: trigger_signal is placeholder`);
|
|
259
|
+
else ok(`ontology ${label}: trigger_signal OK`);
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (jsonMode) {
|
|
266
|
+
console.log(JSON.stringify({
|
|
267
|
+
project: path.basename(abs),
|
|
268
|
+
valid: errors.length === 0,
|
|
269
|
+
errors,
|
|
270
|
+
warnings,
|
|
271
|
+
passed: passed.length,
|
|
272
|
+
total_checks: errors.length + warnings.length + passed.length,
|
|
273
|
+
}, null, 2));
|
|
274
|
+
process.exit(errors.length ? EXIT.VALIDATION_FAILED : EXIT.OK);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (warnings.length) {
|
|
278
|
+
console.log('Warnings:');
|
|
279
|
+
warnings.forEach((w) => console.log(` ⚠ ${w}`));
|
|
280
|
+
}
|
|
281
|
+
if (errors.length) {
|
|
282
|
+
console.error('Errors:');
|
|
283
|
+
errors.forEach((e) => console.error(` ✗ ${e}`));
|
|
284
|
+
}
|
|
285
|
+
if (passed.length) {
|
|
286
|
+
console.log(`✓ ${passed.length} checks passed`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (errors.length) process.exit(EXIT.VALIDATION_FAILED);
|
|
290
|
+
console.log(`✓ All ${passed.length} checks passed (no errors)`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ─── Lock Verify ──────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
function cmdLockVerify(projectPath, args = []) {
|
|
296
|
+
const jsonMode = args.includes('--json');
|
|
297
|
+
const abs = path.resolve(projectPath);
|
|
298
|
+
|
|
299
|
+
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
300
|
+
const project = readJson(abs);
|
|
301
|
+
if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
302
|
+
|
|
303
|
+
const locked = [];
|
|
304
|
+
const unlocked = [];
|
|
305
|
+
const blocking = [];
|
|
306
|
+
|
|
307
|
+
for (const [cardType, cardFile] of Object.entries(project.cards || {})) {
|
|
308
|
+
const cardPath = path.join(path.dirname(abs), cardFile);
|
|
309
|
+
if (!fs.existsSync(cardPath)) {
|
|
310
|
+
blocking.push(`${cardType}: file not found (${cardFile})`);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const cards = readJson(cardPath);
|
|
315
|
+
if (!cards || !Array.isArray(cards)) {
|
|
316
|
+
blocking.push(`${cardType}: invalid file`);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const card of cards) {
|
|
321
|
+
const label = `${cardType}.${card.id || '?'}`;
|
|
322
|
+
if (card.locked === true) {
|
|
323
|
+
if (!card.human_lock || !card.human_lock.by || !card.human_lock.at) {
|
|
324
|
+
locked.push(label);
|
|
325
|
+
} else {
|
|
326
|
+
// Check Feynman restatement for axioms and misunderstandings
|
|
327
|
+
if ((cardType === 'axioms' || cardType === 'misunderstandings') && !card.feynman_restatement) {
|
|
328
|
+
unlocked.push(label);
|
|
329
|
+
blocking.push(`${label} missing Feynman restatement`);
|
|
330
|
+
} else {
|
|
331
|
+
locked.push(label);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
unlocked.push(label);
|
|
336
|
+
blocking.push(`${label} requires human lock`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const publishable = blocking.length === 0 && locked.length > 0;
|
|
342
|
+
|
|
343
|
+
if (jsonMode) {
|
|
344
|
+
console.log(JSON.stringify({
|
|
345
|
+
project: path.basename(abs),
|
|
346
|
+
locked_cards: locked.length,
|
|
347
|
+
unlocked_cards: unlocked.length,
|
|
348
|
+
publishable,
|
|
349
|
+
blocking,
|
|
350
|
+
locked: locked.sort(),
|
|
351
|
+
unlocked: unlocked.sort(),
|
|
352
|
+
}, null, 2));
|
|
353
|
+
process.exit(publishable ? EXIT.OK : EXIT.HUMAN_LOCK_REQUIRED);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(`Lock status for: ${path.basename(abs)}`);
|
|
357
|
+
console.log('');
|
|
358
|
+
console.log(` Locked: ${locked.length}`);
|
|
359
|
+
console.log(` Unlocked: ${unlocked.length}`);
|
|
360
|
+
console.log(` Publishable: ${publishable ? '✓ yes' : '✗ no'}`);
|
|
361
|
+
console.log('');
|
|
362
|
+
|
|
363
|
+
if (blocking.length) {
|
|
364
|
+
console.log('Blocking issues:');
|
|
365
|
+
blocking.forEach((b) => console.log(` ✗ ${b}`));
|
|
366
|
+
console.log('');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (locked.length) {
|
|
370
|
+
console.log('Locked cards:');
|
|
371
|
+
locked.forEach((l) => console.log(` ✓ ${l}`));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
process.exit(publishable ? EXIT.OK : EXIT.HUMAN_LOCK_REQUIRED);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ─── Studio Compile ───────────────────────────────────────────────────
|
|
378
|
+
|
|
379
|
+
function cmdStudioCompile(projectPath, args = []) {
|
|
380
|
+
const abs = path.resolve(projectPath);
|
|
381
|
+
|
|
382
|
+
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
383
|
+
const project = readJson(abs);
|
|
384
|
+
if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
385
|
+
|
|
386
|
+
// Determine output directory
|
|
387
|
+
const outIdx = args.indexOf('--out');
|
|
388
|
+
const outDir = outIdx >= 0
|
|
389
|
+
? path.resolve(args[outIdx + 1])
|
|
390
|
+
: path.join(path.dirname(abs), project.exports?.dir || 'exports');
|
|
391
|
+
|
|
392
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
393
|
+
|
|
394
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
395
|
+
const excluded = [];
|
|
396
|
+
const included = [];
|
|
397
|
+
|
|
398
|
+
// Compile axioms → KDNA_Core.json
|
|
399
|
+
const axioms = loadCards(project, path.dirname(abs), 'axioms');
|
|
400
|
+
const ontology = loadCards(project, path.dirname(abs), 'ontology');
|
|
401
|
+
const _boundaries = loadCards(project, path.dirname(abs), 'boundaries');
|
|
402
|
+
|
|
403
|
+
const core = {
|
|
404
|
+
meta: {
|
|
405
|
+
domain: project.name,
|
|
406
|
+
purpose: '',
|
|
407
|
+
language: 'en',
|
|
408
|
+
created: project.created || today,
|
|
409
|
+
updated: today,
|
|
410
|
+
},
|
|
411
|
+
axioms: axioms.locked.map((ax) => ({
|
|
412
|
+
id: ax.id,
|
|
413
|
+
one_sentence: ax.one_sentence,
|
|
414
|
+
full_statement: ax.full_statement,
|
|
415
|
+
why: ax.why,
|
|
416
|
+
applies_when: ax.applies_when || [],
|
|
417
|
+
does_not_apply_when: ax.does_not_apply_when || [],
|
|
418
|
+
failure_risk: ax.failure_risk || null,
|
|
419
|
+
confidence: ax.confidence || null,
|
|
420
|
+
evidence_type: ax.evidence_type || [],
|
|
421
|
+
})),
|
|
422
|
+
ontology: ontology.locked.map((ont) => ({
|
|
423
|
+
id: ont.id,
|
|
424
|
+
concept: ont.concept,
|
|
425
|
+
essence: ont.essence,
|
|
426
|
+
boundary: ont.boundary,
|
|
427
|
+
trigger_signal: ont.trigger_signal,
|
|
428
|
+
})),
|
|
429
|
+
stances: [],
|
|
430
|
+
frameworks: [],
|
|
431
|
+
core_structure: [],
|
|
432
|
+
};
|
|
433
|
+
excluded.push(
|
|
434
|
+
...axioms.unlocked.map((ax) => `axiom ${ax.id || '?'} not locked`),
|
|
435
|
+
...ontology.unlocked.map((ont) => `ontology ${ont.id || '?'} not locked`),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
// Compile misunderstandings + self_checks + banned_terms → KDNA_Patterns.json
|
|
439
|
+
const misunderstandings = loadCards(project, path.dirname(abs), 'misunderstandings');
|
|
440
|
+
const selfChecks = loadCards(project, path.dirname(abs), 'self_checks');
|
|
441
|
+
|
|
442
|
+
const patterns = {
|
|
443
|
+
misunderstandings: misunderstandings.locked.map((ms) => ({
|
|
444
|
+
id: ms.id,
|
|
445
|
+
wrong: ms.wrong,
|
|
446
|
+
correct: ms.correct,
|
|
447
|
+
key_distinction: ms.key_distinction,
|
|
448
|
+
failure_risk: ms.failure_risk || null,
|
|
449
|
+
})),
|
|
450
|
+
self_check: selfChecks.locked.map((sc) => sc.question).filter(Boolean),
|
|
451
|
+
terminology: {
|
|
452
|
+
banned_terms: [],
|
|
453
|
+
preferred_terms: [],
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
excluded.push(
|
|
457
|
+
...misunderstandings.unlocked.map((ms) => `misunderstanding ${ms.id || '?'} not locked`),
|
|
458
|
+
...selfChecks.unlocked.map((sc) => `self_check ${sc.id || '?'} not locked`),
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
// Compile manifest → kdna.json
|
|
462
|
+
const manifest = {
|
|
463
|
+
kdna_spec: '1.0-rc',
|
|
464
|
+
name: `@aikdna/${project.name}`,
|
|
465
|
+
version: '0.1.0',
|
|
466
|
+
status: 'experimental',
|
|
467
|
+
access: 'open',
|
|
468
|
+
language: 'en',
|
|
469
|
+
author: { name: '', id: '' },
|
|
470
|
+
license: { type: 'CC-BY-4.0' },
|
|
471
|
+
description: '',
|
|
472
|
+
created: project.created || today,
|
|
473
|
+
updated: today,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
writeJson(path.join(outDir, 'KDNA_Core.json'), core);
|
|
477
|
+
writeJson(path.join(outDir, 'KDNA_Patterns.json'), patterns);
|
|
478
|
+
writeJson(path.join(outDir, 'kdna.json'), manifest);
|
|
479
|
+
|
|
480
|
+
included.push(
|
|
481
|
+
`axioms: ${axioms.locked.length} included`,
|
|
482
|
+
`ontology: ${ontology.locked.length} included`,
|
|
483
|
+
`misunderstandings: ${misunderstandings.locked.length} included`,
|
|
484
|
+
`self_checks: ${selfChecks.locked.length} included`,
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
console.log(`✓ Compiled: ${outDir}/`);
|
|
488
|
+
console.log(` Included:`);
|
|
489
|
+
included.forEach((i) => console.log(` + ${i}`));
|
|
490
|
+
|
|
491
|
+
if (excluded.length) {
|
|
492
|
+
console.log(` Excluded (not locked):`);
|
|
493
|
+
excluded.forEach((e) => console.log(` - ${e}`));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
console.log('');
|
|
497
|
+
console.log('Next:');
|
|
498
|
+
console.log(` kdna validate ${outDir}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function loadCards(project, projectDir, cardType) {
|
|
502
|
+
const cardFile = project.cards?.[cardType];
|
|
503
|
+
if (!cardFile) return { locked: [], unlocked: [] };
|
|
504
|
+
|
|
505
|
+
const cardPath = path.join(projectDir, cardFile);
|
|
506
|
+
if (!fs.existsSync(cardPath)) return { locked: [], unlocked: [] };
|
|
507
|
+
|
|
508
|
+
const cards = readJson(cardPath) || [];
|
|
509
|
+
return {
|
|
510
|
+
locked: cards.filter((c) => c.locked === true),
|
|
511
|
+
unlocked: cards.filter((c) => c.locked !== true),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ─── Studio Readiness ─────────────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
function cmdStudioReadiness(projectPath, args = []) {
|
|
518
|
+
const jsonMode = args.includes('--json');
|
|
519
|
+
const abs = path.resolve(projectPath);
|
|
520
|
+
|
|
521
|
+
if (!fs.existsSync(abs)) error(`Project file not found: ${abs}`, EXIT.INPUT_ERROR);
|
|
522
|
+
const project = readJson(abs);
|
|
523
|
+
if (!project || !project.kdna_studio) error(`Not a KDNA Studio project: ${abs}`, EXIT.INPUT_ERROR);
|
|
524
|
+
|
|
525
|
+
const readiness = {
|
|
526
|
+
axioms: loadCardStats(project, path.dirname(abs), 'axioms'),
|
|
527
|
+
ontology: loadCardStats(project, path.dirname(abs), 'ontology'),
|
|
528
|
+
misunderstandings: loadCardStats(project, path.dirname(abs), 'misunderstandings'),
|
|
529
|
+
boundaries: loadCardStats(project, path.dirname(abs), 'boundaries'),
|
|
530
|
+
self_checks: loadCardStats(project, path.dirname(abs), 'self_checks'),
|
|
531
|
+
test_cases: 0,
|
|
532
|
+
human_pass: '0/0',
|
|
533
|
+
publishable: false,
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// Determine publishability
|
|
537
|
+
const allTypes = Object.values(readiness).filter((v) => v && typeof v === 'object' && 'total' in v);
|
|
538
|
+
let allLocked = allTypes.every((t) => t.total > 0 && t.total === t.locked);
|
|
539
|
+
if (allTypes.length === 0) allLocked = false;
|
|
540
|
+
readiness.publishable = allLocked;
|
|
541
|
+
|
|
542
|
+
if (jsonMode) {
|
|
543
|
+
console.log(JSON.stringify(readiness, null, 2));
|
|
544
|
+
process.exit(readiness.publishable ? EXIT.OK : EXIT.HUMAN_LOCK_REQUIRED);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
console.log(`Domain Readiness: ${project.name}`);
|
|
548
|
+
console.log('');
|
|
549
|
+
for (const [type, stats] of Object.entries(readiness)) {
|
|
550
|
+
if (!stats || typeof stats !== 'object' || !('total' in stats)) {
|
|
551
|
+
console.log(` ${type}: ${stats}`);
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const marker = stats.total > 0 && stats.total === stats.locked ? '✓' : '○';
|
|
555
|
+
console.log(` ${marker} ${type}: ${stats.locked}/${stats.total} locked`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
console.log('');
|
|
559
|
+
console.log(` Publishable: ${readiness.publishable ? '✓ yes' : '✗ no'}`);
|
|
560
|
+
process.exit(readiness.publishable ? EXIT.OK : EXIT.HUMAN_LOCK_REQUIRED);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function loadCardStats(project, projectDir, cardType) {
|
|
564
|
+
const result = loadCards(project, projectDir, cardType);
|
|
565
|
+
return {
|
|
566
|
+
total: result.locked.length + result.unlocked.length,
|
|
567
|
+
locked: result.locked.length,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
module.exports = {
|
|
572
|
+
cmdStudioScaffold,
|
|
573
|
+
cmdCardsValidate,
|
|
574
|
+
cmdLockVerify,
|
|
575
|
+
cmdStudioCompile,
|
|
576
|
+
cmdStudioReadiness,
|
|
577
|
+
};
|