xdrs-core 0.35.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/lint.js CHANGED
@@ -99,23 +99,12 @@ function lintWorkspace(targetPath, options = {}) {
99
99
  const rootEntries = safeReadDir(xdrsRoot, errors, 'read XDRS root directory');
100
100
  const scopeEntries = rootEntries.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'));
101
101
 
102
- // Pass 1: collect all core-type scopes by reading index.md directly (bypasses external-skip)
103
- const coreScopes = [];
104
- for (const scopeEntry of scopeEntries) {
105
- const scopeIndexPath = path.join(xdrsRoot, scopeEntry.name, 'index.md');
106
- if (!existsFile(scopeIndexPath)) continue;
107
- const content = fs.readFileSync(scopeIndexPath, 'utf8');
108
- const scopeTypeMatch = content.match(/^scope-type:\s*(.+)$/m);
109
- if (scopeTypeMatch && scopeTypeMatch[1].trim() === 'core') {
110
- coreScopes.push(path.join(xdrsRoot, scopeEntry.name));
111
- }
112
- }
113
-
114
- // Pass 2: collect known scope-type names from NNN-{scope-type}-scope-type.md files in core scopes
102
+ // Pass 1: collect known scope-type names from NNN-{scope-type}-scope-type.md files in any scope
115
103
  const knownScopeTypes = new Map(); // scopeTypeName -> filePath
116
- for (const coreScopePath of coreScopes) {
104
+ for (const scopeEntry of scopeEntries) {
105
+ const scopePath = path.join(xdrsRoot, scopeEntry.name);
117
106
  for (const typeName of Object.keys(TYPE_TO_ID)) {
118
- const principlesDir = path.join(coreScopePath, typeName, 'principles');
107
+ const principlesDir = path.join(scopePath, typeName, 'principles');
119
108
  if (!existsDirectory(principlesDir)) continue;
120
109
  let principlesEntries;
121
110
  try { principlesEntries = fs.readdirSync(principlesDir, { withFileTypes: true }); } catch { continue; }
@@ -261,7 +250,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
261
250
  return;
262
251
  }
263
252
  if (!knownScopeTypes.has(scopeType) && scopeType !== '_local') {
264
- errors.push(`Scope index scope-type "${scopeType}" has no corresponding ${scopeType}-scope-type policy in the principles subject of any core-type scope: ${toDisplayPath(scopeIndexPath)}`);
253
+ errors.push(`Scope index scope-type "${scopeType}" has no corresponding ${scopeType}-scope-type policy in the principles of any scope: ${toDisplayPath(scopeIndexPath)}`);
265
254
  return;
266
255
  }
267
256
  // Validate inheritance chain: ensure all ancestors exist in knownScopeTypes
@@ -278,7 +267,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
278
267
  break;
279
268
  }
280
269
  if (!knownScopeTypes.has(parentType)) {
281
- errors.push(`Scope type "${currentType}" declares parent "${parentType}" but no ${parentType}-scope-type policy exists in any core-type scope: ${toDisplayPath(scopeIndexPath)}`);
270
+ errors.push(`Scope type "${currentType}" declares parent "${parentType}" but no ${parentType}-scope-type policy exists in any scope: ${toDisplayPath(scopeIndexPath)}`);
282
271
  break;
283
272
  }
284
273
  visited.add(parentType);
package/lib/lint.test.js CHANGED
@@ -885,6 +885,34 @@ test('accepts custom scope-type when policy exists in core-type scope', () => {
885
885
  expect(result.errors.join('\n')).not.toContain('Scope index frontmatter');
886
886
  });
887
887
 
888
+ test('accepts custom scope-type when policy exists in a non-core scope', () => {
889
+ // domain-scope-type.md is in a platform-type scope (not a core scope), but lint should still find it
890
+ const workspaceRoot = createWorkspace('custom-scope-type-in-non-core-scope', {
891
+ '.xdrs/index.md': rootIndex(['[nnb-mt-domain](nnb-mt-domain/index.md)', '[my-platform](my-platform/index.md)']),
892
+ '.xdrs/nnb-mt-domain/index.md': '---\nscope-type: domain\nname: nnb-mt-domain\ndescription: Domain scope.\napply-to: All\nvalid-from: 2026-01-01\n---\n\n# nnb-mt-domain Scope Overview\n\nDomain scope.\n\n[ADRs](adrs/index.md)\n',
893
+ '.xdrs/nnb-mt-domain/adrs/index.md': teamAdrIndex([]),
894
+ // scope-type 'domain' defined in a non-core platform scope (not _core)
895
+ '.xdrs/my-platform/index.md': '---\nscope-type: platform\nname: my-platform\ndescription: Platform scope.\napply-to: All\nvalid-from: 2026-01-01\n---\n\n# my-platform Scope Overview\n\nPlatform scope.\n\n[ADRs](adrs/index.md)\n',
896
+ '.xdrs/my-platform/adrs/index.md': [
897
+ '# my-platform ADR Index', '', 'Platform ADRs.', '', '## principles', '',
898
+ '- [001-domain-scope-type](principles/001-domain-scope-type.md) - domain type', ''
899
+ ].join('\n'),
900
+ '.xdrs/my-platform/adrs/principles/001-domain-scope-type.md': [
901
+ '---', 'name: my-platform-adr-policy-001-domain-scope-type',
902
+ 'description: Defines the domain scope type.', 'apply-to: All', 'valid-from: 2026-01-01', '---', '',
903
+ '# my-platform-adr-policy-001: domain scope type', '', '## Context and Problem Statement', '',
904
+ 'Defines the domain scope type.', '', '## Decision Outcome', '', 'Use scope-type: domain.', ''
905
+ ].join('\n'),
906
+ // _core only defines 'core' and 'platform' — no 'domain' here
907
+ ...coreWithScopeTypes(['platform']),
908
+ });
909
+
910
+ const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
911
+
912
+ expect(result.errors.join('\n')).not.toContain('has no corresponding');
913
+ expect(result.errors.join('\n')).not.toContain('Scope index frontmatter');
914
+ });
915
+
888
916
  test('reports custom scope-type when no policy exists', () => {
889
917
  const workspaceRoot = createWorkspace('custom-scope-type-missing-policy', {
890
918
  '.xdrs/index.md': rootIndex(['[my-scope](my-scope/index.md)']),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xdrs-core",
3
- "version": "0.35.0",
3
+ "version": "0.35.1",
4
4
  "description": "A framework to structure, compile and distribute Architectural (ADR), Business (BDR), and Engineering (EDR) decision records contents so that AI agents and humans can reliably find and use them with hierarchical scopes and controlled rollout in the format of distributable versioned packages.",
5
5
  "repository": {
6
6
  "type": "git",