xdrs-core 0.21.0 → 0.21.2
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/principles/001-xdrs-core.md +3 -2
- package/.xdrs/_core/adrs/principles/002-xdr-standards.md +13 -11
- package/.xdrs/_core/adrs/principles/003-skill-standards.md +3 -2
- package/.xdrs/_core/adrs/principles/004-article-standards.md +3 -2
- package/.xdrs/_core/adrs/principles/006-research-standards.md +2 -1
- package/.xdrs/_core/adrs/principles/007-plan-standards.md +2 -1
- package/.xdrs/_core/adrs/principles/skills/001-lint/SKILL.md +2 -2
- package/.xdrs/_core/adrs/principles/skills/002-write-xdr/SKILL.md +8 -8
- package/.xdrs/_core/adrs/principles/skills/003-write-skill/SKILL.md +1 -1
- package/.xdrs/_core/adrs/principles/skills/004-write-article/SKILL.md +1 -1
- package/.xdrs/_core/adrs/principles/skills/005-write-research/SKILL.md +1 -1
- package/.xdrs/_core/adrs/principles/skills/006-write-plan/SKILL.md +1 -1
- package/.xdrs/_core/bdrs/principles/001-xdr-decisions-and-skills-usage.md +1 -1
- package/.xdrs/index.md +1 -1
- package/README.md +2 -2
- package/lib/lint.js +72 -15
- package/lib/lint.test.js +223 -1
- package/package.json +1 -1
|
@@ -51,15 +51,16 @@ Collectively, these are referred to as XDRs.
|
|
|
51
51
|
- `_core-adr-006` defines research standards
|
|
52
52
|
- `_core-adr-007` defines plan standards
|
|
53
53
|
- For simple structures, flows, layout, or relationship indications, documents SHOULD prefer plain Markdown, tables, Mermaid.js (sequence, state, activity, entity diagrams) or ASCII art instead of external assets.
|
|
54
|
-
-
|
|
54
|
+
- Any non-Markdown supporting files referenced by a document (schemas, JSON examples, images, diagrams, binaries, or any other data files) SHOULD be used only when they are materially necessary to preserve clarity, fidelity, or evidence. When used, they MUST live in a sibling `.assets/` folder next to the document.
|
|
55
55
|
- XDRs in the subject root use `[xdrs-root]/[scope]/[type]/[subject]/.assets/`
|
|
56
56
|
- Articles use `[xdrs-root]/[scope]/[type]/[subject]/articles/.assets/`
|
|
57
57
|
- Research uses `[xdrs-root]/[scope]/[type]/[subject]/researches/.assets/`
|
|
58
58
|
- Skills use `[xdrs-root]/[scope]/[type]/[subject]/skills/[number]-[skill-name]/.assets/`
|
|
59
|
+
- Sub-directories inside `.assets/` are allowed to keep related files organized only when that `.assets/` folder already has more than 10 files. Otherwise, keep files flat in `.assets/`.
|
|
59
60
|
- **Scopes:**
|
|
60
61
|
- Short name that defines a group or a package of xdrs
|
|
61
62
|
- examples: `business-x`, `business-y`, `team-43`, `_core`
|
|
62
|
-
- `_local` is a reserved scope for XDRs created locally to a specific project or repository. XDRs in `_local`
|
|
63
|
+
- `_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 the root `index.md` so that its decisions override or extend any decisions from higher-positioned scopes. Shared root `index.md` files MUST NOT include an explicit link to `_local`, because `_local` remains workspace-local and is not distributed with shared packages. Readers, tools, and agents SHOULD still try the default workspace-local path `_local/index.md` when it exists, even without a root-index link. Documents in non-`_local` scopes MUST NEVER link to any document inside `_local`; documents inside `_local` MAY link to other documents inside `_local`.
|
|
63
64
|
- **Types:** `adrs`, `bdrs`, `edrs`
|
|
64
65
|
- there can exist sufixes to the standard scope names (e.g: `business-x-mobileapp`, `business-y-servicedesk`)
|
|
65
66
|
- **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,8 +25,8 @@ 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
|
-
| `
|
|
29
|
-
| `
|
|
28
|
+
| `apply-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
|
+
| `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. |
|
|
32
32
|
|
|
@@ -42,23 +42,23 @@ XDR documents are the authoritative policy for their scope, type, and subject. T
|
|
|
42
42
|
---
|
|
43
43
|
name: _core-adr-002-xdr-standards
|
|
44
44
|
description: Defines how XDR documents should be written. Use when writing or reviewing any XDR.
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
apply-to: All XDR scopes
|
|
46
|
+
valid-from: 2026-06-01
|
|
47
47
|
metadata:
|
|
48
48
|
author: example-org
|
|
49
49
|
---
|
|
50
50
|
```
|
|
51
51
|
- All documents present in the collection are considered active. There is no status field. When a decision is no longer relevant, valid or active, it must be removed from the collection. Historical versions are available via versioned packages or git history.
|
|
52
52
|
- Before using, enforcing, or citing an XDR as a current rule, humans and AI agents MUST decide whether the decision is applicable for the current case.
|
|
53
|
-
- Check `
|
|
54
|
-
- Check `
|
|
53
|
+
- Check `valid-from:` first. If a date is present and has not yet been reached, the decision SHOULD be adopted for new implementations but is not enforced during reviews.
|
|
54
|
+
- Check `apply-to:` next to determine whether the decision fits the current codebase, system, workflow, or audience.
|
|
55
55
|
- Check the decision context and implementation details last to determine any additional boundaries, exceptions, or qualifiers that metadata alone cannot express.
|
|
56
56
|
- Research documents MAY be added under the same subject to capture the exploration, findings, and proposals that backed a decision. Research is useful during elaboration, discussion, and updates of XDRs, but the XDR document remains the source of truth.
|
|
57
57
|
- **XDR Id:** [scope]-[type]-[xdr number] (numbers are scoped per type+scope combination and must not be reused within that combination; always use lowercase)
|
|
58
58
|
- Types in IDs: `adr`, `bdr`, `edr`
|
|
59
59
|
- Define the next number of an XDR by checking what is the highest number present in the type+scope. Don't fill numbering gaps, as they might be old deleted XDRs and we should never reuse numbers of different documents/decisions. Numbering gaps are expected.
|
|
60
60
|
- Decisions MUST be concise and reference other XDRs to avoid duplication.
|
|
61
|
-
- The `### Details` section SHOULD state relevant boundaries or exceptions and what a reader should do or avoid in common cases. Use the frontmatter fields `
|
|
61
|
+
- The `### Details` section SHOULD state relevant boundaries or exceptions and what a reader should do or avoid in common cases. Use the frontmatter fields `apply-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
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.
|
|
64
64
|
- Conflict handling applies to XDR documents:
|
|
@@ -67,6 +67,8 @@ XDR documents are the authoritative policy for their scope, type, and subject. T
|
|
|
67
67
|
- When research exists for a decision, the XDR SHOULD mention the related research documents after the `## Considered Options` list.
|
|
68
68
|
- Never use emojis in contents.
|
|
69
69
|
- Always use file names with lowercase.
|
|
70
|
+
- Any non-Markdown files referenced by an XDR (schemas, JSON examples, images, diagrams, binaries, or any other data files) SHOULD be used only when they are materially necessary and MUST live in `[xdrs-root]/[scope]/[type]/[subject]/.assets/`.
|
|
71
|
+
- Sub-directories inside this `.assets/` folder are allowed only when it already has more than 10 files. Otherwise, keep files flat.
|
|
70
72
|
- Avoid using lengthy instructions on the XDR. If there are long and detailed instructions related to the XDR, or instructions that are outside the decision, create another file with a guide. If the guide is small, keep it in the XDR itself.
|
|
71
73
|
- XDRs should be under 1300 words long as a rule of thumb.
|
|
72
74
|
- This is important to make them focused on a clear decision
|
|
@@ -81,8 +83,8 @@ All XDRs MUST follow this template
|
|
|
81
83
|
---
|
|
82
84
|
name: [scope]-[type]-[number]-[short-title]
|
|
83
85
|
description: [What this decision is about and when to use it]
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
apply-to: [Optional. Contexts this decision applies to, under 40 words]
|
|
87
|
+
valid-from: [Optional. ISO date YYYY-MM-DD from when enforcement begins]
|
|
86
88
|
license: [Optional. SPDX license expression]
|
|
87
89
|
metadata:
|
|
88
90
|
[optional-key]: [optional-value]
|
|
@@ -137,8 +139,8 @@ Question: In the end, state explicitly the question that needs to be answered. E
|
|
|
137
139
|
|
|
138
140
|
**Examples:**
|
|
139
141
|
- Frontmatter examples:
|
|
140
|
-
- `
|
|
141
|
-
- `
|
|
142
|
+
- `valid-from: 2026-03-01`
|
|
143
|
+
- `apply-to: JavaScript projects`
|
|
142
144
|
|
|
143
145
|
**XDR ID Examples:**
|
|
144
146
|
- `business-x-adr-001` (not `ADR-business-x-001` or `business-x-adr-1`)
|
|
@@ -36,7 +36,7 @@ Skills are procedures, XDRs are guardrails and decisions, Research documents cap
|
|
|
36
36
|
Always create links back and forth between skills <-> XDRs when the relationship is direct, and link to related Research or Articles when they provide important context.
|
|
37
37
|
- Skills are task-based artifacts. They should have a clear starting trigger, an expected end result, and enough detail for a human or agent to verify that the task finished correctly.
|
|
38
38
|
- A skill is not policy by itself. If following a skill is mandatory, that obligation must come from an XDR or another explicit policy that references the skill.
|
|
39
|
-
- When a skill reads, operationalizes, or enforces XDRs, it MUST evaluate the XDR metadata first. `
|
|
39
|
+
- When a skill reads, operationalizes, or enforces XDRs, it MUST evaluate the XDR metadata first. `valid-from:` determines the convergence date for adoption, `apply-to:` determines whether the decision fits the current task context, and the decision text itself determines any remaining boundaries. All documents present in the collection are considered active. Skills must not treat out-of-window or out-of-scope XDRs as current requirements.
|
|
40
40
|
- Skills and XDRs have a many-to-many relationship: one skill may operationalize multiple XDRs, and one XDR may be executed through multiple skills in different contexts.
|
|
41
41
|
|
|
42
42
|
Place a skill under the XDR type that matches the nature of the activity the skill performs:
|
|
@@ -114,7 +114,8 @@ Rules:
|
|
|
114
114
|
- `## Overview` SHOULD state the task objective, expected outcome, and relevant prerequisites or tools when they matter.
|
|
115
115
|
- `## Instructions` SHOULD include verification steps or acceptance criteria at the end of the task, or at the end of major phases when partial validation matters.
|
|
116
116
|
- For simple structure, flow, layout, or relationship indications, `SKILL.md` SHOULD prefer plain Markdown, tables, or ASCII art instead of external assets.
|
|
117
|
-
-
|
|
117
|
+
- Any non-Markdown files referenced from `SKILL.md` (schemas, JSON examples, images, diagrams, binaries, or any other data files) SHOULD be used only when they are materially necessary and MUST live in `.assets/` inside the same skill package.
|
|
118
|
+
- Sub-directories inside this `.assets/` folder are allowed only when it already has more than 10 files. Otherwise, keep files flat.
|
|
118
119
|
- Keep `SKILL.md` under 6500 words. Move lengthy reference material to `references/`.
|
|
119
120
|
- Use relative paths for all links; never use absolute paths starting with `/`.
|
|
120
121
|
- Always use lowercase file names.
|
|
@@ -24,11 +24,12 @@ Articles are Markdown documents placed inside a subject folder alongside decisio
|
|
|
24
24
|
- Articles must reference the XDRs, Research documents, and Skills they synthesize. Never duplicate decision content; link back to the authoritative sources.
|
|
25
25
|
- Articles may serve as indexes, combining related artifacts on a specific topic into a single navigable document.
|
|
26
26
|
- In more complex cases, an article may be an index of links to other articles, grouping related documentation into a single entry point that guides readers across a set of related topics.
|
|
27
|
-
- When an article tells readers which decisions to follow, it SHOULD check `
|
|
27
|
+
- When an article tells readers which decisions to follow, it SHOULD check `valid-from:` first to determine the convergence date, `apply-to:` second to determine context fit, and the decision text itself last. All documents present in the collection are considered active; articles must not present out-of-window or out-of-scope XDRs as current rules for the discussed context.
|
|
28
28
|
- Articles must remain consistent with the XDRs, Research documents, and Skills they reference. When a referenced artifact changes, the article must be reviewed and updated.
|
|
29
29
|
- Place an article in the subject folder that best matches its topic using the required list of subjects per type defined in `_core-adr-001`. If an article spans more than one subject, place it in `principles`.
|
|
30
30
|
- For simple structure, flow, layout, or relationship indications, articles SHOULD prefer plain Markdown, tables, or ASCII art instead of external assets.
|
|
31
|
-
-
|
|
31
|
+
- Any non-Markdown files referenced by an article (schemas, JSON examples, images, diagrams, binaries, or any other data files) SHOULD be used only when they are materially necessary and MUST live in `articles/.assets/` next to the article files.
|
|
32
|
+
- Sub-directories inside this `.assets/` folder are allowed only when it already has more than 10 files. Otherwise, keep files flat.
|
|
32
33
|
- Always use lowercase file names.
|
|
33
34
|
- Never use emojis in article content.
|
|
34
35
|
- Articles should be kept under 1950 words. Move detailed content to referenced XDRs or Skills.
|
|
@@ -48,7 +48,8 @@ Research documents are Markdown files placed inside a subject folder alongside d
|
|
|
48
48
|
- Research documents SHOULD link in `## References` to the XDRs, skills, articles, discussions, and external references relevant to the subject or that later cite the work.
|
|
49
49
|
- A 1:1 relationship between one research document and one decision will likely be common in practice, but it is not required.
|
|
50
50
|
- One research document MAY also be referenced by multiple XDRs, including a mix of ADRs, BDRs, and EDRs, when the same investigation remains relevant across several decisions.
|
|
51
|
-
-
|
|
51
|
+
- Any non-Markdown files referenced by a research document (schemas, JSON examples, images, diagrams, binaries, or any other data files) SHOULD be used only when they are materially necessary and MUST live in `researches/.assets/` next to the research files.
|
|
52
|
+
- Sub-directories inside this `.assets/` folder are allowed only when it already has more than 10 files. Otherwise, keep files flat.
|
|
52
53
|
- Research file names MUST be lowercase. Never use emojis.
|
|
53
54
|
- A research document MAY exist before the related XDR is written, or remain after the XDR changes, as long as its status and references stay clear.
|
|
54
55
|
|
|
@@ -31,7 +31,8 @@ Plans are Markdown documents placed inside a subject folder alongside decision r
|
|
|
31
31
|
- Plans MUST include an `Expected end date:` field in ISO format (YYYY-MM-DD) inside the `## Proposed Solution` section.
|
|
32
32
|
- Always use lowercase file names.
|
|
33
33
|
- Never use emojis in plan content.
|
|
34
|
-
-
|
|
34
|
+
- Any non-Markdown files referenced by a plan (schemas, JSON examples, images, diagrams, binaries, or any other data files) SHOULD be used only when they are materially necessary and MUST live in `plans/.assets/` next to the plan files.
|
|
35
|
+
- Sub-directories inside this `.assets/` folder are allowed only when it already has more than 10 files. Otherwise, keep files flat.
|
|
35
36
|
|
|
36
37
|
**Folder layout**
|
|
37
38
|
|
|
@@ -26,8 +26,8 @@ Performs a structured review of code changes or files against the XDRs in the re
|
|
|
26
26
|
- XDR scopes are controlled by nested folders; some are broad, others domain-specific.
|
|
27
27
|
- Extract frontmatter first to decide whether each XDR should be used for the current review context.
|
|
28
28
|
- All documents present in the collection are considered active.
|
|
29
|
-
- Check `
|
|
30
|
-
- Check `
|
|
29
|
+
- Check `valid-from:` first. If a date is present and has not yet been reached, the decision SHOULD be adopted for new implementations but is not enforced during reviews.
|
|
30
|
+
- Check `apply-to:` second. Keep only XDRs whose stated scope fits the files, systems, or workflows under review.
|
|
31
31
|
- Check the decision text itself last for additional boundaries or exceptions that metadata does not encode.
|
|
32
32
|
2. Filter relevance based on file types, domains, and architectural patterns in scope.
|
|
33
33
|
|
|
@@ -58,7 +58,7 @@ Choose a title that clearly states the question this XDR answers, not the answer
|
|
|
58
58
|
### Phase 4: Research Related XDRs
|
|
59
59
|
|
|
60
60
|
1. Read all existing XDRs relevant to the topic across all scopes listed in the XDR root `index.md`.
|
|
61
|
-
2. Evaluate XDR metadata before treating any decision as a current constraint. All documents present in the collection are considered active. `
|
|
61
|
+
2. Evaluate XDR metadata before treating any decision as a current constraint. All documents present in the collection are considered active. `valid-from:` determines the convergence date for adoption, `apply-to:` determines whether it fits the current topic, and the decision text defines any remaining boundaries. Treat out-of-window or out-of-scope XDRs as background only when assessing overlaps and conflicts.
|
|
62
62
|
3. Identify decisions that already address the topic (full or partial overlap).
|
|
63
63
|
4. Note decisions that might conflict with the intended outcome.
|
|
64
64
|
5. Read related `researches/` documents when they exist, especially if they contain constraints, findings, or option tradeoffs that should influence the decision.
|
|
@@ -107,8 +107,8 @@ Refer to `_core-adr-008-xdr-standards-structured` for full requirements and cita
|
|
|
107
107
|
---
|
|
108
108
|
name: [scope]-[type]-[number]-[short-title]
|
|
109
109
|
description: [What this decision is about and when to use it]
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
apply-to: [Optional. Contexts this decision applies to, under 40 words]
|
|
111
|
+
valid-from: [Optional. ISO date YYYY-MM-DD from when enforcement begins]
|
|
112
112
|
---
|
|
113
113
|
|
|
114
114
|
# [scope]-[type]-[number]: [Short Title]
|
|
@@ -132,10 +132,10 @@ validFrom: [Optional. ISO date YYYY-MM-DD from when enforcement begins]
|
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
Mandatory rules to apply while drafting:
|
|
135
|
-
- Include frontmatter `
|
|
136
|
-
- Include frontmatter `
|
|
137
|
-
- Keep `
|
|
138
|
-
- When frontmatter metadata is present, write it so a reader can decide whether the XDR should be used for the current case without guessing. `
|
|
135
|
+
- Include frontmatter `apply-to:` only when it adds value by narrowing the decision scope; omit it when the decision applies broadly.
|
|
136
|
+
- Include frontmatter `valid-from:` only when there is a specific future enforcement date; omit it when the decision is immediately effective.
|
|
137
|
+
- Keep `apply-to:` under 40 words and use `valid-from:` only with `YYYY-MM-DD` ISO format.
|
|
138
|
+
- When frontmatter metadata is present, write it so a reader can decide whether the XDR should be used for the current case without guessing. `valid-from:` sets a convergence date for adoption, `apply-to:` narrows the contexts where the decision applies, and the decision text defines any remaining boundaries.
|
|
139
139
|
- Use mandatory language ("must", "always", "never") only for hard requirements; use advisory language ("should", "recommended") for guidance.
|
|
140
140
|
- Do not duplicate content already in referenced XDRs — link instead.
|
|
141
141
|
- Keep the decision itself authoritative in the XDR. Supporting artifacts may elaborate, but they should not restate the full decision when a short reference is enough.
|
|
@@ -152,7 +152,7 @@ Mandatory rules to apply while drafting:
|
|
|
152
152
|
Check every item before finalizing:
|
|
153
153
|
|
|
154
154
|
1. **Length**: Is it under 1300 words? Trim verbose explanations. Move detailed skills to a separate file and link.
|
|
155
|
-
2. **Frontmatter**: Are `
|
|
155
|
+
2. **Frontmatter**: Are `apply-to:` and `valid-from:` present only when they add value, omitted entirely when not needed, and specific enough for a reader to decide whether the XDR is currently valid and applicable?
|
|
156
156
|
3. **Originality**: Does every sentence add value that cannot be found in a generic web search? Remove obvious advice. Keep only the project-specific decision.
|
|
157
157
|
4. **Clarity**: Is the chosen option unambiguous? Is the "why" clear in one reading?
|
|
158
158
|
5. **Redundancy**: Is the XDR the primary source for the decision itself, with related documents linked instead of duplicated wherever possible?
|
|
@@ -51,7 +51,7 @@ Quick test:
|
|
|
51
51
|
|
|
52
52
|
1. List `.xdrs/[scope]/[type]/[subject]/skills/` for existing skills. If one already covers the goal, extend or reference it instead of creating a duplicate.
|
|
53
53
|
2. Read all XDRs relevant to the skill's domain to collect rules and cross-references.
|
|
54
|
-
3. Evaluate XDR metadata before operationalizing those rules. All documents present in the collection are considered active. `
|
|
54
|
+
3. Evaluate XDR metadata before operationalizing those rules. All documents present in the collection are considered active. `valid-from:` determines the convergence date for adoption, `apply-to:` determines whether the decision fits the intended task context, and the decision text defines any remaining boundaries. Keep out-of-window or out-of-scope XDRs as background only.
|
|
55
55
|
4. Decide whether the skill is merely guidance or is being referenced by an XDR as a mandatory procedure. Do not encode policy in the skill unless it comes from a referenced XDR.
|
|
56
56
|
|
|
57
57
|
### Phase 4: Write the SKILL.md
|
|
@@ -63,7 +63,7 @@ If the article spans more than one subject, place it in `principles`.
|
|
|
63
63
|
### Phase 4: Research XDRs and Skills to Synthesize
|
|
64
64
|
|
|
65
65
|
1. Read all XDRs, Research documents, and Skills relevant to the article topic across all scopes listed in the XDR root `index.md`.
|
|
66
|
-
2. Evaluate XDR metadata before synthesizing guidance. All documents present in the collection are considered active. Use `
|
|
66
|
+
2. Evaluate XDR metadata before synthesizing guidance. All documents present in the collection are considered active. Use `valid-from:` to determine the convergence date for adoption, `apply-to:` to determine whether the decision fits the audience or context being discussed, and the decision text itself for any remaining applicability boundaries.
|
|
67
67
|
3. Identify the key points a reader needs to understand the topic end-to-end.
|
|
68
68
|
4. Collect XDR IDs and file paths for cross-references. Never copy decision text verbatim; link to it.
|
|
69
69
|
|
|
@@ -50,7 +50,7 @@ Consult `001-xdrs-core` while making each choice in this phase. The summaries be
|
|
|
50
50
|
### Phase 3: Research Existing Artifacts
|
|
51
51
|
|
|
52
52
|
1. Read relevant XDRs across all scopes listed in the XDR root `index.md`.
|
|
53
|
-
2. Evaluate XDR metadata before treating any decision as current context. All documents present in the collection are considered active. `
|
|
53
|
+
2. Evaluate XDR metadata before treating any decision as current context. All documents present in the collection are considered active. `valid-from:` determines the convergence date for adoption, `apply-to:` determines whether the decision fits the intended task context, and the decision text defines any remaining boundaries. Keep out-of-window or out-of-scope XDRs as background only.
|
|
54
54
|
3. Read existing research documents in the same or overlapping subjects to avoid duplicating the same study.
|
|
55
55
|
4. Read related skills or articles if they contain context, implementation limits, or terminology that must be reflected.
|
|
56
56
|
5. Collect links that should appear in the final `## References` section.
|
|
@@ -53,7 +53,7 @@ Consult `001-xdrs-core` while making each choice in this phase. The summaries be
|
|
|
53
53
|
### Phase 4: Research Related Artifacts
|
|
54
54
|
|
|
55
55
|
1. Read all XDRs, Research documents, Skills, and existing Plans relevant to the plan topic across all scopes listed in the XDR root `index.md`.
|
|
56
|
-
2. Evaluate XDR metadata before treating any decision as current context. All documents present in the collection are considered active. `
|
|
56
|
+
2. Evaluate XDR metadata before treating any decision as current context. All documents present in the collection are considered active. `valid-from:` determines the convergence date for adoption, `apply-to:` determines whether the decision fits the intended context, and the decision text defines any remaining boundaries.
|
|
57
57
|
3. Identify Decisions that this plan implements, Research that informs the planning, and any existing Plans that overlap.
|
|
58
58
|
4. Collect artifact IDs and file paths for cross-references.
|
|
59
59
|
|
|
@@ -19,7 +19,7 @@ XDR decisions are the authoritative source of mandatory policy. Skills are execu
|
|
|
19
19
|
|
|
20
20
|
### Details
|
|
21
21
|
|
|
22
|
-
- Before treating any XDR as a current requirement, evaluate applicability in order: `
|
|
22
|
+
- Before treating any XDR as a current requirement, evaluate applicability in order: `valid-from`, `apply-to`, then the decision text.
|
|
23
23
|
- The set of policies that an agent or human must comply with for a given operational context must be declared in BDRs.
|
|
24
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
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.
|
package/.xdrs/index.md
CHANGED
|
@@ -19,4 +19,4 @@ Decisions about how XDRs work
|
|
|
19
19
|
|
|
20
20
|
### _local (reserved)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Read _local scope index at `_local/index.md` when it exists.
|
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ The folder layout, file naming, and document format are designed so that AI agen
|
|
|
66
66
|
- Each XDR is a small, focused Markdown file (target under 1300 words), covering one decision.
|
|
67
67
|
- The canonical index per scope and type lists all XDRs with short descriptions, enabling agents to identify relevant records without reading every file.
|
|
68
68
|
- The root index at `.xdrs/index.md` provides a single entry point for discovery.
|
|
69
|
-
- XDR metadata gives agents a first-pass filter: check `
|
|
69
|
+
- XDR metadata gives agents a first-pass filter: check `valid-from` for the convergence date, then check `apply-to`, and finally the decision text itself to confirm the decision should be used in the current context. All documents present in the collection are considered active.
|
|
70
70
|
- Decisions cross-reference each other by XDR ID rather than duplicating content, keeping individual files concise.
|
|
71
71
|
- Subject folders reduce the search space when a query maps to a known domain.
|
|
72
72
|
|
|
@@ -166,7 +166,7 @@ The `lint` command reads `./.xdrs/**` from the given workspace path and checks c
|
|
|
166
166
|
- plan `Expected end date:` field presence and ISO date format
|
|
167
167
|
- canonical index presence and link consistency
|
|
168
168
|
- root index coverage for all discovered canonical indexes
|
|
169
|
-
- XDR metadata section placement and `
|
|
169
|
+
- XDR metadata section placement and `valid-from` / `apply-to` field format
|
|
170
170
|
- local markdown links between XDR documents, skills, articles, researches, and plans (excluding fenced code blocks)
|
|
171
171
|
- local image and `.assets/` links resolving inside the sibling `.assets/` folder for each document
|
|
172
172
|
|
package/lib/lint.js
CHANGED
|
@@ -23,6 +23,10 @@ const NUMBERED_DIR_RE = /^(\d{3,})-([a-z0-9-]+)$/;
|
|
|
23
23
|
const REQUIRED_ROOT_INDEX_TEXT = 'XDRs in scopes listed last override the ones listed first';
|
|
24
24
|
const SUBJECT_ARTIFACT_DIRS = new Set(['skills', 'articles', 'researches', 'plans']);
|
|
25
25
|
const RESOURCE_DIR_NAME = '.assets';
|
|
26
|
+
const SKILL_PACKAGE_OPTIONAL_DIRS = new Set(['scripts', 'references', RESOURCE_DIR_NAME]);
|
|
27
|
+
|
|
28
|
+
const XDR_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'apply-to', 'valid-from', 'license', 'metadata']);
|
|
29
|
+
const SKILL_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'license', 'metadata', 'compatibility', 'allowed-tools']);
|
|
26
30
|
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp']);
|
|
27
31
|
|
|
28
32
|
function runLintCli(args) {
|
|
@@ -371,15 +375,20 @@ function lintXdrFrontmatter(content, expectedName, filePath, errors) {
|
|
|
371
375
|
}
|
|
372
376
|
if (fm.validFrom !== undefined) {
|
|
373
377
|
if (!isIsoDate(fm.validFrom)) {
|
|
374
|
-
errors.push(`XDR frontmatter
|
|
378
|
+
errors.push(`XDR frontmatter valid-from must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(filePath)}`);
|
|
375
379
|
}
|
|
376
380
|
}
|
|
377
381
|
if (fm.appliedTo !== undefined) {
|
|
378
382
|
const words = countWords(fm.appliedTo);
|
|
379
383
|
if (words === 0) {
|
|
380
|
-
errors.push(`XDR frontmatter
|
|
384
|
+
errors.push(`XDR frontmatter apply-to must not be empty: ${toDisplayPath(filePath)}`);
|
|
381
385
|
} else if (words >= 40) {
|
|
382
|
-
errors.push(`XDR frontmatter
|
|
386
|
+
errors.push(`XDR frontmatter apply-to must be under 40 words: ${toDisplayPath(filePath)}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const key of fm.topLevelKeys) {
|
|
390
|
+
if (!XDR_ALLOWED_FRONTMATTER_KEYS.has(key)) {
|
|
391
|
+
errors.push(`XDR frontmatter has unknown field "${key}": ${toDisplayPath(filePath)}`);
|
|
383
392
|
}
|
|
384
393
|
}
|
|
385
394
|
}
|
|
@@ -415,6 +424,21 @@ function lintSkillsDirectory(xdrsRoot, scopeName, typeName, subjectName, skillsP
|
|
|
415
424
|
}
|
|
416
425
|
|
|
417
426
|
const skillFilePath = path.join(entryPath, 'SKILL.md');
|
|
427
|
+
const packageEntries = safeReadDir(entryPath, errors, `read skill package ${toDisplayPath(entryPath)}`);
|
|
428
|
+
|
|
429
|
+
for (const packageEntry of packageEntries) {
|
|
430
|
+
const packageEntryPath = path.join(entryPath, packageEntry.name);
|
|
431
|
+
|
|
432
|
+
if (!packageEntry.isDirectory()) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (SKILL_PACKAGE_OPTIONAL_DIRS.has(packageEntry.name)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
errors.push(`Unexpected directory in skill package: ${toDisplayPath(packageEntryPath)}`);
|
|
441
|
+
}
|
|
418
442
|
|
|
419
443
|
if (!existsFile(skillFilePath)) {
|
|
420
444
|
errors.push(`Missing SKILL.md in skill package: ${toDisplayPath(entryPath)}`);
|
|
@@ -437,6 +461,11 @@ function lintSkillsDirectory(xdrsRoot, scopeName, typeName, subjectName, skillsP
|
|
|
437
461
|
if (!skillFm.description) {
|
|
438
462
|
errors.push(`SKILL.md frontmatter must include a non-empty description field: ${toDisplayPath(skillFilePath)}`);
|
|
439
463
|
}
|
|
464
|
+
for (const key of skillFm.topLevelKeys) {
|
|
465
|
+
if (!SKILL_ALLOWED_FRONTMATTER_KEYS.has(key)) {
|
|
466
|
+
errors.push(`SKILL.md frontmatter has unknown field "${key}": ${toDisplayPath(skillFilePath)}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
440
469
|
}
|
|
441
470
|
|
|
442
471
|
lintDocumentLinks(skillFilePath, xdrsRoot, scopeName, errors, externalScopes);
|
|
@@ -673,17 +702,15 @@ function lintOrphanAssets(assetsDir, documentPaths, xdrsRoot, errors) {
|
|
|
673
702
|
return;
|
|
674
703
|
}
|
|
675
704
|
|
|
676
|
-
const
|
|
677
|
-
if (
|
|
705
|
+
const assetTree = collectAssetTree(assetsDir, errors);
|
|
706
|
+
if (assetTree.files.length === 0 && assetTree.directories.length === 0) {
|
|
678
707
|
return;
|
|
679
708
|
}
|
|
680
709
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (assetFiles.length === 0) {
|
|
686
|
-
return;
|
|
710
|
+
if (assetTree.directories.length > 0 && assetTree.files.length <= 10) {
|
|
711
|
+
for (const directoryPath of assetTree.directories) {
|
|
712
|
+
errors.push(`.assets directory must be flat unless it already contains more than 10 files: ${toDisplayPath(directoryPath)}`);
|
|
713
|
+
}
|
|
687
714
|
}
|
|
688
715
|
|
|
689
716
|
const repoRoot = path.dirname(xdrsRoot);
|
|
@@ -701,13 +728,37 @@ function lintOrphanAssets(assetsDir, documentPaths, xdrsRoot, errors) {
|
|
|
701
728
|
}
|
|
702
729
|
}
|
|
703
730
|
|
|
704
|
-
for (const assetPath of
|
|
731
|
+
for (const assetPath of assetTree.files) {
|
|
705
732
|
if (!referencedAssets.has(assetPath)) {
|
|
706
733
|
errors.push(`Orphan asset file not referenced by any document: ${toDisplayPath(assetPath)}`);
|
|
707
734
|
}
|
|
708
735
|
}
|
|
709
736
|
}
|
|
710
737
|
|
|
738
|
+
function collectAssetTree(dirPath, errors) {
|
|
739
|
+
const files = [];
|
|
740
|
+
const directories = [];
|
|
741
|
+
const entries = safeReadDir(dirPath, errors, `read assets directory ${toDisplayPath(dirPath)}`);
|
|
742
|
+
|
|
743
|
+
for (const entry of entries) {
|
|
744
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
745
|
+
|
|
746
|
+
if (entry.isDirectory()) {
|
|
747
|
+
directories.push(normalizePath(entryPath));
|
|
748
|
+
const nestedTree = collectAssetTree(entryPath, errors);
|
|
749
|
+
directories.push(...nestedTree.directories);
|
|
750
|
+
files.push(...nestedTree.files);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (entry.isFile()) {
|
|
755
|
+
files.push(normalizePath(entryPath));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return { files, directories };
|
|
760
|
+
}
|
|
761
|
+
|
|
711
762
|
function lintDocumentLinks(documentPath, xdrsRoot, scopeName, errors, externalScopes = new Set()) {
|
|
712
763
|
const lines = fs.readFileSync(documentPath, 'utf8').split(/\r?\n/);
|
|
713
764
|
const ignoredLines = findIgnoredMarkdownLines(lines);
|
|
@@ -834,18 +885,24 @@ function shouldValidateResourceLink(rawTarget) {
|
|
|
834
885
|
function extractFrontmatter(content) {
|
|
835
886
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/);
|
|
836
887
|
if (!match) {
|
|
837
|
-
return { present: false, name: null, description: false, validFrom: undefined, appliedTo: undefined };
|
|
888
|
+
return { present: false, name: null, description: false, validFrom: undefined, appliedTo: undefined, topLevelKeys: [] };
|
|
838
889
|
}
|
|
839
890
|
const block = match[1];
|
|
840
891
|
const nameMatch = block.match(/^name:\s*(.+)$/m);
|
|
841
|
-
const validFromMatch = block.match(/^
|
|
842
|
-
const appliedToMatch = block.match(/^
|
|
892
|
+
const validFromMatch = block.match(/^valid-from:\s*(.+)$/m);
|
|
893
|
+
const appliedToMatch = block.match(/^apply-to:\s*(.+)$/m);
|
|
894
|
+
const topLevelKeys = [];
|
|
895
|
+
for (const line of block.split('\n')) {
|
|
896
|
+
const keyMatch = line.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*/);
|
|
897
|
+
if (keyMatch) topLevelKeys.push(keyMatch[1]);
|
|
898
|
+
}
|
|
843
899
|
return {
|
|
844
900
|
present: true,
|
|
845
901
|
name: nameMatch ? nameMatch[1].trim() || null : null,
|
|
846
902
|
description: /^description:\s*\S/m.test(block),
|
|
847
903
|
validFrom: validFromMatch ? validFromMatch[1].trim() : undefined,
|
|
848
904
|
appliedTo: appliedToMatch ? appliedToMatch[1].trim() : undefined,
|
|
905
|
+
topLevelKeys,
|
|
849
906
|
};
|
|
850
907
|
}
|
|
851
908
|
|
package/lib/lint.test.js
CHANGED
|
@@ -168,6 +168,134 @@ test('skips entire external scope when only some of its files are in filedist',
|
|
|
168
168
|
expect(result.errors.join('\n')).not.toContain('Broken local link in');
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
test('reports unknown frontmatter fields in XDR documents', () => {
|
|
172
|
+
const workspaceRoot = createWorkspace('xdr-unknown-frontmatter', {
|
|
173
|
+
'.xdrs/index.md': rootIndex(),
|
|
174
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
175
|
+
'- [001-main](principles/001-main.md) - Main decision'
|
|
176
|
+
]),
|
|
177
|
+
'.xdrs/_local/adrs/principles/001-main.md': [
|
|
178
|
+
'---',
|
|
179
|
+
'name: _local-adr-001-main',
|
|
180
|
+
'description: Test XDR document',
|
|
181
|
+
'unknown-field: some value',
|
|
182
|
+
'---',
|
|
183
|
+
'',
|
|
184
|
+
'# _local-adr-001: Main decision',
|
|
185
|
+
'',
|
|
186
|
+
'## Context and Problem Statement',
|
|
187
|
+
'',
|
|
188
|
+
'Test body.',
|
|
189
|
+
'',
|
|
190
|
+
'## Decision Outcome',
|
|
191
|
+
'',
|
|
192
|
+
'Test decision outcome.',
|
|
193
|
+
''
|
|
194
|
+
].join('\n'),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const result = lintWorkspace(workspaceRoot);
|
|
198
|
+
|
|
199
|
+
expect(result.errors.join('\n')).toContain('XDR frontmatter has unknown field "unknown-field"');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('accepts all known frontmatter fields in XDR documents', () => {
|
|
203
|
+
const workspaceRoot = createWorkspace('xdr-known-frontmatter', {
|
|
204
|
+
'.xdrs/index.md': rootIndex(),
|
|
205
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
206
|
+
'- [001-main](principles/001-main.md) - Main decision'
|
|
207
|
+
]),
|
|
208
|
+
'.xdrs/_local/adrs/principles/001-main.md': [
|
|
209
|
+
'---',
|
|
210
|
+
'name: _local-adr-001-main',
|
|
211
|
+
'description: Test XDR document',
|
|
212
|
+
'apply-to: All projects',
|
|
213
|
+
'valid-from: 2026-01-01',
|
|
214
|
+
'license: MIT',
|
|
215
|
+
'metadata:',
|
|
216
|
+
' author: test',
|
|
217
|
+
'---',
|
|
218
|
+
'',
|
|
219
|
+
'# _local-adr-001: Main decision',
|
|
220
|
+
'',
|
|
221
|
+
'## Context and Problem Statement',
|
|
222
|
+
'',
|
|
223
|
+
'Test body.',
|
|
224
|
+
'',
|
|
225
|
+
'## Decision Outcome',
|
|
226
|
+
'',
|
|
227
|
+
'Test decision outcome.',
|
|
228
|
+
''
|
|
229
|
+
].join('\n'),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const result = lintWorkspace(workspaceRoot);
|
|
233
|
+
|
|
234
|
+
expect(result.errors.join('\n')).not.toContain('unknown field');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('reports unknown frontmatter fields in SKILL.md files', () => {
|
|
238
|
+
const workspaceRoot = createWorkspace('skill-unknown-frontmatter', {
|
|
239
|
+
'.xdrs/index.md': rootIndex(),
|
|
240
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
241
|
+
'- [001-check-links](principles/skills/001-check-links/SKILL.md) - Check links'
|
|
242
|
+
]),
|
|
243
|
+
'.xdrs/_local/adrs/principles/skills/001-check-links/SKILL.md': [
|
|
244
|
+
'---',
|
|
245
|
+
'name: 001-check-links',
|
|
246
|
+
'description: Test skill document',
|
|
247
|
+
'unknown-field: some value',
|
|
248
|
+
'---',
|
|
249
|
+
'',
|
|
250
|
+
'## Overview',
|
|
251
|
+
'',
|
|
252
|
+
'Test skill overview.',
|
|
253
|
+
'',
|
|
254
|
+
'## Instructions',
|
|
255
|
+
'',
|
|
256
|
+
'Test instructions.',
|
|
257
|
+
''
|
|
258
|
+
].join('\n'),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const result = lintWorkspace(workspaceRoot);
|
|
262
|
+
|
|
263
|
+
expect(result.errors.join('\n')).toContain('SKILL.md frontmatter has unknown field "unknown-field"');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('accepts all known frontmatter fields in SKILL.md files', () => {
|
|
267
|
+
const workspaceRoot = createWorkspace('skill-known-frontmatter', {
|
|
268
|
+
'.xdrs/index.md': rootIndex(),
|
|
269
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
270
|
+
'- [001-check-links](principles/skills/001-check-links/SKILL.md) - Check links'
|
|
271
|
+
]),
|
|
272
|
+
'.xdrs/_local/adrs/principles/skills/001-check-links/SKILL.md': [
|
|
273
|
+
'---',
|
|
274
|
+
'name: 001-check-links',
|
|
275
|
+
'description: Test skill document',
|
|
276
|
+
'license: MIT',
|
|
277
|
+
'metadata:',
|
|
278
|
+
' version: "1.0"',
|
|
279
|
+
'compatibility: node >= 18',
|
|
280
|
+
'allowed-tools: read_file grep_search',
|
|
281
|
+
'---',
|
|
282
|
+
'',
|
|
283
|
+
'## Overview',
|
|
284
|
+
'',
|
|
285
|
+
'Test skill overview.',
|
|
286
|
+
'',
|
|
287
|
+
'## Instructions',
|
|
288
|
+
'',
|
|
289
|
+
'Test instructions.',
|
|
290
|
+
''
|
|
291
|
+
].join('\n'),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const result = lintWorkspace(workspaceRoot);
|
|
295
|
+
|
|
296
|
+
expect(result.errors.join('\n')).not.toContain('unknown field');
|
|
297
|
+
});
|
|
298
|
+
|
|
171
299
|
test('derives expected frontmatter name from the markdown heading title', () => {
|
|
172
300
|
const workspaceRoot = createWorkspace('heading-name-match', {
|
|
173
301
|
'.xdrs/index.md': rootIndex(),
|
|
@@ -514,6 +642,100 @@ test('reports orphan asset in articles .assets directory', () => {
|
|
|
514
642
|
expect(result.errors.join('\n')).not.toContain('used.png');
|
|
515
643
|
});
|
|
516
644
|
|
|
645
|
+
test('reports unexpected directories inside a skill package', () => {
|
|
646
|
+
const workspaceRoot = createWorkspace('unexpected-skill-package-directory', {
|
|
647
|
+
'.xdrs/index.md': rootIndex(),
|
|
648
|
+
'.xdrs/_local/index.md': '# _local Scope Overview\n\nOverview.\n\n[ADRs](adrs/index.md)\n',
|
|
649
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
650
|
+
'- [001-check-links](principles/skills/001-check-links/SKILL.md) - Check local links'
|
|
651
|
+
]),
|
|
652
|
+
'.xdrs/_local/adrs/principles/skills/001-check-links/SKILL.md': skillDocument('Skill body.'),
|
|
653
|
+
'.xdrs/_local/adrs/principles/skills/001-check-links/extras/note.txt': 'unexpected',
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const result = lintWorkspace(workspaceRoot);
|
|
657
|
+
|
|
658
|
+
expect(result.errors.join('\n')).toContain('Unexpected directory in skill package');
|
|
659
|
+
expect(result.errors.join('\n')).toContain('extras');
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test('reports nested directories in .assets when it has 10 files or fewer', () => {
|
|
663
|
+
const workspaceRoot = createWorkspace('asset-subdir-too-small', {
|
|
664
|
+
'.xdrs/index.md': rootIndex(),
|
|
665
|
+
'.xdrs/_local/index.md': '# _local Scope Overview\n\nOverview.\n\n[ADRs](adrs/index.md)\n',
|
|
666
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
667
|
+
'- [001-main](principles/001-main.md) - Main decision'
|
|
668
|
+
]),
|
|
669
|
+
'.xdrs/_local/adrs/principles/001-main.md': [
|
|
670
|
+
'---',
|
|
671
|
+
'name: _local-adr-001-main-decision',
|
|
672
|
+
'description: Test XDR document',
|
|
673
|
+
'---',
|
|
674
|
+
'',
|
|
675
|
+
'# _local-adr-001: Main decision',
|
|
676
|
+
'',
|
|
677
|
+
'## Context and Problem Statement',
|
|
678
|
+
'',
|
|
679
|
+
'See .',
|
|
680
|
+
'',
|
|
681
|
+
'## Decision Outcome',
|
|
682
|
+
'',
|
|
683
|
+
'Test decision outcome.',
|
|
684
|
+
''
|
|
685
|
+
].join('\n'),
|
|
686
|
+
'.xdrs/_local/adrs/principles/.assets/used.png': Buffer.alloc(0),
|
|
687
|
+
'.xdrs/_local/adrs/principles/.assets/grouped/extra.png': Buffer.alloc(0),
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const result = lintWorkspace(workspaceRoot);
|
|
691
|
+
|
|
692
|
+
expect(result.errors.join('\n')).toContain('.assets directory must be flat unless it already contains more than 10 files');
|
|
693
|
+
expect(result.errors.join('\n')).toContain('.assets/grouped');
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test('allows nested directories in .assets when it has more than 10 files', () => {
|
|
697
|
+
const workspaceRoot = createWorkspace('asset-subdir-large-enough', {
|
|
698
|
+
'.xdrs/index.md': rootIndex(),
|
|
699
|
+
'.xdrs/_local/index.md': '# _local Scope Overview\n\nOverview.\n\n[ADRs](adrs/index.md)\n',
|
|
700
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
701
|
+
'- [001-main](principles/001-main.md) - Main decision'
|
|
702
|
+
]),
|
|
703
|
+
'.xdrs/_local/adrs/principles/001-main.md': [
|
|
704
|
+
'---',
|
|
705
|
+
'name: _local-adr-001-main-decision',
|
|
706
|
+
'description: Test XDR document',
|
|
707
|
+
'---',
|
|
708
|
+
'',
|
|
709
|
+
'# _local-adr-001: Main decision',
|
|
710
|
+
'',
|
|
711
|
+
'## Context and Problem Statement',
|
|
712
|
+
'',
|
|
713
|
+
'See .',
|
|
714
|
+
'',
|
|
715
|
+
'## Decision Outcome',
|
|
716
|
+
'',
|
|
717
|
+
'Test decision outcome.',
|
|
718
|
+
''
|
|
719
|
+
].join('\n'),
|
|
720
|
+
'.xdrs/_local/adrs/principles/.assets/used.png': Buffer.alloc(0),
|
|
721
|
+
'.xdrs/_local/adrs/principles/.assets/01.png': Buffer.alloc(0),
|
|
722
|
+
'.xdrs/_local/adrs/principles/.assets/02.png': Buffer.alloc(0),
|
|
723
|
+
'.xdrs/_local/adrs/principles/.assets/03.png': Buffer.alloc(0),
|
|
724
|
+
'.xdrs/_local/adrs/principles/.assets/04.png': Buffer.alloc(0),
|
|
725
|
+
'.xdrs/_local/adrs/principles/.assets/05.png': Buffer.alloc(0),
|
|
726
|
+
'.xdrs/_local/adrs/principles/.assets/06.png': Buffer.alloc(0),
|
|
727
|
+
'.xdrs/_local/adrs/principles/.assets/07.png': Buffer.alloc(0),
|
|
728
|
+
'.xdrs/_local/adrs/principles/.assets/08.png': Buffer.alloc(0),
|
|
729
|
+
'.xdrs/_local/adrs/principles/.assets/09.png': Buffer.alloc(0),
|
|
730
|
+
'.xdrs/_local/adrs/principles/.assets/10.png': Buffer.alloc(0),
|
|
731
|
+
'.xdrs/_local/adrs/principles/.assets/grouped/11.png': Buffer.alloc(0),
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
const result = lintWorkspace(workspaceRoot);
|
|
735
|
+
|
|
736
|
+
expect(result.errors.join('\n')).not.toContain('.assets directory must be flat unless it already contains more than 10 files');
|
|
737
|
+
});
|
|
738
|
+
|
|
517
739
|
function teamAdrIndex(entries) {
|
|
518
740
|
return [
|
|
519
741
|
'# myteam ADR Index',
|
|
@@ -575,7 +797,7 @@ function rootIndex(extraScopeLinks) {
|
|
|
575
797
|
lines.push(
|
|
576
798
|
'### _local (reserved)',
|
|
577
799
|
'',
|
|
578
|
-
'
|
|
800
|
+
'Read _local scope index at `_local/index.md` when it exists.',
|
|
579
801
|
);
|
|
580
802
|
return lines.join('\n');
|
|
581
803
|
}
|
package/package.json
CHANGED