xdrs-core 0.45.0 → 0.46.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.
@@ -45,6 +45,10 @@ Each artifact type has its own writing standard:
45
45
 
46
46
  Slide presentations that support XDRS documents follow [_core-adr-policy-009](adrs/principles/009-presentation-standards.md). Slides use the Marp Markdown format, live in `.assets/` next to the document they support, and must maintain bidirectional links with the parent document.
47
47
 
48
+ ### Media and asset standards
49
+
50
+ [_core-adr-policy-020](adrs/principles/020-media-and-asset-standards.md) defines the canonical preference order for representing visual information across all XDRS document types: Markdown → ASCII → Mermaid → draw.io → SVG → PNG. It also defines where non-Markdown files must be stored (`.assets/` sibling folder) and prohibits policy content from living inside asset files. All document-type policies and write-* skills reference this policy instead of repeating these rules independently.
51
+
48
52
  ### Standard scope meta governance
49
53
 
50
54
  [_core-adr-policy-010](adrs/principles/010-scope-governance.md) defines the full scope governance model: how to define a custom scope type using `{scope-type}-scope-type` policies, how to define scope-local content standards using local meta-policies (`NNN-core.md` or `NNN-core-{qualifier}.md` in `principles/`), and how all governance mechanisms (`follows:`, scope-type standards, local meta-policies) apply with their precedence chain.
package/README.md CHANGED
@@ -12,6 +12,10 @@ Policies capture Architectural (ADR), Business (BDR), and Engineering (EDR) deci
12
12
 
13
13
  This project defines a standard for organizing XDRS that satisfies the following requirements.
14
14
 
15
+ ## Overview
16
+
17
+ ![XDRS Overview Diagram](.xdrs/_core/adrs/principles/articles/.assets/001-xdrs-overview.svg)
18
+
15
19
  ## XDRS elements
16
20
 
17
21
  A traditional Decision Record normally combines several concerns in the same document:
package/lib/lint.js CHANGED
@@ -670,6 +670,7 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, warnings, actualTypeInd
670
670
  // Determine if this scope is a core-type scope (but not _core which is exempt)
671
671
  // Used to enforce structured format on all policies in core-type scopes
672
672
  let isCoreTypeNonExempt = false;
673
+ let isCompiledScope = false;
673
674
  if (scopeName !== '_core') {
674
675
  const scopeIndexPath = path.join(scopePath, 'index.md');
675
676
  if (existsFile(scopeIndexPath)) {
@@ -678,10 +679,12 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, warnings, actualTypeInd
678
679
  const stMatch = idxContent.match(/^scope-type:\s*(.+)$/m);
679
680
  if (stMatch) {
680
681
  const stRaw = stMatch[1].trim();
682
+ // Handle both "[type1, type2]" YAML array format and "type1, type2" inline CSV format
681
683
  const stList = stRaw.startsWith('[') && stRaw.endsWith(']')
682
684
  ? stRaw.slice(1, -1).split(',').map((s) => s.trim())
683
- : [stRaw];
685
+ : stRaw.split(',').map((s) => s.trim());
684
686
  isCoreTypeNonExempt = stList.includes('core');
687
+ isCompiledScope = stList.includes('compiled');
685
688
  }
686
689
  } catch { /* ignore read errors; will be caught later */ }
687
690
  }
@@ -696,7 +699,7 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, warnings, actualTypeInd
696
699
  errors.push(`Unexpected directory under scope ${scopeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001]`);
697
700
  continue;
698
701
  }
699
- lintTypeDirectory(xdrsRoot, scopeName, entry.name, errors, warnings, actualTypeIndexes, externalScopes, isCoreTypeNonExempt);
702
+ lintTypeDirectory(xdrsRoot, scopeName, entry.name, errors, warnings, actualTypeIndexes, externalScopes, isCoreTypeNonExempt, isCompiledScope);
700
703
  const typeIndexPath = path.join(entryPath, 'index.md');
701
704
  if (existsFile(typeIndexPath)) {
702
705
  typeIndexesInScope.push(typeIndexPath);
@@ -718,10 +721,10 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, warnings, actualTypeInd
718
721
  lintScopeIndex(scopeIndexPath, xdrsRoot, scopeName, typeIndexesInScope, errors, externalScopes);
719
722
  lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot, knownScopeTypes, readOnlyScopes, extendsRelationships);
720
723
  }
721
- lintLocalMetaPolicies(xdrsRoot, scopeName, errors);
724
+ lintLocalMetaPolicies(xdrsRoot, scopeName, errors, isCompiledScope);
722
725
  }
723
726
 
724
- function lintLocalMetaPolicies(xdrsRoot, scopeName, errors) {
727
+ function lintLocalMetaPolicies(xdrsRoot, scopeName, errors, isCompiledScope = false) {
725
728
  const scopePath = path.join(xdrsRoot, scopeName);
726
729
  // Track qualifiers across all type folders: qualifier ('' for primary) -> filePath
727
730
  const seenQualifiers = new Map();
@@ -757,6 +760,21 @@ function lintLocalMetaPolicies(xdrsRoot, scopeName, errors) {
757
760
  errors.push(`Local meta-policy companion "${path.basename(companion.filePath)}" requires a primary "NNN-core.md" in the same type folder "${typeName}": ${toDisplayPath(companion.filePath)} [_core-adr-policy-010.10b-local-primary-required]`);
758
761
  }
759
762
  }
763
+ // For compiled scopes: primary meta-policy must include ## Sources and ## Selectors
764
+ if (isCompiledScope && primaryInFolder) {
765
+ let metaContent;
766
+ try { metaContent = fs.readFileSync(primaryInFolder.filePath, 'utf8'); } catch { /* will be caught elsewhere */ }
767
+ if (metaContent !== undefined) {
768
+ const metaLines = metaContent.split(/\r?\n/);
769
+ const metaIgnored = findIgnoredMarkdownLines(metaLines);
770
+ if (findHeadingLine(metaLines, metaIgnored, '## Sources') === -1) {
771
+ errors.push(`Compiled scope meta-policy must include a "## Sources" section: ${toDisplayPath(primaryInFolder.filePath)} [_core-adr-policy-019.07-compilation-meta-policy]`);
772
+ }
773
+ if (findHeadingLine(metaLines, metaIgnored, '## Selectors') === -1) {
774
+ errors.push(`Compiled scope meta-policy must include a "## Selectors" section: ${toDisplayPath(primaryInFolder.filePath)} [_core-adr-policy-019.07-compilation-meta-policy]`);
775
+ }
776
+ }
777
+ }
760
778
  // Check qualifier uniqueness across all type folders
761
779
  const allInFolder = primaryInFolder ? [primaryInFolder, ...companionsInFolder] : companionsInFolder;
762
780
  for (const { filePath, qualifier } of allInFolder) {
@@ -773,7 +791,7 @@ function lintLocalMetaPolicies(xdrsRoot, scopeName, errors) {
773
791
  }
774
792
  }
775
793
 
776
- function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actualTypeIndexes, externalScopes = new Set(), enforceStructured = false) {
794
+ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actualTypeIndexes, externalScopes = new Set(), enforceStructured = false, isCompiledScope = false) {
777
795
  const typePath = path.join(xdrsRoot, scopeName, typeName);
778
796
  const indexPath = path.join(typePath, 'index.md');
779
797
  const xdrsNumbers = new Map();
@@ -804,7 +822,7 @@ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actu
804
822
  warnings.push(`Invalid subject folder for ${typeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001] [exempt: freeze-reference]`);
805
823
  }
806
824
 
807
- artifacts.push(...lintSubjectDirectory(xdrsRoot, scopeName, typeName, entry.name, xdrsNumbers, errors, warnings, externalScopes, enforceStructured));
825
+ artifacts.push(...lintSubjectDirectory(xdrsRoot, scopeName, typeName, entry.name, xdrsNumbers, errors, warnings, externalScopes, enforceStructured, isCompiledScope));
808
826
  }
809
827
 
810
828
  if (existsFile(indexPath)) {
@@ -812,7 +830,7 @@ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actu
812
830
  }
813
831
  }
814
832
 
815
- function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNumbers, errors, warnings, externalScopes = new Set(), enforceStructured = false) {
833
+ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNumbers, errors, warnings, externalScopes = new Set(), enforceStructured = false, isCompiledScope = false) {
816
834
  const subjectPath = path.join(xdrsRoot, scopeName, typeName, subjectName);
817
835
  const artifacts = [];
818
836
  const entries = safeReadDir(subjectPath, errors, `read subject directory ${scopeName}/${typeName}/${subjectName}`);
@@ -851,7 +869,7 @@ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNu
851
869
  }
852
870
 
853
871
  artifacts.push(entryPath);
854
- lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, entryPath, xdrsNumbers, errors, warnings, externalScopes, enforceStructured);
872
+ lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, entryPath, xdrsNumbers, errors, warnings, externalScopes, enforceStructured, isCompiledScope);
855
873
  }
856
874
 
857
875
  const subjectAssetsDir = path.join(subjectPath, RESOURCE_DIR_NAME);
@@ -861,7 +879,7 @@ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNu
861
879
  return artifacts;
862
880
  }
863
881
 
864
- function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePath, xdrsNumbers, errors, warnings, externalScopes = new Set(), enforceStructured = false) {
882
+ function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePath, xdrsNumbers, errors, warnings, externalScopes = new Set(), enforceStructured = false, isCompiledScope = false) {
865
883
  const baseName = path.basename(filePath);
866
884
  const match = baseName.match(NUMBERED_FILE_RE);
867
885
  if (!match) {
@@ -903,9 +921,15 @@ function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePat
903
921
  lintXdrsElementFrontmatter(content, expectedName, filePath, errors, warnings, freezeReference);
904
922
  if (enforceStructured) {
905
923
  validateStructuredFormatContent(content, filePath, errors, '_core-adr-policy-010.08c-def-core-scope-policies-must-be-structured');
924
+ } else if (isCompiledScope) {
925
+ validateStructuredFormatContent(content, filePath, errors, '_core-adr-policy-019.04-structured-format-required');
906
926
  } else {
907
927
  lintStructuredRuleBlocks(content, filePath, errors);
908
928
  }
929
+ const isLocalMetaPolicy = subjectName === 'principles' && titleParts[0] === 'core';
930
+ if (isCompiledScope && !isLocalMetaPolicy) {
931
+ lintCompiledPolicySourceSection(content, filePath, errors);
932
+ }
909
933
  lintRequiredSections(content, filePath, XDRS_REQUIRED_SECTIONS, 'Policy', errors);
910
934
  lintNoEmojis(content, filePath, 'Policy', errors);
911
935
  lintWordCount(content, filePath, 'Policy', POLICY_MAX_WORDS, errors);
@@ -1708,6 +1732,32 @@ function validateStructuredFormatContent(content, filePath, errors, errorCode) {
1708
1732
  lintStructuredRuleBlocks(content, filePath, errors);
1709
1733
  }
1710
1734
 
1735
+ function lintCompiledPolicySourceSection(content, filePath, errors) {
1736
+ const lines = content.split(/\r?\n/);
1737
+ const ignoredLines = findIgnoredMarkdownLines(lines);
1738
+ const sourceStart = findHeadingLine(lines, ignoredLines, '## Source');
1739
+ if (sourceStart === -1) {
1740
+ errors.push(`Compiled policy must include a "## Source" section: ${toDisplayPath(filePath)} [_core-adr-policy-019.10-source-section]`);
1741
+ return;
1742
+ }
1743
+ // Find end of ## Source section (next ## heading or EOF)
1744
+ let sourceEnd = lines.length;
1745
+ for (let i = sourceStart + 1; i < lines.length; i++) {
1746
+ if (!ignoredLines[i] && /^## /.test(lines[i])) {
1747
+ sourceEnd = i;
1748
+ break;
1749
+ }
1750
+ }
1751
+ // Paths in ## Source MUST NOT be written as markdown links
1752
+ const MD_LINK_RE = /\[.+?\]\(.+?\)/;
1753
+ for (let i = sourceStart + 1; i < sourceEnd; i++) {
1754
+ if (!ignoredLines[i] && MD_LINK_RE.test(lines[i])) {
1755
+ errors.push(`Compiled policy "## Source" section must not use markdown links (use plain text paths): ${toDisplayPath(filePath)}:${i + 1} [_core-adr-policy-019.10-source-section]`);
1756
+ break;
1757
+ }
1758
+ }
1759
+ }
1760
+
1711
1761
  function findIgnoredMarkdownLines(lines) {
1712
1762
  const ignored = [];
1713
1763
  let inCodeFence = false;
package/lib/lint.test.js CHANGED
@@ -2456,6 +2456,509 @@ test('does not require structured format for _core scope policies (exempt)', ()
2456
2456
  expect(result.errors.join('\n')).not.toContain('08c-def-core-scope-policies-must-be-structured');
2457
2457
  });
2458
2458
 
2459
+ // ─── compiled scope-type checks ───────────────────────────────────────────────
2460
+
2461
+ function compiledScopeIndex() {
2462
+ return [
2463
+ '---',
2464
+ 'scope-type: compiled',
2465
+ 'name: my-compiled',
2466
+ 'description: Compiled scope for tests.',
2467
+ 'apply-to: All consumers of compiled content',
2468
+ 'valid-from: 2026-01-01',
2469
+ '---',
2470
+ '',
2471
+ '# my-compiled Scope Overview',
2472
+ '',
2473
+ 'Compiled scope for tests.',
2474
+ '',
2475
+ '[ADRs](adrs/index.md)',
2476
+ ''
2477
+ ].join('\n');
2478
+ }
2479
+
2480
+ function compiledAdrIndex(entries) {
2481
+ return [
2482
+ '# my-compiled ADR Index',
2483
+ '',
2484
+ 'Compiled ADRs for tests.',
2485
+ '',
2486
+ '## principles',
2487
+ '',
2488
+ ...entries,
2489
+ ''
2490
+ ].join('\n');
2491
+ }
2492
+
2493
+ function validCompiledPolicy() {
2494
+ return [
2495
+ '---',
2496
+ 'name: my-compiled-adr-policy-001-a01',
2497
+ 'description: Compiled test policy.',
2498
+ 'apply-to: All teams',
2499
+ 'valid-from: 2026-01-01',
2500
+ '---',
2501
+ '',
2502
+ '# my-compiled-adr-policy-001: A01',
2503
+ '',
2504
+ '## Context and Problem Statement',
2505
+ '',
2506
+ 'External source defines this requirement.',
2507
+ '',
2508
+ '## Decision Outcome',
2509
+ '',
2510
+ 'Follow the rule.',
2511
+ '',
2512
+ '### Details',
2513
+ '',
2514
+ '#### 01-rule',
2515
+ '',
2516
+ 'MUST comply.',
2517
+ '',
2518
+ '## Source',
2519
+ '',
2520
+ '- source-1/standards/a01.md',
2521
+ ''
2522
+ ].join('\n');
2523
+ }
2524
+
2525
+ function validCompiledMetaPolicy() {
2526
+ return [
2527
+ '---',
2528
+ 'name: my-compiled-adr-policy-001-core',
2529
+ 'description: Compilation meta-policy for adrs.',
2530
+ 'apply-to: Compilation process for this scope',
2531
+ 'valid-from: 2026-01-01',
2532
+ '---',
2533
+ '',
2534
+ '# my-compiled-adr-policy-001: core',
2535
+ '',
2536
+ '## Context and Problem Statement',
2537
+ '',
2538
+ 'Defines compilation configuration.',
2539
+ '',
2540
+ '## Decision Outcome',
2541
+ '',
2542
+ 'Use the configured sources.',
2543
+ '',
2544
+ '### Details',
2545
+ '',
2546
+ '#### 01-rule',
2547
+ '',
2548
+ 'MUST use configured sources.',
2549
+ '',
2550
+ '## Sources',
2551
+ '',
2552
+ '- [web] https://example.com/standards',
2553
+ '',
2554
+ '## Selectors',
2555
+ '',
2556
+ '- Include all requirement sections.',
2557
+ ''
2558
+ ].join('\n');
2559
+ }
2560
+
2561
+ test('reports policy in compiled scope missing structured format', () => {
2562
+ const workspaceRoot = createWorkspace('compiled-scope-policy-unstructured', {
2563
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2564
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2565
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2566
+ '- [001-a01](principles/001-a01.md) - A01'
2567
+ ]),
2568
+ '.xdrs/my-compiled/adrs/principles/001-a01.md': [
2569
+ '---',
2570
+ 'name: my-compiled-adr-policy-001-a01',
2571
+ 'description: Compiled test policy.',
2572
+ 'apply-to: All teams',
2573
+ 'valid-from: 2026-01-01',
2574
+ '---',
2575
+ '',
2576
+ '# my-compiled-adr-policy-001: A01',
2577
+ '',
2578
+ '## Context and Problem Statement',
2579
+ '',
2580
+ 'External source.',
2581
+ '',
2582
+ '## Decision Outcome',
2583
+ '',
2584
+ 'Follow rule.',
2585
+ '',
2586
+ '## Source',
2587
+ '',
2588
+ '- source-1/a01.md',
2589
+ ''
2590
+ ].join('\n'),
2591
+ });
2592
+
2593
+ const result = lintWorkspace(workspaceRoot);
2594
+
2595
+ expect(result.errors.join('\n')).toContain('"### Details"');
2596
+ expect(result.errors.join('\n')).toContain('019.04-structured-format-required');
2597
+ });
2598
+
2599
+ test('reports compiled policy missing ## Source section', () => {
2600
+ const workspaceRoot = createWorkspace('compiled-scope-missing-source', {
2601
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2602
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2603
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2604
+ '- [001-a01](principles/001-a01.md) - A01'
2605
+ ]),
2606
+ '.xdrs/my-compiled/adrs/principles/001-a01.md': [
2607
+ '---',
2608
+ 'name: my-compiled-adr-policy-001-a01',
2609
+ 'description: Compiled test policy.',
2610
+ 'apply-to: All teams',
2611
+ 'valid-from: 2026-01-01',
2612
+ '---',
2613
+ '',
2614
+ '# my-compiled-adr-policy-001: A01',
2615
+ '',
2616
+ '## Context and Problem Statement',
2617
+ '',
2618
+ 'External source.',
2619
+ '',
2620
+ '## Decision Outcome',
2621
+ '',
2622
+ 'Follow rule.',
2623
+ '',
2624
+ '### Details',
2625
+ '',
2626
+ '#### 01-rule',
2627
+ '',
2628
+ 'MUST comply.',
2629
+ ''
2630
+ ].join('\n'),
2631
+ });
2632
+
2633
+ const result = lintWorkspace(workspaceRoot);
2634
+
2635
+ expect(result.errors.join('\n')).toContain('Compiled policy must include a "## Source" section');
2636
+ expect(result.errors.join('\n')).toContain('019.10-source-section');
2637
+ });
2638
+
2639
+ test('reports compiled policy with markdown links in ## Source section', () => {
2640
+ const workspaceRoot = createWorkspace('compiled-scope-source-md-links', {
2641
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2642
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2643
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2644
+ '- [001-a01](principles/001-a01.md) - A01'
2645
+ ]),
2646
+ '.xdrs/my-compiled/adrs/principles/001-a01.md': [
2647
+ '---',
2648
+ 'name: my-compiled-adr-policy-001-a01',
2649
+ 'description: Compiled test policy.',
2650
+ 'apply-to: All teams',
2651
+ 'valid-from: 2026-01-01',
2652
+ '---',
2653
+ '',
2654
+ '# my-compiled-adr-policy-001: A01',
2655
+ '',
2656
+ '## Context and Problem Statement',
2657
+ '',
2658
+ 'External source.',
2659
+ '',
2660
+ '## Decision Outcome',
2661
+ '',
2662
+ 'Follow rule.',
2663
+ '',
2664
+ '### Details',
2665
+ '',
2666
+ '#### 01-rule',
2667
+ '',
2668
+ 'MUST comply.',
2669
+ '',
2670
+ '## Source',
2671
+ '',
2672
+ '- [source-1/a01.md](source-1/a01.md)',
2673
+ ''
2674
+ ].join('\n'),
2675
+ });
2676
+
2677
+ const result = lintWorkspace(workspaceRoot);
2678
+
2679
+ expect(result.errors.join('\n')).toContain('"## Source" section must not use markdown links');
2680
+ expect(result.errors.join('\n')).toContain('019.10-source-section');
2681
+ });
2682
+
2683
+ test('accepts valid compiled policy with ## Source section and no markdown links', () => {
2684
+ const workspaceRoot = createWorkspace('compiled-scope-valid', {
2685
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2686
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2687
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2688
+ '- [001-a01](principles/001-a01.md) - A01'
2689
+ ]),
2690
+ '.xdrs/my-compiled/adrs/principles/001-a01.md': validCompiledPolicy(),
2691
+ });
2692
+
2693
+ const result = lintWorkspace(workspaceRoot);
2694
+
2695
+ expect(result.errors.join('\n')).not.toContain('019');
2696
+ });
2697
+
2698
+ test('reports compiled scope meta-policy missing ## Sources section', () => {
2699
+ const workspaceRoot = createWorkspace('compiled-meta-missing-sources', {
2700
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2701
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2702
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2703
+ '- [001-core](principles/001-core.md) - meta-policy',
2704
+ '- [002-a01](principles/002-a01.md) - A01'
2705
+ ]),
2706
+ '.xdrs/my-compiled/adrs/principles/001-core.md': [
2707
+ '---',
2708
+ 'name: my-compiled-adr-policy-001-core',
2709
+ 'description: Compilation meta-policy.',
2710
+ 'apply-to: Compilation process',
2711
+ 'valid-from: 2026-01-01',
2712
+ '---',
2713
+ '',
2714
+ '# my-compiled-adr-policy-001: core',
2715
+ '',
2716
+ '## Context and Problem Statement',
2717
+ '',
2718
+ 'Defines compilation config.',
2719
+ '',
2720
+ '## Decision Outcome',
2721
+ '',
2722
+ 'Use configured sources.',
2723
+ '',
2724
+ '### Details',
2725
+ '',
2726
+ '#### 01-rule',
2727
+ '',
2728
+ 'MUST use configured sources.',
2729
+ '',
2730
+ '## Selectors',
2731
+ '',
2732
+ '- Include requirement sections.',
2733
+ ''
2734
+ ].join('\n'),
2735
+ '.xdrs/my-compiled/adrs/principles/002-a01.md': [
2736
+ '---',
2737
+ 'name: my-compiled-adr-policy-002-a01',
2738
+ 'description: Compiled test policy.',
2739
+ 'apply-to: All teams',
2740
+ 'valid-from: 2026-01-01',
2741
+ '---',
2742
+ '',
2743
+ '# my-compiled-adr-policy-002: A01',
2744
+ '',
2745
+ '## Context and Problem Statement',
2746
+ '',
2747
+ 'External source.',
2748
+ '',
2749
+ '## Decision Outcome',
2750
+ '',
2751
+ 'Follow rule.',
2752
+ '',
2753
+ '### Details',
2754
+ '',
2755
+ '#### 01-rule',
2756
+ '',
2757
+ 'MUST comply.',
2758
+ '',
2759
+ '## Source',
2760
+ '',
2761
+ '- source-1/a01.md',
2762
+ ''
2763
+ ].join('\n'),
2764
+ });
2765
+
2766
+ const result = lintWorkspace(workspaceRoot);
2767
+
2768
+ expect(result.errors.join('\n')).toContain('"## Sources" section');
2769
+ expect(result.errors.join('\n')).toContain('019.07-compilation-meta-policy');
2770
+ });
2771
+
2772
+ test('reports compiled scope meta-policy missing ## Selectors section', () => {
2773
+ const workspaceRoot = createWorkspace('compiled-meta-missing-selectors', {
2774
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2775
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2776
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2777
+ '- [001-core](principles/001-core.md) - meta-policy',
2778
+ '- [002-a01](principles/002-a01.md) - A01'
2779
+ ]),
2780
+ '.xdrs/my-compiled/adrs/principles/001-core.md': [
2781
+ '---',
2782
+ 'name: my-compiled-adr-policy-001-core',
2783
+ 'description: Compilation meta-policy.',
2784
+ 'apply-to: Compilation process',
2785
+ 'valid-from: 2026-01-01',
2786
+ '---',
2787
+ '',
2788
+ '# my-compiled-adr-policy-001: core',
2789
+ '',
2790
+ '## Context and Problem Statement',
2791
+ '',
2792
+ 'Defines compilation config.',
2793
+ '',
2794
+ '## Decision Outcome',
2795
+ '',
2796
+ 'Use configured sources.',
2797
+ '',
2798
+ '### Details',
2799
+ '',
2800
+ '#### 01-rule',
2801
+ '',
2802
+ 'MUST use configured sources.',
2803
+ '',
2804
+ '## Sources',
2805
+ '',
2806
+ '- [web] https://example.com/standards',
2807
+ ''
2808
+ ].join('\n'),
2809
+ '.xdrs/my-compiled/adrs/principles/002-a01.md': [
2810
+ '---',
2811
+ 'name: my-compiled-adr-policy-002-a01',
2812
+ 'description: Compiled test policy.',
2813
+ 'apply-to: All teams',
2814
+ 'valid-from: 2026-01-01',
2815
+ '---',
2816
+ '',
2817
+ '# my-compiled-adr-policy-002: A01',
2818
+ '',
2819
+ '## Context and Problem Statement',
2820
+ '',
2821
+ 'External source.',
2822
+ '',
2823
+ '## Decision Outcome',
2824
+ '',
2825
+ 'Follow rule.',
2826
+ '',
2827
+ '### Details',
2828
+ '',
2829
+ '#### 01-rule',
2830
+ '',
2831
+ 'MUST comply.',
2832
+ '',
2833
+ '## Source',
2834
+ '',
2835
+ '- source-1/a01.md',
2836
+ ''
2837
+ ].join('\n'),
2838
+ });
2839
+
2840
+ const result = lintWorkspace(workspaceRoot);
2841
+
2842
+ expect(result.errors.join('\n')).toContain('"## Selectors" section');
2843
+ expect(result.errors.join('\n')).toContain('019.07-compilation-meta-policy');
2844
+ });
2845
+
2846
+ test('accepts valid compiled scope meta-policy with required sections', () => {
2847
+ const workspaceRoot = createWorkspace('compiled-meta-valid', {
2848
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2849
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2850
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2851
+ '- [001-core](principles/001-core.md) - meta-policy',
2852
+ '- [002-a01](principles/002-a01.md) - A01'
2853
+ ]),
2854
+ '.xdrs/my-compiled/adrs/principles/001-core.md': validCompiledMetaPolicy(),
2855
+ '.xdrs/my-compiled/adrs/principles/002-a01.md': [
2856
+ '---',
2857
+ 'name: my-compiled-adr-policy-002-a01',
2858
+ 'description: Compiled test policy.',
2859
+ 'apply-to: All teams',
2860
+ 'valid-from: 2026-01-01',
2861
+ '---',
2862
+ '',
2863
+ '# my-compiled-adr-policy-002: A01',
2864
+ '',
2865
+ '## Context and Problem Statement',
2866
+ '',
2867
+ 'External source.',
2868
+ '',
2869
+ '## Decision Outcome',
2870
+ '',
2871
+ 'Follow rule.',
2872
+ '',
2873
+ '### Details',
2874
+ '',
2875
+ '#### 01-rule',
2876
+ '',
2877
+ 'MUST comply.',
2878
+ '',
2879
+ '## Source',
2880
+ '',
2881
+ '- source-1/a01.md',
2882
+ ''
2883
+ ].join('\n'),
2884
+ });
2885
+
2886
+ const result = lintWorkspace(workspaceRoot);
2887
+
2888
+ expect(result.errors.join('\n')).not.toContain('019');
2889
+ });
2890
+
2891
+ test('skips ## Source check for local meta-policy files in compiled scope', () => {
2892
+ const workspaceRoot = createWorkspace('compiled-meta-no-source-required', {
2893
+ '.xdrs/index.md': rootIndex(['[my-compiled](my-compiled/index.md)']),
2894
+ '.xdrs/my-compiled/index.md': compiledScopeIndex(),
2895
+ '.xdrs/my-compiled/adrs/index.md': compiledAdrIndex([
2896
+ '- [001-core](principles/001-core.md) - meta-policy'
2897
+ ]),
2898
+ '.xdrs/my-compiled/adrs/principles/001-core.md': validCompiledMetaPolicy(),
2899
+ });
2900
+
2901
+ const result = lintWorkspace(workspaceRoot);
2902
+
2903
+ expect(result.errors.join('\n')).not.toContain('Compiled policy must include a "## Source" section');
2904
+ });
2905
+
2906
+ test('enforces compiled scope checks when scope-type is comma-separated with compiled', () => {
2907
+ const workspaceRoot = createWorkspace('compiled-multi-type-checks', {
2908
+ '.xdrs/index.md': rootIndex(['[my-compiled-scope](my-compiled-scope/index.md)']),
2909
+ '.xdrs/my-compiled-scope/index.md': [
2910
+ '---',
2911
+ 'scope-type: compiled, standard',
2912
+ 'name: my-compiled-scope',
2913
+ 'description: Compiled standard scope for tests.',
2914
+ 'apply-to: All consumers',
2915
+ 'valid-from: 2026-01-01',
2916
+ '---',
2917
+ '',
2918
+ '# my-compiled-scope Scope Overview',
2919
+ '',
2920
+ 'Compiled standard.',
2921
+ '',
2922
+ '[ADRs](adrs/index.md)',
2923
+ ''
2924
+ ].join('\n'),
2925
+ '.xdrs/my-compiled-scope/adrs/index.md': [
2926
+ '# my-compiled-scope ADR Index', '', 'ADRs.', '', '## principles', '',
2927
+ '- [001-a01](principles/001-a01.md) - A01', ''
2928
+ ].join('\n'),
2929
+ '.xdrs/my-compiled-scope/adrs/principles/001-a01.md': [
2930
+ '---',
2931
+ 'name: my-compiled-scope-adr-policy-001-a01',
2932
+ 'description: Compiled test policy.',
2933
+ 'apply-to: All teams',
2934
+ 'valid-from: 2026-01-01',
2935
+ '---',
2936
+ '',
2937
+ '# my-compiled-scope-adr-policy-001: A01',
2938
+ '',
2939
+ '## Context and Problem Statement',
2940
+ '',
2941
+ 'External source.',
2942
+ '',
2943
+ '## Decision Outcome',
2944
+ '',
2945
+ 'Follow rule.',
2946
+ '',
2947
+ '## Source',
2948
+ '',
2949
+ '- source-1/a01.md',
2950
+ ''
2951
+ ].join('\n'),
2952
+ ...coreWithStructuredScopeTypes(['standard', 'compiled']),
2953
+ });
2954
+
2955
+ const result = lintWorkspace(workspaceRoot);
2956
+
2957
+ expect(result.errors.join('\n')).toContain('"### Details"');
2958
+ expect(result.errors.join('\n')).toContain('019.04-structured-format-required');
2959
+ });
2960
+
2961
+
2459
2962
  function localScopeIndex() {
2460
2963
  return [
2461
2964
  '---',