xdrs-core 0.15.4 → 0.17.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/001-xdrs-core.md +1 -1
- package/.xdrs/_core/adrs/principles/002-xdr-standards.md +11 -1
- package/.xdrs/_core/adrs/principles/006-research-standards.md +1 -1
- package/.xdrs/_core/adrs/principles/008-xdr-standards-structured.md +81 -0
- package/.xdrs/_core/adrs/principles/skills/002-write-xdr/SKILL.md +26 -0
- package/.xdrs/_core/bdrs/index.md +9 -0
- package/.xdrs/_core/bdrs/principles/001-xdr-decisions-and-skills-usage.md +52 -0
- package/.xdrs/index.md +2 -1
- package/lib/lint.js +17 -6
- package/lib/lint.test.js +108 -0
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ Foundational standards, principles, and guidelines.
|
|
|
13
13
|
- [_core-adr-005](principles/005-semantic-versioning-for-xdr-packages.md) - **Semantic versioning for XDR packages** — How to version XDR packages to communicate upgrade impact
|
|
14
14
|
- [_core-adr-006](principles/006-research-standards.md) - **Research standards** — How to structure research documents backing XDR decisions
|
|
15
15
|
- [_core-adr-007](principles/007-plan-standards.md) - **Plan standards** — How to structure ephemeral execution plans that implement decisions
|
|
16
|
+
- [_core-adr-008](principles/008-xdr-standards-structured.md) - **XDR standards - structured** — How to expose individually referenceable numbered rules inside an XDR when external citation by identifier is required
|
|
16
17
|
|
|
17
18
|
## Skills
|
|
18
19
|
|
|
@@ -57,7 +57,7 @@ Collectively, these are referred to as XDRs.
|
|
|
57
57
|
- **Scopes:**
|
|
58
58
|
- Short name that defines a group or a package of xdrs
|
|
59
59
|
- examples: `business-x`, `business-y`, `team-43`, `_core`
|
|
60
|
-
- `_local` is a reserved scope for XDRs created locally to a specific project or repository. XDRs in `_local` must not be shared with or propagated to other contexts. This scope must always be placed in the lowest position in `.xdrs/index.md` so that its decisions override or extend any decisions from all higher-positioned scopes. Shared `.xdrs/index.md` files MUST NOT link `_local` canonical type indexes because `_local` stays workspace-local and is not distributed with shared packages. Readers, tools, and agents SHOULD still try to discover existing workspace-local `_local` canonical indexes by default, even when the shared root index does not link them.
|
|
60
|
+
- `_local` is a reserved scope for XDRs created locally to a specific project or repository. XDRs in `_local` must not be shared with or propagated to other contexts. This scope must always be placed in the lowest position in `.xdrs/index.md` so that its decisions override or extend any decisions from all higher-positioned scopes. Shared `.xdrs/index.md` files MUST NOT link `_local` canonical type indexes because `_local` stays workspace-local and is not distributed with shared packages. Readers, tools, and agents SHOULD still try to discover existing workspace-local `_local` canonical indexes by default, even when the shared root index does not link them. Documents in non-`_local` scopes MUST NEVER link to any document inside the `_local` scope, because `_local` is workspace-only and such links would break in any consumer workspace. Documents inside `_local` MAY link to other documents inside `_local`.
|
|
61
61
|
- **Types:** `adrs`, `bdrs`, `edrs`
|
|
62
62
|
- there can exist sufixes to the standard scope names (e.g: `business-x-mobileapp`, `business-y-servicedesk`)
|
|
63
63
|
- **Subjects:** MUST be one of the following depending on the type of the XDR. Use the subject to indicate the main concern of the decision.
|
|
@@ -25,7 +25,7 @@ XDR documents are the authoritative policy for their scope, type, and subject. T
|
|
|
25
25
|
|---|---|---|
|
|
26
26
|
| `name` | Yes | 1-64 characters. Lowercase letters, numbers, hyphens, and leading underscores only. Must not end with a hyphen. Must not contain consecutive hyphens. Must match the document identifier from the heading: `[scope]-[type]-[number]-[short-title]`. |
|
|
27
27
|
| `description` | Yes | 1-1024 characters. Describes what this decision is about and when to use it. Should include keywords that help agents identify when to apply it. |
|
|
28
|
-
| `applied-to` | No | Short description of contexts this decision is applicable to. Keep it under 40 words. If omitted, the decision applies to all logically applicable elements. Examples: `Only frontend code`, `JavaScript projects`. |
|
|
28
|
+
| `applied-to` | No | Short description of contexts this decision is applicable to. Keep it under 40 words. If omitted, the decision applies to all logically applicable elements. ONLY use this section if the usage is very specific to a specific case. Examples: `Only frontend code`, `JavaScript projects`. |
|
|
29
29
|
| `valid-from` | No | ISO date (`YYYY-MM-DD`) indicating from when this decision must be enforced. Before this date it should be used everywhere possible, but compliance is not enforced during reviews until after this date. |
|
|
30
30
|
| `license` | No | SPDX license expression (e.g. `MIT`, `Apache-2.0`, `CC-BY-4.0`). Indicates the license under which the document content is shared. If omitted, the license is governed by the repository or package defaults. |
|
|
31
31
|
| `metadata` | No | Arbitrary key-value map for additional properties not defined by this spec. |
|
|
@@ -60,6 +60,7 @@ XDR documents are the authoritative policy for their scope, type, and subject. T
|
|
|
60
60
|
- Decisions MUST be concise and reference other XDRs to avoid duplication.
|
|
61
61
|
- The `### Implementation Details` section SHOULD state relevant boundaries or exceptions and what a reader should do or avoid in common cases. Use the frontmatter fields `applied-to` and `valid-from` as the first-pass filter for applicability, then keep nuanced boundaries in the decision text.
|
|
62
62
|
- Use concise rules, examples, `Allowed` / `Disallowed` lists or checklists with required items to help the reader apply the decision correctly. Keep them short and decision-specific.
|
|
63
|
+
- When the decision defines strong policies or rules that should be stated explicitly as stable rule blocks, or when other documents, skills, or agents need to cite those rules individually by identifier, the XDR MUST follow the extension [_core-adr-008 - XDR standards - structured](008-xdr-standards-structured.md) instead of using plain bullet lists for those rules.
|
|
63
64
|
- Conflict handling applies to XDR documents:
|
|
64
65
|
- For cross-scope overrides, document the decision conflict in the XDR `## Conflicts` section of the XDR that overrides another scope.
|
|
65
66
|
- **Within-scope conflicts:** XDRs within the same type+scope must not conflict. If two XDRs appear to conflict, one should be updated, removed, or the conflict resolved through a new XDR.
|
|
@@ -107,6 +108,14 @@ Question: In the end, state explicitly the question that needs to be answered. E
|
|
|
107
108
|
|
|
108
109
|
[Optional section with implementation specifics, applicability boundaries, rules, concise examples, or do/don't guidance. This is the answer to the question in the "Context and Problem Statement". (<1300 words)]
|
|
109
110
|
|
|
111
|
+
## Considered Options
|
|
112
|
+
[this section is present ONLY if there was more than one option to choose from]
|
|
113
|
+
|
|
114
|
+
* (CHOSEN) **Option 2** - Brief description of option 2
|
|
115
|
+
* Reason: Brief description of why this option was accepted, containing the strengths, strategical motivations and it's differential over the other options.
|
|
116
|
+
* (REJECTED) **Option 1** - Brief description of option 1
|
|
117
|
+
* Reason: Brief description why this was rejected with important aspects to be re-checked in the case we want to change this decision
|
|
118
|
+
|
|
110
119
|
[Related research, if any]
|
|
111
120
|
- [Research document title](researches/001-example.md) - Brief description of what it informed
|
|
112
121
|
|
|
@@ -144,3 +153,4 @@ Question: In the end, state explicitly the question that needs to be answered. E
|
|
|
144
153
|
- [_core-adr-003 - Skill standards](003-skill-standards.md)
|
|
145
154
|
- [_core-adr-004 - Article standards](004-article-standards.md)
|
|
146
155
|
- [_core-adr-006 - Research standards](006-research-standards.md)
|
|
156
|
+
- [_core-adr-008 - XDR standards - structured](008-xdr-standards-structured.md) - Extension for XDRs that expose individually referenceable rules
|
|
@@ -121,7 +121,7 @@ Prefer tables, bullets, or ASCII art for simple comparisons. Use external figure
|
|
|
121
121
|
|
|
122
122
|
## Considered Options
|
|
123
123
|
|
|
124
|
-
- Related research:
|
|
124
|
+
- Related research: `001-research-and-decision-lifecycle` (workspace-local research)
|
|
125
125
|
|
|
126
126
|
* (REJECTED) **Inline long-form analysis inside the XDR** - Put all research and decision text in one file.
|
|
127
127
|
* Reason: Makes XDRs too long, mixes evidence with the adopted rule set, and hurts fast retrieval by humans and AI agents.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-008-xdr-standards-structured
|
|
3
|
+
description: Extends xdr-standards with a numbered rule format for XDR documents that define strong policies or rules, or need individually referenceable items. Use when an XDR must expose explicit rule blocks that other documents or skills may cite by identifier.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# _core-adr-008: XDR standards - structured
|
|
7
|
+
|
|
8
|
+
## Context and Problem Statement
|
|
9
|
+
|
|
10
|
+
Some XDR documents define multiple strong policies or rules that must be stated explicitly so they can be applied consistently. In other cases, documents, skills, or agents need to refer to one specific rule without copying its content. Without a standard format, prose references like "see the third bullet in Implementation Details" are fragile and ambiguous.
|
|
11
|
+
|
|
12
|
+
Question: How should an XDR document expose strong or individually referenceable rules or policies so they stay explicit, stable, and easy to cite?
|
|
13
|
+
|
|
14
|
+
## Decision Outcome
|
|
15
|
+
|
|
16
|
+
**Numbered rule blocks inside Implementation Details with a canonical citation syntax**
|
|
17
|
+
|
|
18
|
+
When an XDR document defines strong rules or policies that should be stated explicitly, or when documents and skills need to cite individual rules precisely, each rule must be placed inside `### Implementation Details` as a numbered heading block. Referencing documents and skills must cite rules using the canonical dot-notation identifier.
|
|
19
|
+
|
|
20
|
+
### Implementation Details
|
|
21
|
+
|
|
22
|
+
Use this format when the decision defines strong rules or policies that must be stated explicitly as stable rule blocks, or when there is a clear need for external documents, skills, or agents to reference specific items inside an XDR without duplicating the full policy text. Standard XDRs that do not define strong rule sets and do not need item-level citation should follow `_core-adr-002-xdr-standards` without adding numbered rule headings.
|
|
23
|
+
|
|
24
|
+
#### Rule block format
|
|
25
|
+
|
|
26
|
+
Each numbered rule must be written as:
|
|
27
|
+
|
|
28
|
+
```markdown
|
|
29
|
+
#### [NN]-[short-descriptive-title-in-kebab-case]
|
|
30
|
+
[Body using mandatory or advisory language as defined in _core-adr-001. State the requirement and the situations in which it must or should be followed. Under 500 words.]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Where `NN` is a two-digit zero-padded sequence number (e.g. `01`, `02`, `12`). Numbers must be unique within the document and must never be reused after a rule is removed.
|
|
34
|
+
|
|
35
|
+
Rule bodies must use the mandatory or advisory language terms defined in `_core-adr-001`:
|
|
36
|
+
|
|
37
|
+
- Mandatory: "must", "always", "never", "required", "mandatory"
|
|
38
|
+
- Advisory: "should", "recommended", "advised", "preferably", "possibly", "optionally"
|
|
39
|
+
|
|
40
|
+
#### Citation syntax
|
|
41
|
+
|
|
42
|
+
When another document or skill cites a specific rule, it must use the following dot-notation:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
[xdr-name].[NN-short-descriptive-title-in-kebab-case]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
|
|
50
|
+
- `_core-adr-008-xdr-standards-structured.[01-use-numbered-rules-for-strong-or-referenceable-policies]`
|
|
51
|
+
- `_local-bdr-003-data-retention-policy.[02-purge-schedule-for-pii]`
|
|
52
|
+
|
|
53
|
+
The `xdr-name` must match the `name` field in the frontmatter of the source document exactly. The rule identifier after the dot must match the heading text exactly, including the two-digit prefix and kebab-case title.
|
|
54
|
+
|
|
55
|
+
#### 01-use-numbered-rules-for-strong-or-referenceable-policies
|
|
56
|
+
|
|
57
|
+
Numbered rule blocks must be added to an XDR when the decision defines strong rules or policies that must be stated explicitly as stable items, or when there is a clear need for other documents, skills, or agents to cite individual rules by identifier. Adding numbered rules only for cosmetic organization is not recommended. Standard XDR documents that do not define strong rule sets and are not expected to be cited at the rule level should follow `_core-adr-002-xdr-standards` without this structured format.
|
|
58
|
+
|
|
59
|
+
#### 02-rule-numbering-must-be-stable
|
|
60
|
+
|
|
61
|
+
Rule numbers must never be reused within the same document. When a rule is removed, its number becomes permanently retired for that document. Gaps in the sequence are expected and must not be filled by renumbering remaining rules, as existing citations depend on number stability.
|
|
62
|
+
|
|
63
|
+
#### 03-rule-body-must-use-normative-language
|
|
64
|
+
|
|
65
|
+
Every rule body must contain at least one mandatory or advisory language term as defined in `_core-adr-001`. Rule bodies without normative language must not be published, as they fail to communicate whether compliance is required or recommended.
|
|
66
|
+
|
|
67
|
+
#### 04-citations-must-use-exact-identifiers
|
|
68
|
+
|
|
69
|
+
Documents and skills that cite a rule must use the exact dot-notation form: `[xdr-name].[NN-short-descriptive-title-in-kebab-case]`. Prose paraphrases such as "see rule 3" or "the third rule in that XDR" must not be used as citations, because they are ambiguous and break when rules are reordered or reworded.
|
|
70
|
+
|
|
71
|
+
## Considered Options
|
|
72
|
+
|
|
73
|
+
* (REJECTED) **Free-form prose rules with section anchors** — Use markdown heading anchors as citation targets.
|
|
74
|
+
* Reason: Anchors are fragile across editors, rendering tools, and refactors. They do not enforce a stable numbering contract and break silently when headings are reworded.
|
|
75
|
+
* (CHOSEN) **Numbered rule blocks inside Implementation Details** — Prefix each rule heading with a two-digit sequence number and use dot-notation for citations.
|
|
76
|
+
* Reason: Minimal addition to the existing XDR template, stable identifiers independent of heading text, and fully compatible with `_core-adr-002-xdr-standards`.
|
|
77
|
+
|
|
78
|
+
## References
|
|
79
|
+
|
|
80
|
+
- [_core-adr-001 - XDRs core](001-xdrs-core.md)
|
|
81
|
+
- [_core-adr-002 - XDR standards](002-xdr-standards.md)
|
|
@@ -76,6 +76,32 @@ Choose a title that clearly states the question this XDR answers, not the answer
|
|
|
76
76
|
|
|
77
77
|
Use the mandatory template from `002-xdr-standards`:
|
|
78
78
|
|
|
79
|
+
**Check if the decision requires a structured set of rules:**
|
|
80
|
+
If the decision defines strong rules or policies that must be stated explicitly, or if other documents, skills, or agents have a clear need to reference individual rules, you MUST apply the structured rule format from `_core-adr-008-xdr-standards-structured`. This means:
|
|
81
|
+
- Place each rule as a numbered heading block inside `### Implementation Details`.
|
|
82
|
+
- Use the format:
|
|
83
|
+
#### [NN]-[short-descriptive-title-in-kebab-case]
|
|
84
|
+
[Rule body with mandatory/advisory language.]
|
|
85
|
+
- Ensure each rule is uniquely numbered (two digits, zero-padded) and never reuse numbers if a rule is removed.
|
|
86
|
+
- Other documents must cite rules using the canonical dot-notation: `[xdr-name].[NN-short-descriptive-title-in-kebab-case]`.
|
|
87
|
+
|
|
88
|
+
**Example of a structured set of rules:**
|
|
89
|
+
|
|
90
|
+
```markdown
|
|
91
|
+
### Implementation Details
|
|
92
|
+
|
|
93
|
+
#### 01-data-must-be-encrypted-at-rest
|
|
94
|
+
All user data must be encrypted at rest using AES-256 or stronger algorithms.
|
|
95
|
+
|
|
96
|
+
#### 02-access-logs-must-be-retained
|
|
97
|
+
Access logs must be retained for at least 90 days and reviewed monthly for suspicious activity.
|
|
98
|
+
|
|
99
|
+
#### 03-external-integrations-should-be-reviewed
|
|
100
|
+
All external integrations should be reviewed annually for compliance with current security standards.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Refer to `_core-adr-008-xdr-standards-structured` for full requirements and citation syntax.
|
|
104
|
+
|
|
79
105
|
```
|
|
80
106
|
---
|
|
81
107
|
name: [scope]-[type]-[number]-[short-title]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# _core BDRs Index
|
|
2
|
+
|
|
3
|
+
Business and operational decisions about how the XDR framework operates, including policy and process rules for framework users. Owned by the platform team. Propose changes via pull request.
|
|
4
|
+
|
|
5
|
+
## Principles
|
|
6
|
+
|
|
7
|
+
Foundational business principles and policy decisions that guide all framework users.
|
|
8
|
+
|
|
9
|
+
- [_core-bdr-001](principles/001-xdr-decisions-and-skills-usage.md) - **XDR decisions and skills usage** — How agents and humans must use XDR decisions and skills, separating policy authority from execution guidance
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-bdr-001-xdr-decisions-and-skills-usage
|
|
3
|
+
description: Defines how agents and humans must use XDR decisions and skills, separating policy authority from execution guidance across single-agent and multi-agent workflows.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# _core-bdr-001: XDR decisions and skills usage
|
|
7
|
+
|
|
8
|
+
## Context and Problem Statement
|
|
9
|
+
|
|
10
|
+
Repositories using the XDR framework contain both decision records (BDRs, ADRs, EDRs) and skills. In multi-agent or multi-role workflows, each actor needs to know which artifact carries policy authority and which carries execution guidance.
|
|
11
|
+
|
|
12
|
+
Question: How must agents and humans use XDR decisions and skills so policy, execution guidance, and role boundaries stay clear?
|
|
13
|
+
|
|
14
|
+
## Decision Outcome
|
|
15
|
+
|
|
16
|
+
**BDRs define compliance policy; skills operationalize it**
|
|
17
|
+
|
|
18
|
+
XDR decisions are the authoritative source of mandatory policy. Skills are execution artifacts that operationalize those decisions and must never substitute them as policy authority.
|
|
19
|
+
|
|
20
|
+
### Implementation Details
|
|
21
|
+
|
|
22
|
+
- Before treating any XDR as a current requirement, evaluate applicability in order: `valid-from`, `applied-to`, then the decision text.
|
|
23
|
+
- The set of policies that an agent or human must comply with for a given operational context must be declared in BDRs.
|
|
24
|
+
- If a policy set becomes too large or too mixed in purpose to review clearly in one record, it must be split into multiple focused BDRs.
|
|
25
|
+
- Specific work instructions that operationalize BDR policies must be structured as skills when the procedure is detailed enough to benefit from a dedicated operational document.
|
|
26
|
+
- Every skill that operationalizes or verifies BDR policies must link to the BDRs it implements or checks.
|
|
27
|
+
- If a skill conflicts with an applicable XDR, the XDR prevails. The human or agent must stop relying on the conflicting skill behavior and report the inconsistency.
|
|
28
|
+
- If an applicable BDR exists but no supporting skill exists yet, the human or agent may proceed by following the BDR directly when the decision is actionable, and must report the missing skill as an operational gap.
|
|
29
|
+
- In multi-agent graphs or staged workflows, every participating agent must remain bounded by the same applicable BDR policies even when agents have different local objectives, prompts, or skills.
|
|
30
|
+
- Each agent or human role involved in a workflow must make explicit which applicable BDRs govern its work and which skills it is following.
|
|
31
|
+
- Different documents may be consumed by different actors. For example: one agent uses skills for data acquisition, another for plan execution, and a reviewer reads BDRs directly to validate outcomes.
|
|
32
|
+
|
|
33
|
+
Allowed:
|
|
34
|
+
- using multiple skills to operationalize one BDR in different phases of a workflow;
|
|
35
|
+
- using one skill to operationalize multiple related BDRs when the skill links them clearly;
|
|
36
|
+
- having human-only, agent-only, or mixed human-agent execution for the same skill.
|
|
37
|
+
|
|
38
|
+
Disallowed:
|
|
39
|
+
- treating a skill as policy authority by itself;
|
|
40
|
+
- inventing mandatory policy rules only inside prompts, plans, or skills;
|
|
41
|
+
- allowing one agent in a workflow to bypass an applicable BDR because another agent checked a different subset of rules.
|
|
42
|
+
|
|
43
|
+
## Considered Options
|
|
44
|
+
|
|
45
|
+
- (REJECTED) **Keep policy and execution guidance mixed inside operational BDRs** — Makes decisions harder to reuse, review, and assign across humans and multiple agent roles.
|
|
46
|
+
- (CHOSEN) **Separate mandatory policy in BDRs from executable guidance in skills** — Preserves a clear source of truth while allowing different humans and agents to execute specialized procedures.
|
|
47
|
+
|
|
48
|
+
## References
|
|
49
|
+
|
|
50
|
+
- [_core-adr-001](../../adrs/principles/001-xdrs-core.md)
|
|
51
|
+
- [_core-adr-002](../../adrs/principles/002-xdr-standards.md)
|
|
52
|
+
- [_core-adr-003](../../adrs/principles/003-skill-standards.md)
|
package/.xdrs/index.md
CHANGED
|
@@ -10,6 +10,7 @@ XDRs in scopes listed last override the ones listed first
|
|
|
10
10
|
|
|
11
11
|
Decisions about how XDRs work
|
|
12
12
|
[View _core ADRs Index](_core/adrs/index.md)
|
|
13
|
+
[View _core BDRs Index](_core/bdrs/index.md)
|
|
13
14
|
|
|
14
15
|
---
|
|
15
16
|
|
|
@@ -19,4 +20,4 @@ Decisions about how XDRs work
|
|
|
19
20
|
|
|
20
21
|
### _local (reserved)
|
|
21
22
|
|
|
22
|
-
Project-local XDRs that must not be shared with other contexts. Always keep this scope last so its decisions override or extend all scopes listed above. Keep `_local` canonical indexes in the workspace tree only; do not link them from this shared index. Readers and tools should still try to discover existing `_local` indexes in the current workspace by default.
|
|
23
|
+
Project-local XDRs that must not be shared with other contexts. Always keep this scope last so its decisions override or extend all scopes listed above. Keep `_local` canonical indexes in the workspace tree only; do not link them from this shared index. Readers and tools should still try to discover existing `_local` indexes in the current workspace by default. Documents in non-`_local` scopes must never link into `_local`; only `_local` documents may link to other `_local` documents.
|
package/lib/lint.js
CHANGED
|
@@ -269,7 +269,7 @@ function lintXdrFile(xdrsRoot, scopeName, typeName, filePath, xdrNumbers, errors
|
|
|
269
269
|
const expectedName = extractExpectedXdrNameFromHeading(firstLine)
|
|
270
270
|
|| `${scopeName}-${TYPE_TO_ID[typeName]}-${number}-${match[2]}`;
|
|
271
271
|
lintXdrFrontmatter(content, expectedName, filePath, errors);
|
|
272
|
-
lintDocumentLinks(filePath, errors);
|
|
272
|
+
lintDocumentLinks(filePath, xdrsRoot, scopeName, errors);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
function extractExpectedXdrNameFromHeading(headingLine) {
|
|
@@ -384,7 +384,7 @@ function lintSkillsDirectory(xdrsRoot, scopeName, typeName, subjectName, skillsP
|
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
lintDocumentLinks(skillFilePath, errors);
|
|
387
|
+
lintDocumentLinks(skillFilePath, xdrsRoot, scopeName, errors);
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
return artifacts;
|
|
@@ -437,7 +437,7 @@ function lintArticlesDirectory(xdrsRoot, scopeName, typeName, subjectName, artic
|
|
|
437
437
|
errors.push(`Article title must start with "${expectedHeader}": ${toDisplayPath(entryPath)}`);
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
lintDocumentLinks(entryPath, errors);
|
|
440
|
+
lintDocumentLinks(entryPath, xdrsRoot, scopeName, errors);
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
return artifacts;
|
|
@@ -490,7 +490,7 @@ function lintResearchDirectory(xdrsRoot, scopeName, typeName, subjectName, resea
|
|
|
490
490
|
errors.push(`Research title must start with "${expectedHeader}": ${toDisplayPath(entryPath)}`);
|
|
491
491
|
}
|
|
492
492
|
|
|
493
|
-
lintDocumentLinks(entryPath, errors);
|
|
493
|
+
lintDocumentLinks(entryPath, xdrsRoot, scopeName, errors);
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
return artifacts;
|
|
@@ -544,7 +544,7 @@ function lintPlansDirectory(xdrsRoot, scopeName, typeName, subjectName, plansPat
|
|
|
544
544
|
}
|
|
545
545
|
|
|
546
546
|
lintPlanExpectedEndDate(content, entryPath, errors);
|
|
547
|
-
lintDocumentLinks(entryPath, errors);
|
|
547
|
+
lintDocumentLinks(entryPath, xdrsRoot, scopeName, errors);
|
|
548
548
|
}
|
|
549
549
|
|
|
550
550
|
return artifacts;
|
|
@@ -574,6 +574,8 @@ function lintTypeIndex(indexPath, xdrsRoot, artifacts, errors) {
|
|
|
574
574
|
const content = fs.readFileSync(indexPath, 'utf8');
|
|
575
575
|
const localLinks = parseLocalLinks(content, path.dirname(indexPath));
|
|
576
576
|
const linkedSet = new Set();
|
|
577
|
+
const scopeName = path.relative(xdrsRoot, indexPath).split(path.sep)[0];
|
|
578
|
+
const localScopePath = normalizePath(path.join(xdrsRoot, '_local'));
|
|
577
579
|
|
|
578
580
|
for (const linkPath of localLinks) {
|
|
579
581
|
if (!fs.existsSync(linkPath)) {
|
|
@@ -581,6 +583,10 @@ function lintTypeIndex(indexPath, xdrsRoot, artifacts, errors) {
|
|
|
581
583
|
continue;
|
|
582
584
|
}
|
|
583
585
|
|
|
586
|
+
if (scopeName !== '_local' && (isPathInside(localScopePath, linkPath) || normalizePath(linkPath) === localScopePath)) {
|
|
587
|
+
errors.push(`Non-_local document must not link into _local scope: ${displayPath(indexPath, linkPath)}`);
|
|
588
|
+
}
|
|
589
|
+
|
|
584
590
|
linkedSet.add(normalizePath(linkPath));
|
|
585
591
|
}
|
|
586
592
|
|
|
@@ -591,11 +597,12 @@ function lintTypeIndex(indexPath, xdrsRoot, artifacts, errors) {
|
|
|
591
597
|
}
|
|
592
598
|
}
|
|
593
599
|
|
|
594
|
-
function lintDocumentLinks(documentPath, errors) {
|
|
600
|
+
function lintDocumentLinks(documentPath, xdrsRoot, scopeName, errors) {
|
|
595
601
|
const lines = fs.readFileSync(documentPath, 'utf8').split(/\r?\n/);
|
|
596
602
|
const ignoredLines = findIgnoredMarkdownLines(lines);
|
|
597
603
|
const documentDir = path.dirname(documentPath);
|
|
598
604
|
const resourceDir = path.join(documentDir, RESOURCE_DIR_NAME);
|
|
605
|
+
const localScopePath = normalizePath(path.join(xdrsRoot, '_local'));
|
|
599
606
|
|
|
600
607
|
for (let index = 0; index < lines.length; index += 1) {
|
|
601
608
|
if (ignoredLines[index]) {
|
|
@@ -614,6 +621,10 @@ function lintDocumentLinks(documentPath, errors) {
|
|
|
614
621
|
continue;
|
|
615
622
|
}
|
|
616
623
|
|
|
624
|
+
if (scopeName !== '_local' && (isPathInside(localScopePath, link.resolvedPath) || normalizePath(link.resolvedPath) === localScopePath)) {
|
|
625
|
+
errors.push(`Non-_local document must not link into _local scope in ${toDisplayPath(documentPath)}:${index + 1}: ${link.rawTarget}`);
|
|
626
|
+
}
|
|
627
|
+
|
|
617
628
|
if (isResourceLink && !isPathInside(resourceDir, link.resolvedPath)) {
|
|
618
629
|
errors.push(`Asset links in ${toDisplayPath(documentPath)} must point to ${toDisplayPath(resourceDir)}: ${link.rawTarget}`);
|
|
619
630
|
}
|
package/lib/lint.test.js
CHANGED
|
@@ -108,6 +108,114 @@ test('derives expected frontmatter name from the markdown heading title', () =>
|
|
|
108
108
|
expect(result.errors.join('\n')).not.toContain('XDR frontmatter name must be');
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
test('reports non-_local XDR linking to _local scope document', () => {
|
|
112
|
+
const workspaceRoot = createWorkspace('non-local-links-to-local-xdr', {
|
|
113
|
+
'.xdrs/index.md': rootIndex(),
|
|
114
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
115
|
+
'- [001-main](principles/001-main.md) - Main decision'
|
|
116
|
+
]),
|
|
117
|
+
'.xdrs/_local/adrs/principles/001-main.md': xdrDocument('Local decision.'),
|
|
118
|
+
'.xdrs/myteam/adrs/index.md': teamAdrIndex([
|
|
119
|
+
'- [001-team](principles/001-team.md) - Team decision'
|
|
120
|
+
]),
|
|
121
|
+
'.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument(
|
|
122
|
+
'See [local doc](../../../_local/adrs/principles/001-main.md).'
|
|
123
|
+
),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = lintWorkspace(workspaceRoot, { ignoreReadOnly: false });
|
|
127
|
+
|
|
128
|
+
expect(result.errors.join('\n')).toContain('Non-_local document must not link into _local scope');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('allows _local XDR linking to another _local scope document', () => {
|
|
132
|
+
const workspaceRoot = createWorkspace('local-links-to-local', {
|
|
133
|
+
'.xdrs/index.md': rootIndex(),
|
|
134
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
135
|
+
'- [001-main](principles/001-main.md) - Main decision',
|
|
136
|
+
'- [002-second](principles/002-second.md) - Second decision'
|
|
137
|
+
]),
|
|
138
|
+
'.xdrs/_local/adrs/principles/001-main.md': [
|
|
139
|
+
'---',
|
|
140
|
+
'name: _local-adr-001-main',
|
|
141
|
+
'description: Test XDR document',
|
|
142
|
+
'---',
|
|
143
|
+
'',
|
|
144
|
+
'# _local-adr-001: Main decision',
|
|
145
|
+
'',
|
|
146
|
+
'## Context and Problem Statement',
|
|
147
|
+
'',
|
|
148
|
+
'See [second](002-second.md).',
|
|
149
|
+
''
|
|
150
|
+
].join('\n'),
|
|
151
|
+
'.xdrs/_local/adrs/principles/002-second.md': [
|
|
152
|
+
'---',
|
|
153
|
+
'name: _local-adr-002-second',
|
|
154
|
+
'description: Second test XDR document',
|
|
155
|
+
'---',
|
|
156
|
+
'',
|
|
157
|
+
'# _local-adr-002: Second decision',
|
|
158
|
+
'',
|
|
159
|
+
'## Context and Problem Statement',
|
|
160
|
+
'',
|
|
161
|
+
'Second body.',
|
|
162
|
+
''
|
|
163
|
+
].join('\n'),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const result = lintWorkspace(workspaceRoot, { ignoreReadOnly: false });
|
|
167
|
+
|
|
168
|
+
expect(result.errors.join('\n')).not.toContain('Non-_local document must not link into _local scope');
|
|
169
|
+
expect(result.errors.join('\n')).not.toContain('Broken local link');
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('reports non-_local canonical index linking to _local scope document', () => {
|
|
173
|
+
const workspaceRoot = createWorkspace('non-local-type-index-links-to-local', {
|
|
174
|
+
'.xdrs/index.md': rootIndex(),
|
|
175
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
176
|
+
'- [001-main](principles/001-main.md) - Main decision'
|
|
177
|
+
]),
|
|
178
|
+
'.xdrs/_local/adrs/principles/001-main.md': xdrDocument('Local decision.'),
|
|
179
|
+
'.xdrs/myteam/adrs/index.md': teamAdrIndex([
|
|
180
|
+
'- [_local 001](../../_local/adrs/principles/001-main.md) - Cross-scope link'
|
|
181
|
+
]),
|
|
182
|
+
'.xdrs/myteam/adrs/principles/001-team.md': teamXdrDocument('Team decision.'),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const result = lintWorkspace(workspaceRoot, { ignoreReadOnly: false });
|
|
186
|
+
|
|
187
|
+
expect(result.errors.join('\n')).toContain('Non-_local document must not link into _local scope');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
function teamAdrIndex(entries) {
|
|
191
|
+
return [
|
|
192
|
+
'# myteam ADR Index',
|
|
193
|
+
'',
|
|
194
|
+
'Team ADRs for tests.',
|
|
195
|
+
'',
|
|
196
|
+
'## principles',
|
|
197
|
+
'',
|
|
198
|
+
...entries,
|
|
199
|
+
''
|
|
200
|
+
].join('\n');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function teamXdrDocument(body) {
|
|
204
|
+
return [
|
|
205
|
+
'---',
|
|
206
|
+
'name: myteam-adr-001-team',
|
|
207
|
+
'description: Team test XDR document',
|
|
208
|
+
'---',
|
|
209
|
+
'',
|
|
210
|
+
'# myteam-adr-001: Team decision',
|
|
211
|
+
'',
|
|
212
|
+
'## Context and Problem Statement',
|
|
213
|
+
'',
|
|
214
|
+
body,
|
|
215
|
+
''
|
|
216
|
+
].join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
111
219
|
function createWorkspace(name, files) {
|
|
112
220
|
const workspaceRoot = path.join(tmpRoot, name);
|
|
113
221
|
fs.mkdirSync(workspaceRoot, { recursive: true });
|
package/package.json
CHANGED