@lythos/skill-arena 0.15.7 → 0.16.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.
@@ -10,7 +10,7 @@
10
10
  * formatSkillWarnings — warning string generation
11
11
  */
12
12
 
13
- import { describe, test, expect } from 'bun:test'
13
+ import { describe, it, expect } from 'bun:test'
14
14
  import {
15
15
  parseDeckSkills,
16
16
  checkSkillExistence,
@@ -26,15 +26,15 @@ import {
26
26
 
27
27
  describe('parseDeckSkills', () => {
28
28
 
29
- test('empty deck → empty array', () => {
29
+ it('empty deck → empty array', () => {
30
30
  expect(parseDeckSkills({})).toEqual([])
31
31
  })
32
32
 
33
- test('deck with no skill sections → empty array', () => {
33
+ it('deck with no skill sections → empty array', () => {
34
34
  expect(parseDeckSkills({ deck: { max_cards: 10 } })).toEqual([])
35
35
  })
36
36
 
37
- test('inline-table format: single tool skill with path', () => {
37
+ it('inline-table format: single tool skill with path', () => {
38
38
  const parsed = {
39
39
  tool: {
40
40
  skills: {
@@ -47,7 +47,7 @@ describe('parseDeckSkills', () => {
47
47
  ])
48
48
  })
49
49
 
50
- test('inline-table format: multiple skills', () => {
50
+ it('inline-table format: multiple skills', () => {
51
51
  const parsed = {
52
52
  tool: {
53
53
  skills: {
@@ -62,7 +62,7 @@ describe('parseDeckSkills', () => {
62
62
  ])
63
63
  })
64
64
 
65
- test('array format: skills = ["a", "b"]', () => {
65
+ it('array format: skills = ["a", "b"]', () => {
66
66
  const parsed = {
67
67
  tool: {
68
68
  skills: ['web-search', 'docx']
@@ -74,7 +74,7 @@ describe('parseDeckSkills', () => {
74
74
  ])
75
75
  })
76
76
 
77
- test('innate section parsed separately', () => {
77
+ it('innate section parsed separately', () => {
78
78
  const parsed = {
79
79
  innate: {
80
80
  skills: {
@@ -93,7 +93,7 @@ describe('parseDeckSkills', () => {
93
93
  ])
94
94
  })
95
95
 
96
- test('transient section parsed', () => {
96
+ it('transient section parsed', () => {
97
97
  const parsed = {
98
98
  transient: {
99
99
  skills: {
@@ -106,7 +106,7 @@ describe('parseDeckSkills', () => {
106
106
  ])
107
107
  })
108
108
 
109
- test('object entry without path → path=null', () => {
109
+ it('object entry without path → path=null', () => {
110
110
  const parsed = {
111
111
  tool: {
112
112
  skills: {
@@ -119,7 +119,7 @@ describe('parseDeckSkills', () => {
119
119
  ])
120
120
  })
121
121
 
122
- test('object entry with non-string path → path=null', () => {
122
+ it('object entry with non-string path → path=null', () => {
123
123
  const parsed = {
124
124
  tool: {
125
125
  skills: {
@@ -132,7 +132,7 @@ describe('parseDeckSkills', () => {
132
132
  ])
133
133
  })
134
134
 
135
- test('array entry that is not a string → skipped', () => {
135
+ it('array entry that is not a string → skipped', () => {
136
136
  const parsed = {
137
137
  tool: { skills: ['valid', 123, null, 'also-valid'] }
138
138
  }
@@ -142,7 +142,7 @@ describe('parseDeckSkills', () => {
142
142
  ])
143
143
  })
144
144
 
145
- test('all three sections populated → ordered innate, tool, transient', () => {
145
+ it('all three sections populated → ordered innate, tool, transient', () => {
146
146
  const parsed = {
147
147
  innate: { skills: { a: { path: '/a' } } },
148
148
  tool: { skills: { b: { path: '/b' } } },
@@ -162,12 +162,12 @@ describe('parseDeckSkills', () => {
162
162
 
163
163
  describe('checkSkillExistence', () => {
164
164
 
165
- test('empty skills → empty array', () => {
165
+ it('empty skills → empty array', () => {
166
166
  const exists = (_: string) => true
167
167
  expect(checkSkillExistence([], '/cold', exists)).toEqual([])
168
168
  })
169
169
 
170
- test('skill with explicit path → resolves <coldPool>/<path>/SKILL.md', () => {
170
+ it('skill with explicit path → resolves <coldPool>/<path>/SKILL.md', () => {
171
171
  const exists = (p: string) => p === '/cold/github.com/owner/repo/skills/my-skill/SKILL.md'
172
172
  const skills = [{ name: 'my-skill', path: 'github.com/owner/repo/skills/my-skill', section: 'tool' }]
173
173
  const result = checkSkillExistence(skills, '/cold', exists)
@@ -176,7 +176,7 @@ describe('checkSkillExistence', () => {
176
176
  ])
177
177
  })
178
178
 
179
- test('skill without path (array format) → resolves <coldPool>/<name>/SKILL.md', () => {
179
+ it('skill without path (array format) → resolves <coldPool>/<name>/SKILL.md', () => {
180
180
  const exists = (p: string) => p === '/cold/web-search/SKILL.md'
181
181
  const skills = [{ name: 'web-search', path: null, section: 'tool' }]
182
182
  const result = checkSkillExistence(skills, '/cold', exists)
@@ -185,7 +185,7 @@ describe('checkSkillExistence', () => {
185
185
  ])
186
186
  })
187
187
 
188
- test('HTTP path → uses name as fallback for path resolution', () => {
188
+ it('HTTP path → uses name as fallback for path resolution', () => {
189
189
  const exists = (p: string) => p === '/cold/my-skill/SKILL.md'
190
190
  const skills = [{ name: 'my-skill', path: 'https://example.com/deck.toml', section: 'tool' }]
191
191
  const result = checkSkillExistence(skills, '/cold', exists)
@@ -194,7 +194,7 @@ describe('checkSkillExistence', () => {
194
194
  ])
195
195
  })
196
196
 
197
- test('all found → all found=true', () => {
197
+ it('all found → all found=true', () => {
198
198
  const exists = (_: string) => true
199
199
  const skills = [
200
200
  { name: 'a', path: '/a', section: 'tool' },
@@ -206,7 +206,7 @@ describe('checkSkillExistence', () => {
206
206
  ])
207
207
  })
208
208
 
209
- test('some missing → mixed found/not-found', () => {
209
+ it('some missing → mixed found/not-found', () => {
210
210
  const exists = (p: string) => p.includes('a')
211
211
  const skills = [
212
212
  { name: 'a', path: '/a', section: 'tool' },
@@ -218,7 +218,7 @@ describe('checkSkillExistence', () => {
218
218
  ])
219
219
  })
220
220
 
221
- test('different coldPoolDir → different expectedPath prefix', () => {
221
+ it('different coldPoolDir → different expectedPath prefix', () => {
222
222
  const exists = (_: string) => true
223
223
  const skills = [{ name: 'x', path: 'github.com/x', section: 'tool' }]
224
224
  const a = checkSkillExistence(skills, '/home/user/.agents/skill-repos', exists)
@@ -234,35 +234,35 @@ describe('checkSkillExistence', () => {
234
234
 
235
235
  describe('validateLinkResult', () => {
236
236
 
237
- test('exitCode 0 → ok', () => {
237
+ it('exitCode 0 → ok', () => {
238
238
  expect(validateLinkResult(0, '')).toEqual({ ok: true })
239
239
  })
240
240
 
241
- test('exitCode 0 with stderr → still ok (stderr is not always errors)', () => {
241
+ it('exitCode 0 with stderr → still ok (stderr is not always errors)', () => {
242
242
  expect(validateLinkResult(0, 'some warning output')).toEqual({ ok: true })
243
243
  })
244
244
 
245
- test('exitCode 1 → not ok, error contains snippet', () => {
245
+ it('exitCode 1 → not ok, error contains snippet', () => {
246
246
  const result = validateLinkResult(1, 'something went wrong')
247
247
  expect(result.ok).toBe(false)
248
248
  expect(result.error).toContain('exit 1')
249
249
  expect(result.error).toContain('something went wrong')
250
250
  })
251
251
 
252
- test('exitCode null → not ok (null !== 0)', () => {
252
+ it('exitCode null → not ok (null !== 0)', () => {
253
253
  const result = validateLinkResult(null, 'process killed')
254
254
  expect(result.ok).toBe(false)
255
255
  expect(result.error).toContain('exit null')
256
256
  })
257
257
 
258
- test('stderr truncated to 300 chars in error message', () => {
258
+ it('stderr truncated to 300 chars in error message', () => {
259
259
  const longStderr = 'x'.repeat(500)
260
260
  const result = validateLinkResult(1, longStderr)
261
261
  expect(result.ok).toBe(false)
262
262
  expect(result.error!.length).toBeLessThan(350) // "Deck link failed (exit 1): " + 300 chars
263
263
  })
264
264
 
265
- test('exitCode 0, empty stderr → ok with no error field', () => {
265
+ it('exitCode 0, empty stderr → ok with no error field', () => {
266
266
  const result = validateLinkResult(0, '')
267
267
  expect(result.ok).toBe(true)
268
268
  expect(result.error).toBeUndefined()
@@ -275,16 +275,16 @@ describe('validateLinkResult', () => {
275
275
 
276
276
  describe('buildCopyPlan', () => {
277
277
 
278
- test('empty entries → empty plan', () => {
278
+ it('empty entries → empty plan', () => {
279
279
  expect(buildCopyPlan('/work', '/out', [], new Set())).toEqual([])
280
280
  })
281
281
 
282
- test('all skipped → empty plan', () => {
282
+ it('all skipped → empty plan', () => {
283
283
  const skip = new Set(['.claude', 'skill-deck.toml'])
284
284
  expect(buildCopyPlan('/work', '/out', ['.claude', 'skill-deck.toml'], skip)).toEqual([])
285
285
  })
286
286
 
287
- test('normal entries → mapped to outDir', () => {
287
+ it('normal entries → mapped to outDir', () => {
288
288
  const skip = new Set<string>()
289
289
  expect(buildCopyPlan('/work', '/out', ['output.md', 'report.docx'], skip)).toEqual([
290
290
  { src: '/work/output.md', dest: '/out/output.md', name: 'output.md' },
@@ -292,7 +292,7 @@ describe('buildCopyPlan', () => {
292
292
  ])
293
293
  })
294
294
 
295
- test('mixed skip and non-skip → only non-skipped', () => {
295
+ it('mixed skip and non-skip → only non-skipped', () => {
296
296
  const skip = new Set(['.claude', 'skill-deck.toml', 'skill-deck.lock'])
297
297
  const entries = ['.claude', 'output.md', 'skill-deck.toml', 'report.docx', 'skill-deck.lock']
298
298
  expect(buildCopyPlan('/work', '/out', entries, skip)).toEqual([
@@ -301,13 +301,13 @@ describe('buildCopyPlan', () => {
301
301
  ])
302
302
  })
303
303
 
304
- test('preserves entry order', () => {
304
+ it('preserves entry order', () => {
305
305
  const skip = new Set<string>()
306
306
  const entries = ['c', 'a', 'b']
307
307
  expect(buildCopyPlan('/w', '/o', entries, skip).map(e => e.name)).toEqual(['c', 'a', 'b'])
308
308
  })
309
309
 
310
- test('nested paths work (agent-produced subdirectories)', () => {
310
+ it('nested paths work (agent-produced subdirectories)', () => {
311
311
  const skip = new Set<string>()
312
312
  expect(buildCopyPlan('/work', '/out', ['subdir/output.pdf'], skip)).toEqual([
313
313
  { src: '/work/subdir/output.pdf', dest: '/out/subdir/output.pdf', name: 'subdir/output.pdf' },
@@ -321,30 +321,30 @@ describe('buildCopyPlan', () => {
321
321
 
322
322
  describe('resolveColdPoolDir', () => {
323
323
 
324
- test('explicit absolute path → returned as-is', () => {
324
+ it('explicit absolute path → returned as-is', () => {
325
325
  expect(resolveColdPoolDir('/opt/cold', '/home/user', '/fallback')).toBe('/opt/cold')
326
326
  })
327
327
 
328
- test('explicit relative path → returned as-is', () => {
328
+ it('explicit relative path → returned as-is', () => {
329
329
  expect(resolveColdPoolDir('my-cold-pool', '/home/user', '/fallback')).toBe('my-cold-pool')
330
330
  })
331
331
 
332
- test('tilde path → expanded with homeDir', () => {
332
+ it('tilde path → expanded with homeDir', () => {
333
333
  expect(resolveColdPoolDir('~/.agents/skill-repos', '/home/user', '/fallback'))
334
334
  .toBe('/home/user/.agents/skill-repos')
335
335
  })
336
336
 
337
- test('tilde at start only → expanded; tilde elsewhere not expanded', () => {
337
+ it('tilde at start only → expanded; tilde elsewhere not expanded', () => {
338
338
  expect(resolveColdPoolDir('path/with~/tilde', '/home/user', '/fallback'))
339
339
  .toBe('path/with~/tilde')
340
340
  })
341
341
 
342
- test('undefined → uses fallback', () => {
342
+ it('undefined → uses fallback', () => {
343
343
  expect(resolveColdPoolDir(undefined, '/home/user', '/default/cold'))
344
344
  .toBe('/default/cold')
345
345
  })
346
346
 
347
- test('empty string → uses fallback (|| operator)', () => {
347
+ it('empty string → uses fallback (|| operator)', () => {
348
348
  expect(resolveColdPoolDir('', '/home/user', '/default/cold'))
349
349
  .toBe('/default/cold')
350
350
  })
@@ -356,7 +356,7 @@ describe('resolveColdPoolDir', () => {
356
356
 
357
357
  describe('formatSkillWarnings', () => {
358
358
 
359
- test('all found → empty array', () => {
359
+ it('all found → empty array', () => {
360
360
  const checks = [
361
361
  { name: 'a', expectedPath: '/p/a/SKILL.md', found: true, section: 'tool' },
362
362
  { name: 'b', expectedPath: '/p/b/SKILL.md', found: true, section: 'tool' },
@@ -364,7 +364,7 @@ describe('formatSkillWarnings', () => {
364
364
  expect(formatSkillWarnings(checks)).toEqual([])
365
365
  })
366
366
 
367
- test('some missing → one warning per missing skill', () => {
367
+ it('some missing → one warning per missing skill', () => {
368
368
  const checks = [
369
369
  { name: 'pdf', expectedPath: '/cold/pdf/SKILL.md', found: false, section: 'tool' },
370
370
  { name: 'docx', expectedPath: '/cold/docx/SKILL.md', found: true, section: 'tool' },
@@ -374,7 +374,7 @@ describe('formatSkillWarnings', () => {
374
374
  ])
375
375
  })
376
376
 
377
- test('all missing → warning for each', () => {
377
+ it('all missing → warning for each', () => {
378
378
  const checks = [
379
379
  { name: 'a', expectedPath: '/p/a/SKILL.md', found: false, section: 'innate' },
380
380
  { name: 'b', expectedPath: '/p/b/SKILL.md', found: false, section: 'tool' },
@@ -382,11 +382,11 @@ describe('formatSkillWarnings', () => {
382
382
  expect(formatSkillWarnings(checks)).toHaveLength(2)
383
383
  })
384
384
 
385
- test('empty array → empty array', () => {
385
+ it('empty array → empty array', () => {
386
386
  expect(formatSkillWarnings([])).toEqual([])
387
387
  })
388
388
 
389
- test('section name appears in warning string', () => {
389
+ it('section name appears in warning string', () => {
390
390
  const checks = [
391
391
  { name: 'x', expectedPath: '/p/x', found: false, section: 'transient' },
392
392
  ]
@@ -411,14 +411,14 @@ const TMP = '/tmp/arena-test'
411
411
 
412
412
  describe('buildArchiveSidePlan', () => {
413
413
 
414
- test('default: sides=["."] maps to fromDir', () => {
414
+ it('default: sides=["."] maps to fromDir', () => {
415
415
  const plan = buildArchiveSidePlan(TMP, ['.'], _p => true)
416
416
  expect(plan).toEqual([
417
417
  { side: '.', sourceDir: TMP, found: true },
418
418
  ])
419
419
  })
420
420
 
421
- test('single side, subdirectory exists → source = fromDir/side', () => {
421
+ it('single side, subdirectory exists → source = fromDir/side', () => {
422
422
  const exists = (p: string) => p === pathJoin(TMP, 'side-a')
423
423
  const plan = buildArchiveSidePlan(TMP, ['side-a'], exists)
424
424
  expect(plan).toEqual([
@@ -426,14 +426,14 @@ describe('buildArchiveSidePlan', () => {
426
426
  ])
427
427
  })
428
428
 
429
- test('single side, subdirectory MISSING → fallback to fromDir root', () => {
429
+ it('single side, subdirectory MISSING → fallback to fromDir root', () => {
430
430
  const plan = buildArchiveSidePlan(TMP, ['side-a'], _p => false)
431
431
  expect(plan).toEqual([
432
432
  { side: 'side-a', sourceDir: TMP, found: true },
433
433
  ])
434
434
  })
435
435
 
436
- test('multi side, all subdirectories exist', () => {
436
+ it('multi side, all subdirectories exist', () => {
437
437
  const exists = (p: string) =>
438
438
  p === pathJoin(TMP, 'side-a') || p === pathJoin(TMP, 'side-b')
439
439
  const plan = buildArchiveSidePlan(TMP, ['side-a', 'side-b'], exists)
@@ -443,7 +443,7 @@ describe('buildArchiveSidePlan', () => {
443
443
  ])
444
444
  })
445
445
 
446
- test('multi side, one missing → found=false (caller handles warn+skip)', () => {
446
+ it('multi side, one missing → found=false (caller handles warn+skip)', () => {
447
447
  const exists = (p: string) => p === pathJoin(TMP, 'side-a')
448
448
  const plan = buildArchiveSidePlan(TMP, ['side-a', 'side-b'], exists)
449
449
  expect(plan).toEqual([
@@ -452,19 +452,19 @@ describe('buildArchiveSidePlan', () => {
452
452
  ])
453
453
  })
454
454
 
455
- test('"." side does NOT trigger fallback when missing (found=false)', () => {
455
+ it('"." side does NOT trigger fallback when missing (found=false)', () => {
456
456
  const plan = buildArchiveSidePlan(TMP, ['.'], _p => false)
457
457
  expect(plan).toEqual([
458
458
  { side: '.', sourceDir: TMP, found: false },
459
459
  ])
460
460
  })
461
461
 
462
- test('empty sides array → empty plan', () => {
462
+ it('empty sides array → empty plan', () => {
463
463
  const plan = buildArchiveSidePlan(TMP, [], _p => true)
464
464
  expect(plan).toEqual([])
465
465
  })
466
466
 
467
- test('three sides, middle missing', () => {
467
+ it('three sides, middle missing', () => {
468
468
  const exists = (p: string) =>
469
469
  p === pathJoin(TMP, 'side-a') || p === pathJoin(TMP, 'side-c')
470
470
  const plan = buildArchiveSidePlan(TMP, ['side-a', 'side-b', 'side-c'], exists)
@@ -509,7 +509,7 @@ path = "github.com/anthropics/skills/skills/pdf"
509
509
 
510
510
  describe('buildPreparePlan', () => {
511
511
 
512
- test('single skill deck → plan with 1 skill, hasSkills=true', () => {
512
+ it('single skill deck → plan with 1 skill, hasSkills=true', () => {
513
513
  const plan = buildPreparePlan({
514
514
  deckPath: '/tmp/test-deck.toml',
515
515
  deckContent: DECK_ONE_SKILL,
@@ -524,7 +524,7 @@ describe('buildPreparePlan', () => {
524
524
  expect(plan.deckPath).toBe('/tmp/test-deck.toml')
525
525
  })
526
526
 
527
- test('empty deck → skills=[], hasSkills=false', () => {
527
+ it('empty deck → skills=[], hasSkills=false', () => {
528
528
  const plan = buildPreparePlan({
529
529
  deckPath: '/tmp/empty.toml',
530
530
  deckContent: DECK_EMPTY,
@@ -535,7 +535,7 @@ describe('buildPreparePlan', () => {
535
535
  expect(plan.hasSkills).toBe(false)
536
536
  })
537
537
 
538
- test('two skills (innate + tool) → both parsed with correct sections', () => {
538
+ it('two skills (innate + tool) → both parsed with correct sections', () => {
539
539
  const plan = buildPreparePlan({
540
540
  deckPath: '/tmp/two.toml',
541
541
  deckContent: DECK_TWO_SKILLS,
@@ -548,7 +548,7 @@ describe('buildPreparePlan', () => {
548
548
  expect(plan.hasSkills).toBe(true)
549
549
  })
550
550
 
551
- test('AGENTS.md contains mandatory sections', () => {
551
+ it('AGENTS.md contains mandatory sections', () => {
552
552
  const plan = buildPreparePlan({
553
553
  deckPath: '/tmp/d.toml',
554
554
  deckContent: DECK_ONE_SKILL,
@@ -561,7 +561,7 @@ describe('buildPreparePlan', () => {
561
561
  expect(plan.agentsMd).toContain('skill-deck.toml')
562
562
  })
563
563
 
564
- test('invalid TOML → skills=[], hasSkills=false (no crash)', () => {
564
+ it('invalid TOML → skills=[], hasSkills=false (no crash)', () => {
565
565
  const plan = buildPreparePlan({
566
566
  deckPath: '/tmp/bad.toml',
567
567
  deckContent: 'this is not toml {{{',
@@ -572,7 +572,7 @@ describe('buildPreparePlan', () => {
572
572
  expect(plan.hasSkills).toBe(false)
573
573
  })
574
574
 
575
- test('deckContent is preserved in plan', () => {
575
+ it('deckContent is preserved in plan', () => {
576
576
  const plan = buildPreparePlan({
577
577
  deckPath: '/tmp/d.toml',
578
578
  deckContent: DECK_ONE_SKILL,