xdrs-core 0.44.0 → 0.46.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.
Files changed (31) hide show
  1. package/.xdrs/_core/adrs/index.md +3 -0
  2. package/.xdrs/_core/adrs/principles/001-xdrs-standards.md +7 -4
  3. package/.xdrs/_core/adrs/principles/002-policy-standards.md +1 -1
  4. package/.xdrs/_core/adrs/principles/003-skill-standards.md +1 -2
  5. package/.xdrs/_core/adrs/principles/004-article-standards.md +1 -2
  6. package/.xdrs/_core/adrs/principles/006-research-standards.md +6 -5
  7. package/.xdrs/_core/adrs/principles/007-plan-standards.md +1 -1
  8. package/.xdrs/_core/adrs/principles/009-presentation-standards.md +2 -1
  9. package/.xdrs/_core/adrs/principles/010-scope-governance.md +64 -16
  10. package/.xdrs/_core/adrs/principles/019-compiled-scope-type.md +117 -0
  11. package/.xdrs/_core/adrs/principles/020-media-and-asset-standards.md +48 -0
  12. package/.xdrs/_core/adrs/principles/articles/.assets/001-xdrs-overview.svg +258 -0
  13. package/.xdrs/_core/adrs/principles/articles/001-xdrs-overview.md +2 -0
  14. package/.xdrs/_core/adrs/principles/skills/.assets/lint-verification.md +12 -0
  15. package/.xdrs/_core/adrs/principles/skills/.assets/meta-policy-compliance.md +12 -0
  16. package/.xdrs/_core/adrs/principles/skills/.assets/prerequisites-gate.md +19 -0
  17. package/.xdrs/_core/adrs/principles/skills/.assets/scope-placement.md +47 -0
  18. package/.xdrs/_core/adrs/principles/skills/.assets/scope-selection.md +25 -0
  19. package/.xdrs/_core/adrs/principles/skills/001-review/SKILL.md +7 -1
  20. package/.xdrs/_core/adrs/principles/skills/002-write-policy/SKILL.md +8 -24
  21. package/.xdrs/_core/adrs/principles/skills/003-write-skill/SKILL.md +8 -19
  22. package/.xdrs/_core/adrs/principles/skills/004-write-article/SKILL.md +6 -16
  23. package/.xdrs/_core/adrs/principles/skills/005-write-research/SKILL.md +7 -18
  24. package/.xdrs/_core/adrs/principles/skills/006-write-plan/SKILL.md +7 -18
  25. package/.xdrs/_core/adrs/principles/skills/007-write-presentation/SKILL.md +4 -15
  26. package/.xdrs/_core/adrs/principles/skills/008-write-xdrs-doc/SKILL.md +1 -8
  27. package/.xdrs/_core/adrs/principles/skills/009-compile-scope/SKILL.md +121 -0
  28. package/README.md +15 -2
  29. package/lib/lint.js +160 -47
  30. package/lib/lint.test.js +659 -41
  31. package/package.json +1 -1
package/lib/lint.test.js CHANGED
@@ -1733,83 +1733,198 @@ test('reports scope index description exceeding 40 words', () => {
1733
1733
  expect(result.errors.join('\n')).toContain('Scope index frontmatter description must be 40 words or fewer');
1734
1734
  });
1735
1735
 
1736
- test('accepts related-scopes as a list of scope names in scope index frontmatter', () => {
1737
- const workspaceRoot = createWorkspace('valid-scope-related-scopes-list', {
1738
- '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[sibling-team](sibling-team/index.md)', '[parent-domain](parent-domain/index.md)']),
1739
- '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes:\n - sibling-team\n - parent-domain\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1736
+ test('reports related-scopes as unknown frontmatter field in scope index (field removed)', () => {
1737
+ const workspaceRoot = createWorkspace('scope-related-scopes-unknown-field', {
1738
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)']),
1739
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes: sibling-team\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1740
1740
  '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1741
1741
  '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1742
- '.xdrs/sibling-team/index.md': '---\nscope-type: standard\nname: sibling-team\ndescription: Sibling team.\napply-to: Sibling team\nvalid-from: 2026-01-01\n---\n\n# sibling-team Scope Overview\n\n[ADRs](adrs/index.md)\n',
1743
- '.xdrs/sibling-team/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1744
- '.xdrs/sibling-team/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1745
- '.xdrs/parent-domain/index.md': '---\nscope-type: standard\nname: parent-domain\ndescription: Parent domain.\napply-to: Parent domain\nvalid-from: 2026-01-01\n---\n\n# parent-domain Scope Overview\n\n[ADRs](adrs/index.md)\n',
1746
- '.xdrs/parent-domain/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1747
- '.xdrs/parent-domain/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1748
1742
  });
1749
1743
 
1750
1744
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1751
1745
 
1752
- expect(result.errors.join('\n')).not.toContain('Scope index frontmatter related-scopes');
1746
+ expect(result.errors.join('\n')).toContain('unknown field "related-scopes"');
1753
1747
  });
1754
1748
 
1755
- test('accepts related-scopes as a single scope name string in scope index frontmatter', () => {
1756
- const workspaceRoot = createWorkspace('valid-scope-related-scopes-string', {
1757
- '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[sibling-team](sibling-team/index.md)']),
1758
- '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes: sibling-team\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1749
+ test('accepts extends: single scope name in scope index frontmatter', () => {
1750
+ const workspaceRoot = createWorkspace('extends-single-scope', {
1751
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[shared](shared/index.md)']),
1752
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: shared\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1759
1753
  '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1760
1754
  '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1761
- '.xdrs/sibling-team/index.md': '---\nscope-type: standard\nname: sibling-team\ndescription: Sibling team.\napply-to: Sibling team\nvalid-from: 2026-01-01\n---\n\n# sibling-team Scope Overview\n\n[ADRs](adrs/index.md)\n',
1762
- '.xdrs/sibling-team/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1763
- '.xdrs/sibling-team/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1755
+ '.xdrs/shared/index.md': '---\nscope-type: standard\nname: shared\ndescription: Shared policies.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared Scope Overview\n\n[ADRs](adrs/index.md)\n',
1756
+ '.xdrs/shared/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Shared decision']),
1757
+ '.xdrs/shared/adrs/principles/001-team.md': teamXdrDocument('Shared decision.'),
1758
+ });
1759
+
1760
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1761
+
1762
+ expect(result.errors.filter((e) => /\[_core-adr-policy-010\.\d+-extends-/.test(e)).join('\n')).toBe('');
1763
+ });
1764
+
1765
+ test('accepts extends: multiple scopes comma-separated in scope index frontmatter', () => {
1766
+ const workspaceRoot = createWorkspace('extends-multiple-scopes', {
1767
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[shared-a](shared-a/index.md)', '[shared-b](shared-b/index.md)']),
1768
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: shared-a, shared-b\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1769
+ '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1770
+ '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1771
+ '.xdrs/shared-a/index.md': '---\nscope-type: standard\nname: shared-a\ndescription: Shared A.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared-a Scope Overview\n\n[ADRs](adrs/index.md)\n',
1772
+ '.xdrs/shared-a/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Shared A decision']),
1773
+ '.xdrs/shared-a/adrs/principles/001-team.md': teamXdrDocument('Shared A decision.'),
1774
+ '.xdrs/shared-b/index.md': '---\nscope-type: standard\nname: shared-b\ndescription: Shared B.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared-b Scope Overview\n\n[ADRs](adrs/index.md)\n',
1775
+ '.xdrs/shared-b/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Shared B decision']),
1776
+ '.xdrs/shared-b/adrs/principles/001-team.md': teamXdrDocument('Shared B decision.'),
1764
1777
  });
1765
1778
 
1766
1779
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1767
1780
 
1768
- expect(result.errors.join('\n')).not.toContain('Scope index frontmatter related-scopes');
1781
+ expect(result.errors.filter((e) => /\[_core-adr-policy-010\.\d+-extends-/.test(e)).join('\n')).toBe('');
1769
1782
  });
1770
1783
 
1771
- test('reports invalid related-scopes value in scope index frontmatter', () => {
1772
- const workspaceRoot = createWorkspace('invalid-scope-related-scopes', {
1784
+ test('accepts extends: YAML list format in scope index frontmatter', () => {
1785
+ const workspaceRoot = createWorkspace('extends-yaml-list', {
1786
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[shared-a](shared-a/index.md)', '[shared-b](shared-b/index.md)']),
1787
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends:\n - shared-a\n - shared-b\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1788
+ '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1789
+ '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1790
+ '.xdrs/shared-a/index.md': '---\nscope-type: standard\nname: shared-a\ndescription: Shared A.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared-a Scope Overview\n\n[ADRs](adrs/index.md)\n',
1791
+ '.xdrs/shared-a/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Shared A decision']),
1792
+ '.xdrs/shared-a/adrs/principles/001-team.md': teamXdrDocument('Shared A decision.'),
1793
+ '.xdrs/shared-b/index.md': '---\nscope-type: standard\nname: shared-b\ndescription: Shared B.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared-b Scope Overview\n\n[ADRs](adrs/index.md)\n',
1794
+ '.xdrs/shared-b/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Shared B decision']),
1795
+ '.xdrs/shared-b/adrs/principles/001-team.md': teamXdrDocument('Shared B decision.'),
1796
+ });
1797
+
1798
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1799
+
1800
+ expect(result.errors.filter((e) => /\[_core-adr-policy-010\.\d+-extends-/.test(e)).join('\n')).toBe('');
1801
+ });
1802
+
1803
+ test('reports extends: referencing a missing scope', () => {
1804
+ const workspaceRoot = createWorkspace('extends-missing-scope', {
1773
1805
  '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)']),
1774
- '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes: 123\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1806
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: nonexistent-scope\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1775
1807
  '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1776
1808
  '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1777
1809
  });
1778
1810
 
1779
1811
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1780
1812
 
1781
- expect(result.errors.join('\n')).toContain('Scope index frontmatter related-scopes must be a scope name or list of scope names');
1813
+ expect(result.errors.join('\n')).toContain('[_core-adr-policy-010.32-extends-mandatory-presence]');
1814
+ expect(result.errors.join('\n')).toContain('nonexistent-scope');
1782
1815
  });
1783
1816
 
1784
- test('reports related-scopes scope that does not exist in workspace', () => {
1785
- const workspaceRoot = createWorkspace('related-scopes-missing-scope', {
1817
+ test('reports extends: self-reference in scope index frontmatter', () => {
1818
+ const workspaceRoot = createWorkspace('extends-self-reference', {
1786
1819
  '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)']),
1787
- '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes: nonexistent-scope\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1820
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: myteam\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1788
1821
  '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1789
1822
  '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1790
1823
  });
1791
1824
 
1792
1825
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1793
1826
 
1794
- expect(result.errors.join('\n')).toContain('related-scopes references scope "nonexistent-scope" which does not exist in the workspace');
1827
+ expect(result.errors.join('\n')).toContain('[_core-adr-policy-010.30-extends-no-self]');
1795
1828
  });
1796
1829
 
1797
- test('accepts related-scopes scope that exists in workspace', () => {
1798
- const workspaceRoot = createWorkspace('related-scopes-existing-scope', {
1799
- '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[sibling-team](sibling-team/index.md)']),
1800
- '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes: sibling-team\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1830
+ test('reports extends: referencing reserved scope _local', () => {
1831
+ const workspaceRoot = createWorkspace('extends-reserved-local', {
1832
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)']),
1833
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: _local\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1801
1834
  '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1802
1835
  '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1803
- '.xdrs/sibling-team/index.md': '---\nscope-type: standard\nname: sibling-team\ndescription: Sibling team.\napply-to: Sibling team\nvalid-from: 2026-01-01\n---\n\n# sibling-team Scope Overview\n\n[ADRs](adrs/index.md)\n',
1804
- '.xdrs/sibling-team/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1805
- '.xdrs/sibling-team/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1806
1836
  });
1807
1837
 
1808
1838
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1809
1839
 
1810
- expect(result.errors.join('\n')).not.toContain('related-scopes references scope "sibling-team"');
1840
+ expect(result.errors.join('\n')).toContain('[_core-adr-policy-010.29-extends-reserved-scopes]');
1841
+ });
1842
+
1843
+ test('reports extends: referencing reserved scope _core', () => {
1844
+ const workspaceRoot = createWorkspace('extends-reserved-core', {
1845
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)']),
1846
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: _core\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1847
+ '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1848
+ '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1849
+ });
1850
+
1851
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1852
+
1853
+ expect(result.errors.join('\n')).toContain('[_core-adr-policy-010.29-extends-reserved-scopes]');
1854
+ });
1855
+
1856
+ test('reports extends: cycle between two scopes', () => {
1857
+ const workspaceRoot = createWorkspace('extends-cycle', {
1858
+ '.xdrs/index.md': rootIndex(['[scope-a](scope-a/index.md)', '[scope-b](scope-b/index.md)']),
1859
+ '.xdrs/scope-a/index.md': '---\nscope-type: standard\nname: scope-a\ndescription: Scope A.\napply-to: Scope A\nvalid-from: 2026-01-01\nextends: scope-b\n---\n\n# scope-a Scope Overview\n\n[ADRs](adrs/index.md)\n',
1860
+ '.xdrs/scope-a/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - A decision']),
1861
+ '.xdrs/scope-a/adrs/principles/001-team.md': teamXdrDocument('A decision.'),
1862
+ '.xdrs/scope-b/index.md': '---\nscope-type: standard\nname: scope-b\ndescription: Scope B.\napply-to: Scope B\nvalid-from: 2026-01-01\nextends: scope-a\n---\n\n# scope-b Scope Overview\n\n[ADRs](adrs/index.md)\n',
1863
+ '.xdrs/scope-b/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - B decision']),
1864
+ '.xdrs/scope-b/adrs/principles/001-team.md': teamXdrDocument('B decision.'),
1865
+ });
1866
+
1867
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1868
+
1869
+ expect(result.errors.join('\n')).toContain('[_core-adr-policy-010.31-extends-no-cycle]');
1811
1870
  });
1812
1871
 
1872
+ test('reports extends: and follows: referencing same scope (disjoint violation)', () => {
1873
+ const workspaceRoot = createWorkspace('extends-follows-overlap', {
1874
+ '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[myarea-core](myarea-core/index.md)']),
1875
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nfollows: myarea-core\nextends: myarea-core\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1876
+ '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1877
+ '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1878
+ '.xdrs/myarea-core/index.md': '---\nscope-type: core\nname: myarea-core\ndescription: Meta-governance for myarea.\napply-to: All myarea scopes\nvalid-from: 2026-01-01\n---\n\n# myarea-core Scope Overview\n\n[ADRs](adrs/index.md)\n',
1879
+ '.xdrs/myarea-core/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Core decision']),
1880
+ '.xdrs/myarea-core/adrs/principles/001-team.md': teamXdrDocument('Core decision.'),
1881
+ });
1882
+
1883
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1884
+
1885
+ expect(result.errors.join('\n')).toContain('[_core-adr-policy-010.28-extends-disjoint]');
1886
+ });
1887
+
1888
+ test('extends-only scope not in root index is exempt from missing link error', () => {
1889
+ const workspaceRoot = createWorkspace('extends-only-scope-not-in-index', {
1890
+ // shared-policies is NOT linked in the root index — it is only referenced via extends:
1891
+ '.xdrs/index.md': rootIndex(['[_core](_core/index.md)', '[myteam](myteam/index.md)']),
1892
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: shared-policies\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1893
+ '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1894
+ '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1895
+ '.xdrs/shared-policies/index.md': '---\nscope-type: standard\nname: shared-policies\ndescription: Shared policies.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared-policies Scope Overview\n\n[ADRs](adrs/index.md)\n',
1896
+ '.xdrs/shared-policies/adrs/index.md': teamAdrIndex(['- [001-shared](principles/001-shared.md) - Shared decision']),
1897
+ '.xdrs/shared-policies/adrs/principles/001-shared.md': teamXdrDocument('Shared decision.'),
1898
+ });
1899
+
1900
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1901
+
1902
+ // shared-policies should NOT trigger "missing root index link" since it is extends-only
1903
+ expect(result.errors.filter((e) => e.includes('Root index is missing scope index link') && e.includes('shared-policies')).length).toBe(0);
1904
+ // No extends: validation errors should fire for the extends: field
1905
+ expect(result.errors.filter((e) => /\[_core-adr-policy-010\.\d+-extends-/.test(e)).join('\n')).toBe('');
1906
+ });
1907
+
1908
+ test('warns when extends-referenced scope also appears in root index', () => {
1909
+ const workspaceRoot = createWorkspace('extends-scope-in-root-index', {
1910
+ '.xdrs/index.md': rootIndex(['[_core](_core/index.md)', '[myteam](myteam/index.md)', '[shared-policies](shared-policies/index.md)']),
1911
+ '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nextends: shared-policies\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
1912
+ '.xdrs/myteam/adrs/index.md': teamAdrIndex(['- [001-team](principles/001-team.md) - Team decision']),
1913
+ '.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
1914
+ '.xdrs/shared-policies/index.md': '---\nscope-type: standard\nname: shared-policies\ndescription: Shared policies.\napply-to: All teams\nvalid-from: 2026-01-01\n---\n\n# shared-policies Scope Overview\n\n[ADRs](adrs/index.md)\n',
1915
+ '.xdrs/shared-policies/adrs/index.md': teamAdrIndex(['- [001-shared](principles/001-shared.md) - Shared decision']),
1916
+ '.xdrs/shared-policies/adrs/principles/001-shared.md': teamXdrDocument('Shared decision.'),
1917
+ });
1918
+
1919
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1920
+
1921
+ expect(result.errors.join('\n')).not.toContain('[_core-adr-policy-010.35-extends-index]');
1922
+ expect(result.warnings.join('\n')).toContain('[_core-adr-policy-010.35-extends-index]');
1923
+ expect(result.warnings.join('\n')).toContain('shared-policies');
1924
+ });
1925
+
1926
+
1927
+
1813
1928
  test('reports duplicate entries in follows list', () => {
1814
1929
  const workspaceRoot = createWorkspace('follows-duplicate-entries', {
1815
1930
  '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[myarea-core](myarea-core/index.md)']),
@@ -1842,7 +1957,7 @@ test('reports duplicate entries in follows comma-separated list', () => {
1842
1957
  expect(result.errors.join('\n')).toContain('follows has duplicate entry "myarea-core"');
1843
1958
  });
1844
1959
 
1845
- test('reports related-scopes that references the scope itself', () => {
1960
+ test('reports related-scopes that references the scope itself (field now removed, reports unknown field)', () => {
1846
1961
  const workspaceRoot = createWorkspace('related-scopes-self-reference', {
1847
1962
  '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)']),
1848
1963
  '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes: myteam\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
@@ -1852,10 +1967,10 @@ test('reports related-scopes that references the scope itself', () => {
1852
1967
 
1853
1968
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1854
1969
 
1855
- expect(result.errors.join('\n')).toContain('related-scopes must not reference the scope itself');
1970
+ expect(result.errors.join('\n')).toContain('unknown field "related-scopes"');
1856
1971
  });
1857
1972
 
1858
- test('reports duplicate entries in related-scopes list', () => {
1973
+ test('reports duplicate entries in related-scopes list (field now removed, reports unknown field)', () => {
1859
1974
  const workspaceRoot = createWorkspace('related-scopes-duplicate-entries', {
1860
1975
  '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[sibling-team](sibling-team/index.md)']),
1861
1976
  '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nrelated-scopes:\n - sibling-team\n - sibling-team\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
@@ -1868,10 +1983,10 @@ test('reports duplicate entries in related-scopes list', () => {
1868
1983
 
1869
1984
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1870
1985
 
1871
- expect(result.errors.join('\n')).toContain('related-scopes has duplicate entry "sibling-team"');
1986
+ expect(result.errors.join('\n')).toContain('unknown field "related-scopes"');
1872
1987
  });
1873
1988
 
1874
- test('reports related-scopes entry that is already declared in follows', () => {
1989
+ test('reports related-scopes entry already in follows (field now removed, reports unknown field)', () => {
1875
1990
  const workspaceRoot = createWorkspace('related-scopes-follows-overlap', {
1876
1991
  '.xdrs/index.md': rootIndex(['[myteam](myteam/index.md)', '[myarea-core](myarea-core/index.md)']),
1877
1992
  '.xdrs/myteam/index.md': '---\nscope-type: standard\nname: myteam\ndescription: Team.\napply-to: Test team\nvalid-from: 2026-01-01\nfollows: myarea-core\nrelated-scopes: myarea-core\n---\n\n# myteam Scope Overview\n\n[ADRs](adrs/index.md)\n',
@@ -1884,7 +1999,7 @@ test('reports related-scopes entry that is already declared in follows', () => {
1884
1999
 
1885
2000
  const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
1886
2001
 
1887
- expect(result.errors.join('\n')).toContain('related-scopes must not repeat scope "myarea-core" already declared in follows');
2002
+ expect(result.errors.join('\n')).toContain('unknown field "related-scopes"');
1888
2003
  });
1889
2004
 
1890
2005
  test('reports orphan asset files not referenced by any document', () => {
@@ -2341,6 +2456,509 @@ test('does not require structured format for _core scope policies (exempt)', ()
2341
2456
  expect(result.errors.join('\n')).not.toContain('08c-def-core-scope-policies-must-be-structured');
2342
2457
  });
2343
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
+
2344
2962
  function localScopeIndex() {
2345
2963
  return [
2346
2964
  '---',