xdrs-core 0.35.0 → 0.36.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.
|
@@ -39,7 +39,7 @@ Do not use `scope-type: reference` for scopes that represent a live service, an
|
|
|
39
39
|
|
|
40
40
|
#### 03-naming-convention
|
|
41
41
|
|
|
42
|
-
A scope of type `reference` MUST
|
|
42
|
+
A scope of type `reference` MUST follow the naming pattern `{domain}-ref-{name}`, where `{domain}` identifies the owning domain or team and `{name}` identifies the specific reference area. Examples: `domain1-ref-mobile`, `security-ref-baseline`, `aws-ref-landing-zone`, `sales-ref-procedures`, `finance-ref-compliance`, `marketing-ref-standards`.
|
|
43
43
|
|
|
44
44
|
#### 04-content-guidance
|
|
45
45
|
|
|
@@ -35,7 +35,7 @@ Do not use `scope-type: platform` for blueprints or reference architectures. Use
|
|
|
35
35
|
|
|
36
36
|
#### 03-naming-convention
|
|
37
37
|
|
|
38
|
-
A scope of type `platform` MUST
|
|
38
|
+
A scope of type `platform` MUST follow the naming pattern `{domain}-plat-{name}`, where `{domain}` identifies the owning domain or team and `{name}` identifies the specific platform area. Examples: `domain2-plat-aws`, `domain2-plat-callcenter`, `cloud-plat-infra`.
|
|
39
39
|
|
|
40
40
|
#### 04-content-guidance
|
|
41
41
|
|
package/AGENTS.md
CHANGED
|
@@ -25,10 +25,7 @@ This rule has NO exceptions. Simple questions ("which command?", "what pattern?"
|
|
|
25
25
|
- Analyse your work against the Policies and ensure implementation decisions follow guidelines and patterns
|
|
26
26
|
- Fix any issues
|
|
27
27
|
|
|
28
|
-
4. **
|
|
29
|
-
- Check if what is being performed shouldn't be documented as a Policy in _local scope (because the decision has potential to be reused in the future or the topic is complex and would benefit from a document for clarity). Create or update existing documents accordingly.
|
|
30
|
-
|
|
31
|
-
5. **Do not perform git operations unless explicitelly asked**
|
|
28
|
+
4. **Do not perform risky git operations unless explicitelly asked**
|
|
32
29
|
- The developer should be in control of possible destructive operations on the workspace
|
|
33
30
|
|
|
34
31
|
**This AGENTS.md file was created with xdrs-core and shouldn't be changed**
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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);
|
|
@@ -297,11 +286,11 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
297
286
|
if (scopeType === '_local' && scopeName !== '_local') {
|
|
298
287
|
errors.push(`Scope type "_local" is reserved for the "_local" scope: ${toDisplayPath(scopeIndexPath)}`);
|
|
299
288
|
}
|
|
300
|
-
if (scopeType === 'reference' && !scopeName.includes('
|
|
301
|
-
errors.push(`Scope with type "reference" must
|
|
289
|
+
if (scopeType === 'reference' && !scopeName.includes('-ref-')) {
|
|
290
|
+
errors.push(`Scope with type "reference" must follow the naming pattern {domain}-ref-{name} (e.g. "security-ref-baseline"): ${toDisplayPath(scopeIndexPath)}`);
|
|
302
291
|
}
|
|
303
|
-
if (scopeType === 'platform' && !scopeName.includes('
|
|
304
|
-
errors.push(`Scope with type "platform" must
|
|
292
|
+
if (scopeType === 'platform' && !scopeName.includes('-plat-')) {
|
|
293
|
+
errors.push(`Scope with type "platform" must follow the naming pattern {domain}-plat-{name} (e.g. "cloud-plat-aws"): ${toDisplayPath(scopeIndexPath)}`);
|
|
305
294
|
}
|
|
306
295
|
|
|
307
296
|
if (!fm.name) {
|
package/lib/lint.test.js
CHANGED
|
@@ -716,18 +716,18 @@ test('accepts all valid scope-type values', () => {
|
|
|
716
716
|
expect(result.errors.join('\n')).not.toContain('Scope index must start with');
|
|
717
717
|
});
|
|
718
718
|
|
|
719
|
-
test('accepts scope-type reference when scope name
|
|
719
|
+
test('accepts scope-type reference when scope name follows {domain}-ref-{name} pattern', () => {
|
|
720
720
|
const workspaceRoot = createWorkspace('valid-reference-type', {
|
|
721
|
-
'.xdrs/index.md': rootIndex(['[security-
|
|
722
|
-
'.xdrs/security-
|
|
723
|
-
'.xdrs/security-
|
|
724
|
-
'# security-
|
|
721
|
+
'.xdrs/index.md': rootIndex(['[security-ref-baseline](security-ref-baseline/index.md)']),
|
|
722
|
+
'.xdrs/security-ref-baseline/index.md': '---\nscope-type: reference\nname: security-ref-baseline\ndescription: Security reference architecture baseline for all teams.\napply-to: All teams following security reference patterns\nvalid-from: 2026-01-01\n---\n\n# security-ref-baseline Scope Overview\n\nSecurity reference.\n\n[ADRs](adrs/index.md)\n',
|
|
723
|
+
'.xdrs/security-ref-baseline/adrs/index.md': [
|
|
724
|
+
'# security-ref-baseline ADR Index', '', 'Security reference ADRs.', '', '## principles', '',
|
|
725
725
|
'- [001-baseline](principles/001-baseline.md) - Security baseline', ''
|
|
726
726
|
].join('\n'),
|
|
727
|
-
'.xdrs/security-
|
|
728
|
-
'---', 'name: security-
|
|
727
|
+
'.xdrs/security-ref-baseline/adrs/principles/001-baseline.md': [
|
|
728
|
+
'---', 'name: security-ref-baseline-adr-policy-001-baseline',
|
|
729
729
|
'description: Security baseline policy', 'apply-to: All scopes', 'valid-from: 2026-01-01', '---', '',
|
|
730
|
-
'# security-
|
|
730
|
+
'# security-ref-baseline-adr-policy-001: Security baseline', '',
|
|
731
731
|
'## Context and Problem Statement', '', 'Baseline context.', '',
|
|
732
732
|
'## Decision Outcome', '', 'Baseline outcome.', ''
|
|
733
733
|
].join('\n'),
|
|
@@ -758,21 +758,21 @@ test('reports scope-type reference when scope name does not contain reference',
|
|
|
758
758
|
|
|
759
759
|
const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
|
|
760
760
|
|
|
761
|
-
expect(result.errors.join('\n')).toContain('Scope with type "reference" must
|
|
761
|
+
expect(result.errors.join('\n')).toContain('Scope with type "reference" must follow the naming pattern {domain}-ref-{name}');
|
|
762
762
|
});
|
|
763
763
|
|
|
764
|
-
test('accepts scope-type platform when scope name
|
|
764
|
+
test('accepts scope-type platform when scope name follows {domain}-plat-{name} pattern', () => {
|
|
765
765
|
const workspaceRoot = createWorkspace('valid-platform-type', {
|
|
766
|
-
'.xdrs/index.md': rootIndex(['[cloud-
|
|
767
|
-
'.xdrs/cloud-
|
|
768
|
-
'.xdrs/cloud-
|
|
769
|
-
'# cloud-
|
|
766
|
+
'.xdrs/index.md': rootIndex(['[cloud-plat-infra](cloud-plat-infra/index.md)']),
|
|
767
|
+
'.xdrs/cloud-plat-infra/index.md': '---\nscope-type: platform\nname: cloud-plat-infra\ndescription: Cloud platform infrastructure decisions for all teams.\napply-to: All teams using the cloud platform\nvalid-from: 2026-01-01\n---\n\n# cloud-plat-infra Scope Overview\n\nCloud platform.\n\n[ADRs](adrs/index.md)\n',
|
|
768
|
+
'.xdrs/cloud-plat-infra/adrs/index.md': [
|
|
769
|
+
'# cloud-plat-infra ADR Index', '', 'Cloud platform ADRs.', '', '## principles', '',
|
|
770
770
|
'- [001-baseline](principles/001-baseline.md) - Cloud baseline', ''
|
|
771
771
|
].join('\n'),
|
|
772
|
-
'.xdrs/cloud-
|
|
773
|
-
'---', 'name: cloud-
|
|
772
|
+
'.xdrs/cloud-plat-infra/adrs/principles/001-baseline.md': [
|
|
773
|
+
'---', 'name: cloud-plat-infra-adr-policy-001-baseline',
|
|
774
774
|
'description: Cloud platform baseline', 'apply-to: All scopes', 'valid-from: 2026-01-01', '---', '',
|
|
775
|
-
'# cloud-
|
|
775
|
+
'# cloud-plat-infra-adr-policy-001: Cloud baseline', '',
|
|
776
776
|
'## Context and Problem Statement', '', 'Platform context.', '',
|
|
777
777
|
'## Decision Outcome', '', 'Platform outcome.', ''
|
|
778
778
|
].join('\n'),
|
|
@@ -804,7 +804,7 @@ test('reports scope-type platform when scope name does not contain platform', ()
|
|
|
804
804
|
|
|
805
805
|
const result = lintWorkspace(workspaceRoot, { ignoreExternal: false });
|
|
806
806
|
|
|
807
|
-
expect(result.errors.join('\n')).toContain('Scope with type "platform" must
|
|
807
|
+
expect(result.errors.join('\n')).toContain('Scope with type "platform" must follow the naming pattern {domain}-plat-{name}');
|
|
808
808
|
});
|
|
809
809
|
|
|
810
810
|
test('reports scope-type core when scope name does not contain core', () => {
|
|
@@ -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-plat-custom](my-plat-custom/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-plat-custom/index.md': '---\nscope-type: platform\nname: my-plat-custom\ndescription: Platform scope.\napply-to: All\nvalid-from: 2026-01-01\n---\n\n# my-plat-custom Scope Overview\n\nPlatform scope.\n\n[ADRs](adrs/index.md)\n',
|
|
896
|
+
'.xdrs/my-plat-custom/adrs/index.md': [
|
|
897
|
+
'# my-plat-custom ADR Index', '', 'Platform ADRs.', '', '## principles', '',
|
|
898
|
+
'- [001-domain-scope-type](principles/001-domain-scope-type.md) - domain type', ''
|
|
899
|
+
].join('\n'),
|
|
900
|
+
'.xdrs/my-plat-custom/adrs/principles/001-domain-scope-type.md': [
|
|
901
|
+
'---', 'name: my-plat-custom-adr-policy-001-domain-scope-type',
|
|
902
|
+
'description: Defines the domain scope type.', 'apply-to: All', 'valid-from: 2026-01-01', '---', '',
|
|
903
|
+
'# my-plat-custom-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.
|
|
3
|
+
"version": "0.36.0",
|
|
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",
|