@aikdna/kdna-cli 0.21.1 → 0.22.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/src/agent.js CHANGED
@@ -16,17 +16,15 @@
16
16
  * not treat as a fit decision; many false positives expected)
17
17
  * The agent makes the final call using its own language understanding.
18
18
  *
19
- * kdna load <name|file.kdna> [--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
19
+ * kdna load <name|file.kdna> [--as=prompt]
20
+ * Read the domain's judgment and emit context suitable for agent
21
+ * system-prompt injection (axioms one-liners + stances +
22
+ * banned-terms + misunderstandings + self-checks).
23
+ * For raw inspection use: kdna dev decode <file.kdna> --reveal
26
24
  *
27
25
  * 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.
26
+ * skill and the KDNA file format. The skill should not read KDNA
27
+ * internals directly.
30
28
  */
31
29
 
32
30
  const fs = require('fs');
@@ -309,7 +307,7 @@ function cmdMatch(taskText, args = []) {
309
307
  }
310
308
  console.log('');
311
309
  console.log(
312
- 'To consider any of these, read its full data: kdna load <name|file.kdna> --as=json',
310
+ 'To load a domain: kdna load <name|file.kdna>',
313
311
  );
314
312
  }
315
313
  }
@@ -407,61 +405,12 @@ function cmdLoad(input, args = []) {
407
405
  const core = container.core || {};
408
406
  const pat = container.patterns || {};
409
407
 
410
- // JSON format
411
- if (format === 'json') {
412
- process.stdout.write(
413
- JSON.stringify(
414
- {
415
- manifest,
416
- core,
417
- patterns: pat,
418
- trust: {
419
- signature: isPlaceholder ? 'unsigned' : 'present',
420
- risk_level: riskLevel,
421
- deprecated: manifest.status === 'deprecated',
422
- yanked: false,
423
- warnings: loadWarnings,
424
- asset_digest: asset.asset_digest || null,
425
- content_digest: asset.content_digest || null,
426
- license_id: licenseActivation?.license_id || null,
427
- },
428
- },
429
- null,
430
- 2,
431
- ) + '\n',
432
- );
433
- recordTrace({
434
- timestamp: new Date().toISOString(),
435
- agent: detectAgent(),
436
- domain: label,
437
- format: 'json',
438
- asset: traceAssetFields(asset, manifest, licenseActivation),
439
- });
440
- return;
441
- }
442
-
443
- // Raw format
444
- if (format === 'raw') {
445
- for (const f of ['KDNA_Core.json', 'KDNA_Patterns.json']) {
446
- const encrypted = encryptedEntries.includes(f);
447
- const buf = encrypted
448
- ? Buffer.from(
449
- JSON.stringify(container[f === 'KDNA_Core.json' ? 'core' : 'patterns'], null, 2),
450
- )
451
- : readContainerEntry(asset.asset_path, f);
452
- if (buf) {
453
- process.stdout.write(`\n=== ${f} ===\n`);
454
- process.stdout.write(buf.toString('utf8'));
455
- }
456
- }
457
- recordTrace({
458
- timestamp: new Date().toISOString(),
459
- agent: detectAgent(),
460
- domain: label,
461
- format: 'raw',
462
- asset: traceAssetFields(asset, manifest, licenseActivation),
463
- });
464
- return;
408
+ // JSON format — removed from agent runtime
409
+ if (format === 'json' || format === 'raw') {
410
+ console.error(`ERR_RAW_LOAD_REMOVED: --as=${format} is not supported in agent runtime.`);
411
+ console.error('Use: kdna dev decode <asset.kdna> --reveal');
412
+ console.error('Agent consumption: kdna load @scope/name [--as=prompt]');
413
+ process.exit(2);
465
414
  }
466
415
 
467
416
  // Load profiles
package/src/cli.js CHANGED
@@ -206,9 +206,26 @@ switch (cmd) {
206
206
  if (sub === 'validate') {
207
207
  const schemaFlag = args.includes('--schema');
208
208
  const jsonFlag = args.includes('--json');
209
- const target = args.filter((a, i) => i > 1 && a !== '--schema' && a !== '--json')[0];
210
- if (!target) error('Usage: kdna dev validate <source-dir>');
211
- cmdValidate(target, schemaFlag, jsonFlag);
209
+ const antiMonolithicFlag = args.includes('--anti-monolithic');
210
+ const strictFlag = args.includes('--strict');
211
+ const target = args.filter(
212
+ (a, i) =>
213
+ i > 1 &&
214
+ a !== '--schema' &&
215
+ a !== '--json' &&
216
+ a !== '--anti-monolithic' &&
217
+ a !== '--strict',
218
+ )[0];
219
+ if (!target)
220
+ error(
221
+ 'Usage: kdna dev validate <source-dir> [--schema] [--json] [--anti-monolithic] [--strict]',
222
+ );
223
+ if (antiMonolithicFlag) {
224
+ const { cmdValidateAntiMonolithic } = require('./cmds/domain');
225
+ cmdValidateAntiMonolithic(target, { json: jsonFlag, strict: strictFlag });
226
+ } else {
227
+ cmdValidate(target, schemaFlag, jsonFlag);
228
+ }
212
229
  break;
213
230
  }
214
231
  if (sub === 'pack') {
@@ -260,6 +277,16 @@ switch (cmd) {
260
277
  break;
261
278
  }
262
279
  case 'validate': {
280
+ const v1Target = args.filter((a) => !a.startsWith('--'))[1];
281
+ if (v1Target) {
282
+ const v1 = require('./v1-cli');
283
+ const abs = require('node:path').resolve(v1Target);
284
+ if (v1.isV1SourceDir(abs) || v1.detectContainerFormat(abs) === 'v1') {
285
+ const result = v1.validate(v1Target);
286
+ console.log(JSON.stringify(result, null, 2));
287
+ process.exit(result.overall_valid ? 0 : 1);
288
+ }
289
+ }
263
290
  error(
264
291
  'Directory validation is a dev-only operation. Use: kdna dev validate <source-dir>',
265
292
  EXIT.INPUT_ERROR,
@@ -267,6 +294,23 @@ switch (cmd) {
267
294
  break;
268
295
  }
269
296
  case 'pack': {
297
+ const v1Target = args.filter((a) => !a.startsWith('--'))[1];
298
+ if (v1Target) {
299
+ const v1 = require('./v1-cli');
300
+ const abs = require('node:path').resolve(v1Target);
301
+ if (v1.isV1SourceDir(abs)) {
302
+ const out = args.filter((a) => !a.startsWith('--'))[2];
303
+ if (!out) {
304
+ process.stderr.write('Usage: kdna pack <source-dir> <output.kdna>\n');
305
+ process.exit(2);
306
+ }
307
+ const r = v1.pack(v1Target, out);
308
+ process.stdout.write(
309
+ `Packed: ${r.outputPath}\nEntries: ${r.entries.length} (${r.entries.join(', ')})\n`,
310
+ );
311
+ return;
312
+ }
313
+ }
270
314
  error(
271
315
  'Directory packaging is a dev-only operation. Use: kdna dev pack <source-dir>',
272
316
  EXIT.INPUT_ERROR,
@@ -274,6 +318,23 @@ switch (cmd) {
274
318
  break;
275
319
  }
276
320
  case 'unpack': {
321
+ const v1Target = args.filter((a) => !a.startsWith('--'))[1];
322
+ if (v1Target) {
323
+ const v1 = require('./v1-cli');
324
+ const abs = require('node:path').resolve(v1Target);
325
+ if (v1.detectContainerFormat(abs) === 'v1') {
326
+ const out = args.filter((a) => !a.startsWith('--'))[2];
327
+ if (!out) {
328
+ process.stderr.write('Usage: kdna unpack <input.kdna> <output-dir>\n');
329
+ process.exit(2);
330
+ }
331
+ const r = v1.unpack(v1Target, out);
332
+ process.stdout.write(
333
+ `Unpacked: ${r.outputDir}\nEntries: ${r.entries.length} (${r.entries.join(', ')})\n`,
334
+ );
335
+ return;
336
+ }
337
+ }
277
338
  error(
278
339
  'Unpacking exposes internal files and is dev-only. Use: kdna dev unpack <file.kdna>',
279
340
  EXIT.INPUT_ERROR,
@@ -337,7 +398,14 @@ switch (cmd) {
337
398
  }
338
399
  case 'inspect': {
339
400
  const target = args.filter((a) => !a.startsWith('--'))[1];
340
- if (!target) error('Usage: kdna inspect <file.kdna> [--json] [--locale zh-CN]');
401
+ if (!target) error('Usage: kdna inspect <path> [--json] [--locale zh-CN]');
402
+ const v1 = require('./v1-cli');
403
+ const abs = require('node:path').resolve(target);
404
+ if (v1.isV1SourceDir(abs) || v1.detectContainerFormat(abs) === 'v1') {
405
+ const out = v1.inspect(target);
406
+ console.log(JSON.stringify(out, null, 2));
407
+ return;
408
+ }
341
409
  const localeIdx = args.indexOf('--locale');
342
410
  const locale = localeIdx >= 0 ? args[localeIdx + 1] : null;
343
411
  cmdInspect(target, args.includes('--json'), locale);
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Anti-Monolithic Domain lint for KDNA dev source workspaces.
3
+ *
4
+ * Implements the Anti-Monolithic Domain Principle (RFC-0013 §4).
5
+ * The principle is codified in SPEC §1.6; this file is the runtime
6
+ * check that enforces it.
7
+ *
8
+ * Default mode: WARN (does not block CI).
9
+ * Strict mode: ERROR (blocks CI / official preflight).
10
+ *
11
+ * Trigger conditions (all three must hold):
12
+ * 1. KDNA_Core.json.axioms.length > 6
13
+ * 2. KDNA_Core.json.frameworks.length >= 3
14
+ * 3. The source workspace either:
15
+ * (a) has no module_manifest.json, OR
16
+ * (b) has module_manifest.json but no domain-level
17
+ * decomposition_rationale field.
18
+ *
19
+ * If a trigger fires, the lint produces:
20
+ * - WARNING: domain appears monolithic; recommend split or
21
+ * add module_manifest.json + decomposition_rationale.
22
+ * - ERROR (--strict only): same content, with the strict banner.
23
+ *
24
+ * Optional auxiliary checks (always WARN, never ERROR):
25
+ * - module_manifest.json exists and references modules
26
+ * but has no sub_domain (or all modules are internal_module)
27
+ * and the domain has multiple distinct judgment questions.
28
+ * - module_manifest.json exists with decomposition_rationale
29
+ * but the rationale text is shorter than 30 characters
30
+ * (likely placeholder).
31
+ *
32
+ * Boundary rules per RFC-0013 §3.3:
33
+ * - internal_module: in-domain, not independently loadable
34
+ * - sub_domain: independently loadable as its own .kdna
35
+ * - reference: pure data, not judgment
36
+ */
37
+
38
+ const fs = require('fs');
39
+ const path = require('path');
40
+
41
+ const AXIOM_THRESHOLD = 6; // SPEC §5.2 says "between 2 and 6 axioms"
42
+ const FRAMEWORK_THRESHOLD = 3; // RFC-0013 §4 companion rule
43
+ const RATIONALE_MIN_LENGTH = 30;
44
+
45
+ function readJsonSafe(filePath) {
46
+ try {
47
+ if (!fs.existsSync(filePath)) return null;
48
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
49
+ } catch (e) {
50
+ return { __readError: e.message, __path: filePath };
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Run the Anti-Monolithic check on a dev source directory.
56
+ * Returns { triggered, warnings, errors, summary }.
57
+ */
58
+ function runAntiMonolithicCheck(dir, opts = {}) {
59
+ const strict = !!opts.strict;
60
+ const abs = path.resolve(dir);
61
+ const result = {
62
+ path: abs,
63
+ triggered: false,
64
+ warnings: [],
65
+ errors: [],
66
+ summary: {
67
+ axiom_count: 0,
68
+ framework_count: 0,
69
+ has_module_manifest: false,
70
+ has_decomposition_rationale: false,
71
+ },
72
+ };
73
+
74
+ const corePath = path.join(abs, 'KDNA_Core.json');
75
+ const core = readJsonSafe(corePath);
76
+ if (!core || core.__readError) {
77
+ result.errors.push(
78
+ `Cannot read KDNA_Core.json at ${corePath}: ${(core && core.__readError) || 'file not found'}`,
79
+ );
80
+ return result;
81
+ }
82
+
83
+ const axioms = Array.isArray(core.axioms) ? core.axioms : [];
84
+ const frameworks = Array.isArray(core.frameworks) ? core.frameworks : [];
85
+ result.summary.axiom_count = axioms.length;
86
+ result.summary.framework_count = frameworks.length;
87
+
88
+ const axiomOver = axioms.length > AXIOM_THRESHOLD;
89
+ const frameworkOver = frameworks.length >= FRAMEWORK_THRESHOLD;
90
+ if (!axiomOver || !frameworkOver) {
91
+ return result; // Below threshold: domain is not monolithic by the rule.
92
+ }
93
+
94
+ // Both thresholds exceeded: now check module_manifest.json.
95
+ const manifestPath = path.join(abs, 'module_manifest.json');
96
+ const manifest = readJsonSafe(manifestPath);
97
+ const manifestExists = manifest !== null && !manifest.__readError;
98
+ result.summary.has_module_manifest = manifestExists;
99
+
100
+ let hasRationale = false;
101
+ if (manifestExists) {
102
+ const rationale = (manifest && manifest.decomposition_rationale) || '';
103
+ hasRationale = typeof rationale === 'string' && rationale.trim().length >= RATIONALE_MIN_LENGTH;
104
+ result.summary.has_decomposition_rationale = hasRationale;
105
+
106
+ if (rationale && rationale.trim().length > 0 && rationale.trim().length < RATIONALE_MIN_LENGTH) {
107
+ result.warnings.push(
108
+ `module_manifest.json: decomposition_rationale is only ${rationale.trim().length} chars; ` +
109
+ `minimum is ${RATIONALE_MIN_LENGTH} chars to count as a real sign-off.`,
110
+ );
111
+ }
112
+ }
113
+
114
+ if (!manifestExists) {
115
+ result.triggered = true;
116
+ const msg =
117
+ `Anti-Monolithic Domain Principle: KDNA_Core.json has ` +
118
+ `${axioms.length} axioms (>${AXIOM_THRESHOLD}) and ${frameworks.length} frameworks (>=${FRAMEWORK_THRESHOLD}). ` +
119
+ `No module_manifest.json found. Either split into sub-domains and compose via cluster, ` +
120
+ `or create a module_manifest.json with a decomposition_rationale (>=${RATIONALE_MIN_LENGTH} chars) ` +
121
+ `and a maintainer sign-off. See SPEC §1.6 and RFC-0013 §4.`;
122
+ if (strict) {
123
+ result.errors.push(msg);
124
+ } else {
125
+ result.warnings.push(msg);
126
+ }
127
+ } else if (!hasRationale) {
128
+ result.triggered = true;
129
+ const msg =
130
+ `Anti-Monolithic Domain Principle: KDNA_Core.json has ` +
131
+ `${axioms.length} axioms (>${AXIOM_THRESHOLD}) and ${frameworks.length} frameworks (>=${FRAMEWORK_THRESHOLD}). ` +
132
+ `module_manifest.json exists but decomposition_rationale is missing or too short. ` +
133
+ `Add a substantive rationale (>=${RATIONALE_MIN_LENGTH} chars) to record the maintainer sign-off. ` +
134
+ `See SPEC §1.6 and RFC-0013 §4.`;
135
+ if (strict) {
136
+ result.errors.push(msg);
137
+ } else {
138
+ result.warnings.push(msg);
139
+ }
140
+ } else {
141
+ // Thresholds exceeded but maintainer sign-off is present:
142
+ // emit a soft warning so the maintainer rationale is visible in
143
+ // CI logs even though the rule is satisfied.
144
+ result.warnings.push(
145
+ `Anti-Monolithic Domain Principle: KDNA_Core.json has ${axioms.length} axioms and ` +
146
+ `${frameworks.length} frameworks. Maintainer sign-off recorded in module_manifest.json ` +
147
+ `(decomposition_rationale). Review periodically that the rationale still holds.`,
148
+ );
149
+ }
150
+
151
+ return result;
152
+ }
153
+
154
+ /**
155
+ * Print the check result to stdout. Returns the recommended process
156
+ * exit code: 0 if no errors (warnings OK), 1 if errors.
157
+ */
158
+ function printAndExit(result, opts = {}) {
159
+ const jsonMode = !!opts.json;
160
+ if (jsonMode) {
161
+ console.log(JSON.stringify(result, null, 2));
162
+ return result.errors.length ? 1 : 0;
163
+ }
164
+ if (result.errors.length) {
165
+ console.error('Errors:');
166
+ result.errors.forEach((e) => console.error(` - ${e}`));
167
+ }
168
+ if (result.warnings.length) {
169
+ console.log('Warnings:');
170
+ result.warnings.forEach((w) => console.log(` - ${w}`));
171
+ }
172
+ if (!result.errors.length && !result.warnings.length) {
173
+ console.log(
174
+ `✓ Anti-Monolithic check passed: ${result.path} ` +
175
+ `(${result.summary.axiom_count} axioms, ${result.summary.framework_count} frameworks)`,
176
+ );
177
+ } else if (!result.errors.length) {
178
+ console.log(
179
+ `✓ Anti-Monolithic check: ${result.warnings.length} warning(s), 0 error(s) ` +
180
+ `(passing; --strict to upgrade warnings to errors)`,
181
+ );
182
+ }
183
+ return result.errors.length ? 1 : 0;
184
+ }
185
+
186
+ module.exports = {
187
+ runAntiMonolithicCheck,
188
+ printAndExit,
189
+ AXIOM_THRESHOLD,
190
+ FRAMEWORK_THRESHOLD,
191
+ RATIONALE_MIN_LENGTH,
192
+ };
@@ -200,6 +200,109 @@ function cmdValidate(dir, schemaOnly, jsonMode = false) {
200
200
  }
201
201
  }
202
202
 
203
+ /**
204
+ * Anti-Monolithic Domain lint entry point.
205
+ *
206
+ * Runs schema validation (same as `kdna dev validate`) and then runs
207
+ * the Anti-Monolithic Domain Principle check (RFC-0013 §4 / SPEC §1.6)
208
+ * on top. The Anti-Monolithic check produces warnings by default and
209
+ * errors under `--strict`.
210
+ *
211
+ * Exits with non-zero only on schema errors or (under --strict) on
212
+ * Anti-Monolithic trigger.
213
+ */
214
+ function cmdValidateAntiMonolithic(dir, opts = {}) {
215
+ const jsonMode = !!opts.json;
216
+ const strict = !!opts.strict;
217
+
218
+ // First, run the standard schema validation but capture its result
219
+ // rather than letting it process.exit(0).
220
+ // We achieve this by re-using the underlying helpers.
221
+ const abs = path.resolve(dir);
222
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
223
+ error(`Not a directory: ${abs}`, EXIT.INPUT_ERROR);
224
+ }
225
+
226
+ const SCHEMA_DIR = path.join(
227
+ path.dirname(require.resolve('@aikdna/kdna-core/package.json')),
228
+ 'schema',
229
+ );
230
+ const { schemaMap, loadedSchemas, missingSchemas } = loadSchemaMap(SCHEMA_DIR);
231
+
232
+ const schemaResult = isClusterDirectory(abs)
233
+ ? validateClusterDirectory(abs, schemaMap, false)
234
+ : validateDomainDirectory(abs, schemaMap, false);
235
+
236
+ // Run Anti-Monolithic check on the top-level (or first sub-) directory.
237
+ // For clusters, run on each domain dir and aggregate.
238
+ const amResults = [];
239
+ if (schemaResult.cluster && Array.isArray(schemaResult.domains)) {
240
+ for (const d of schemaResult.domains) {
241
+ amResults.push(
242
+ runAntiMonolithicCheckOnCore(d.path || abs, { strict }),
243
+ );
244
+ }
245
+ } else {
246
+ amResults.push(runAntiMonolithicCheckOnCore(abs, { strict }));
247
+ }
248
+
249
+ // Aggregate.
250
+ const allErrors = [...schemaResult.errors];
251
+ const allWarnings = [...schemaResult.warnings];
252
+ for (const r of amResults) {
253
+ allErrors.push(...r.errors);
254
+ allWarnings.push(...r.warnings);
255
+ }
256
+
257
+ if (jsonMode) {
258
+ console.log(
259
+ JSON.stringify(
260
+ {
261
+ path: abs,
262
+ schema_validation: {
263
+ valid: schemaResult.errors.length === 0,
264
+ errors: schemaResult.errors,
265
+ warnings: schemaResult.warnings,
266
+ cluster: !!schemaResult.cluster,
267
+ files: schemaResult.files,
268
+ domains: schemaResult.domains,
269
+ schemas_loaded: loadedSchemas.length,
270
+ missing_schemas: missingSchemas,
271
+ },
272
+ anti_monolithic: amResults,
273
+ },
274
+ null,
275
+ 2,
276
+ ),
277
+ );
278
+ process.exit(allErrors.length ? EXIT.VALIDATION_FAILED : EXIT.OK);
279
+ }
280
+
281
+ if (allErrors.length) {
282
+ console.error('Errors:');
283
+ allErrors.forEach((e) => console.error(` - ${e}`));
284
+ }
285
+ if (allWarnings.length) {
286
+ console.log('Warnings:');
287
+ allWarnings.forEach((w) => console.log(` - ${w}`));
288
+ }
289
+ if (allErrors.length === 0) {
290
+ if (amResults.some((r) => r.triggered)) {
291
+ console.log(
292
+ `✓ Schema valid; Anti-Monolithic triggered (${amResults.filter((r) => r.triggered).length}/${amResults.length} domain(s))`,
293
+ );
294
+ } else {
295
+ console.log(`✓ Schema valid; Anti-Monolithic check passed (${amResults.length} domain(s))`);
296
+ }
297
+ }
298
+ process.exit(allErrors.length ? EXIT.VALIDATION_FAILED : EXIT.OK);
299
+ }
300
+
301
+ function runAntiMonolithicCheckOnCore(dir, opts) {
302
+ const { runAntiMonolithicCheck } = require('./anti-monolithic');
303
+ return runAntiMonolithicCheck(dir, opts);
304
+ }
305
+
203
306
  // ─── Pack / Unpack (.kdna ZIP container) ──────────────────────────────────
204
307
 
205
308
  function cmdPack(dir, outputDir) {
@@ -918,6 +1021,7 @@ function cmdCard(dir, jsonMode = false, locale = null, options = {}) {
918
1021
 
919
1022
  module.exports = {
920
1023
  cmdValidate,
1024
+ cmdValidateAntiMonolithic,
921
1025
  cmdPack,
922
1026
  cmdUnpack,
923
1027
  cmdInspect,