xdrs-core 0.49.0 → 0.50.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/.xdrs/_core/adrs/index.md +1 -0
- package/.xdrs/_core/adrs/principles/003-skill-standards.md +98 -12
- package/.xdrs/_core/adrs/principles/021-skill-bundling.md +85 -0
- package/.xdrs/_core/adrs/principles/skills/compile-scope/SKILL.md +52 -1
- package/.xdrs/_core/adrs/principles/skills/review/SKILL.md +48 -2
- package/.xdrs/_core/adrs/principles/skills/write-article/SKILL.md +46 -1
- package/.xdrs/_core/adrs/principles/skills/write-initiative/SKILL.md +44 -2
- package/.xdrs/_core/adrs/principles/skills/write-policy/SKILL.md +44 -1
- package/.xdrs/_core/adrs/principles/skills/write-presentation/SKILL.md +48 -1
- package/.xdrs/_core/adrs/principles/skills/write-research/SKILL.md +54 -9
- package/.xdrs/_core/adrs/principles/skills/write-skill/SKILL.md +99 -6
- package/.xdrs/_core/adrs/principles/skills/write-xdrs-doc/SKILL.md +48 -1
- package/lib/lint.js +495 -9
- package/lib/lint.test.js +669 -31
- package/package.json +1 -1
package/lib/lint.test.js
CHANGED
|
@@ -53,9 +53,9 @@ test('reports broken local document links in skill files', () => {
|
|
|
53
53
|
const workspaceRoot = createWorkspace('broken-skill-link', {
|
|
54
54
|
'.xdrs/index.md': rootIndex(),
|
|
55
55
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
56
|
-
'- [
|
|
56
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check local links'
|
|
57
57
|
]),
|
|
58
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
58
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('See [Missing](missing.md).'),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const result = lintWorkspace(workspaceRoot);
|
|
@@ -300,11 +300,11 @@ test('reports unknown frontmatter fields in SKILL.md files', () => {
|
|
|
300
300
|
const workspaceRoot = createWorkspace('skill-unknown-frontmatter', {
|
|
301
301
|
'.xdrs/index.md': rootIndex(),
|
|
302
302
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
303
|
-
'- [
|
|
303
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
304
304
|
]),
|
|
305
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
305
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
306
306
|
'---',
|
|
307
|
-
'name:
|
|
307
|
+
'name: check-links',
|
|
308
308
|
'description: Test skill document',
|
|
309
309
|
'unknown-field: some value',
|
|
310
310
|
'---',
|
|
@@ -329,17 +329,15 @@ test('accepts all known frontmatter fields in SKILL.md files', () => {
|
|
|
329
329
|
const workspaceRoot = createWorkspace('skill-known-frontmatter', {
|
|
330
330
|
'.xdrs/index.md': rootIndex(),
|
|
331
331
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
332
|
-
'- [
|
|
332
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
333
333
|
]),
|
|
334
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
334
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
335
335
|
'---',
|
|
336
|
-
'name:
|
|
336
|
+
'name: check-links',
|
|
337
337
|
'description: Test skill document',
|
|
338
338
|
'license: MIT',
|
|
339
339
|
'metadata:',
|
|
340
340
|
' version: "1.0"',
|
|
341
|
-
'compatibility: node >= 18',
|
|
342
|
-
'allowed-tools: read_file grep_search',
|
|
343
341
|
'---',
|
|
344
342
|
'',
|
|
345
343
|
'## Overview',
|
|
@@ -358,6 +356,581 @@ test('accepts all known frontmatter fields in SKILL.md files', () => {
|
|
|
358
356
|
expect(result.errors.join('\n')).not.toContain('unknown field');
|
|
359
357
|
});
|
|
360
358
|
|
|
359
|
+
test('reports missing ### Inputs subsection in SKILL.md ## Overview', () => {
|
|
360
|
+
const workspaceRoot = createWorkspace('skill-missing-inputs', {
|
|
361
|
+
'.xdrs/index.md': rootIndex(),
|
|
362
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
363
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
364
|
+
]),
|
|
365
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ omitInputs: true }),
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const result = lintWorkspace(workspaceRoot);
|
|
369
|
+
|
|
370
|
+
expect(result.errors.join('\n')).toContain('Skill "## Overview" is missing required subsection "### Inputs"');
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('reports missing ### Outputs subsection in SKILL.md ## Overview', () => {
|
|
374
|
+
const workspaceRoot = createWorkspace('skill-missing-outputs', {
|
|
375
|
+
'.xdrs/index.md': rootIndex(),
|
|
376
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
377
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
378
|
+
]),
|
|
379
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ omitOutputs: true }),
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const result = lintWorkspace(workspaceRoot);
|
|
383
|
+
|
|
384
|
+
expect(result.errors.join('\n')).toContain('Skill "## Overview" is missing required subsection "### Outputs"');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test('reports missing ### Halt Conditions subsection in SKILL.md ## Overview', () => {
|
|
388
|
+
const workspaceRoot = createWorkspace('skill-missing-halt-conditions', {
|
|
389
|
+
'.xdrs/index.md': rootIndex(),
|
|
390
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
391
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
392
|
+
]),
|
|
393
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ omitHaltConditions: true }),
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const result = lintWorkspace(workspaceRoot);
|
|
397
|
+
|
|
398
|
+
expect(result.errors.join('\n')).toContain('Skill "## Overview" is missing required subsection "### Halt Conditions"');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('reports SKILL.md ### Inputs missing #### Optional subsection', () => {
|
|
402
|
+
const workspaceRoot = createWorkspace('skill-inputs-missing-optional', {
|
|
403
|
+
'.xdrs/index.md': rootIndex(),
|
|
404
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
405
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
406
|
+
]),
|
|
407
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ omitInputsOptional: true }),
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const result = lintWorkspace(workspaceRoot);
|
|
411
|
+
|
|
412
|
+
expect(result.errors.join('\n')).toContain('Skill "### Inputs" section is missing "#### Optional"');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('reports SKILL.md ### Inputs subsections out of order', () => {
|
|
416
|
+
const workspaceRoot = createWorkspace('skill-inputs-out-of-order', {
|
|
417
|
+
'.xdrs/index.md': rootIndex(),
|
|
418
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
419
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
420
|
+
]),
|
|
421
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ swapInputsOrder: true }),
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const result = lintWorkspace(workspaceRoot);
|
|
425
|
+
|
|
426
|
+
expect(result.errors.join('\n')).toContain('Skill "### Inputs" subsections must appear in order (#### Required, #### Optional)');
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test('reports SKILL.md ### Outputs missing #### Changes subsection', () => {
|
|
430
|
+
const workspaceRoot = createWorkspace('skill-outputs-missing-changes', {
|
|
431
|
+
'.xdrs/index.md': rootIndex(),
|
|
432
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
433
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
434
|
+
]),
|
|
435
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ omitOutputsChanges: true }),
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const result = lintWorkspace(workspaceRoot);
|
|
439
|
+
|
|
440
|
+
expect(result.errors.join('\n')).toContain('Skill "### Outputs" section is missing "#### Changes"');
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('passes SKILL.md with valid None bullets in Inputs, Outputs, and Halt Conditions', () => {
|
|
444
|
+
const workspaceRoot = createWorkspace('skill-valid-none-bullets', {
|
|
445
|
+
'.xdrs/index.md': rootIndex(),
|
|
446
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
447
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
448
|
+
]),
|
|
449
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({
|
|
450
|
+
inputsRequired: '- None',
|
|
451
|
+
inputsOptional: '- None',
|
|
452
|
+
outputsContents: '- None',
|
|
453
|
+
outputsChanges: '- None',
|
|
454
|
+
haltConditions: '- None',
|
|
455
|
+
}),
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const result = lintWorkspace(workspaceRoot);
|
|
459
|
+
|
|
460
|
+
expect(result.errors.join('\n')).not.toContain('must be under 10 words or a single "None"');
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test('reports SKILL.md bullet over 10 words in #### Required', () => {
|
|
464
|
+
const workspaceRoot = createWorkspace('skill-required-bullet-too-long', {
|
|
465
|
+
'.xdrs/index.md': rootIndex(),
|
|
466
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
467
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
468
|
+
]),
|
|
469
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({
|
|
470
|
+
inputsRequired: '- A file path that is way too long to fit under the ten word limit',
|
|
471
|
+
}),
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const result = lintWorkspace(workspaceRoot);
|
|
475
|
+
|
|
476
|
+
expect(result.errors.join('\n')).toContain('Skill "### Inputs" -> "#### Required" bullet must be under 10 words');
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test('passes SKILL.md when both optional sections are omitted', () => {
|
|
480
|
+
const workspaceRoot = createWorkspace('skill-optional-sections-omitted', {
|
|
481
|
+
'.xdrs/index.md': rootIndex(),
|
|
482
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
483
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
484
|
+
]),
|
|
485
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument(),
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const result = lintWorkspace(workspaceRoot);
|
|
489
|
+
|
|
490
|
+
expect(result.errors.join('\n')).not.toContain('### Runtime Requirements');
|
|
491
|
+
expect(result.errors.join('\n')).not.toContain('### User Interaction');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test('reports empty ### Runtime Requirements section in SKILL.md', () => {
|
|
495
|
+
const workspaceRoot = createWorkspace('skill-empty-runtime-requirements', {
|
|
496
|
+
'.xdrs/index.md': rootIndex(),
|
|
497
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
498
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
499
|
+
]),
|
|
500
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ runtimeRequirements: '' }),
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const result = lintWorkspace(workspaceRoot);
|
|
504
|
+
|
|
505
|
+
expect(result.errors.join('\n')).toContain('Skill section "### Runtime Requirements" must not be empty');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test('reports empty ### User Interaction section in SKILL.md', () => {
|
|
509
|
+
const workspaceRoot = createWorkspace('skill-empty-user-interaction', {
|
|
510
|
+
'.xdrs/index.md': rootIndex(),
|
|
511
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
512
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
513
|
+
]),
|
|
514
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({ userInteraction: '' }),
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const result = lintWorkspace(workspaceRoot);
|
|
518
|
+
|
|
519
|
+
expect(result.errors.join('\n')).toContain('Skill section "### User Interaction" must not be empty');
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test('passes SKILL.md with a long free-form Runtime Requirements bullet', () => {
|
|
523
|
+
const workspaceRoot = createWorkspace('skill-runtime-requirements-free-form', {
|
|
524
|
+
'.xdrs/index.md': rootIndex(),
|
|
525
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
526
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
527
|
+
]),
|
|
528
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({
|
|
529
|
+
runtimeRequirements: '- Write access to a directory that is not gitignored and has enough free disk space.',
|
|
530
|
+
}),
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const result = lintWorkspace(workspaceRoot);
|
|
534
|
+
|
|
535
|
+
expect(result.errors.join('\n')).not.toContain('### Runtime Requirements');
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test('reports SKILL.md User Interaction bullet over 10 words', () => {
|
|
539
|
+
const workspaceRoot = createWorkspace('skill-user-interaction-bullet-too-long', {
|
|
540
|
+
'.xdrs/index.md': rootIndex(),
|
|
541
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
542
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
543
|
+
]),
|
|
544
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': fullSkillDocument({
|
|
545
|
+
userInteraction: '- A clarifying question that is much too long to fit under the ten word limit',
|
|
546
|
+
}),
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const result = lintWorkspace(workspaceRoot);
|
|
550
|
+
|
|
551
|
+
expect(result.errors.join('\n')).toContain('Skill "### User Interaction" bullet must be under 10 words');
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test('reports SKILL.md top-level sections out of template order', () => {
|
|
555
|
+
const workspaceRoot = createWorkspace('skill-sections-out-of-order', {
|
|
556
|
+
'.xdrs/index.md': rootIndex(),
|
|
557
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
558
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
559
|
+
]),
|
|
560
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
561
|
+
'---',
|
|
562
|
+
'name: check-links',
|
|
563
|
+
'description: Test skill document',
|
|
564
|
+
'---',
|
|
565
|
+
'',
|
|
566
|
+
'## Overview',
|
|
567
|
+
'',
|
|
568
|
+
'Test skill overview.',
|
|
569
|
+
'',
|
|
570
|
+
'### Inputs',
|
|
571
|
+
'',
|
|
572
|
+
'#### Required',
|
|
573
|
+
'- None',
|
|
574
|
+
'',
|
|
575
|
+
'#### Optional',
|
|
576
|
+
'- None',
|
|
577
|
+
'',
|
|
578
|
+
'### Outputs',
|
|
579
|
+
'',
|
|
580
|
+
'#### Contents',
|
|
581
|
+
'- None',
|
|
582
|
+
'',
|
|
583
|
+
'#### Changes',
|
|
584
|
+
'- None',
|
|
585
|
+
'',
|
|
586
|
+
'### Halt Conditions',
|
|
587
|
+
'- None',
|
|
588
|
+
'',
|
|
589
|
+
'## Anti-Patterns',
|
|
590
|
+
'',
|
|
591
|
+
'- **Mistake:** First mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
592
|
+
'- **Mistake:** Second mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
593
|
+
'- **Mistake:** Third mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
594
|
+
'',
|
|
595
|
+
'## Instructions',
|
|
596
|
+
'',
|
|
597
|
+
'Test instructions.',
|
|
598
|
+
''
|
|
599
|
+
].join('\n'),
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const result = lintWorkspace(workspaceRoot);
|
|
603
|
+
|
|
604
|
+
expect(result.errors.join('\n')).toContain('Skill sections must appear in template order');
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test('reports SKILL.md ## Overview subsections out of template order', () => {
|
|
608
|
+
const workspaceRoot = createWorkspace('skill-overview-subsections-out-of-order', {
|
|
609
|
+
'.xdrs/index.md': rootIndex(),
|
|
610
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
611
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
612
|
+
]),
|
|
613
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
614
|
+
'---',
|
|
615
|
+
'name: check-links',
|
|
616
|
+
'description: Test skill document',
|
|
617
|
+
'---',
|
|
618
|
+
'',
|
|
619
|
+
'## Overview',
|
|
620
|
+
'',
|
|
621
|
+
'Test skill overview.',
|
|
622
|
+
'',
|
|
623
|
+
'### Halt Conditions',
|
|
624
|
+
'- None',
|
|
625
|
+
'',
|
|
626
|
+
'### Inputs',
|
|
627
|
+
'',
|
|
628
|
+
'#### Required',
|
|
629
|
+
'- None',
|
|
630
|
+
'',
|
|
631
|
+
'#### Optional',
|
|
632
|
+
'- None',
|
|
633
|
+
'',
|
|
634
|
+
'### Outputs',
|
|
635
|
+
'',
|
|
636
|
+
'#### Contents',
|
|
637
|
+
'- None',
|
|
638
|
+
'',
|
|
639
|
+
'#### Changes',
|
|
640
|
+
'- None',
|
|
641
|
+
'',
|
|
642
|
+
'## Instructions',
|
|
643
|
+
'',
|
|
644
|
+
'Test instructions.',
|
|
645
|
+
'',
|
|
646
|
+
'## Anti-Patterns',
|
|
647
|
+
'',
|
|
648
|
+
'- **Mistake:** First mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
649
|
+
'- **Mistake:** Second mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
650
|
+
'- **Mistake:** Third mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
651
|
+
''
|
|
652
|
+
].join('\n'),
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const result = lintWorkspace(workspaceRoot);
|
|
656
|
+
|
|
657
|
+
expect(result.errors.join('\n')).toContain('Skill "## Overview" subsections must appear in order');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test('reports skill Anti-Patterns section with fewer than 3 entries', () => {
|
|
661
|
+
const workspaceRoot = createWorkspace('skill-anti-patterns-too-few', {
|
|
662
|
+
'.xdrs/index.md': rootIndex(),
|
|
663
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
664
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
665
|
+
]),
|
|
666
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
667
|
+
'---',
|
|
668
|
+
'name: check-links',
|
|
669
|
+
'description: Test skill document',
|
|
670
|
+
'---',
|
|
671
|
+
'',
|
|
672
|
+
'## Overview',
|
|
673
|
+
'',
|
|
674
|
+
'Test skill overview.',
|
|
675
|
+
'',
|
|
676
|
+
'## Instructions',
|
|
677
|
+
'',
|
|
678
|
+
'Test instructions.',
|
|
679
|
+
'',
|
|
680
|
+
'## Anti-Patterns',
|
|
681
|
+
'',
|
|
682
|
+
'- **Mistake:** First mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
683
|
+
'- **Mistake:** Second mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
684
|
+
''
|
|
685
|
+
].join('\n'),
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const result = lintWorkspace(workspaceRoot);
|
|
689
|
+
|
|
690
|
+
expect(result.errors.join('\n')).toContain('"## Anti-Patterns" section must include at least 3 entries (found 2)');
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test('passes when skill Anti-Patterns section has at least 3 entries', () => {
|
|
694
|
+
const workspaceRoot = createWorkspace('skill-anti-patterns-enough', {
|
|
695
|
+
'.xdrs/index.md': rootIndex(),
|
|
696
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
697
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
698
|
+
]),
|
|
699
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
700
|
+
'---',
|
|
701
|
+
'name: check-links',
|
|
702
|
+
'description: Test skill document',
|
|
703
|
+
'---',
|
|
704
|
+
'',
|
|
705
|
+
'## Overview',
|
|
706
|
+
'',
|
|
707
|
+
'Test skill overview.',
|
|
708
|
+
'',
|
|
709
|
+
'## Instructions',
|
|
710
|
+
'',
|
|
711
|
+
'Test instructions.',
|
|
712
|
+
'',
|
|
713
|
+
'## Anti-Patterns',
|
|
714
|
+
'',
|
|
715
|
+
'- **Mistake:** First mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
716
|
+
'- **Mistake:** Second mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
717
|
+
'- **Mistake:** Third mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
718
|
+
''
|
|
719
|
+
].join('\n'),
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const result = lintWorkspace(workspaceRoot);
|
|
723
|
+
|
|
724
|
+
expect(result.errors.join('\n')).not.toContain('"## Anti-Patterns" section must include at least');
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test('reports skill bundling Makefile missing required targets', () => {
|
|
728
|
+
const workspaceRoot = createWorkspace('skill-makefile-missing-targets', {
|
|
729
|
+
'.xdrs/index.md': rootIndex(),
|
|
730
|
+
'.gitignore': 'dist\n',
|
|
731
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
732
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
733
|
+
]),
|
|
734
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
735
|
+
'.xdrs/_local/adrs/principles/skills/check-links/Makefile': [
|
|
736
|
+
'build:',
|
|
737
|
+
'\techo build',
|
|
738
|
+
''
|
|
739
|
+
].join('\n'),
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
const result = lintWorkspace(workspaceRoot);
|
|
743
|
+
|
|
744
|
+
expect(result.errors.join('\n')).toContain('Skill bundling Makefile is missing required "test" target');
|
|
745
|
+
expect(result.errors.join('\n')).toContain('Skill bundling Makefile is missing required "clean" target');
|
|
746
|
+
expect(result.errors.join('\n')).not.toContain('Skill bundling Makefile is missing required "build" target');
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
test('passes when skill bundling Makefile has build, test, and clean targets', () => {
|
|
750
|
+
const workspaceRoot = createWorkspace('skill-makefile-complete', {
|
|
751
|
+
'.xdrs/index.md': rootIndex(),
|
|
752
|
+
'.gitignore': 'dist\n',
|
|
753
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
754
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
755
|
+
]),
|
|
756
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
757
|
+
'.xdrs/_local/adrs/principles/skills/check-links/Makefile': [
|
|
758
|
+
'build: clean',
|
|
759
|
+
'\techo build',
|
|
760
|
+
'test:',
|
|
761
|
+
'\techo test',
|
|
762
|
+
'clean:',
|
|
763
|
+
'\trm -rf dist',
|
|
764
|
+
''
|
|
765
|
+
].join('\n'),
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
const result = lintWorkspace(workspaceRoot);
|
|
769
|
+
|
|
770
|
+
expect(result.errors.join('\n')).not.toContain('Skill bundling Makefile is missing required');
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
test('reports skill bundling Makefile build target not depending on clean', () => {
|
|
774
|
+
const workspaceRoot = createWorkspace('skill-makefile-build-no-clean-dep', {
|
|
775
|
+
'.xdrs/index.md': rootIndex(),
|
|
776
|
+
'.gitignore': 'dist\n',
|
|
777
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
778
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
779
|
+
]),
|
|
780
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
781
|
+
'.xdrs/_local/adrs/principles/skills/check-links/Makefile': [
|
|
782
|
+
'build:',
|
|
783
|
+
'\trm -rf dist',
|
|
784
|
+
'\techo build',
|
|
785
|
+
'test:',
|
|
786
|
+
'\techo test',
|
|
787
|
+
'clean:',
|
|
788
|
+
'\trm -rf dist',
|
|
789
|
+
''
|
|
790
|
+
].join('\n'),
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
const result = lintWorkspace(workspaceRoot);
|
|
794
|
+
|
|
795
|
+
expect(result.errors.join('\n')).toContain('Skill bundling Makefile\'s "build" target must depend on "clean"');
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test('passes when skill bundling Makefile build target depends on clean', () => {
|
|
799
|
+
const workspaceRoot = createWorkspace('skill-makefile-build-clean-dep', {
|
|
800
|
+
'.xdrs/index.md': rootIndex(),
|
|
801
|
+
'.gitignore': 'dist\n',
|
|
802
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
803
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
804
|
+
]),
|
|
805
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
806
|
+
'.xdrs/_local/adrs/principles/skills/check-links/Makefile': [
|
|
807
|
+
'build: clean',
|
|
808
|
+
'\techo build',
|
|
809
|
+
'test:',
|
|
810
|
+
'\techo test',
|
|
811
|
+
'clean:',
|
|
812
|
+
'\trm -rf dist',
|
|
813
|
+
''
|
|
814
|
+
].join('\n'),
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
const result = lintWorkspace(workspaceRoot);
|
|
818
|
+
|
|
819
|
+
expect(result.errors.join('\n')).not.toContain('must depend on "clean"');
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
test('reports skill bundling Makefile dist output not covered by any .gitignore', () => {
|
|
823
|
+
const workspaceRoot = createWorkspace('skill-makefile-no-gitignore', {
|
|
824
|
+
'.xdrs/index.md': rootIndex(),
|
|
825
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
826
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
827
|
+
]),
|
|
828
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
829
|
+
'.xdrs/_local/adrs/principles/skills/check-links/Makefile': [
|
|
830
|
+
'build: clean',
|
|
831
|
+
'\techo build',
|
|
832
|
+
'test:',
|
|
833
|
+
'\techo test',
|
|
834
|
+
'clean:',
|
|
835
|
+
'\trm -rf dist',
|
|
836
|
+
''
|
|
837
|
+
].join('\n'),
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
const result = lintWorkspace(workspaceRoot);
|
|
841
|
+
|
|
842
|
+
expect(result.errors.join('\n')).toContain('is not covered by any .gitignore');
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test('passes when skill bundling Makefile dist output is covered by a local .gitignore', () => {
|
|
846
|
+
const workspaceRoot = createWorkspace('skill-makefile-local-gitignore', {
|
|
847
|
+
'.xdrs/index.md': rootIndex(),
|
|
848
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
849
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
850
|
+
]),
|
|
851
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
852
|
+
'.xdrs/_local/adrs/principles/skills/check-links/Makefile': [
|
|
853
|
+
'build: clean',
|
|
854
|
+
'\techo build',
|
|
855
|
+
'test:',
|
|
856
|
+
'\techo test',
|
|
857
|
+
'clean:',
|
|
858
|
+
'\trm -rf dist',
|
|
859
|
+
''
|
|
860
|
+
].join('\n'),
|
|
861
|
+
'.xdrs/_local/adrs/principles/skills/check-links/.gitignore': 'dist/\n',
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
const result = lintWorkspace(workspaceRoot);
|
|
865
|
+
|
|
866
|
+
expect(result.errors.join('\n')).not.toContain('is not covered by any .gitignore');
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test('reports scripts/ file missing a shebang or header comment', () => {
|
|
870
|
+
const workspaceRoot = createWorkspace('skill-script-missing-header', {
|
|
871
|
+
'.xdrs/index.md': rootIndex(),
|
|
872
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
873
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
874
|
+
]),
|
|
875
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
876
|
+
'.xdrs/_local/adrs/principles/skills/check-links/scripts/run.sh': [
|
|
877
|
+
'echo "no header here"',
|
|
878
|
+
''
|
|
879
|
+
].join('\n'),
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
const result = lintWorkspace(workspaceRoot);
|
|
883
|
+
|
|
884
|
+
expect(result.errors.join('\n')).toContain('Script must declare its runtime/interpreter at the top');
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test('passes when scripts/ file declares a shebang', () => {
|
|
888
|
+
const workspaceRoot = createWorkspace('skill-script-with-shebang', {
|
|
889
|
+
'.xdrs/index.md': rootIndex(),
|
|
890
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
891
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
892
|
+
]),
|
|
893
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Test instructions.'),
|
|
894
|
+
'.xdrs/_local/adrs/principles/skills/check-links/scripts/run.sh': [
|
|
895
|
+
'#!/bin/bash',
|
|
896
|
+
'echo "has a shebang"',
|
|
897
|
+
''
|
|
898
|
+
].join('\n'),
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
const result = lintWorkspace(workspaceRoot);
|
|
902
|
+
|
|
903
|
+
expect(result.errors.join('\n')).not.toContain('Script must declare its runtime/interpreter at the top');
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
test('accepts skill package names containing digits, including as a prefix', () => {
|
|
907
|
+
const workspaceRoot = createWorkspace('skill-name-with-digits', {
|
|
908
|
+
'.xdrs/index.md': rootIndex(),
|
|
909
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
910
|
+
'- [2fa-setup](principles/skills/2fa-setup/SKILL.md) - 2FA setup'
|
|
911
|
+
]),
|
|
912
|
+
'.xdrs/_local/adrs/principles/skills/2fa-setup/SKILL.md': [
|
|
913
|
+
'---',
|
|
914
|
+
'name: 2fa-setup',
|
|
915
|
+
'description: Test skill document',
|
|
916
|
+
'---',
|
|
917
|
+
'',
|
|
918
|
+
'## Overview',
|
|
919
|
+
'',
|
|
920
|
+
'Test skill overview.',
|
|
921
|
+
'',
|
|
922
|
+
'## Instructions',
|
|
923
|
+
'',
|
|
924
|
+
'Test instructions.',
|
|
925
|
+
''
|
|
926
|
+
].join('\n'),
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
const result = lintWorkspace(workspaceRoot);
|
|
930
|
+
|
|
931
|
+
expect(result.errors.join('\n')).not.toContain('Invalid skill package name');
|
|
932
|
+
});
|
|
933
|
+
|
|
361
934
|
test('derives expected frontmatter name from the markdown heading title', () => {
|
|
362
935
|
const workspaceRoot = createWorkspace('heading-name-match', {
|
|
363
936
|
'.xdrs/index.md': rootIndex(),
|
|
@@ -2063,10 +2636,10 @@ test('reports unexpected directories inside a skill package', () => {
|
|
|
2063
2636
|
'.xdrs/index.md': rootIndex(),
|
|
2064
2637
|
'.xdrs/_local/index.md': localScopeIndex(),
|
|
2065
2638
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
2066
|
-
'- [
|
|
2639
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check local links'
|
|
2067
2640
|
]),
|
|
2068
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
2069
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
2641
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Skill body.'),
|
|
2642
|
+
'.xdrs/_local/adrs/principles/skills/check-links/extras/note.txt': 'unexpected',
|
|
2070
2643
|
});
|
|
2071
2644
|
|
|
2072
2645
|
const result = lintWorkspace(workspaceRoot);
|
|
@@ -3181,7 +3754,7 @@ function xdrDocument(body) {
|
|
|
3181
3754
|
function skillDocument(body) {
|
|
3182
3755
|
return [
|
|
3183
3756
|
'---',
|
|
3184
|
-
'name:
|
|
3757
|
+
'name: check-links',
|
|
3185
3758
|
'description: Test skill document',
|
|
3186
3759
|
'---',
|
|
3187
3760
|
'',
|
|
@@ -3198,6 +3771,71 @@ function skillDocument(body) {
|
|
|
3198
3771
|
].join('\n');
|
|
3199
3772
|
}
|
|
3200
3773
|
|
|
3774
|
+
function fullSkillDocument(overrides = {}) {
|
|
3775
|
+
const {
|
|
3776
|
+
omitInputs = false,
|
|
3777
|
+
omitInputsOptional = false,
|
|
3778
|
+
swapInputsOrder = false,
|
|
3779
|
+
inputsRequired = '- A file path to scan',
|
|
3780
|
+
inputsOptional = '- None',
|
|
3781
|
+
runtimeRequirements = null,
|
|
3782
|
+
omitOutputs = false,
|
|
3783
|
+
omitOutputsChanges = false,
|
|
3784
|
+
outputsContents = '- A list of broken links',
|
|
3785
|
+
outputsChanges = '- None',
|
|
3786
|
+
omitHaltConditions = false,
|
|
3787
|
+
haltConditions = '- None',
|
|
3788
|
+
userInteraction = null,
|
|
3789
|
+
} = overrides;
|
|
3790
|
+
|
|
3791
|
+
const lines = ['---', 'name: check-links', 'description: Test skill document', '---', '', '## Overview', '', 'Test skill overview.', ''];
|
|
3792
|
+
|
|
3793
|
+
if (!omitInputs) {
|
|
3794
|
+
lines.push('### Inputs', '');
|
|
3795
|
+
if (omitInputsOptional) {
|
|
3796
|
+
lines.push('#### Required', inputsRequired, '');
|
|
3797
|
+
} else if (swapInputsOrder) {
|
|
3798
|
+
lines.push('#### Optional', inputsOptional, '', '#### Required', inputsRequired, '');
|
|
3799
|
+
} else {
|
|
3800
|
+
lines.push('#### Required', inputsRequired, '', '#### Optional', inputsOptional, '');
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
if (!omitOutputs) {
|
|
3805
|
+
lines.push('### Outputs', '');
|
|
3806
|
+
if (omitOutputsChanges) {
|
|
3807
|
+
lines.push('#### Contents', outputsContents, '');
|
|
3808
|
+
} else {
|
|
3809
|
+
lines.push('#### Contents', outputsContents, '', '#### Changes', outputsChanges, '');
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
if (!omitHaltConditions) {
|
|
3814
|
+
lines.push('### Halt Conditions', '', haltConditions, '');
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
if (userInteraction !== null) {
|
|
3818
|
+
lines.push('### User Interaction', '', userInteraction, '');
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
if (runtimeRequirements !== null) {
|
|
3822
|
+
lines.push('### Runtime Requirements', '', runtimeRequirements, '');
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
lines.push('## Instructions', '', 'Test instructions.', '');
|
|
3826
|
+
|
|
3827
|
+
lines.push(
|
|
3828
|
+
'## Anti-Patterns',
|
|
3829
|
+
'',
|
|
3830
|
+
'- **Mistake:** First mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
3831
|
+
'- **Mistake:** Second mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
3832
|
+
'- **Mistake:** Third mistake. **Why it happens:** Reason. **Instead:** Fix.',
|
|
3833
|
+
''
|
|
3834
|
+
);
|
|
3835
|
+
|
|
3836
|
+
return lines.join('\n');
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3201
3839
|
test('accepts a custom root folder name as the XDRS root when it contains index.md', () => {
|
|
3202
3840
|
const doc = [
|
|
3203
3841
|
'---',
|
|
@@ -3302,9 +3940,9 @@ test('reports emoji in SKILL.md', () => {
|
|
|
3302
3940
|
const workspaceRoot = createWorkspace('skill-emoji', {
|
|
3303
3941
|
'.xdrs/index.md': rootIndex(),
|
|
3304
3942
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
3305
|
-
'- [
|
|
3943
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
3306
3944
|
]),
|
|
3307
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
3945
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Step 1: do something \u2705'),
|
|
3308
3946
|
});
|
|
3309
3947
|
|
|
3310
3948
|
const result = lintWorkspace(workspaceRoot);
|
|
@@ -3585,13 +4223,13 @@ test('reports XDRS element frontmatter description exceeding 1024 characters', (
|
|
|
3585
4223
|
});
|
|
3586
4224
|
|
|
3587
4225
|
test('reports SKILL.md frontmatter name exceeding 64 characters', () => {
|
|
3588
|
-
const longName = '
|
|
4226
|
+
const longName = 'check-links-' + 'a'.repeat(60);
|
|
3589
4227
|
const workspaceRoot = createWorkspace('skill-name-too-long', {
|
|
3590
4228
|
'.xdrs/index.md': rootIndex(),
|
|
3591
4229
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
3592
|
-
'- [
|
|
4230
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
3593
4231
|
]),
|
|
3594
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
4232
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
3595
4233
|
'---',
|
|
3596
4234
|
`name: ${longName}`,
|
|
3597
4235
|
'description: Test skill document',
|
|
@@ -3617,11 +4255,11 @@ test('reports SKILL.md frontmatter description exceeding 1024 characters', () =>
|
|
|
3617
4255
|
const workspaceRoot = createWorkspace('skill-description-too-long', {
|
|
3618
4256
|
'.xdrs/index.md': rootIndex(),
|
|
3619
4257
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
3620
|
-
'- [
|
|
4258
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
3621
4259
|
]),
|
|
3622
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
4260
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': [
|
|
3623
4261
|
'---',
|
|
3624
|
-
'name:
|
|
4262
|
+
'name: check-links',
|
|
3625
4263
|
`description: ${'x'.repeat(1025)}`,
|
|
3626
4264
|
'---',
|
|
3627
4265
|
'',
|
|
@@ -3688,19 +4326,19 @@ test('reports research exceeding 5000 word limit', () => {
|
|
|
3688
4326
|
expect(result.errors.join('\n')).toContain('Research exceeds maximum word count of 5000');
|
|
3689
4327
|
});
|
|
3690
4328
|
|
|
3691
|
-
test('reports SKILL.md exceeding
|
|
3692
|
-
const longBody = ('word '.repeat(
|
|
4329
|
+
test('reports SKILL.md exceeding 7000 word limit', () => {
|
|
4330
|
+
const longBody = ('word '.repeat(7001)).trimEnd();
|
|
3693
4331
|
const workspaceRoot = createWorkspace('skill-too-many-words', {
|
|
3694
4332
|
'.xdrs/index.md': rootIndex(),
|
|
3695
4333
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
3696
|
-
'- [
|
|
4334
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
3697
4335
|
]),
|
|
3698
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
4336
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument(longBody),
|
|
3699
4337
|
});
|
|
3700
4338
|
|
|
3701
4339
|
const result = lintWorkspace(workspaceRoot);
|
|
3702
4340
|
|
|
3703
|
-
expect(result.errors.join('\n')).toContain('Skill exceeds maximum word count of
|
|
4341
|
+
expect(result.errors.join('\n')).toContain('Skill exceeds maximum word count of 7000');
|
|
3704
4342
|
});
|
|
3705
4343
|
|
|
3706
4344
|
// ─── Files outside .assets in skill package root ────────────────────────────
|
|
@@ -3709,11 +4347,11 @@ test('passes when extra files exist at skill package root', () => {
|
|
|
3709
4347
|
const workspaceRoot = createWorkspace('extra-skill-file', {
|
|
3710
4348
|
'.xdrs/index.md': rootIndex(),
|
|
3711
4349
|
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
3712
|
-
'- [
|
|
4350
|
+
'- [check-links](principles/skills/check-links/SKILL.md) - Check links'
|
|
3713
4351
|
]),
|
|
3714
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
3715
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
3716
|
-
'.xdrs/_local/adrs/principles/skills/
|
|
4352
|
+
'.xdrs/_local/adrs/principles/skills/check-links/SKILL.md': skillDocument('Body.'),
|
|
4353
|
+
'.xdrs/_local/adrs/principles/skills/check-links/extra.md': 'Allowed extra file',
|
|
4354
|
+
'.xdrs/_local/adrs/principles/skills/check-links/schema.json': '{"type":"object"}',
|
|
3717
4355
|
});
|
|
3718
4356
|
|
|
3719
4357
|
const result = lintWorkspace(workspaceRoot);
|