@aikdna/kdna-cli 0.16.1 → 0.16.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-cli",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "description": "KDNA CLI — create, validate, install, and manage domain cognition packages for AI agents.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -215,6 +215,51 @@ KDNA does not override:
215
215
 
216
216
  ---
217
217
 
218
+ ## Safety & Governance
219
+
220
+ KDNA domains influence agent judgment. The loader MUST apply safety rules before loading any domain.
221
+
222
+ ### Loading Priority
223
+
224
+ When KDNA is loaded, the agent MUST respect this priority order:
225
+ 1. System safety policy (highest — cannot be overridden)
226
+ 2. Legal and compliance requirements
227
+ 3. User's explicit intent
228
+ 4. KDNA domain judgment
229
+ 5. Tool/skill instructions (lowest)
230
+
231
+ KDNA MUST NOT override system safety policies, legal requirements, or the user's explicit refusal to apply domain judgment.
232
+
233
+ ### Risk-Level Checks
234
+
235
+ Before loading a KDNA domain, check its risk level in `kdna.json` or `KDNA_CARD.json`:
236
+
237
+ | Risk Level | Loading Behavior |
238
+ |-----------|-----------------|
239
+ | **R0** (Low) | Load silently |
240
+ | **R1** (Medium) | Load silently; log |
241
+ | **R2** (High) | Warn user before loading; require confirmation |
242
+ | **R3** (Restricted) | Reject loading unless explicitly authorized |
243
+
244
+ ### Signature & Trust Checks
245
+
246
+ - Yanked domains: **REJECT** loading (domain has been withdrawn for safety)
247
+ - Deprecated domains: warn; suggest replacement if `replaced_by` is set
248
+ - Unsigned domains: warn; load only if user confirms
249
+ - Unknown scope domains: warn; load only if user confirms
250
+
251
+ ### Runtime Logging
252
+
253
+ Every KDNA load MUST be logged with:
254
+ - Domain name and version
255
+ - Risk level
256
+ - Signature status
257
+ - Which axioms were triggered
258
+ - Which misunderstandings were avoided
259
+ - Self-check pass rate
260
+
261
+ This enables audit and accountability.
262
+
218
263
  ## Failure handling
219
264
 
220
265
  | Situation | What to do |
package/src/agent.js CHANGED
@@ -371,7 +371,12 @@ function cmdLoad(input, args = []) {
371
371
  // JSON format
372
372
  if (format === 'json') {
373
373
  process.stdout.write(JSON.stringify({ manifest, core, patterns: pat }, null, 2) + '\n');
374
- recordTrace({ timestamp: new Date().toISOString(), agent: detectAgent(), domain: parsed.full, format: 'json' });
374
+ recordTrace({
375
+ timestamp: new Date().toISOString(),
376
+ agent: detectAgent(),
377
+ domain: parsed.full,
378
+ format: 'json',
379
+ });
375
380
  return;
376
381
  }
377
382
 
@@ -384,20 +389,35 @@ function cmdLoad(input, args = []) {
384
389
  process.stdout.write(fs.readFileSync(p, 'utf8'));
385
390
  }
386
391
  }
387
- recordTrace({ timestamp: new Date().toISOString(), agent: detectAgent(), domain: parsed.full, format: 'raw' });
392
+ recordTrace({
393
+ timestamp: new Date().toISOString(),
394
+ agent: detectAgent(),
395
+ domain: parsed.full,
396
+ format: 'raw',
397
+ });
388
398
  return;
389
399
  }
390
400
 
391
401
  // Load profiles
392
402
  if (profile) {
393
403
  emitProfile(parsed, manifest, core, pat, profile, profileInput);
394
- recordTrace({ timestamp: new Date().toISOString(), agent: detectAgent(), domain: parsed.full, format: `profile:${profile}` });
404
+ recordTrace({
405
+ timestamp: new Date().toISOString(),
406
+ agent: detectAgent(),
407
+ domain: parsed.full,
408
+ format: `profile:${profile}`,
409
+ });
395
410
  return;
396
411
  }
397
412
 
398
413
  // Default: --as=prompt — compact text optimized for system-prompt injection.
399
414
  emitCompact(parsed, manifest, core, pat);
400
- recordTrace({ timestamp: new Date().toISOString(), agent: detectAgent(), domain: parsed.full, format: 'prompt' });
415
+ recordTrace({
416
+ timestamp: new Date().toISOString(),
417
+ agent: detectAgent(),
418
+ domain: parsed.full,
419
+ format: 'prompt',
420
+ });
401
421
  }
402
422
 
403
423
  // ─── Load profiles ─────────────────────────────────────────────────────
@@ -420,7 +440,8 @@ function emitProfile(parsed, manifest, core, pat, profile, input) {
420
440
  for (const a of axioms) {
421
441
  lines.push(`- ${a.one_sentence}`);
422
442
  if (a.applies_when?.length) lines.push(` APPLIES: ${a.applies_when.join('; ')}`);
423
- if (a.does_not_apply_when?.length) lines.push(` NOT: ${a.does_not_apply_when.join('; ')}`);
443
+ if (a.does_not_apply_when?.length)
444
+ lines.push(` NOT: ${a.does_not_apply_when.join('; ')}`);
424
445
  }
425
446
  lines.push('');
426
447
  }
@@ -434,7 +455,11 @@ function emitProfile(parsed, manifest, core, pat, profile, input) {
434
455
  const taskTokens = tokenize(input || '');
435
456
  const relevant = axioms.filter((a) => {
436
457
  if (!a.applies_when?.length) return false;
437
- const combinedText = [...a.applies_when, a.one_sentence || '', a.full_statement || ''].join(' ');
458
+ const combinedText = [
459
+ ...a.applies_when,
460
+ a.one_sentence || '',
461
+ a.full_statement || '',
462
+ ].join(' ');
438
463
  const score = overlapScore(taskTokens, combinedText);
439
464
  return score.hits >= 1 || score.coverage >= 0.1;
440
465
  });
@@ -603,7 +628,9 @@ function cmdSelect(args = []) {
603
628
 
604
629
  if (!input) {
605
630
  if (wantJson) {
606
- console.log(JSON.stringify({ error: 'Usage: kdna select --input "<task>" [--max-domains=N] [--json]' }));
631
+ console.log(
632
+ JSON.stringify({ error: 'Usage: kdna select --input "<task>" [--max-domains=N] [--json]' }),
633
+ );
607
634
  process.exit(2);
608
635
  }
609
636
  console.error('Usage: kdna select --input "<task>" [--max-domains=N] [--json]');
@@ -664,12 +691,18 @@ function cmdSelect(args = []) {
664
691
  const selected = scores.slice(0, maxDomains);
665
692
 
666
693
  if (wantJson) {
667
- console.log(JSON.stringify({
668
- input: input.slice(0, 200),
669
- selected,
670
- max_domains: maxDomains,
671
- total_candidates: scores.length,
672
- }, null, 2));
694
+ console.log(
695
+ JSON.stringify(
696
+ {
697
+ input: input.slice(0, 200),
698
+ selected,
699
+ max_domains: maxDomains,
700
+ total_candidates: scores.length,
701
+ },
702
+ null,
703
+ 2,
704
+ ),
705
+ );
673
706
  return;
674
707
  }
675
708
 
@@ -775,7 +808,9 @@ function cmdPostvalidate(args = []) {
775
808
  for (const sc of selfChecks) {
776
809
  const text = typeof sc === 'string' ? sc : sc.question;
777
810
  if (text) {
778
- const keywords = tokenize(text).filter((t) => t.length > 3).slice(0, 3);
811
+ const keywords = tokenize(text)
812
+ .filter((t) => t.length > 3)
813
+ .slice(0, 3);
779
814
  const found = keywords.some((k) => agentOutput.toLowerCase().includes(k));
780
815
  if (found) foundChecks++;
781
816
  }
@@ -794,7 +829,9 @@ function cmdPostvalidate(args = []) {
794
829
  for (const notApply of ax.does_not_apply_when || []) {
795
830
  if (overlapScore(tokenize(agentOutput), notApply).hits >= 2) {
796
831
  boundaryViolations++;
797
- results.violations.push(`boundary violation: ${ax.id} (should not apply when "${notApply.slice(0, 80)}")`);
832
+ results.violations.push(
833
+ `boundary violation: ${ax.id} (should not apply when "${notApply.slice(0, 80)}")`,
834
+ );
798
835
  break;
799
836
  }
800
837
  }
@@ -828,14 +865,20 @@ function cmdPostvalidate(args = []) {
828
865
  agent: detectAgent(),
829
866
  domain: parsed.full,
830
867
  type: 'postvalidate',
831
- postvalidate: { result: results.violations.length ? 'fail' : 'pass', violations: results.violations.length, passed: results.passed.length },
868
+ postvalidate: {
869
+ result: results.violations.length ? 'fail' : 'pass',
870
+ violations: results.violations.length,
871
+ passed: results.passed.length,
872
+ },
832
873
  });
833
874
  process.exit(results.violations.length ? 1 : 0);
834
875
  }
835
876
 
836
877
  console.log(`Post-validation: ${parsed.full}`);
837
878
  console.log('');
838
- console.log(` Violations: ${results.violations.length} Warnings: ${results.warnings.length} Passed: ${results.passed.length}`);
879
+ console.log(
880
+ ` Violations: ${results.violations.length} Warnings: ${results.warnings.length} Passed: ${results.passed.length}`,
881
+ );
839
882
  console.log('');
840
883
 
841
884
  if (results.violations.length) {
@@ -858,7 +901,11 @@ function cmdPostvalidate(args = []) {
858
901
  agent: detectAgent(),
859
902
  domain: parsed.full,
860
903
  type: 'postvalidate',
861
- postvalidate: { result: results.violations.length ? 'fail' : 'pass', violations: results.violations.length, passed: results.passed.length },
904
+ postvalidate: {
905
+ result: results.violations.length ? 'fail' : 'pass',
906
+ violations: results.violations.length,
907
+ passed: results.passed.length,
908
+ },
862
909
  });
863
910
  process.exit(results.violations.length ? 1 : 0);
864
911
  }
@@ -9,7 +9,11 @@ const AGENTS = [
9
9
  { name: 'Codex', dir: path.join(process.env.HOME || '', '.codex'), skillsDir: 'skills' },
10
10
  { name: 'Claude Code', dir: path.join(process.env.HOME || '', '.claude'), skillsDir: 'skills' },
11
11
  { name: 'Cursor', dir: path.join(process.env.HOME || '', '.cursor'), skillsDir: 'skills' },
12
- { name: 'Gemini Antigravity', dir: path.join(process.env.HOME || '', '.gemini', 'antigravity'), skillsDir: 'skills' },
12
+ {
13
+ name: 'Gemini Antigravity',
14
+ dir: path.join(process.env.HOME || '', '.gemini', 'antigravity'),
15
+ skillsDir: 'skills',
16
+ },
13
17
  ];
14
18
 
15
19
  const V2_1_MARKER = 'kdna available';
@@ -91,7 +95,11 @@ function cmdDoctor(args) {
91
95
  detail: `${domains} domain${domains !== 1 ? 's' : ''} installed`,
92
96
  });
93
97
  } else {
94
- checks.push({ name: 'Domains directory', status: 'warn', detail: '~/.kdna/domains/ not found' });
98
+ checks.push({
99
+ name: 'Domains directory',
100
+ status: 'warn',
101
+ detail: '~/.kdna/domains/ not found',
102
+ });
95
103
  }
96
104
  }
97
105
 
@@ -100,7 +108,9 @@ function cmdDoctor(args) {
100
108
  const detected = detectAgents();
101
109
  for (const agent of AGENTS) {
102
110
  const agentDirExists = fs.existsSync(agent.dir);
103
- const skill = agentDirExists ? checkAgentSkill(agent) : { installed: false, version: null, path: null };
111
+ const skill = agentDirExists
112
+ ? checkAgentSkill(agent)
113
+ : { installed: false, version: null, path: null };
104
114
 
105
115
  let status, detail;
106
116
  if (!agentDirExists) {
@@ -159,7 +169,11 @@ function cmdDoctor(args) {
159
169
  checks.push({ name: 'Registry cache', status: 'warn', detail: 'cannot read cache' });
160
170
  }
161
171
  } else {
162
- checks.push({ name: 'Registry cache', status: 'warn', detail: 'not cached (run: kdna registry refresh)' });
172
+ checks.push({
173
+ name: 'Registry cache',
174
+ status: 'warn',
175
+ detail: 'not cached (run: kdna registry refresh)',
176
+ });
163
177
  }
164
178
 
165
179
  // 8. Schema files available
@@ -184,7 +198,11 @@ function cmdDoctor(args) {
184
198
  if (fs.existsSync(projectConfig)) {
185
199
  checks.push({ name: 'Project config', status: 'ok', detail: projectConfig });
186
200
  } else {
187
- checks.push({ name: 'Project config', status: 'warn', detail: 'No .kdna/config.json in current project' });
201
+ checks.push({
202
+ name: 'Project config',
203
+ status: 'warn',
204
+ detail: 'No .kdna/config.json in current project',
205
+ });
188
206
  }
189
207
  }
190
208
 
@@ -196,7 +214,11 @@ function cmdDoctor(args) {
196
214
  name: c.name,
197
215
  status: c.status,
198
216
  detail: c.detail,
199
- ...(c.agent && { agent: c.agent, skillInstalled: c.skillInstalled, skillVersion: c.skillVersion }),
217
+ ...(c.agent && {
218
+ agent: c.agent,
219
+ skillInstalled: c.skillInstalled,
220
+ skillVersion: c.skillVersion,
221
+ }),
200
222
  })),
201
223
  ok: checks.filter((c) => c.status === 'ok').length,
202
224
  warnings: checks.filter((c) => c.status === 'warn').length,
@@ -218,9 +240,13 @@ function cmdDoctor(args) {
218
240
  const fails = checks.filter((c) => c.status === 'fail').length;
219
241
  console.log('');
220
242
  if (fails > 0) {
221
- console.log(`${ok}/${checks.length} checks passed (${fails} failure${fails !== 1 ? 's' : ''}, ${warns} warning${warns !== 1 ? 's' : ''})`);
243
+ console.log(
244
+ `${ok}/${checks.length} checks passed (${fails} failure${fails !== 1 ? 's' : ''}, ${warns} warning${warns !== 1 ? 's' : ''})`,
245
+ );
222
246
  } else if (warns > 0) {
223
- console.log(`${ok}/${checks.length} checks passed (${warns} warning${warns !== 1 ? 's' : ''})`);
247
+ console.log(
248
+ `${ok}/${checks.length} checks passed (${warns} warning${warns !== 1 ? 's' : ''})`,
249
+ );
224
250
  } else {
225
251
  console.log(`${ok}/${checks.length} checks passed`);
226
252
  }
@@ -656,6 +656,19 @@ function cmdInspect(dir, jsonMode = false) {
656
656
  },
657
657
  axioms: (c.axioms || []).map((a) => a.one_sentence || null).filter(Boolean),
658
658
  };
659
+
660
+ // Governance metadata (KDNA_CARD.json)
661
+ const card = readJson(path.join(abs, 'KDNA_CARD.json'));
662
+ if (card) {
663
+ result.governance = {
664
+ risk_level: card.risk_level || null,
665
+ review_status: card.review_status || null,
666
+ intended_use: card.intended_use || [],
667
+ out_of_scope: card.out_of_scope || [],
668
+ known_limitations: card.known_limitations || [],
669
+ requires_expert_review: card.requires_expert_review || false,
670
+ };
671
+ }
659
672
  console.log(JSON.stringify(result, null, 2));
660
673
  return;
661
674
  }
@@ -703,7 +716,20 @@ function cmdInspect(dir, jsonMode = false) {
703
716
 
704
717
  if (rea) console.log(` Reasoning chains: ${(rea.reasoning_chains || []).length}`);
705
718
 
706
- if (evo) console.log(` Evolution stages: ${(evo.stages || []).length}`);
719
+ if (evo) console.log(` Evolution stages: ${(evo.stages || []).length}`);
720
+
721
+ // Governance metadata
722
+ const kdnaCard = readJson(path.join(abs, 'KDNA_CARD.json'));
723
+ if (kdnaCard) {
724
+ console.log('');
725
+ console.log(' ── Governance ──');
726
+ console.log(` Risk level: ${kdnaCard.risk_level || '?'}`);
727
+ console.log(` Review status: ${kdnaCard.review_status || '?'}`);
728
+ if (kdnaCard.intended_use?.length) console.log(` Intended use: ${kdnaCard.intended_use[0]}${kdnaCard.intended_use.length > 1 ? ` (+${kdnaCard.intended_use.length - 1} more)` : ''}`);
729
+ if (kdnaCard.out_of_scope?.length) console.log(` Out of scope: ${kdnaCard.out_of_scope[0]}${kdnaCard.out_of_scope.length > 1 ? ` (+${kdnaCard.out_of_scope.length - 1} more)` : ''}`);
730
+ if (kdnaCard.known_limitations?.length) console.log(` Limitations: ${kdnaCard.known_limitations[0]}${kdnaCard.known_limitations.length > 1 ? ` (+${kdnaCard.known_limitations.length - 1} more)` : ''}`);
731
+ if (kdnaCard.requires_expert_review) console.log(` ⚠ Expert review required`);
732
+ }
707
733
 
708
734
  console.log('');
709
735
  console.log(' ── Axioms ──');
@@ -49,23 +49,32 @@ function machineFingerprint() {
49
49
  const uuid = execSync('ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID', {
50
50
  encoding: 'utf8',
51
51
  timeout: 3000,
52
- }).match(/"[A-F0-9-]{36}"/)?.[0]?.replace(/"/g, '');
52
+ })
53
+ .match(/"[A-F0-9-]{36}"/)?.[0]
54
+ ?.replace(/"/g, '');
53
55
  if (uuid) parts.push(uuid);
54
56
  }
55
57
  if (os.platform() === 'linux') {
56
58
  try {
57
59
  const mid = require('fs').readFileSync('/etc/machine-id', 'utf8').trim();
58
60
  if (mid) parts.push(mid);
59
- } catch { /* ignore */ }
61
+ } catch {
62
+ /* ignore */
63
+ }
60
64
  }
61
- } catch { /* non-critical */ }
65
+ } catch {
66
+ /* non-critical */
67
+ }
62
68
  return crypto.createHash('sha256').update(parts.join('|')).digest('hex');
63
69
  }
64
70
 
65
71
  // ─── Key Derivation ─────────────────────────────────────────────────────
66
72
 
67
73
  function deriveKey(licenseKey, fingerprint) {
68
- const salt = crypto.createHash('sha256').update(fingerprint || machineFingerprint()).digest();
74
+ const salt = crypto
75
+ .createHash('sha256')
76
+ .update(fingerprint || machineFingerprint())
77
+ .digest();
69
78
  return crypto.pbkdf2Sync(licenseKey, salt, PBKDF2_ITERATIONS, KEY_LENGTH, 'sha512');
70
79
  }
71
80
 
@@ -103,7 +112,7 @@ function isEncryptedContainer(filePath) {
103
112
  function createLicense(domain, options = {}) {
104
113
  const {
105
114
  issuedTo = 'licensee@example.com',
106
- expiresAt = null, // ISO date or null for perpetual
115
+ expiresAt = null, // ISO date or null for perpetual
107
116
  maxAgents = 1,
108
117
  requireMachineBinding = true,
109
118
  requireOnlineCheck = false,
@@ -11,9 +11,19 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const crypto = require('crypto');
13
13
  const { EXIT, error } = require('./_common');
14
- const { createLicense, signLicense, verifyLicense, verifyLicenseSignature, machineFingerprint } = require('./encrypt');
15
-
16
- const IDENTITY_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna', 'identity');
14
+ const {
15
+ createLicense,
16
+ signLicense,
17
+ verifyLicense,
18
+ verifyLicenseSignature,
19
+ machineFingerprint,
20
+ } = require('./encrypt');
21
+
22
+ const IDENTITY_DIR = path.join(
23
+ process.env.HOME || process.env.USERPROFILE || '.',
24
+ '.kdna',
25
+ 'identity',
26
+ );
17
27
  const PRIVATE_KEY_PATH = path.join(IDENTITY_DIR, 'kdna.key');
18
28
  const PUBLIC_KEY_PATH = path.join(IDENTITY_DIR, 'kdna.pub');
19
29
 
@@ -29,7 +39,11 @@ function readIdentity() {
29
39
 
30
40
  function cmdLicenseGenerate(args) {
31
41
  const domain = args[0];
32
- if (!domain) error('Usage: kdna license generate <domain> --to <email> [--expires <date>] [--max-agents <n>]', EXIT.INPUT_ERROR);
42
+ if (!domain)
43
+ error(
44
+ 'Usage: kdna license generate <domain> --to <email> [--expires <date>] [--max-agents <n>]',
45
+ EXIT.INPUT_ERROR,
46
+ );
33
47
 
34
48
  const emailIdx = args.indexOf('--to');
35
49
  const email = emailIdx >= 0 ? args[emailIdx + 1] : null;
@@ -82,7 +96,7 @@ function cmdLicenseGenerate(args) {
82
96
 
83
97
  function cmdLicenseVerify(args) {
84
98
  const jsonMode = args.includes('--json');
85
- const filtered = args.filter(a => !a.startsWith('--'));
99
+ const filtered = args.filter((a) => !a.startsWith('--'));
86
100
  const licensePath = filtered[0];
87
101
  if (!licensePath) error('Usage: kdna license verify <license.json>', EXIT.INPUT_ERROR);
88
102
 
@@ -99,15 +113,21 @@ function cmdLicenseVerify(args) {
99
113
  const result = verifyLicense(license, publicKey, fp);
100
114
 
101
115
  if (jsonMode) {
102
- console.log(JSON.stringify({
103
- domain: license.domain,
104
- license_id: license.license_id,
105
- issued_to: license.issued_to,
106
- signature_valid: signatureValid,
107
- valid: result.valid,
108
- issues: result.issues,
109
- fingerprint: license.require_machine_binding ? fp : 'not required',
110
- }, null, 2));
116
+ console.log(
117
+ JSON.stringify(
118
+ {
119
+ domain: license.domain,
120
+ license_id: license.license_id,
121
+ issued_to: license.issued_to,
122
+ signature_valid: signatureValid,
123
+ valid: result.valid,
124
+ issues: result.issues,
125
+ fingerprint: license.require_machine_binding ? fp : 'not required',
126
+ },
127
+ null,
128
+ 2,
129
+ ),
130
+ );
111
131
  } else {
112
132
  console.log(`License: ${license.license_id}`);
113
133
  console.log(`Domain: ${license.domain}`);
@@ -124,7 +144,7 @@ function cmdLicenseVerify(args) {
124
144
  if (result.issues.length) {
125
145
  console.log('');
126
146
  console.log('Issues:');
127
- result.issues.forEach(i => console.log(` ✗ ${i}`));
147
+ result.issues.forEach((i) => console.log(` ✗ ${i}`));
128
148
  } else {
129
149
  console.log('');
130
150
  console.log('✓ License valid');
@@ -135,7 +155,7 @@ function cmdLicenseVerify(args) {
135
155
  }
136
156
 
137
157
  function cmdLicenseBind(args) {
138
- const filtered = args.filter(a => !a.startsWith('--'));
158
+ const filtered = args.filter((a) => !a.startsWith('--'));
139
159
  const licensePath = filtered[0];
140
160
  if (!licensePath) error('Usage: kdna license bind <license.json>', EXIT.INPUT_ERROR);
141
161
 
@@ -166,7 +186,7 @@ function cmdLicenseBind(args) {
166
186
  }
167
187
 
168
188
  function cmdLicenseShow(args) {
169
- const filtered = args.filter(a => !a.startsWith('--'));
189
+ const filtered = args.filter((a) => !a.startsWith('--'));
170
190
  const licensePath = filtered[0];
171
191
  if (!licensePath) {
172
192
  const local = path.join(process.cwd(), 'license.json');
@@ -189,7 +209,11 @@ function cmdLicenseInstall(args) {
189
209
 
190
210
  if (!license.domain) error('License missing domain field', EXIT.INPUT_ERROR);
191
211
 
192
- const licenseDir = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna', 'licenses');
212
+ const licenseDir = path.join(
213
+ process.env.HOME || process.env.USERPROFILE || '.',
214
+ '.kdna',
215
+ 'licenses',
216
+ );
193
217
  fs.mkdirSync(licenseDir, { recursive: true });
194
218
 
195
219
  const safeName = license.domain.replace(/^@/, '').replace('/', '-');
@@ -204,4 +228,10 @@ function cmdLicenseInstall(args) {
204
228
  console.log(`Now install the domain: kdna install ${license.domain}`);
205
229
  }
206
230
 
207
- module.exports = { cmdLicenseGenerate, cmdLicenseVerify, cmdLicenseBind, cmdLicenseShow, cmdLicenseInstall };
231
+ module.exports = {
232
+ cmdLicenseGenerate,
233
+ cmdLicenseVerify,
234
+ cmdLicenseBind,
235
+ cmdLicenseShow,
236
+ cmdLicenseInstall,
237
+ };
package/src/cmds/trace.js CHANGED
@@ -20,7 +20,10 @@ function todayFile() {
20
20
 
21
21
  function traceFiles(sinceDate) {
22
22
  ensureTracesDir();
23
- let files = fs.readdirSync(TRACES_DIR).filter((f) => f.endsWith('.jsonl')).sort();
23
+ let files = fs
24
+ .readdirSync(TRACES_DIR)
25
+ .filter((f) => f.endsWith('.jsonl'))
26
+ .sort();
24
27
  if (sinceDate) {
25
28
  const since = sinceDate instanceof Date ? sinceDate : new Date(sinceDate);
26
29
  files = files.filter((f) => {
@@ -45,9 +48,13 @@ function readAllTraces(opts = {}) {
45
48
  if (agent && entry.agent !== agent) continue;
46
49
  if (domain && entry.domain !== domain) continue;
47
50
  entries.push(entry);
48
- } catch { /* skip malformed lines */ }
51
+ } catch {
52
+ /* skip malformed lines */
53
+ }
49
54
  }
50
- } catch { /* skip unreadable files */ }
55
+ } catch {
56
+ /* skip unreadable files */
57
+ }
51
58
  }
52
59
  return entries;
53
60
  }
@@ -129,14 +136,18 @@ function cmdTrace(args) {
129
136
  console.log(`${'Timestamp'.padEnd(20)} ${'Agent'.padEnd(15)} ${'Domain'.padEnd(25)} ${'Result'}`);
130
137
  console.log('-'.repeat(75));
131
138
  for (const e of entries.slice(-50).reverse()) {
132
- const ts = e.timestamp ? new Date(e.timestamp).toISOString().replace('T', ' ').slice(0, 19) : 'unknown';
139
+ const ts = e.timestamp
140
+ ? new Date(e.timestamp).toISOString().replace('T', ' ').slice(0, 19)
141
+ : 'unknown';
133
142
  const agent = (e.agent || 'unknown').padEnd(15);
134
143
  const domain = (e.domain || '(none)').padEnd(25);
135
144
  const result = e.postvalidate?.result || 'loaded';
136
145
  console.log(`${ts} ${agent} ${domain} ${result}`);
137
146
  }
138
147
  console.log('');
139
- console.log(`${entries.length} entries total. --export <file> for audit export. --clear to reset.`);
148
+ console.log(
149
+ `${entries.length} entries total. --export <file> for audit export. --clear to reset.`,
150
+ );
140
151
  }
141
152
 
142
153
  function cmdHistory(args) {
@@ -166,13 +177,19 @@ function cmdHistory(args) {
166
177
  }
167
178
 
168
179
  if (json) {
169
- console.log(JSON.stringify({
170
- total,
171
- skipped,
172
- domainCounts,
173
- agentCounts,
174
- skipRate: total > 0 ? Math.round((skipped / total) * 100) : 0,
175
- }, null, 2));
180
+ console.log(
181
+ JSON.stringify(
182
+ {
183
+ total,
184
+ skipped,
185
+ domainCounts,
186
+ agentCounts,
187
+ skipRate: total > 0 ? Math.round((skipped / total) * 100) : 0,
188
+ },
189
+ null,
190
+ 2,
191
+ ),
192
+ );
176
193
  } else {
177
194
  console.log(`Total KDNA loads: ${total}`);
178
195
  console.log(`Skipped (no domain): ${skipped}`);
@@ -208,10 +225,14 @@ function cmdHistory(args) {
208
225
  process.exit(EXIT.OK);
209
226
  }
210
227
 
211
- console.log(`${'Timestamp'.padEnd(20)} ${'Agent'.padEnd(15)} ${'Domain'.padEnd(28)} ${'Result'.padEnd(10)} ${'Score'}`);
228
+ console.log(
229
+ `${'Timestamp'.padEnd(20)} ${'Agent'.padEnd(15)} ${'Domain'.padEnd(28)} ${'Result'.padEnd(10)} ${'Score'}`,
230
+ );
212
231
  console.log('-'.repeat(85));
213
232
  for (const e of recent) {
214
- const ts = e.timestamp ? new Date(e.timestamp).toISOString().replace('T', ' ').slice(0, 19) : 'unknown';
233
+ const ts = e.timestamp
234
+ ? new Date(e.timestamp).toISOString().replace('T', ' ').slice(0, 19)
235
+ : 'unknown';
215
236
  const agent = (e.agent || 'unknown').padEnd(15);
216
237
  const domain = (e.domain || '(none)').padEnd(28);
217
238
  const result = (e.postvalidate?.result || 'loaded').padEnd(10);
@@ -219,7 +240,9 @@ function cmdHistory(args) {
219
240
  console.log(`${ts} ${agent} ${domain} ${result} ${score}`);
220
241
  }
221
242
  console.log('');
222
- console.log(`Showing ${recent.length} of ${entries.length} total entries. --stats for summary. --domain <name> to filter.`);
243
+ console.log(
244
+ `Showing ${recent.length} of ${entries.length} total entries. --stats for summary. --domain <name> to filter.`,
245
+ );
223
246
  }
224
247
 
225
248
  module.exports = { cmdTrace, cmdHistory, recordTrace, readAllTraces };
package/src/compare.js CHANGED
@@ -299,17 +299,21 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
299
299
  const domainScore = scoreDiff(axes);
300
300
  const axioms = core.axioms || [];
301
301
  const selfChecks = pat.self_check || [];
302
- const bannedTerms = (pat.terminology?.banned_terms || []).map(t => typeof t === 'string' ? t : t.term);
302
+ const bannedTerms = (pat.terminology?.banned_terms || []).map((t) =>
303
+ typeof t === 'string' ? t : t.term,
304
+ );
303
305
  const misunderstandings = pat.misunderstandings || [];
304
306
 
305
307
  const lines = [];
306
308
  lines.push('# KDNA Judgment Comparison Report');
307
309
  lines.push('');
308
310
  lines.push(`**Domain:** ${parsed.full} (v${manifest.version || '?'})`);
309
- lines.push(`**Input:** "${(args => {
310
- const i = args.indexOf('--input');
311
- return i >= 0 ? args[i + 1].slice(0, 120) : '?';
312
- })(process.argv.slice(2))}"`);
311
+ lines.push(
312
+ `**Input:** "${((args) => {
313
+ const i = args.indexOf('--input');
314
+ return i >= 0 ? args[i + 1].slice(0, 120) : '?';
315
+ })(process.argv.slice(2))}"`,
316
+ );
313
317
  lines.push(`**Model:** ${llm.provider} / ${llm.model}`);
314
318
  lines.push(`**Date:** ${new Date().toISOString()}`);
315
319
  lines.push('');
@@ -318,7 +322,14 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
318
322
  lines.push('## Without KDNA');
319
323
  lines.push('');
320
324
  lines.push('### Judgment Path');
321
- lines.push(responseA.split('\n').filter(l => l.trim()).slice(0, 3).map(l => `- ${l}`).join('\n'));
325
+ lines.push(
326
+ responseA
327
+ .split('\n')
328
+ .filter((l) => l.trim())
329
+ .slice(0, 3)
330
+ .map((l) => `- ${l}`)
331
+ .join('\n'),
332
+ );
322
333
  lines.push('');
323
334
  lines.push('### Key Deficiencies');
324
335
  lines.push('- No domain-specific diagnosis applied');
@@ -331,12 +342,21 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
331
342
  lines.push(`### Domain Loaded`);
332
343
  lines.push(`- Name: ${parsed.full}`);
333
344
  lines.push(`- Axioms applied: ${axioms.length} total`);
334
- lines.push(`- Frameworks: ${(core.frameworks || []).map(f => f.id).join(', ') || 'none declared'}`);
345
+ lines.push(
346
+ `- Frameworks: ${(core.frameworks || []).map((f) => f.id).join(', ') || 'none declared'}`,
347
+ );
335
348
  lines.push(`- Self-checks: ${selfChecks.length} items`);
336
349
  lines.push(`- Banned terms: ${bannedTerms.length}`);
337
350
  lines.push('');
338
351
  lines.push('### Judgment Path');
339
- lines.push(responseB.split('\n').filter(l => l.trim()).slice(0, 3).map(l => `- ${l}`).join('\n'));
352
+ lines.push(
353
+ responseB
354
+ .split('\n')
355
+ .filter((l) => l.trim())
356
+ .slice(0, 3)
357
+ .map((l) => `- ${l}`)
358
+ .join('\n'),
359
+ );
340
360
  lines.push('');
341
361
  lines.push('---');
342
362
  lines.push('');
@@ -354,9 +374,13 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
354
374
  for (const d of dims) {
355
375
  const v = axes[d.axis];
356
376
  const changed = v && v.toUpperCase() !== 'SAME';
357
- lines.push(`| **${d.name}** | Generic | Domain-specific | **${changed ? 'Improved' : 'Same'}** |`);
377
+ lines.push(
378
+ `| **${d.name}** | Generic | Domain-specific | **${changed ? 'Improved' : 'Same'}** |`,
379
+ );
358
380
  }
359
- lines.push(`| **Self-check rate** | N/A | ${selfChecks.length > 0 ? 'Domain applied' : 'N/A'} | **Improved** |`);
381
+ lines.push(
382
+ `| **Self-check rate** | N/A | ${selfChecks.length > 0 ? 'Domain applied' : 'N/A'} | **Improved** |`,
383
+ );
360
384
  lines.push('');
361
385
  lines.push(`**Verdict:** ${verdict.replace(/_/g, ' ')}`);
362
386
  lines.push('');
@@ -366,27 +390,47 @@ function emitMarkdownReport(parsed, manifest, core, pat, responseA, responseB, d
366
390
  lines.push('');
367
391
  lines.push(`| D# | Dimension | Score (0-10) |`);
368
392
  lines.push('|----|-----------|:-----------:|');
369
- lines.push(`| D1 | Diagnostic depth | ${domainScore.changed.includes('diagnosis') ? '8' : '5'} |`);
370
- lines.push(`| D2 | Terminology precision | ${domainScore.changed.includes('terminology') ? '8' : '5'} |`);
393
+ lines.push(
394
+ `| D1 | Diagnostic depth | ${domainScore.changed.includes('diagnosis') ? '8' : '5'} |`,
395
+ );
396
+ lines.push(
397
+ `| D2 | Terminology precision | ${domainScore.changed.includes('terminology') ? '8' : '5'} |`,
398
+ );
371
399
  lines.push(`| D3 | Misunderstanding detection | 5 |`);
372
400
  lines.push(`| D4 | Axiom alignment | ${domainScore.score} |`);
373
401
  lines.push(`| D5 | Self-check pass rate | ${selfChecks.length > 0 ? '100%' : 'N/A'} |`);
374
- lines.push(`| D6 | Boundary respect | ${domainScore.changed.includes('boundary') ? 'Pass' : 'N/A'} |`);
402
+ lines.push(
403
+ `| D6 | Boundary respect | ${domainScore.changed.includes('boundary') ? 'Pass' : 'N/A'} |`,
404
+ );
375
405
  lines.push(`| D7 | Risk avoidance | ${axes.failure ? 'Pass' : 'N/A'} |`);
376
406
  lines.push('');
377
407
  lines.push('---');
378
408
  lines.push('');
379
409
  lines.push('## Summary');
380
410
  lines.push('');
381
- const changedDims = domainScore.changed.map(c => `**${c}**`).join(', ');
382
- lines.push(`Loading \`${parsed.full}\` changed the agent's response across ${domainScore.changed.length} dimensions: ${changedDims || 'no significant change'}. ${verdict.includes('changed') ? 'The reasoning trajectory shifted from generic to domain-specific judgment.' : 'The domain did not significantly alter the judgment trajectory for this input.'}`);
411
+ const changedDims = domainScore.changed.map((c) => `**${c}**`).join(', ');
412
+ lines.push(
413
+ `Loading \`${parsed.full}\` changed the agent's response across ${domainScore.changed.length} dimensions: ${changedDims || 'no significant change'}. ${verdict.includes('changed') ? 'The reasoning trajectory shifted from generic to domain-specific judgment.' : 'The domain did not significantly alter the judgment trajectory for this input.'}`,
414
+ );
383
415
  lines.push('');
384
- lines.push('*Generated by kdna compare. Copy-pasteable as a GitHub comment, Slack message, or tweet.*');
416
+ lines.push(
417
+ '*Generated by kdna compare. Copy-pasteable as a GitHub comment, Slack message, or tweet.*',
418
+ );
385
419
 
386
420
  return lines.join('\n');
387
421
  }
388
422
 
389
- function emitJsonReport(parsed, manifest, core, pat, responseA, responseB, diffText, llm, userInput) {
423
+ function emitJsonReport(
424
+ parsed,
425
+ manifest,
426
+ core,
427
+ pat,
428
+ responseA,
429
+ responseB,
430
+ diffText,
431
+ llm,
432
+ userInput,
433
+ ) {
390
434
  const { axes, verdict } = parseDiffText(diffText);
391
435
  const domainScore = scoreDiff(axes);
392
436
  const axioms = core.axioms || [];
@@ -442,7 +486,10 @@ async function cmdCompare(input, args = []) {
442
486
  const outputFile = args.includes('--output') ? args[args.indexOf('--output') + 1] : null;
443
487
  const idxInput = args.indexOf('--input');
444
488
  if (idxInput < 0 || !args[idxInput + 1]) {
445
- error('Usage: kdna compare <name> --input "<text>" [--report-md|--report-json] [--output <file>]', EXIT.INPUT_ERROR);
489
+ error(
490
+ 'Usage: kdna compare <name> --input "<text>" [--report-md|--report-json] [--output <file>]',
491
+ EXIT.INPUT_ERROR,
492
+ );
446
493
  }
447
494
  const userInput = args[idxInput + 1];
448
495
 
@@ -477,11 +524,13 @@ async function cmdCompare(input, args = []) {
477
524
 
478
525
  if (!jsonMode && !reportMd && !reportJson) console.log('[1/3] Running baseline (no KDNA)...');
479
526
  const responseA = await callLlm(llm, BASELINE_SYSTEM, userInput);
480
- if (!jsonMode && !reportMd && !reportJson) console.log(` ${responseA.length} chars returned`);
527
+ if (!jsonMode && !reportMd && !reportJson)
528
+ console.log(` ${responseA.length} chars returned`);
481
529
 
482
530
  if (!jsonMode && !reportMd && !reportJson) console.log('[2/3] Running with KDNA loaded...');
483
531
  const responseB = await callLlm(llm, TREATMENT_SYSTEM, userInput);
484
- if (!jsonMode && !reportMd && !reportJson) console.log(` ${responseB.length} chars returned`);
532
+ if (!jsonMode && !reportMd && !reportJson)
533
+ console.log(` ${responseB.length} chars returned`);
485
534
 
486
535
  if (!jsonMode && !reportMd && !reportJson) console.log('[3/3] Diffing reasoning trajectories...');
487
536
  const diffPrompt = makeDiffPrompt(userInput, responseA, responseB);
@@ -508,7 +557,17 @@ async function cmdCompare(input, args = []) {
508
557
  }
509
558
 
510
559
  if (reportJson) {
511
- const report = emitJsonReport(parsed, manifest, core, pat, responseA, responseB, diff, llm, userInput);
560
+ const report = emitJsonReport(
561
+ parsed,
562
+ manifest,
563
+ core,
564
+ pat,
565
+ responseA,
566
+ responseB,
567
+ diff,
568
+ llm,
569
+ userInput,
570
+ );
512
571
  if (outputFile) {
513
572
  fs.writeFileSync(outputFile, JSON.stringify(report, null, 2) + '\n');
514
573
  console.log(`Report saved to ${outputFile}`);
package/src/install.js CHANGED
@@ -20,7 +20,13 @@ const crypto = require('crypto');
20
20
  const { execSync, execFileSync } = require('child_process');
21
21
  const { RegistryResolver, parseName } = require('./registry');
22
22
  const { EXIT, error } = require('./cmds/_common');
23
- const { decrypt, deriveKey, machineFingerprint, isEncryptedContainer, ENCRYPTED_FILES } = require('./cmds/encrypt');
23
+ const {
24
+ decrypt,
25
+ deriveKey,
26
+ machineFingerprint,
27
+ isEncryptedContainer,
28
+ ENCRYPTED_FILES,
29
+ } = require('./cmds/encrypt');
24
30
 
25
31
  const USER_KDNA_DIR = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.kdna');
26
32
  const INSTALL_DIR = path.join(USER_KDNA_DIR, 'domains');
@@ -306,11 +312,16 @@ function extractAndDecrypt(kdnaPath, destDir, licenseKey) {
306
312
 
307
313
  function findLicense(domainName) {
308
314
  const licenseDir = path.join(USER_KDNA_DIR, 'licenses');
309
- const licensePath = path.join(licenseDir, `${domainName.replace(/^@/, '').replace('/', '-')}.json`);
315
+ const licensePath = path.join(
316
+ licenseDir,
317
+ `${domainName.replace(/^@/, '').replace('/', '-')}.json`,
318
+ );
310
319
  if (fs.existsSync(licensePath)) {
311
320
  try {
312
321
  return JSON.parse(fs.readFileSync(licensePath, 'utf8'));
313
- } catch { /* invalid license */ }
322
+ } catch {
323
+ /* invalid license */
324
+ }
314
325
  }
315
326
  return null;
316
327
  }
@@ -325,7 +336,11 @@ function findLicenseForDomain(domainFull) {
325
336
  for (const c of candidates) {
326
337
  const p = path.join(licenseDir, `${c}.json`);
327
338
  if (fs.existsSync(p)) {
328
- try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { /* skip */ }
339
+ try {
340
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
341
+ } catch {
342
+ /* skip */
343
+ }
329
344
  }
330
345
  }
331
346
  return null;
@@ -362,7 +377,10 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
362
377
 
363
378
  // Author pubkey fingerprint must match scope trust_pubkey
364
379
  if (manifest.author?.pubkey !== trustKey) {
365
- error(`${entry.name}: author.pubkey does not match scope trust key. Refusing to install.`, EXIT.TRUST_FAILED);
380
+ error(
381
+ `${entry.name}: author.pubkey does not match scope trust key. Refusing to install.`,
382
+ EXIT.TRUST_FAILED,
383
+ );
366
384
  }
367
385
 
368
386
  // Full Ed25519 verify (requires public_key_pem embedded in the package)
@@ -414,7 +432,10 @@ function verifySignature({ destDir, scope, entry, lenient = true }) {
414
432
  const publicKey = crypto.createPublicKey(pem);
415
433
  const ok = crypto.verify(null, Buffer.from(payload), publicKey, Buffer.from(sigHex, 'hex'));
416
434
  if (!ok) {
417
- error(`${entry.name}: Ed25519 signature INVALID. Package may be tampered. Refusing.`, EXIT.TRUST_FAILED);
435
+ error(
436
+ `${entry.name}: Ed25519 signature INVALID. Package may be tampered. Refusing.`,
437
+ EXIT.TRUST_FAILED,
438
+ );
418
439
  }
419
440
  console.log(' ✓ Signature OK (Ed25519 verified)');
420
441
  } catch (e) {
@@ -510,7 +531,10 @@ function installFromRegistry(parsed, yes, jsonMode = false) {
510
531
  }
511
532
  }
512
533
  if (entry.access && entry.access !== 'open') {
513
- error(`${entry.name} requires "${entry.access}" access. Not installable via CLI yet.`, EXIT.POLICY_VIOLATION);
534
+ error(
535
+ `${entry.name} requires "${entry.access}" access. Not installable via CLI yet.`,
536
+ EXIT.POLICY_VIOLATION,
537
+ );
514
538
  }
515
539
 
516
540
  if (entry.type === 'cluster') {
@@ -587,13 +611,15 @@ function installSingleFromUrl({ entry, scope }, jsonMode = false) {
587
611
  fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(manifest, null, 2) + '\n');
588
612
 
589
613
  if (jsonMode) {
590
- console.log(JSON.stringify({
591
- name: entry.name,
592
- version: entry.version,
593
- installed: true,
594
- path: dest,
595
- type: entry.type || 'domain',
596
- }));
614
+ console.log(
615
+ JSON.stringify({
616
+ name: entry.name,
617
+ version: entry.version,
618
+ installed: true,
619
+ path: dest,
620
+ type: entry.type || 'domain',
621
+ }),
622
+ );
597
623
  } else {
598
624
  console.log(`✓ Installed ${entry.name}@${entry.version}`);
599
625
  console.log(` Location: ${dest}`);
@@ -643,14 +669,16 @@ function installCluster(clusterEntry, resolver, _yes, jsonMode = false) {
643
669
  );
644
670
 
645
671
  if (jsonMode) {
646
- console.log(JSON.stringify({
647
- name: clusterEntry.name,
648
- version: clusterEntry.version,
649
- type: 'cluster',
650
- installed: true,
651
- path: clusterDest,
652
- subdomains: subdomains.length,
653
- }));
672
+ console.log(
673
+ JSON.stringify({
674
+ name: clusterEntry.name,
675
+ version: clusterEntry.version,
676
+ type: 'cluster',
677
+ installed: true,
678
+ path: clusterDest,
679
+ subdomains: subdomains.length,
680
+ }),
681
+ );
654
682
  } else {
655
683
  console.log('');
656
684
  console.log(`✓ Cluster ${clusterEntry.name} installed`);
@@ -677,7 +705,9 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
677
705
  try {
678
706
  const lic = JSON.parse(fs.readFileSync(licenseFromArgs, 'utf8'));
679
707
  licenseKey = lic.license_id;
680
- } catch { /* invalid license file */ }
708
+ } catch {
709
+ /* invalid license file */
710
+ }
681
711
  }
682
712
 
683
713
  if (!licenseKey) {
@@ -734,14 +764,16 @@ function installFromLocalFile(filePath, _yes, jsonMode = false) {
734
764
  fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
735
765
 
736
766
  if (jsonMode) {
737
- console.log(JSON.stringify({
738
- name: declared,
739
- installed: true,
740
- path: dest,
741
- source: 'local-file',
742
- source_path: abs,
743
- encrypted: isEncrypted,
744
- }));
767
+ console.log(
768
+ JSON.stringify({
769
+ name: declared,
770
+ installed: true,
771
+ path: dest,
772
+ source: 'local-file',
773
+ source_path: abs,
774
+ encrypted: isEncrypted,
775
+ }),
776
+ );
745
777
  } else {
746
778
  console.log(`✓ Installed ${declared} from ${isEncrypted ? 'encrypted' : 'local'} file`);
747
779
  console.log(` Location: ${dest}`);
@@ -770,13 +802,15 @@ function installFromLocalDir(dirPath, _yes, jsonMode = false) {
770
802
  fs.writeFileSync(path.join(dest, 'kdna.json'), JSON.stringify(destManifest, null, 2) + '\n');
771
803
 
772
804
  if (jsonMode) {
773
- console.log(JSON.stringify({
774
- name: declared,
775
- installed: true,
776
- path: dest,
777
- source: 'local-dir',
778
- source_path: abs,
779
- }));
805
+ console.log(
806
+ JSON.stringify({
807
+ name: declared,
808
+ installed: true,
809
+ path: dest,
810
+ source: 'local-dir',
811
+ source_path: abs,
812
+ }),
813
+ );
780
814
  } else {
781
815
  console.log(`✓ Installed ${declared} from local directory (dev mode)`);
782
816
  console.log(` Location: ${dest}`);