@aikdna/kdna-cli 0.9.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/NOTICE +9 -0
- package/README.md +73 -0
- package/package.json +58 -0
- package/skills/kdna-loader/SKILL.md +257 -0
- package/src/agent.js +434 -0
- package/src/cli.js +260 -0
- package/src/cluster.js +235 -0
- package/src/cmds/_common.js +100 -0
- package/src/cmds/cluster.js +235 -0
- package/src/cmds/domain.js +638 -0
- package/src/cmds/identity.js +31 -0
- package/src/cmds/legacy.js +83 -0
- package/src/cmds/quality.js +87 -0
- package/src/cmds/registry.js +114 -0
- package/src/cmds/setup.js +8 -0
- package/src/compare.js +324 -0
- package/src/diff.js +288 -0
- package/src/identity.js +211 -0
- package/src/init.js +168 -0
- package/src/install.js +849 -0
- package/src/loader.js +70 -0
- package/src/publish.js +600 -0
- package/src/registry.js +258 -0
- package/src/search.js +73 -0
- package/src/setup.js +197 -0
- package/src/verify.js +423 -0
- package/src/version.js +112 -0
- package/templates/cluster/KDNA_Cluster.json +25 -0
- package/templates/cluster/README.md +32 -0
- package/templates/minimal-domain/KDNA_Core.json +54 -0
- package/templates/minimal-domain/KDNA_Patterns.json +37 -0
- package/templates/minimal-domain/kdna.json +31 -0
- package/templates/minimal-domain/tests/before-after.json +16 -0
- package/templates/standard-domain/KDNA_Core.json +76 -0
- package/templates/standard-domain/KDNA_Patterns.json +44 -0
- package/templates/standard-domain/README.md +74 -0
- package/templates/standard-domain/USAGE.md +59 -0
- package/templates/standard-domain/evals/1_excluded_case.json +16 -0
- package/templates/standard-domain/evals/3_boundary_cases.json +38 -0
- package/templates/standard-domain/evals/3_core_cases.json +35 -0
- package/templates/standard-domain/evals/3_failure_cases.json +35 -0
- package/templates/standard-domain/evals/scoring.json +60 -0
- package/templates/standard-domain/kdna.json +28 -0
- package/validators/kdna-lint.js +53 -0
- package/validators/kdna-validate.js +92 -0
package/src/agent.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-facing commands — what the kdna-loader skill calls.
|
|
3
|
+
*
|
|
4
|
+
* kdna available --json
|
|
5
|
+
* List installed domains, lean JSON, includes v2.1 applies_when fields
|
|
6
|
+
* and yanked status. Excludes yanked. ~200 bytes per domain.
|
|
7
|
+
* The agent uses this as its primary discovery source and decides
|
|
8
|
+
* which domain (if any) fits the task by reading the applies_when
|
|
9
|
+
* and does_not_apply_when fields against the task in its own words.
|
|
10
|
+
*
|
|
11
|
+
* kdna match "<task>" [--json]
|
|
12
|
+
* Auxiliary signal — does NOT decide which domain to use. Returns:
|
|
13
|
+
* - dropped: domains whose does_not_apply_when clearly matches the
|
|
14
|
+
* task (hard disqualification — agent should respect)
|
|
15
|
+
* - hints: substring overlap signals per domain (weak — agent should
|
|
16
|
+
* not treat as a fit decision; many false positives expected)
|
|
17
|
+
* The agent makes the final call using its own language understanding.
|
|
18
|
+
*
|
|
19
|
+
* kdna load <name> [--as=prompt|json|raw]
|
|
20
|
+
* Read the domain's Core + Patterns and emit:
|
|
21
|
+
* --as=prompt (default): compact text suitable for system-prompt
|
|
22
|
+
* injection (axioms one-liners + stances +
|
|
23
|
+
* banned-terms + misunderstandings + self-checks)
|
|
24
|
+
* --as=json: raw JSON of Core + Patterns
|
|
25
|
+
* --as=raw: concatenated raw file contents
|
|
26
|
+
*
|
|
27
|
+
* These commands are the supported interface between the kdna-loader
|
|
28
|
+
* skill and the KDNA file format. The skill should not parse JSON
|
|
29
|
+
* directly.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { parseName } = require('./registry');
|
|
35
|
+
|
|
36
|
+
const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
|
|
37
|
+
const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
|
|
38
|
+
|
|
39
|
+
function readJson(p) {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function listInstalled() {
|
|
48
|
+
if (!fs.existsSync(INSTALL_DIR)) return [];
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const scopeName of fs.readdirSync(INSTALL_DIR)) {
|
|
51
|
+
if (!scopeName.startsWith('@')) continue;
|
|
52
|
+
const scopeDir = path.join(INSTALL_DIR, scopeName);
|
|
53
|
+
try {
|
|
54
|
+
if (!fs.statSync(scopeDir).isDirectory()) continue;
|
|
55
|
+
for (const ident of fs.readdirSync(scopeDir)) {
|
|
56
|
+
if (ident.startsWith('.')) continue;
|
|
57
|
+
const dir = path.join(scopeDir, ident);
|
|
58
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
59
|
+
out.push({ scope: scopeName, ident, dir, full: `${scopeName}/${ident}` });
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
/* skip */
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── kdna available ────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
function cmdAvailable(args = []) {
|
|
71
|
+
const wantJson = args.includes('--json');
|
|
72
|
+
const installed = listInstalled();
|
|
73
|
+
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const e of installed) {
|
|
76
|
+
const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
|
|
77
|
+
if (manifest.yanked === true) continue;
|
|
78
|
+
|
|
79
|
+
const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
|
|
80
|
+
|
|
81
|
+
// Pull applies_when across all axioms (this is what the agent needs
|
|
82
|
+
// for fit-check). Collapsing per-axiom into one set makes the agent's
|
|
83
|
+
// matching decision much cheaper.
|
|
84
|
+
const applies_when = [];
|
|
85
|
+
const does_not_apply_when = [];
|
|
86
|
+
const failure_risks = [];
|
|
87
|
+
for (const a of core.axioms || []) {
|
|
88
|
+
if (Array.isArray(a.applies_when)) applies_when.push(...a.applies_when);
|
|
89
|
+
if (Array.isArray(a.does_not_apply_when)) does_not_apply_when.push(...a.does_not_apply_when);
|
|
90
|
+
if (a.failure_risk) failure_risks.push(a.failure_risk);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
out.push({
|
|
94
|
+
name: manifest.name || e.full,
|
|
95
|
+
version: manifest.version || null,
|
|
96
|
+
judgment_version: manifest.judgment_version || null,
|
|
97
|
+
status: manifest.status || 'experimental',
|
|
98
|
+
description: manifest.description || '',
|
|
99
|
+
core_insight: manifest.core_insight || '',
|
|
100
|
+
keywords: manifest.keywords || [],
|
|
101
|
+
applies_when,
|
|
102
|
+
does_not_apply_when,
|
|
103
|
+
failure_risks,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (wantJson) {
|
|
108
|
+
const result = out.length
|
|
109
|
+
? out
|
|
110
|
+
: {
|
|
111
|
+
count: 0,
|
|
112
|
+
domains: [],
|
|
113
|
+
note: 'No domains installed. Run: kdna install <name> See: kdna list --available',
|
|
114
|
+
};
|
|
115
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Human format
|
|
120
|
+
if (!out.length) {
|
|
121
|
+
console.log('No KDNA domains installed.');
|
|
122
|
+
console.log('Install with: kdna install <name>');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
console.log(`${out.length} installed KDNA domain(s):`);
|
|
126
|
+
for (const d of out) {
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(` ${d.name} v${d.version || '?'} [${d.status}]`);
|
|
129
|
+
if (d.description) console.log(` ${d.description}`);
|
|
130
|
+
if (d.applies_when.length) {
|
|
131
|
+
console.log(` applies when: ${d.applies_when.length} situations declared`);
|
|
132
|
+
}
|
|
133
|
+
if (d.does_not_apply_when.length) {
|
|
134
|
+
console.log(` does NOT apply when: ${d.does_not_apply_when.length} situations declared`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── kdna match ────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
function tokenize(text) {
|
|
142
|
+
return (text || '')
|
|
143
|
+
.toLowerCase()
|
|
144
|
+
.split(/[^a-z0-9_一-鿿]+/g)
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function overlapScore(taskTokens, declaredText) {
|
|
149
|
+
if (!declaredText) return { hits: 0, coverage: 0 };
|
|
150
|
+
const declaredTokens = tokenize(declaredText);
|
|
151
|
+
if (!declaredTokens.length) return { hits: 0, coverage: 0 };
|
|
152
|
+
const dSet = new Set(declaredTokens);
|
|
153
|
+
let hits = 0;
|
|
154
|
+
for (const t of taskTokens) if (dSet.has(t)) hits++;
|
|
155
|
+
const coverage = taskTokens.length ? hits / taskTokens.length : 0;
|
|
156
|
+
return { hits, coverage };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Minimum signal strength to report a hint (avoid noise from single-word matches)
|
|
160
|
+
const MIN_HITS = 2;
|
|
161
|
+
const MIN_COVERAGE = 0.15;
|
|
162
|
+
|
|
163
|
+
function domainRelevanceScore(taskTokens, domain) {
|
|
164
|
+
let score = 0;
|
|
165
|
+
const sources = [
|
|
166
|
+
(domain.description || '').toLowerCase(),
|
|
167
|
+
(domain.core_insight || '').toLowerCase(),
|
|
168
|
+
(domain.keywords || []).join(' ').toLowerCase(),
|
|
169
|
+
];
|
|
170
|
+
for (const source of sources) {
|
|
171
|
+
const tokens = tokenize(source);
|
|
172
|
+
const srcSet = new Set(tokens);
|
|
173
|
+
for (const t of taskTokens) {
|
|
174
|
+
if (srcSet.has(t)) score += 2;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return score;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function cmdMatch(taskText, args = []) {
|
|
181
|
+
const wantJson = args.includes('--json');
|
|
182
|
+
if (!taskText) {
|
|
183
|
+
console.error('Usage: kdna match "<task description>" [--json]');
|
|
184
|
+
process.exit(2);
|
|
185
|
+
}
|
|
186
|
+
const taskTokens = tokenize(taskText);
|
|
187
|
+
const installed = listInstalled();
|
|
188
|
+
|
|
189
|
+
const dropped = [];
|
|
190
|
+
const hints = [];
|
|
191
|
+
|
|
192
|
+
for (const e of installed) {
|
|
193
|
+
const manifest = readJson(path.join(e.dir, 'kdna.json')) || {};
|
|
194
|
+
if (manifest.yanked === true) {
|
|
195
|
+
dropped.push({ name: manifest.name || e.full, reason: 'yanked' });
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const core = readJson(path.join(e.dir, 'KDNA_Core.json')) || {};
|
|
199
|
+
|
|
200
|
+
// does_not_apply_when disqualification (HARD signal)
|
|
201
|
+
let disqualified = null;
|
|
202
|
+
for (const a of core.axioms || []) {
|
|
203
|
+
for (const d of a.does_not_apply_when || []) {
|
|
204
|
+
const score = overlapScore(taskTokens, d);
|
|
205
|
+
if (score.hits >= 2) {
|
|
206
|
+
disqualified = { axiom: a.id, text: d };
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (disqualified) break;
|
|
211
|
+
}
|
|
212
|
+
if (disqualified) {
|
|
213
|
+
dropped.push({
|
|
214
|
+
name: manifest.name || e.full,
|
|
215
|
+
reason: `does_not_apply_when matched on ${disqualified.axiom}`,
|
|
216
|
+
evidence: disqualified.text.slice(0, 120),
|
|
217
|
+
});
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// applies_when hint signals (WEAK — for context only, not a decision)
|
|
222
|
+
const signals = [];
|
|
223
|
+
for (const a of core.axioms || []) {
|
|
224
|
+
for (const ap of a.applies_when || []) {
|
|
225
|
+
const score = overlapScore(taskTokens, ap);
|
|
226
|
+
if (score.hits >= MIN_HITS || score.coverage >= MIN_COVERAGE) {
|
|
227
|
+
signals.push({
|
|
228
|
+
source: `${a.id}.applies_when`,
|
|
229
|
+
hits: score.hits,
|
|
230
|
+
coverage: score.coverage,
|
|
231
|
+
text: ap.slice(0, 120),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Domain-level relevance: check description, core_insight, keywords
|
|
238
|
+
const domainRelevance = domainRelevanceScore(taskTokens, manifest);
|
|
239
|
+
|
|
240
|
+
if (signals.length || domainRelevance >= 2) {
|
|
241
|
+
hints.push({
|
|
242
|
+
name: manifest.name || e.full,
|
|
243
|
+
description: manifest.description || '',
|
|
244
|
+
status: manifest.status || 'experimental',
|
|
245
|
+
domain_relevance: domainRelevance,
|
|
246
|
+
top_signals: signals.sort((a, b) => b.hits - a.hits).slice(0, 3),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Sort hints by combined relevance (applies_when hits + domain relevance)
|
|
252
|
+
hints.sort((a, b) => {
|
|
253
|
+
const aScore = a.top_signals.reduce((s, sig) => s + sig.hits, 0) + (a.domain_relevance || 0);
|
|
254
|
+
const bScore = b.top_signals.reduce((s, sig) => s + sig.hits, 0) + (b.domain_relevance || 0);
|
|
255
|
+
return bScore - aScore;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = {
|
|
259
|
+
task: taskText.slice(0, 200),
|
|
260
|
+
dropped,
|
|
261
|
+
hints,
|
|
262
|
+
no_strong_matches: hints.length === 0,
|
|
263
|
+
note:
|
|
264
|
+
'These are surface keyword signals only — many false positives are normal. ' +
|
|
265
|
+
"The agent must read each candidate domain's description + applies_when " +
|
|
266
|
+
'in full and decide using language understanding. dropped is a hard signal: ' +
|
|
267
|
+
'do not load any domain in dropped.',
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (wantJson) {
|
|
271
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Human format — make it clear this is hint not decision
|
|
276
|
+
console.log(`Task: ${taskText.slice(0, 100)}${taskText.length > 100 ? '…' : ''}`);
|
|
277
|
+
console.log('');
|
|
278
|
+
console.log(`This is a HINT report. The agent makes the final fit decision`);
|
|
279
|
+
console.log(`by reading each candidate's full description and applies_when.`);
|
|
280
|
+
console.log('');
|
|
281
|
+
|
|
282
|
+
if (dropped.length) {
|
|
283
|
+
console.log(`Dropped (does_not_apply_when matched — do NOT load these):`);
|
|
284
|
+
for (const d of dropped) {
|
|
285
|
+
console.log(` ✗ ${d.name}: ${d.reason}`);
|
|
286
|
+
if (d.evidence) console.log(` "${d.evidence}"`);
|
|
287
|
+
}
|
|
288
|
+
console.log('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!hints.length) {
|
|
292
|
+
console.log('No strong keyword matches from installed domains.');
|
|
293
|
+
console.log('This means one of:');
|
|
294
|
+
console.log(' - No KDNA domain fits this task yet');
|
|
295
|
+
console.log(' - The fit is by meaning, not by word overlap');
|
|
296
|
+
console.log(' - The matching domain is not installed');
|
|
297
|
+
console.log('');
|
|
298
|
+
console.log('Run: kdna available to see what is installed and decide.');
|
|
299
|
+
console.log('See the registry: kdna search "<query>" (searches all published domains)');
|
|
300
|
+
} else {
|
|
301
|
+
console.log(`Keyword hints (${hints.length} domains had some token overlap):`);
|
|
302
|
+
for (const h of hints) {
|
|
303
|
+
console.log(` ${h.name} [${h.status}]`);
|
|
304
|
+
console.log(` ${h.description}`);
|
|
305
|
+
const relevanceIndicator =
|
|
306
|
+
h.domain_relevance >= 2
|
|
307
|
+
? ` (domain relevance: ${'★'.repeat(Math.min(h.domain_relevance, 5))})`
|
|
308
|
+
: '';
|
|
309
|
+
if (relevanceIndicator) console.log(relevanceIndicator);
|
|
310
|
+
for (const s of h.top_signals) {
|
|
311
|
+
const pct = Math.round((s.coverage || 0) * 100);
|
|
312
|
+
console.log(` ↳ ${s.source} (${s.hits} hits, ${pct}% coverage): ${s.text}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
console.log('');
|
|
316
|
+
console.log('To consider any of these, read its full data: kdna load <name> --as=json');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ─── kdna load ─────────────────────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
function cmdLoad(input, args = []) {
|
|
323
|
+
const formatIdx = args.findIndex((a) => a.startsWith('--as'));
|
|
324
|
+
let format = 'prompt';
|
|
325
|
+
if (formatIdx >= 0) {
|
|
326
|
+
const eq = args[formatIdx].indexOf('=');
|
|
327
|
+
format = eq > 0 ? args[formatIdx].slice(eq + 1) : args[formatIdx + 1];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const parsed = parseName(input);
|
|
331
|
+
if (!parsed) {
|
|
332
|
+
console.error(`Invalid name "${input}". Use @scope/name or bare name.`);
|
|
333
|
+
process.exit(2);
|
|
334
|
+
}
|
|
335
|
+
const dir = path.join(INSTALL_DIR, parsed.scope, parsed.ident);
|
|
336
|
+
if (!fs.existsSync(dir)) {
|
|
337
|
+
console.error(`${parsed.full} is not installed. Run: kdna install ${input}`);
|
|
338
|
+
process.exit(2);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const manifest = readJson(path.join(dir, 'kdna.json')) || {};
|
|
342
|
+
if (manifest.yanked === true) {
|
|
343
|
+
console.error(`${parsed.full}@${manifest.version} has been yanked.`);
|
|
344
|
+
if (manifest.replaced_by) console.error(`Try: ${manifest.replaced_by}`);
|
|
345
|
+
process.exit(2);
|
|
346
|
+
}
|
|
347
|
+
const core = readJson(path.join(dir, 'KDNA_Core.json')) || {};
|
|
348
|
+
const pat = readJson(path.join(dir, 'KDNA_Patterns.json')) || {};
|
|
349
|
+
|
|
350
|
+
if (format === 'json') {
|
|
351
|
+
process.stdout.write(JSON.stringify({ manifest, core, patterns: pat }, null, 2) + '\n');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (format === 'raw') {
|
|
356
|
+
for (const f of ['KDNA_Core.json', 'KDNA_Patterns.json']) {
|
|
357
|
+
const p = path.join(dir, f);
|
|
358
|
+
if (fs.existsSync(p)) {
|
|
359
|
+
process.stdout.write(`\n=== ${f} ===\n`);
|
|
360
|
+
process.stdout.write(fs.readFileSync(p, 'utf8'));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Default: --as=prompt — compact text optimized for system-prompt injection.
|
|
367
|
+
// Goal: minimum token cost while preserving all judgment surface.
|
|
368
|
+
const lines = [];
|
|
369
|
+
lines.push(`# KDNA loaded: ${manifest.name || parsed.full}`);
|
|
370
|
+
if (manifest.judgment_version) lines.push(`# judgment_version: ${manifest.judgment_version}`);
|
|
371
|
+
if (manifest.core_insight) lines.push(`# core insight: ${manifest.core_insight}`);
|
|
372
|
+
lines.push('');
|
|
373
|
+
|
|
374
|
+
if (core.axioms?.length) {
|
|
375
|
+
lines.push('## Axioms (reason from these)');
|
|
376
|
+
for (const a of core.axioms) {
|
|
377
|
+
lines.push(`- ${a.one_sentence}`);
|
|
378
|
+
if (a.applies_when?.length) {
|
|
379
|
+
lines.push(` APPLIES WHEN: ${a.applies_when.join('; ')}`);
|
|
380
|
+
}
|
|
381
|
+
if (a.does_not_apply_when?.length) {
|
|
382
|
+
lines.push(` DOES NOT APPLY WHEN: ${a.does_not_apply_when.join('; ')}`);
|
|
383
|
+
}
|
|
384
|
+
if (a.failure_risk) lines.push(` RISK IF MISAPPLIED: ${a.failure_risk}`);
|
|
385
|
+
}
|
|
386
|
+
lines.push('');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (core.stances?.length) {
|
|
390
|
+
lines.push('## Stances');
|
|
391
|
+
for (const s of core.stances) {
|
|
392
|
+
const text = typeof s === 'string' ? s : s.stance;
|
|
393
|
+
if (text) lines.push(`- ${text}`);
|
|
394
|
+
}
|
|
395
|
+
lines.push('');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (pat.terminology?.banned_terms?.length) {
|
|
399
|
+
lines.push('## Banned terms (do not use even if user uses them)');
|
|
400
|
+
for (const t of pat.terminology.banned_terms) {
|
|
401
|
+
const term = typeof t === 'string' ? t : t.term;
|
|
402
|
+
const replace = typeof t === 'object' ? t.replace_with : null;
|
|
403
|
+
lines.push(`- "${term}"${replace ? ` → use: ${replace}` : ''}`);
|
|
404
|
+
}
|
|
405
|
+
lines.push('');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (pat.misunderstandings?.length) {
|
|
409
|
+
lines.push('## Misunderstandings to detect and avoid');
|
|
410
|
+
for (const m of pat.misunderstandings) {
|
|
411
|
+
lines.push(`- WRONG: ${m.wrong}`);
|
|
412
|
+
lines.push(` CORRECT: ${m.correct}`);
|
|
413
|
+
if (m.failure_risk) lines.push(` RISK: ${m.failure_risk}`);
|
|
414
|
+
}
|
|
415
|
+
lines.push('');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (pat.self_check?.length) {
|
|
419
|
+
lines.push('## Self-checks (answer before final output)');
|
|
420
|
+
for (const q of pat.self_check) {
|
|
421
|
+
const text = typeof q === 'string' ? q : q.question;
|
|
422
|
+
if (text) lines.push(`- ${text}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push('');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
lines.push('---');
|
|
428
|
+
lines.push('Apply silently. Do not quote KDNA to the user. Do not say "according to KDNA".');
|
|
429
|
+
lines.push('User intent + evidence always override KDNA axioms.');
|
|
430
|
+
|
|
431
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
module.exports = { cmdAvailable, cmdMatch, cmdLoad };
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* kdna — Unified CLI for KDNA domain cognition assets.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* kdna validate <path> Validate a domain directory or .kdna file
|
|
7
|
+
* kdna verify <path> Verify a domain (alias for validate)
|
|
8
|
+
* kdna pack <path> Pack a domain folder into .kdna container (ZIP)
|
|
9
|
+
* kdna unpack <path> Unpack .kdna container to domain folder
|
|
10
|
+
* kdna install <domain-id> Install a domain from registry
|
|
11
|
+
* kdna inspect <path> Inspect a domain directory or .kdna file
|
|
12
|
+
* kdna list List installed domains
|
|
13
|
+
* kdna compare <before> <after> Compare judgment with/without KDNA
|
|
14
|
+
* kdna match "<task>" Match task against available domains
|
|
15
|
+
* kdna setup One-command setup: install CLI + skills + data root
|
|
16
|
+
* kdna cluster lint <path> Validate a cluster manifest
|
|
17
|
+
* kdna identity init Generate Ed25519 identity key pair
|
|
18
|
+
* kdna identity show Display public key and buyer ID
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const { usage, error } = require('./cmds/_common');
|
|
22
|
+
const { cmdValidate, cmdPack, cmdUnpack, cmdInspect } = require('./cmds/domain');
|
|
23
|
+
const { cmdList, cmdRegistry } = require('./cmds/registry');
|
|
24
|
+
const {
|
|
25
|
+
cmdCompare,
|
|
26
|
+
cmdDiff,
|
|
27
|
+
cmdSearch,
|
|
28
|
+
cmdAvailable,
|
|
29
|
+
cmdMatch,
|
|
30
|
+
cmdLoad,
|
|
31
|
+
} = require('./cmds/quality');
|
|
32
|
+
const { cmdCluster } = require('./cmds/cluster');
|
|
33
|
+
const { cmdIdentity } = require('./cmds/identity');
|
|
34
|
+
const { cmdSetup } = require('./cmds/setup');
|
|
35
|
+
const { cmdPreview, cmdProject, cmdEval, cmdSelect, cmdExport, cmdDemo } = require('./cmds/legacy');
|
|
36
|
+
|
|
37
|
+
// ─── Main ─────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
const args = process.argv.slice(2);
|
|
40
|
+
if (!args.length || args[0] === 'help' || args[0] === '--help' || args[0] === '-h') {
|
|
41
|
+
usage();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const cmd = args[0];
|
|
46
|
+
|
|
47
|
+
switch (cmd) {
|
|
48
|
+
case 'validate': {
|
|
49
|
+
const schemaFlag = args.includes('--schema');
|
|
50
|
+
const target = args.filter((a, i) => i > 0 && a !== '--schema')[0];
|
|
51
|
+
if (!target) error('Usage: kdna validate <path>');
|
|
52
|
+
cmdValidate(target, schemaFlag);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'pack': {
|
|
56
|
+
let output = null;
|
|
57
|
+
let target = null;
|
|
58
|
+
for (let i = 1; i < args.length; i++) {
|
|
59
|
+
if (args[i] === '--output' || args[i] === '-o') {
|
|
60
|
+
output = args[i + 1];
|
|
61
|
+
i++;
|
|
62
|
+
} else if (!target) {
|
|
63
|
+
target = args[i];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!target) error('Usage: kdna pack <path>');
|
|
67
|
+
cmdPack(target, output);
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'unpack': {
|
|
71
|
+
const target = args[1];
|
|
72
|
+
if (!target) error('Usage: kdna unpack <file.kdna>');
|
|
73
|
+
cmdUnpack(target, args.includes('--force'));
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case 'preview': {
|
|
77
|
+
cmdPreview();
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'install': {
|
|
81
|
+
let domainId = null;
|
|
82
|
+
let fromGit = null;
|
|
83
|
+
for (let i = 1; i < args.length; i++) {
|
|
84
|
+
if (args[i] === '--from-git') {
|
|
85
|
+
fromGit = args[i + 1];
|
|
86
|
+
i++;
|
|
87
|
+
} else if (!domainId) {
|
|
88
|
+
domainId = args[i];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!domainId) error('Usage: kdna install <domain-id|github:user/repo|./folder>');
|
|
92
|
+
|
|
93
|
+
const { cmdInstallExtended } = require('./install');
|
|
94
|
+
if (fromGit) {
|
|
95
|
+
// Legacy --from-git: treat as github: URL
|
|
96
|
+
const url = fromGit.replace(/^https:\/\/github\.com\//, '').replace(/\.git$/, '');
|
|
97
|
+
cmdInstallExtended(`github:${url}`, args);
|
|
98
|
+
} else {
|
|
99
|
+
cmdInstallExtended(domainId, args);
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
case 'registry': {
|
|
104
|
+
cmdRegistry(args[1]);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'remove': {
|
|
108
|
+
const { cmdRemove } = require('./install');
|
|
109
|
+
const target = args[1];
|
|
110
|
+
if (!target) error('Usage: kdna remove <domain>');
|
|
111
|
+
cmdRemove(target);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case 'info': {
|
|
115
|
+
const { cmdInfo } = require('./install');
|
|
116
|
+
const target = args[1];
|
|
117
|
+
if (!target) error('Usage: kdna info <domain>');
|
|
118
|
+
cmdInfo(target);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case 'update': {
|
|
122
|
+
const { cmdUpdate, cmdUpdateAll } = require('./install');
|
|
123
|
+
if (args.includes('--all')) {
|
|
124
|
+
cmdUpdateAll();
|
|
125
|
+
} else {
|
|
126
|
+
const target = args[1];
|
|
127
|
+
if (!target) error('Usage: kdna update <domain>');
|
|
128
|
+
cmdUpdate(target);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'inspect': {
|
|
133
|
+
const target = args[1];
|
|
134
|
+
if (!target) error('Usage: kdna inspect <path>');
|
|
135
|
+
cmdInspect(target);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'verify': {
|
|
139
|
+
const { cmdVerify } = require('./verify');
|
|
140
|
+
const target = args.filter((a) => !a.startsWith('--'))[1];
|
|
141
|
+
if (!target) {
|
|
142
|
+
error(
|
|
143
|
+
'Usage:\n' +
|
|
144
|
+
' kdna verify <name> Run all three layers (structure / trust / judgment)\n' +
|
|
145
|
+
' kdna verify <name> --structure Files + schema only\n' +
|
|
146
|
+
' kdna verify <name> --trust Signature + scope + Ed25519 only\n' +
|
|
147
|
+
' kdna verify <name> --judgment v2.1 governance fields + eval cases only',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
cmdVerify(target, args);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'compare': {
|
|
154
|
+
cmdCompare(args);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case 'diff': {
|
|
158
|
+
cmdDiff(args);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'search': {
|
|
162
|
+
cmdSearch(args);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case 'available': {
|
|
166
|
+
cmdAvailable(args);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case 'match': {
|
|
170
|
+
cmdMatch(args);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case 'load': {
|
|
174
|
+
cmdLoad(args);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case 'project': {
|
|
178
|
+
cmdProject();
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'eval': {
|
|
182
|
+
cmdEval();
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'select': {
|
|
186
|
+
cmdSelect();
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case 'export': {
|
|
190
|
+
cmdExport();
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'list': {
|
|
194
|
+
cmdList(args.includes('--available'));
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'demo': {
|
|
198
|
+
cmdDemo();
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
case 'setup': {
|
|
202
|
+
cmdSetup();
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
case 'cluster': {
|
|
206
|
+
cmdCluster(args);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'identity': {
|
|
210
|
+
cmdIdentity(args);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'init': {
|
|
214
|
+
const { cmdInit } = require('./init');
|
|
215
|
+
cmdInit(args[1]);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 'publish': {
|
|
219
|
+
if (args.includes('--check')) {
|
|
220
|
+
const { cmdPublishCheck } = require('./publish');
|
|
221
|
+
const idx = args.indexOf('--check');
|
|
222
|
+
const target = args[idx + 1] || args.filter((a) => !a.startsWith('--'))[1] || '.';
|
|
223
|
+
if (!target || target.startsWith('--')) error('Usage: kdna publish --check <path>');
|
|
224
|
+
cmdPublishCheck(target);
|
|
225
|
+
} else {
|
|
226
|
+
const { cmdPublish } = require('./publish');
|
|
227
|
+
const target = args.filter((a) => !a.startsWith('--'))[1];
|
|
228
|
+
if (!target) {
|
|
229
|
+
error(
|
|
230
|
+
'Usage:\n' +
|
|
231
|
+
' kdna publish <path> Pack + sign, output patch JSON\n' +
|
|
232
|
+
' kdna publish <path> --release-tag <tag> --repo <owner/name>\n' +
|
|
233
|
+
' ...also upload to GitHub Release\n' +
|
|
234
|
+
' kdna publish --check <path> Quality gate only',
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
cmdPublish(target, args);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 'version': {
|
|
242
|
+
const { cmdVersionBump } = require('./version');
|
|
243
|
+
const sub = args[1];
|
|
244
|
+
if (sub === 'bump') {
|
|
245
|
+
const level = args[2];
|
|
246
|
+
const target = args[3] || '.';
|
|
247
|
+
if (!level || !['patch', 'minor', 'major'].includes(level)) {
|
|
248
|
+
error('Usage: kdna version bump <patch|minor|major> [path]');
|
|
249
|
+
}
|
|
250
|
+
cmdVersionBump(level, target);
|
|
251
|
+
} else {
|
|
252
|
+
console.log(`kdna v${require('../package.json').version}`);
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log('Usage: kdna version bump <patch|minor|major> [path]');
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
default:
|
|
259
|
+
error(`Unknown command: ${cmd}\nRun: kdna help`);
|
|
260
|
+
}
|