@jamaynor/hal-config 1.0.1

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/test/test.js ADDED
@@ -0,0 +1,586 @@
1
+ import { describe, it, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname } from 'node:path';
7
+
8
+ import { makeTmpDir, writeJson, cleanup } from '../test-utils.js';
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+
12
+ // Fresh dynamic import for each test to get a clean module-level cache
13
+ async function freshImport() {
14
+ const modPath = path.resolve(__dirname, '../lib/config.js');
15
+ const url = `file:///${modPath.replace(/\\/g, '/')}?t=${Date.now()}`;
16
+ return import(url);
17
+ }
18
+
19
+ function readConfig(dir) {
20
+ return JSON.parse(fs.readFileSync(
21
+ path.join(dir, 'hal-system-config.json'), 'utf8'));
22
+ }
23
+
24
+ const SAMPLE_CONFIG = {
25
+ version: '2.0',
26
+ repoOrg: 'jamaynor',
27
+ 'system-emails': [
28
+ 'me@gmail.com',
29
+ 'work@company.com',
30
+ ],
31
+ 'hal-obsidian-vaults': [
32
+ { name: 'ja-vault', path: '/vaults/ja', label: 'Jeremy', primary: true,
33
+ 'projects-folder': '1-Projects',
34
+ 'daily-notes-folder': 'areas/daily-notes',
35
+ 'email-folder': 'areas/email',
36
+ 'meetings-folder': 'areas/meetings',
37
+ 'people-folder': 'areas/people',
38
+ },
39
+ { name: 'lmb-vault', path: '/vaults/lmb', label: 'LMB',
40
+ 'projects-folder': '1-Projects' },
41
+ ],
42
+ 'hal-communication-accounts': [
43
+ { label: 'me@gmail.com', provider: 'google', email: 'me@gmail.com',
44
+ scopes: ['email', 'calendar'] },
45
+ { label: 'work@company.com', provider: 'ms365', email: 'work@company.com',
46
+ scopes: ['email'] },
47
+ ],
48
+ 'hal-timezone': 'America/Chicago',
49
+ 'hal-work-hours': { start: '09:00', end: '17:00' },
50
+ skills: {
51
+ 'secret-manager-bws': {
52
+ enabled: true, homeAgent: null,
53
+ 'environment-variable-prefix': 'HAL_BWS_',
54
+ install: { repo: 'openclaw-skill-secret-manager-bws',
55
+ package: 'openclaw-skill-secret-manager-bws',
56
+ binary: 'secrets-bws', version: 'main' },
57
+ runtime: {},
58
+ },
59
+ 'project-manager': {
60
+ enabled: true, homeAgent: 'cto',
61
+ 'environment-variable-prefix': 'HAL_PROJ_MGR_',
62
+ install: { repo: 'openclaw-skill-project-manager',
63
+ package: 'openclaw-skill-project-manager',
64
+ binary: ['project', 'project-mgmt'], version: 'latest' },
65
+ runtime: { workspace: '/data/agents/hal' },
66
+ },
67
+ 'plan-your-day': {
68
+ enabled: true, homeAgent: null,
69
+ 'environment-variable-prefix': 'HAL_PLAN_DAY_',
70
+ install: { repo: 'openclaw-skill-plan-your-day',
71
+ package: 'openclaw-skill-plan-your-day',
72
+ binary: 'plan-day', version: 'main' },
73
+ runtime: {},
74
+ },
75
+ 'disabled-skill': {
76
+ enabled: false, homeAgent: null,
77
+ install: { binary: 'disabled-bin' },
78
+ runtime: {},
79
+ },
80
+ },
81
+ halManagedEntries: [],
82
+ };
83
+
84
+ // ============================================================================
85
+ // Tests
86
+ // ============================================================================
87
+
88
+ describe('hal-shared-config', () => {
89
+ let tmpDir;
90
+ let hal;
91
+ let originalAdminRoot;
92
+
93
+ beforeEach(async () => {
94
+ tmpDir = makeTmpDir('hsc-test-');
95
+ writeJson(path.join(tmpDir, 'hal-system-config.json'), SAMPLE_CONFIG);
96
+ originalAdminRoot = process.env.HAL_SKILL_ADMIN_ROOT;
97
+ process.env.HAL_SKILL_ADMIN_ROOT = tmpDir;
98
+
99
+ // Skill independent config file (deterministic under HAL_SKILL_ADMIN_ROOT)
100
+ writeJson(
101
+ path.join(tmpDir, 'email-triage', 'config', 'email-triage.json'),
102
+ { gog_client_id: 'test-gog-client-id' }
103
+ );
104
+ hal = await freshImport();
105
+ });
106
+
107
+ afterEach(() => {
108
+ if (originalAdminRoot === undefined) delete process.env.HAL_SKILL_ADMIN_ROOT;
109
+ else process.env.HAL_SKILL_ADMIN_ROOT = originalAdminRoot;
110
+ cleanup(tmpDir);
111
+ });
112
+
113
+ // --------------------------------------------------------------------------
114
+ // Loading
115
+ // --------------------------------------------------------------------------
116
+
117
+ describe('load()', () => {
118
+ it('reads hal-system-config.json from given dir', () => {
119
+ const cfg = hal.load(tmpDir);
120
+ assert.equal(cfg.version, '2.0');
121
+ assert.equal(cfg.repoOrg, 'jamaynor');
122
+ });
123
+
124
+ it('returns empty object when file is missing', () => {
125
+ const emptyDir = makeTmpDir('hsc-test-');
126
+ const cfg = hal.load(emptyDir);
127
+ assert.deepStrictEqual(cfg, {});
128
+ cleanup(emptyDir);
129
+ });
130
+
131
+ it('throws on corrupt JSON', () => {
132
+ fs.writeFileSync(path.join(tmpDir, 'hal-system-config.json'), '{bad', 'utf8');
133
+ assert.throws(() => hal.load(tmpDir), { name: 'SyntaxError' });
134
+ });
135
+
136
+ it('auto-loads on first accessor call', () => {
137
+ // Set env so auto-load finds our tmp dir
138
+ const orig = process.env.HAL_SYSTEM_CONFIG;
139
+ process.env.HAL_SYSTEM_CONFIG = tmpDir;
140
+ try {
141
+ const v = hal.vaults();
142
+ assert.equal(v.length, 2);
143
+ } finally {
144
+ if (orig === undefined) delete process.env.HAL_SYSTEM_CONFIG;
145
+ else process.env.HAL_SYSTEM_CONFIG = orig;
146
+ }
147
+ });
148
+ });
149
+
150
+ describe('reload()', () => {
151
+ it('re-reads from disk after external change', () => {
152
+ hal.load(tmpDir);
153
+ assert.equal(hal.timezone(), 'America/Chicago');
154
+
155
+ const cfg = readConfig(tmpDir);
156
+ cfg['hal-timezone'] = 'US/Eastern';
157
+ writeJson(path.join(tmpDir, 'hal-system-config.json'), cfg);
158
+
159
+ hal.reload();
160
+ assert.equal(hal.timezone(), 'US/Eastern');
161
+ });
162
+ });
163
+
164
+ describe('raw() and configDir()', () => {
165
+ it('returns full config and resolved dir', () => {
166
+ hal.load(tmpDir);
167
+ assert.equal(hal.raw().repoOrg, 'jamaynor');
168
+ assert.equal(hal.configDir(), tmpDir);
169
+ });
170
+ });
171
+
172
+ // --------------------------------------------------------------------------
173
+ // Shared data accessors
174
+ // --------------------------------------------------------------------------
175
+
176
+ describe('vaults()', () => {
177
+ it('returns vault array', () => {
178
+ hal.load(tmpDir);
179
+ const v = hal.vaults();
180
+ assert.equal(v.length, 2);
181
+ assert.equal(v[0].name, 'ja-vault');
182
+ assert.equal(v[1].label, 'LMB');
183
+ });
184
+
185
+ it('returns empty array when key missing', () => {
186
+ writeJson(path.join(tmpDir, 'hal-system-config.json'), { version: '1.1' });
187
+ hal.load(tmpDir);
188
+ assert.deepStrictEqual(hal.vaults(), []);
189
+ });
190
+ });
191
+
192
+ describe('vaultFolders() and vaultDirectoryPaths()', () => {
193
+ it('returns configured folder subpaths with defaults', () => {
194
+ hal.load(tmpDir);
195
+ const f = hal.vaultFolders('ja-vault');
196
+ assert.equal(f.projectsFolder, '1-Projects');
197
+ assert.equal(f.dailyNotesFolder, 'areas/daily-notes');
198
+ assert.equal(f.emailFolder, 'areas/email');
199
+ assert.equal(f.meetingsFolder, 'areas/meetings');
200
+ assert.equal(f.peopleFolder, 'areas/people');
201
+
202
+ const f2 = hal.vaultFolders('lmb-vault');
203
+ assert.equal(f2.projectsFolder, '1-Projects');
204
+ // defaults
205
+ assert.equal(f2.dailyNotesFolder, 'areas/daily-notes');
206
+ assert.equal(f2.emailFolder, 'areas/email');
207
+ });
208
+
209
+ it('returns resolved full directory paths for a vault', () => {
210
+ hal.load(tmpDir);
211
+ const p = hal.vaultDirectoryPaths('ja-vault');
212
+ assert.equal(p.vaultPath, '/vaults/ja');
213
+ assert.equal(p.projectsPath, '/vaults/ja/1-Projects');
214
+ assert.equal(p.dailyNotesPath, '/vaults/ja/areas/daily-notes');
215
+ assert.equal(p.peoplePath, '/vaults/ja/areas/people');
216
+ });
217
+ });
218
+
219
+ describe('accounts()', () => {
220
+ it('returns all accounts', () => {
221
+ hal.load(tmpDir);
222
+ assert.equal(hal.accounts().length, 2);
223
+ });
224
+
225
+ it('filters by provider', () => {
226
+ hal.load(tmpDir);
227
+ const google = hal.accounts('google');
228
+ assert.equal(google.length, 1);
229
+ assert.equal(google[0].label, 'me@gmail.com');
230
+ });
231
+
232
+ it('returns empty for unknown provider', () => {
233
+ hal.load(tmpDir);
234
+ assert.deepStrictEqual(hal.accounts('imap'), []);
235
+ });
236
+ });
237
+
238
+ // --------------------------------------------------------------------------
239
+ // Settings (getSetting)
240
+ // --------------------------------------------------------------------------
241
+
242
+ describe('getSetting()', () => {
243
+ it('returns a top-level global setting by key', () => {
244
+ hal.load(tmpDir);
245
+ assert.deepStrictEqual(hal.getSetting('system-emails'), ['me@gmail.com', 'work@company.com']);
246
+ });
247
+
248
+ it('returns a skill-scoped setting from the skill config file', () => {
249
+ hal.load(tmpDir);
250
+ assert.equal(hal.getSetting('email-triage', 'gog_client_id'), 'test-gog-client-id');
251
+ });
252
+
253
+ it('normalizes skill scope prefixes (hal-*)', () => {
254
+ hal.load(tmpDir);
255
+ assert.equal(hal.getSetting('hal-email-triage', 'gog_client_id'), 'test-gog-client-id');
256
+ });
257
+
258
+ it('exposes PascalCase alias GetSetting', () => {
259
+ hal.load(tmpDir);
260
+ assert.equal(hal.GetSetting('hal-email-triage', 'gog_client_id'), 'test-gog-client-id');
261
+ });
262
+ });
263
+
264
+ describe('timezone()', () => {
265
+ it('returns timezone string', () => {
266
+ hal.load(tmpDir);
267
+ assert.equal(hal.timezone(), 'America/Chicago');
268
+ });
269
+
270
+ it('returns null when missing', () => {
271
+ writeJson(path.join(tmpDir, 'hal-system-config.json'), {});
272
+ hal.load(tmpDir);
273
+ assert.equal(hal.timezone(), null);
274
+ });
275
+ });
276
+
277
+ describe('workHours()', () => {
278
+ it('returns work hours object', () => {
279
+ hal.load(tmpDir);
280
+ assert.deepStrictEqual(hal.workHours(), { start: '09:00', end: '17:00' });
281
+ });
282
+ });
283
+
284
+ // --------------------------------------------------------------------------
285
+ // Skill accessors
286
+ // --------------------------------------------------------------------------
287
+
288
+ describe('skill()', () => {
289
+ it('returns skill config block', () => {
290
+ hal.load(tmpDir);
291
+ const pm = hal.skill('project-manager');
292
+ assert.equal(pm.enabled, true);
293
+ });
294
+
295
+ it('returns null for unknown skill', () => {
296
+ hal.load(tmpDir);
297
+ assert.equal(hal.skill('nonexistent'), null);
298
+ });
299
+ });
300
+
301
+ describe('isEnabled()', () => {
302
+ it('returns true for enabled skill', () => {
303
+ hal.load(tmpDir);
304
+ assert.equal(hal.isEnabled('project-manager'), true);
305
+ });
306
+
307
+ it('returns false for disabled skill', () => {
308
+ hal.load(tmpDir);
309
+ assert.equal(hal.isEnabled('disabled-skill'), false);
310
+ });
311
+
312
+ it('returns false for unknown skill', () => {
313
+ hal.load(tmpDir);
314
+ assert.equal(hal.isEnabled('nonexistent'), false);
315
+ });
316
+ });
317
+
318
+ describe('skillWorkspace()', () => {
319
+ it('returns workspace path from runtime', () => {
320
+ hal.load(tmpDir);
321
+ assert.equal(hal.skillWorkspace('project-manager'), '/data/agents/hal');
322
+ });
323
+
324
+ it('returns null when runtime has no workspace', () => {
325
+ hal.load(tmpDir);
326
+ assert.equal(hal.skillWorkspace('secret-manager-bws'), null);
327
+ });
328
+
329
+ it('returns null for unknown skill', () => {
330
+ hal.load(tmpDir);
331
+ assert.equal(hal.skillWorkspace('nonexistent'), null);
332
+ });
333
+ });
334
+
335
+ describe('skillBinaries()', () => {
336
+ it('returns array for single binary', () => {
337
+ hal.load(tmpDir);
338
+ assert.deepStrictEqual(hal.skillBinaries('secret-manager-bws'), ['secrets-bws']);
339
+ });
340
+
341
+ it('returns array for multiple binaries', () => {
342
+ hal.load(tmpDir);
343
+ assert.deepStrictEqual(hal.skillBinaries('project-manager'),
344
+ ['project', 'project-mgmt']);
345
+ });
346
+
347
+ it('returns empty array for unknown skill', () => {
348
+ hal.load(tmpDir);
349
+ assert.deepStrictEqual(hal.skillBinaries('nonexistent'), []);
350
+ });
351
+ });
352
+
353
+ describe('homeAgent()', () => {
354
+ it('returns homeAgent string when set', () => {
355
+ hal.load(tmpDir);
356
+ assert.equal(hal.homeAgent('project-manager'), 'cto');
357
+ });
358
+
359
+ it('returns null when homeAgent is null', () => {
360
+ hal.load(tmpDir);
361
+ assert.equal(hal.homeAgent('plan-your-day'), null);
362
+ });
363
+
364
+ it('returns null for unknown skill', () => {
365
+ hal.load(tmpDir);
366
+ assert.equal(hal.homeAgent('nonexistent'), null);
367
+ });
368
+ });
369
+
370
+ describe('skillConfigPath()', () => {
371
+ it('returns canonical path under HAL_SKILL_ADMIN_ROOT', () => {
372
+ hal.load(tmpDir);
373
+ const p = hal.skillConfigPath('project-manager');
374
+ assert.equal(p, path.join(tmpDir, 'project-manager', 'config', 'project-manager.json'));
375
+ });
376
+ });
377
+
378
+ describe('skillConfigDir()', () => {
379
+ it('returns the directory containing the skill config file', () => {
380
+ hal.load(tmpDir);
381
+ const d = hal.skillConfigDir('project-manager');
382
+ assert.equal(d, path.join(tmpDir, 'project-manager', 'config'));
383
+ });
384
+ });
385
+
386
+ describe('deterministic filesystem locations', () => {
387
+ it('returns OpenClaw shared workspace root', () => {
388
+ assert.equal(hal.openclawSharedWorkspaceRoot(), '/data/openclaw/workspace');
389
+ });
390
+
391
+ it('returns OpenClaw config file path', () => {
392
+ assert.equal(hal.openclawConfigFilePath(), '/data/openclaw/openclaw.json');
393
+ });
394
+
395
+ it('returns HAL system config dir/file (default)', () => {
396
+ const orig = process.env.HAL_SYSTEM_CONFIG;
397
+ delete process.env.HAL_SYSTEM_CONFIG;
398
+ try {
399
+ assert.equal(hal.halSystemConfigDir(), '/data/openclaw/hal');
400
+ assert.equal(hal.halSystemConfigFilePath(), path.join('/data/openclaw/hal', 'hal-system-config.json'));
401
+ } finally {
402
+ if (orig === undefined) delete process.env.HAL_SYSTEM_CONFIG;
403
+ else process.env.HAL_SYSTEM_CONFIG = orig;
404
+ }
405
+ });
406
+
407
+ it('returns HAL system config dir/file (env override)', () => {
408
+ const orig = process.env.HAL_SYSTEM_CONFIG;
409
+ process.env.HAL_SYSTEM_CONFIG = '/tmp/halcfg';
410
+ try {
411
+ assert.equal(hal.halSystemConfigDir(), '/tmp/halcfg');
412
+ assert.equal(hal.halSystemConfigFilePath(), path.join('/tmp/halcfg', 'hal-system-config.json'));
413
+ } finally {
414
+ if (orig === undefined) delete process.env.HAL_SYSTEM_CONFIG;
415
+ else process.env.HAL_SYSTEM_CONFIG = orig;
416
+ }
417
+ });
418
+
419
+ it('returns per-skill admin/config/log dirs under HAL_SKILL_ADMIN_ROOT', () => {
420
+ hal.load(tmpDir);
421
+ assert.equal(hal.halSkillAdminRoot('email-triage'), path.join(tmpDir, 'email-triage'));
422
+ assert.equal(hal.halSkillConfigDir('email-triage'), path.join(tmpDir, 'email-triage', 'config'));
423
+ assert.equal(hal.halSkillLogDir('email-triage'), path.join(tmpDir, 'email-triage', 'log'));
424
+ });
425
+ });
426
+
427
+ // --------------------------------------------------------------------------
428
+ // halSkillConfig (independent per-skill config files)
429
+ // --------------------------------------------------------------------------
430
+
431
+ describe('halSkillConfig', () => {
432
+ it('read() returns {} when skill config file is missing', () => {
433
+ hal.load(tmpDir);
434
+ const cfg = hal.halSkillConfig.read('missing-skill');
435
+ assert.deepStrictEqual(cfg, {});
436
+ });
437
+
438
+ it('write() writes the full skill config file (atomic)', () => {
439
+ hal.load(tmpDir);
440
+ hal.halSkillConfig.write('email-triage', { a: 1, b: 'x' });
441
+
442
+ const fp = path.join(tmpDir, 'email-triage', 'config', 'email-triage.json');
443
+ const onDisk = JSON.parse(fs.readFileSync(fp, 'utf8'));
444
+ assert.deepStrictEqual(onDisk, { a: 1, b: 'x' });
445
+ });
446
+
447
+ it('setSetting() creates/updates a single key', () => {
448
+ hal.load(tmpDir);
449
+ hal.halSkillConfig.setSetting('email-triage', 'gog_client_id', 'new-id');
450
+ assert.equal(hal.halSkillConfig.getSetting('hal-email-triage', 'gog_client_id'), 'new-id');
451
+ });
452
+
453
+ it('merge() deep-merges plain objects and replaces arrays', () => {
454
+ hal.load(tmpDir);
455
+ hal.halSkillConfig.write('email-triage', { nested: { a: 1 }, arr: [1, 2] });
456
+ const merged = hal.halSkillConfig.merge('email-triage', { nested: { b: 2 }, arr: [9] });
457
+ assert.deepStrictEqual(merged, { nested: { a: 1, b: 2 }, arr: [9] });
458
+ });
459
+ });
460
+
461
+ // isInstalled and isInstalledAsync are not tested here because they
462
+ // probe actual binaries on PATH — tested via integration only.
463
+
464
+ // --------------------------------------------------------------------------
465
+ // Registration
466
+ // --------------------------------------------------------------------------
467
+
468
+ describe('register()', () => {
469
+ it('writes runtime data to skill block and flushes', () => {
470
+ hal.load(tmpDir);
471
+ hal.register('plan-your-day', { workspace: '/data/agents/hal' });
472
+
473
+ const ondisk = readConfig(tmpDir);
474
+ assert.equal(ondisk.skills['plan-your-day'].runtime.workspace,
475
+ '/data/agents/hal');
476
+ });
477
+
478
+ it('merges with existing runtime data', () => {
479
+ hal.load(tmpDir);
480
+ hal.register('project-manager', { configFile: '/data/agents/hal/config/pm.json' });
481
+
482
+ const ondisk = readConfig(tmpDir);
483
+ assert.equal(ondisk.skills['project-manager'].runtime.workspace,
484
+ '/data/agents/hal');
485
+ assert.equal(ondisk.skills['project-manager'].runtime.configFile,
486
+ '/data/agents/hal/config/pm.json');
487
+ });
488
+
489
+ it('creates skill entry if not present', () => {
490
+ hal.load(tmpDir);
491
+ hal.register('new-skill', { workspace: '/tmp/new' });
492
+
493
+ const ondisk = readConfig(tmpDir);
494
+ assert.equal(ondisk.skills['new-skill'].runtime.workspace, '/tmp/new');
495
+ });
496
+ });
497
+
498
+ describe('writeAccounts()', () => {
499
+ it('appends new accounts', () => {
500
+ hal.load(tmpDir);
501
+ hal.writeAccounts([
502
+ { label: 'imap@example.com', provider: 'imap', email: 'imap@example.com' },
503
+ ]);
504
+
505
+ const ondisk = readConfig(tmpDir);
506
+ assert.equal(ondisk['hal-communication-accounts'].length, 3);
507
+ });
508
+
509
+ it('replaces existing account by label', () => {
510
+ hal.load(tmpDir);
511
+ hal.writeAccounts([
512
+ { label: 'me@gmail.com', provider: 'google', email: 'me@gmail.com',
513
+ scopes: ['email'] },
514
+ ]);
515
+
516
+ const ondisk = readConfig(tmpDir);
517
+ assert.equal(ondisk['hal-communication-accounts'].length, 2);
518
+ const google = ondisk['hal-communication-accounts']
519
+ .find(a => a.label === 'me@gmail.com');
520
+ assert.deepStrictEqual(google.scopes, ['email']);
521
+ });
522
+
523
+ it('does not remove unmentioned accounts', () => {
524
+ hal.load(tmpDir);
525
+ hal.writeAccounts([
526
+ { label: 'new@example.com', provider: 'imap', email: 'new@example.com' },
527
+ ]);
528
+
529
+ const ondisk = readConfig(tmpDir);
530
+ const labels = ondisk['hal-communication-accounts'].map(a => a.label);
531
+ assert.ok(labels.includes('me@gmail.com'));
532
+ assert.ok(labels.includes('work@company.com'));
533
+ assert.ok(labels.includes('new@example.com'));
534
+ });
535
+ });
536
+
537
+ describe('writeSkillConfig()', () => {
538
+ it('merges into skill block', () => {
539
+ hal.load(tmpDir);
540
+ hal.writeSkillConfig('plan-your-day', {
541
+ recentDays: 3,
542
+ primaryVault: 'Jeremy',
543
+ });
544
+
545
+ const ondisk = readConfig(tmpDir);
546
+ assert.equal(ondisk.skills['plan-your-day'].recentDays, 3);
547
+ assert.equal(ondisk.skills['plan-your-day'].primaryVault, 'Jeremy');
548
+ // existing keys preserved
549
+ assert.equal(ondisk.skills['plan-your-day'].enabled, true);
550
+ });
551
+ });
552
+
553
+ describe('writeSharedSettings()', () => {
554
+ it('writes top-level keys', () => {
555
+ hal.load(tmpDir);
556
+ hal.writeSharedSettings({
557
+ 'hal-timezone': 'US/Pacific',
558
+ 'hal-daily-note-filename': 'custom.md',
559
+ });
560
+
561
+ const ondisk = readConfig(tmpDir);
562
+ assert.equal(ondisk['hal-timezone'], 'US/Pacific');
563
+ assert.equal(ondisk['hal-daily-note-filename'], 'custom.md');
564
+ // skills untouched
565
+ assert.ok(ondisk.skills['project-manager']);
566
+ });
567
+ });
568
+
569
+ // --------------------------------------------------------------------------
570
+ // index.js re-export
571
+ // --------------------------------------------------------------------------
572
+
573
+ describe('index.js', () => {
574
+ it('re-exports lib/config', async () => {
575
+ const idx = await import('../index.js');
576
+ assert.equal(typeof idx.load, 'function');
577
+ assert.equal(typeof idx.vaults, 'function');
578
+ });
579
+
580
+ it('default export has all functions', async () => {
581
+ const idx = await import('../index.js');
582
+ assert.equal(typeof idx.default.load, 'function');
583
+ assert.equal(typeof idx.default.vaults, 'function');
584
+ });
585
+ });
586
+ });