xdrs-core 0.55.0 → 0.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.xdrs/_core/adrs/index.md +2 -0
- package/.xdrs/_core/adrs/principles/001-xdrs-standards.md +1 -0
- package/.xdrs/_core/adrs/principles/002-policy-standards.md +2 -0
- package/.xdrs/_core/adrs/principles/003-skill-standards.md +6 -33
- package/.xdrs/_core/adrs/principles/004-article-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/006-research-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/007-initiative-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/010-scope-governance.md +2 -2
- package/.xdrs/_core/adrs/principles/011-core-scope-type.md +1 -1
- package/.xdrs/_core/adrs/principles/019-compiled-scope-type.md +6 -1
- package/.xdrs/_core/adrs/principles/022-scope-activation.md +3 -1
- package/.xdrs/_core/adrs/principles/023-skill-runtime-standards.md +63 -0
- package/.xdrs/_core/adrs/principles/024-sync-date-frontmatter.md +76 -0
- package/.xdrs/_core/adrs/principles/skills/compile-scope/SKILL.md +10 -8
- package/.xdrs/_core/adrs/principles/skills/review/SKILL.md +5 -6
- package/.xdrs/_core/adrs/principles/skills/write-article/SKILL.md +3 -3
- package/.xdrs/_core/adrs/principles/skills/write-initiative/SKILL.md +3 -3
- package/.xdrs/_core/adrs/principles/skills/write-policy/SKILL.md +3 -3
- package/.xdrs/_core/adrs/principles/skills/write-research/SKILL.md +3 -3
- package/.xdrs/_core/adrs/principles/skills/write-skill/SKILL.md +9 -8
- package/.xdrs/_core/adrs/principles/skills/write-xdrs-doc/SKILL.md +4 -4
- package/.xdrs/_core/index.md +1 -1
- package/README.md +2 -1
- package/lib/lint.js +61 -16
- package/lib/lint.test.js +293 -4
- package/package.json +1 -1
|
@@ -6,13 +6,13 @@ description: >
|
|
|
6
6
|
Activate this skill when the user asks to create, add, or write a new skill, agent skill, or SKILL.md file.
|
|
7
7
|
metadata:
|
|
8
8
|
author: flaviostutz
|
|
9
|
-
version: "1.
|
|
10
|
-
updated: 2026-
|
|
9
|
+
version: "1.3.1"
|
|
10
|
+
updated: 2026-11-04
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## Overview
|
|
14
14
|
|
|
15
|
-
Guides the creation of a well-structured skill package by following `_core-adr-policy-003` skill standards, consulting `xdrs-core` for every core element definition, checking existing skills to avoid duplication, and producing a complete SKILL.md ready to activate in VS Code.
|
|
15
|
+
Guides the creation of a well-structured skill package by following `_core-adr-policy-003` skill standards and `_core-adr-policy-023` runtime standards, consulting `xdrs-core` for every core element definition, checking existing skills to avoid duplication, and producing a complete SKILL.md ready to activate in VS Code.
|
|
16
16
|
|
|
17
17
|
### Inputs
|
|
18
18
|
|
|
@@ -57,7 +57,7 @@ Keep every question and intermediate message <100 words and the final summary <1
|
|
|
57
57
|
|
|
58
58
|
### Phase 1: Understand the Skill Goal
|
|
59
59
|
|
|
60
|
-
1. Read `.xdrs/_core/adrs/principles/003-skill-standards.md` in full to internalize the SKILL.md format and
|
|
60
|
+
1. Read `.xdrs/_core/adrs/principles/003-skill-standards.md` and `.xdrs/_core/adrs/principles/023-skill-runtime-standards.md` in full to internalize the SKILL.md format, folder layout and runtime rules.
|
|
61
61
|
2. Read `.xdrs/_core/adrs/principles/001-xdrs-standards.md` in full before defining any core element for the skill package. Treat it as the canonical source for type, scope, subject, naming constraints, and folder placement rules.
|
|
62
62
|
3. Identify what the skill must do, the concrete outcome it should produce, and the exact conditions under which an agent should activate it. Do NOT proceed without a clear goal, outcome, and activation trigger.
|
|
63
63
|
|
|
@@ -90,7 +90,7 @@ Quick test:
|
|
|
90
90
|
|
|
91
91
|
### Phase 4: Write the SKILL.md
|
|
92
92
|
|
|
93
|
-
Use the mandatory agentskills format:
|
|
93
|
+
Use the mandatory agentskills format. Omit the optional `metadata.sync-date` unless the user explicitly asks for it for a specific sync purpose (`_core-adr-policy-024`):
|
|
94
94
|
|
|
95
95
|
```
|
|
96
96
|
---
|
|
@@ -170,9 +170,9 @@ Rules:
|
|
|
170
170
|
- The `description` field must state both *what* the skill does and *when* to activate it.
|
|
171
171
|
- Keep the skill task-oriented. It should have a clear starting trigger and a concrete ending result.
|
|
172
172
|
- Mention tools or prerequisites when they are required to complete the task reliably.
|
|
173
|
-
- Declare a "<N words" cap, or "(uncapped: <reason>)", exactly once for every natural-language content the skill generates, per **Generated content caps** in `_core-adr-policy-
|
|
173
|
+
- Declare a "<N words" cap, or "(uncapped: <reason>)", exactly once for every natural-language content the skill generates, per **Generated content caps** in `_core-adr-policy-023`.
|
|
174
174
|
- Do not duplicate content from referenced Policies — link instead.
|
|
175
|
-
- If the skill creates files at runtime (outputs, intermediate files, ad-hoc scripts), restate in `## Instructions`, next to the steps writing them, the parts of **Work files** in `_core-adr-policy-
|
|
175
|
+
- If the skill creates files at runtime (outputs, intermediate files, ad-hoc scripts), restate in `## Instructions`, next to the steps writing them, the parts of **Work files** in `_core-adr-policy-023` it uses: `.tmp/[skill-name]/[run-name]/` (never files directly in `.tmp/[skill-name]/`), `.work/`, the OS temp dir for throwaway files, and the final `results-path:` line when a run folder is created. This restatement is the one exception to the no-duplication rule, so the skill runs standalone.
|
|
176
176
|
- Do not present the skill itself as policy; mandatory behavior must come from referenced Policies or other policy artifacts.
|
|
177
177
|
- When the skill depends on Policies, make the activation logic and instructions consistent with the Policy metadata so the skill does not operationalize inactive or out-of-scope decisions.
|
|
178
178
|
- For diagrams and non-Markdown assets, follow `_core-adr-policy-020`: prefer plain Markdown tables/lists first, then ASCII art for very simple cases, then Mermaid.js (sequence, state, activity, entity diagrams) for complex ones, then draw.io when Mermaid is insufficient — save as Editable Vector (File → Save As → Editable Vector) and store as `.svg` in the sibling `.assets/` folder.
|
|
@@ -193,7 +193,7 @@ Before writing files, verify:
|
|
|
193
193
|
7. **Required sections**: Does `## Overview` contain nested `### Inputs` (Required/Optional bullets), `### Outputs` (Contents/Changes bullets), and `### Halt Conditions` listing this skill's specific stop triggers — each bullet under 20 words or a single "None"?
|
|
194
194
|
8. **Meta-policy compliance**: Run the shared module at `.xdrs/_core/adrs/principles/skills/.assets/meta-policy-compliance.md`. Substitute `[DOCUMENT]` with `skill`.
|
|
195
195
|
9. **Output caps**: Does every natural-language content the skill generates (files, messages, questions, summaries, external posts) have exactly one "<N words" cap or "(uncapped: <reason>)" marker, with code and structured data unmarked?
|
|
196
|
-
10. **Work files**: If the skill creates files at runtime, do its Instructions restate the
|
|
196
|
+
10. **Work files**: If the skill creates files at runtime, do its Instructions restate the run folder, `.work/`, OS temp dir and `results-path:` rules from `_core-adr-policy-023`, or an explicit alternative location?
|
|
197
197
|
|
|
198
198
|
If any check fails, revise before continuing.
|
|
199
199
|
|
|
@@ -261,6 +261,7 @@ Follow the lint verification steps in `.xdrs/_core/adrs/principles/skills/.asset
|
|
|
261
261
|
## References
|
|
262
262
|
|
|
263
263
|
- [_core-adr-policy-003 - Skill standards](../../003-skill-standards.md)
|
|
264
|
+
- [_core-adr-policy-023 - Skill runtime standards](../../023-skill-runtime-standards.md)
|
|
264
265
|
- [_core-adr-policy-001 - XDRS standards](../../001-xdrs-standards.md)
|
|
265
266
|
- [_core-adr-policy-021 - Skill bundling](../../021-skill-bundling.md)
|
|
266
267
|
- [write-policy skill](../write-policy/SKILL.md)
|
|
@@ -5,8 +5,8 @@ description: >
|
|
|
5
5
|
Activate this skill when the user asks to create or write any XDRS element.
|
|
6
6
|
metadata:
|
|
7
7
|
author: flaviostutz
|
|
8
|
-
version: "1.
|
|
9
|
-
updated: 2026-
|
|
8
|
+
version: "1.3.0"
|
|
9
|
+
updated: 2026-10-08
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
@@ -81,12 +81,12 @@ Read the full content of the skill file for the inferred type, then follow all i
|
|
|
81
81
|
### Phase 3: Validate Mermaid Diagrams
|
|
82
82
|
|
|
83
83
|
1. After the delegated skill completes, scan all files written in this session for fenced ` ```mermaid ` code blocks.
|
|
84
|
-
2. For each diagram found, extract the content to a `.mmd` file in
|
|
84
|
+
2. For each diagram found, extract the content to a `.mmd` file in a temporary folder under the OS temp dir (create it only when the first diagram is found) and run:
|
|
85
85
|
```bash
|
|
86
86
|
npx -y @mermaid-js/mermaid-cli -i <file>.mmd --quiet 2>&1
|
|
87
87
|
```
|
|
88
88
|
3. For each diagram that fails validation, report the error to the user and ask them to correct the diagram before saving.
|
|
89
|
-
4.
|
|
89
|
+
4. Delete the temporary folder and its `.mmd` files when the run ends, including when halting.
|
|
90
90
|
|
|
91
91
|
### Constraints
|
|
92
92
|
|
package/.xdrs/_core/index.md
CHANGED
|
@@ -32,7 +32,7 @@ Each artifact type has its own writing standard:
|
|
|
32
32
|
|
|
33
33
|
- **Policy documents** follow [_core-adr-policy-002](adrs/principles/002-policy-standards.md), which defines the mandatory template, frontmatter metadata, applicability rules, conflict handling, and word limits that keep decisions concise and authoritative.
|
|
34
34
|
- **Structured Policies** with individually referenceable rules follow the extension [_core-adr-policy-008](adrs/principles/008-policy-structured-standards.md), adding numbered rule blocks and a dot-notation citation syntax.
|
|
35
|
-
- **Skills** follow [_core-adr-policy-003](adrs/principles/003-skill-standards.md), using the agentskills format so they work for both humans and AI agents on an automation gradient from fully manual to fully automated.
|
|
35
|
+
- **Skills** follow [_core-adr-policy-003](adrs/principles/003-skill-standards.md) and [_core-adr-policy-023](adrs/principles/023-skill-runtime-standards.md), using the agentskills format so they work for both humans and AI agents on an automation gradient from fully manual to fully automated.
|
|
36
36
|
- **Articles** follow [_core-adr-policy-004](adrs/principles/004-article-standards.md), providing synthetic views that combine and link multiple Policies, research, and skills without replacing them as the source of truth.
|
|
37
37
|
- **Research** follows [_core-adr-policy-006](adrs/principles/006-research-standards.md), using an IMRAD-based structure for studies that back decisions with reproducible evidence.
|
|
38
38
|
- **Initiatives** follow [_core-adr-policy-007](adrs/principles/007-initiative-standards.md), capturing ephemeral execution initiatives with problem context, proposed solutions, milestones, and deliverables that are deleted after implementation.
|
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ Scopes tagged `disabled` MUST be ignored; `extends-only` scopes apply only via e
|
|
|
190
190
|
```
|
|
191
191
|
|
|
192
192
|
- `extends-only`: the scope's policies apply only through scopes that list it in `extends:`.
|
|
193
|
-
- `disabled`: the scope is ignored; scopes that follow, extend or take their type from it become READ-ONLY.
|
|
193
|
+
- `disabled`: the scope is ignored; enabled scopes that follow, extend or take their type from it become READ-ONLY, while a disabled scope may depend on other disabled scopes freely.
|
|
194
194
|
- Scope authors declare `extends-only: true` in the scope `index.md` frontmatter when the scope is meant only to be extended; lint requires the root index tag to match.
|
|
195
195
|
- The root index belongs to the consumer: packages ship it unmanaged so tags survive reinstalls.
|
|
196
196
|
|
|
@@ -217,6 +217,7 @@ The folder layout, file naming, and document format are designed so that AI agen
|
|
|
217
217
|
- The canonical index per scope and type lists all XDRS elements with short descriptions, enabling agents to identify relevant records without reading every file.
|
|
218
218
|
- The root index at `.xdrs/index.md` provides a single entry point for discovery.
|
|
219
219
|
- Policy 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.
|
|
220
|
+
- An optional, opt-in `sync-date` frontmatter field records when an element was last verified or reconciled against external entities (even if nothing changed). It is never added by default; compiled scopes set it in their scope index on every compile. See [_core-adr-policy-024](.xdrs/_core/adrs/principles/024-sync-date-frontmatter.md).
|
|
220
221
|
- Decisions cross-reference each other by Policy ID rather than duplicating content, keeping individual files concise.
|
|
221
222
|
- Subject folders reduce the search space when a query maps to a known domain.
|
|
222
223
|
|
package/lib/lint.js
CHANGED
|
@@ -32,9 +32,10 @@ const SOURCE_SNAPSHOT_DIR_NAME = 'sources';
|
|
|
32
32
|
const SOURCE_TRACKING_FILE_NAME = 'source.md';
|
|
33
33
|
const SKILL_PACKAGE_OPTIONAL_DIRS = new Set(['scripts', 'references', RESOURCE_DIR_NAME, 'dist']);
|
|
34
34
|
|
|
35
|
-
const POLICY_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'apply-to', 'valid-from', 'license', 'metadata', 'freeze-reference']);
|
|
35
|
+
const POLICY_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'apply-to', 'valid-from', 'license', 'metadata', 'freeze-reference', 'sync-date']);
|
|
36
36
|
const SKILL_ALLOWED_FRONTMATTER_KEYS = new Set(['name', 'description', 'license', 'metadata']);
|
|
37
|
-
const SCOPE_INDEX_ALLOWED_FRONTMATTER_KEYS = new Set(['scope-type', 'name', 'description', 'apply-to', 'valid-from', 'license', 'metadata', 'follows', 'extends', 'extends-only']);
|
|
37
|
+
const SCOPE_INDEX_ALLOWED_FRONTMATTER_KEYS = new Set(['scope-type', 'name', 'description', 'apply-to', 'valid-from', 'license', 'metadata', 'follows', 'extends', 'extends-only', 'sync-date']);
|
|
38
|
+
const SYNC_DATE_ONLY_FRONTMATTER_KEYS = new Set(['sync-date']);
|
|
38
39
|
const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.bmp']);
|
|
39
40
|
const SLIDE_FILE_RE = /^.+-slides(?:-[a-z0-9-]+)?\.md$/;
|
|
40
41
|
const SLIDE_MAX_NAME_LENGTH = 64;
|
|
@@ -238,8 +239,11 @@ function lintWorkspace(targetPath, options = {}) {
|
|
|
238
239
|
// Populated during lintScopeIndexFrontmatter: scopeName -> [extendedScopeNames...]
|
|
239
240
|
const extendsRelationships = new Map();
|
|
240
241
|
|
|
242
|
+
// A disabled scope is treated as not present, so it is exempt from dependency checks (_core-adr-policy-022.06)
|
|
243
|
+
const disabledScopes = new Set([...activation].filter(([, entry]) => entry.tag === 'disabled').map(([name]) => name));
|
|
244
|
+
|
|
241
245
|
for (const scopeEntry of scopeEntries) {
|
|
242
|
-
lintScopeDirectory(xdrsRoot, scopeEntry.name, errors, actualTypeIndexes, ignoreExternal, effectiveExternalScopes, knownScopeTypes, extendsRelationships, externalScopes);
|
|
246
|
+
lintScopeDirectory(xdrsRoot, scopeEntry.name, errors, actualTypeIndexes, ignoreExternal, effectiveExternalScopes, knownScopeTypes, extendsRelationships, externalScopes, disabledScopes);
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
// Detect cycles in extends: chains (DFS)
|
|
@@ -434,7 +438,7 @@ function readScopeMeta(scopeIndexPath) {
|
|
|
434
438
|
function lintLocalReadOnlyDependencies(xdrsRoot, scopeMeta, activation, externalScopes, knownScopeTypes, scopeTypeDefiningScopes, errors) {
|
|
435
439
|
const isDisabled = (scopeName) => (activation.get(scopeName) || {}).tag === 'disabled';
|
|
436
440
|
for (const [scopeName, meta] of scopeMeta) {
|
|
437
|
-
if (!meta || externalScopes.has(scopeName)) continue;
|
|
441
|
+
if (!meta || externalScopes.has(scopeName) || isDisabled(scopeName)) continue;
|
|
438
442
|
const causes = [];
|
|
439
443
|
for (const [field, targets] of [['follows', meta.follows], ['extends', meta.extends]]) {
|
|
440
444
|
for (const target of targets) {
|
|
@@ -576,8 +580,9 @@ function deriveScopeNameListEntries(block, lineMatch, inlineValue) {
|
|
|
576
580
|
return parseYamlListItems(block, lineMatch).filter(Boolean);
|
|
577
581
|
}
|
|
578
582
|
|
|
579
|
-
function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot, knownScopeTypes = new Map(), extendsRelationships = new Map(), externalScopes = new Set()) {
|
|
583
|
+
function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot, knownScopeTypes = new Map(), extendsRelationships = new Map(), externalScopes = new Set(), disabledScopes = new Set()) {
|
|
580
584
|
const isExternal = externalScopes.has(scopeName);
|
|
585
|
+
const skipMissingDependencies = isExternal || disabledScopes.has(scopeName);
|
|
581
586
|
const content = fs.readFileSync(scopeIndexPath, 'utf8');
|
|
582
587
|
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
583
588
|
if (!fmMatch) {
|
|
@@ -638,7 +643,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
638
643
|
for (const st of scopeTypes) {
|
|
639
644
|
if (st === '_local') continue;
|
|
640
645
|
if (!knownScopeTypes.has(st)) {
|
|
641
|
-
if (!
|
|
646
|
+
if (!skipMissingDependencies) {
|
|
642
647
|
errors.push(`Scope index scope-type "${st}" has no corresponding ${st}-scope-type policy in the principles of any scope: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-010.10-def-valid-iff-policy-exists]`);
|
|
643
648
|
}
|
|
644
649
|
continue;
|
|
@@ -728,6 +733,9 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
728
733
|
} else if (!isIsoDate(fm.validFrom)) {
|
|
729
734
|
errors.push(`Scope index frontmatter valid-from must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-001]`);
|
|
730
735
|
}
|
|
736
|
+
if (fm.syncDate !== undefined && !isIsoDate(fm.syncDate)) {
|
|
737
|
+
errors.push(`Scope index frontmatter sync-date must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-024.05-placement-and-format]`);
|
|
738
|
+
}
|
|
731
739
|
|
|
732
740
|
let followsEntries = [];
|
|
733
741
|
const followsLineMatch = block.match(/^follows:[ \t]*(.*)/m);
|
|
@@ -759,8 +767,8 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
759
767
|
}
|
|
760
768
|
const followedIndexPath = path.join(xdrsRoot, entry, 'index.md');
|
|
761
769
|
if (!existsFile(followedIndexPath)) {
|
|
762
|
-
// A missing dependency of an external scope makes it READ-ONLY (_core-adr-policy-022.06); lint does not report it.
|
|
763
|
-
if (!
|
|
770
|
+
// A missing dependency of an external scope makes it READ-ONLY and a disabled scope is exempt (_core-adr-policy-022.06); lint does not report it.
|
|
771
|
+
if (!skipMissingDependencies) {
|
|
764
772
|
errors.push(`Scope index frontmatter follows references scope "${entry}" which does not exist in the workspace: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-001]`);
|
|
765
773
|
}
|
|
766
774
|
} else {
|
|
@@ -816,7 +824,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
816
824
|
continue;
|
|
817
825
|
}
|
|
818
826
|
if (!existsFile(path.join(xdrsRoot, entry, 'index.md'))) {
|
|
819
|
-
if (!
|
|
827
|
+
if (!skipMissingDependencies) {
|
|
820
828
|
errors.push(`Scope index frontmatter extends references scope "${entry}" which does not exist in the workspace: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-010.32-extends-mandatory-presence]`);
|
|
821
829
|
}
|
|
822
830
|
}
|
|
@@ -829,7 +837,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
829
837
|
}
|
|
830
838
|
|
|
831
839
|
|
|
832
|
-
function lintScopeDirectory(xdrsRoot, scopeName, errors, actualTypeIndexes, ignoreExternal, externalScopes, knownScopeTypes = new Map(), extendsRelationships = new Map(), trackedExternalScopes = new Set()) {
|
|
840
|
+
function lintScopeDirectory(xdrsRoot, scopeName, errors, actualTypeIndexes, ignoreExternal, externalScopes, knownScopeTypes = new Map(), extendsRelationships = new Map(), trackedExternalScopes = new Set(), disabledScopes = new Set()) {
|
|
833
841
|
const scopePath = path.join(xdrsRoot, scopeName);
|
|
834
842
|
|
|
835
843
|
if (ignoreExternal && externalScopes.has(scopeName)) {
|
|
@@ -888,7 +896,7 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, actualTypeIndexes, igno
|
|
|
888
896
|
errors.push(`Missing required scope index: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-001]`);
|
|
889
897
|
} else {
|
|
890
898
|
lintScopeIndex(scopeIndexPath, xdrsRoot, scopeName, typeIndexesInScope, errors, externalScopes);
|
|
891
|
-
lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot, knownScopeTypes, extendsRelationships, trackedExternalScopes);
|
|
899
|
+
lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot, knownScopeTypes, extendsRelationships, trackedExternalScopes, disabledScopes);
|
|
892
900
|
}
|
|
893
901
|
lintLocalMetaPolicies(xdrsRoot, scopeName, errors, isCompiledScope);
|
|
894
902
|
}
|
|
@@ -1122,7 +1130,7 @@ function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePat
|
|
|
1122
1130
|
|
|
1123
1131
|
const expectedName = extractExpectedXdrsNameFromHeading(firstLine)
|
|
1124
1132
|
|| `${scopeName}-${TYPE_TO_ID[typeName]}-${number}-${match[2]}`;
|
|
1125
|
-
lintXdrsElementFrontmatter(content, expectedName, filePath, errors, freezeReference);
|
|
1133
|
+
lintXdrsElementFrontmatter(content, expectedName, filePath, errors, freezeReference, isCompiledScope);
|
|
1126
1134
|
if (enforceStructured) {
|
|
1127
1135
|
validateStructuredFormatContent(content, filePath, errors, '_core-adr-policy-010.12-def-core-scope-policies-must-be-structured');
|
|
1128
1136
|
} else if (isCompiledScope) {
|
|
@@ -1166,7 +1174,7 @@ function slugifyTitle(title) {
|
|
|
1166
1174
|
.replace(/--+/g, '-');
|
|
1167
1175
|
}
|
|
1168
1176
|
|
|
1169
|
-
function lintXdrsElementFrontmatter(content, expectedName, filePath, errors, freezeReference = false) {
|
|
1177
|
+
function lintXdrsElementFrontmatter(content, expectedName, filePath, errors, freezeReference = false, isCompiledScope = false) {
|
|
1170
1178
|
const fm = extractFrontmatter(content);
|
|
1171
1179
|
if (!fm.present) {
|
|
1172
1180
|
errors.push(`Policy must start with a YAML frontmatter block: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
@@ -1210,6 +1218,34 @@ function lintXdrsElementFrontmatter(content, expectedName, filePath, errors, fre
|
|
|
1210
1218
|
errors.push(`Policy frontmatter has unknown field "${key}": ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
1211
1219
|
}
|
|
1212
1220
|
}
|
|
1221
|
+
if (fm.syncDate !== undefined) {
|
|
1222
|
+
if (isCompiledScope) {
|
|
1223
|
+
errors.push(`Policy frontmatter must not include sync-date in a compiled scope (it belongs in the scope index): ${toDisplayPath(filePath)} [_core-adr-policy-024.07-compiled-scopes]`);
|
|
1224
|
+
} else if (!isIsoDate(fm.syncDate)) {
|
|
1225
|
+
errors.push(`Policy frontmatter sync-date must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(filePath)} [_core-adr-policy-024.05-placement-and-format]`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/**
|
|
1231
|
+
* Validates the optional frontmatter block of articles, research and initiatives: it may only
|
|
1232
|
+
* contain a valid `sync-date` (_core-adr-policy-024.05-placement-and-format).
|
|
1233
|
+
*/
|
|
1234
|
+
function lintSyncDateOnlyFrontmatter(content, filePath, docType, policyId, errors) {
|
|
1235
|
+
const fm = extractFrontmatter(content);
|
|
1236
|
+
if (!fm.present) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
for (const key of fm.topLevelKeys) {
|
|
1240
|
+
if (!SYNC_DATE_ONLY_FRONTMATTER_KEYS.has(key)) {
|
|
1241
|
+
errors.push(`${docType} frontmatter has unknown field "${key}" (only sync-date is allowed): ${toDisplayPath(filePath)} [${policyId}]`);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (fm.syncDate === undefined) {
|
|
1245
|
+
errors.push(`${docType} frontmatter must include a sync-date field when present: ${toDisplayPath(filePath)} [${policyId}]`);
|
|
1246
|
+
} else if (!isIsoDate(fm.syncDate)) {
|
|
1247
|
+
errors.push(`${docType} frontmatter sync-date must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(filePath)} [_core-adr-policy-024.05-placement-and-format]`);
|
|
1248
|
+
}
|
|
1213
1249
|
}
|
|
1214
1250
|
|
|
1215
1251
|
function lintSkillsDirectory(xdrsRoot, scopeName, skillsPath, errors, externalScopes = new Set()) {
|
|
@@ -1346,6 +1382,10 @@ function lintSkillMetadata(metadata, skillFilePath, errors) {
|
|
|
1346
1382
|
} else if (!isIsoDate(fields.updated)) {
|
|
1347
1383
|
errors.push(`SKILL.md frontmatter metadata.updated must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(skillFilePath)} [_core-adr-policy-003]`);
|
|
1348
1384
|
}
|
|
1385
|
+
|
|
1386
|
+
if (fields['sync-date'] !== undefined && !isIsoDate(fields['sync-date'])) {
|
|
1387
|
+
errors.push(`SKILL.md frontmatter metadata.sync-date must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(skillFilePath)} [_core-adr-policy-024.05-placement-and-format]`);
|
|
1388
|
+
}
|
|
1349
1389
|
}
|
|
1350
1390
|
|
|
1351
1391
|
/**
|
|
@@ -1715,10 +1755,11 @@ function lintArticlesDirectory(xdrsRoot, scopeName, typeName, subjectName, artic
|
|
|
1715
1755
|
const content = fs.readFileSync(entryPath, 'utf8');
|
|
1716
1756
|
const typeId = TYPE_TO_ID[typeName].split('-')[0]; // 'adr', 'bdr', or 'edr'
|
|
1717
1757
|
const expectedHeader = `# ${scopeName}-${typeId}-article-${number}:`;
|
|
1718
|
-
const firstLine = firstNonEmptyLine(content);
|
|
1758
|
+
const firstLine = firstNonEmptyLine(stripFrontmatter(content));
|
|
1719
1759
|
if (!firstLine.startsWith(expectedHeader)) {
|
|
1720
1760
|
errors.push(`Article title must start with "${expectedHeader}": ${toDisplayPath(entryPath)} [_core-adr-policy-004]`);
|
|
1721
1761
|
}
|
|
1762
|
+
lintSyncDateOnlyFrontmatter(content, entryPath, 'Article', '_core-adr-policy-004', errors);
|
|
1722
1763
|
|
|
1723
1764
|
lintRequiredSections(content, entryPath, ARTICLE_REQUIRED_SECTIONS, 'Article', errors);
|
|
1724
1765
|
lintNoEmojis(content, entryPath, 'Article', errors);
|
|
@@ -1770,10 +1811,11 @@ function lintResearchDirectory(xdrsRoot, scopeName, typeName, subjectName, resea
|
|
|
1770
1811
|
const content = fs.readFileSync(entryPath, 'utf8');
|
|
1771
1812
|
const typeId = TYPE_TO_ID[typeName].split('-')[0]; // 'adr', 'bdr', or 'edr'
|
|
1772
1813
|
const expectedHeader = `# ${scopeName}-${typeId}-research-${number}:`;
|
|
1773
|
-
const firstLine = firstNonEmptyLine(content);
|
|
1814
|
+
const firstLine = firstNonEmptyLine(stripFrontmatter(content));
|
|
1774
1815
|
if (!firstLine.startsWith(expectedHeader)) {
|
|
1775
1816
|
errors.push(`Research title must start with "${expectedHeader}": ${toDisplayPath(entryPath)} [_core-adr-policy-006]`);
|
|
1776
1817
|
}
|
|
1818
|
+
lintSyncDateOnlyFrontmatter(content, entryPath, 'Research', '_core-adr-policy-006', errors);
|
|
1777
1819
|
|
|
1778
1820
|
lintRequiredSections(content, entryPath, RESEARCH_REQUIRED_SECTIONS, 'Research', errors);
|
|
1779
1821
|
lintResearchIntroductionQuestion(content, entryPath, errors);
|
|
@@ -1827,10 +1869,11 @@ function lintInitiativesDirectory(xdrsRoot, scopeName, typeName, subjectName, in
|
|
|
1827
1869
|
const content = fs.readFileSync(entryPath, 'utf8');
|
|
1828
1870
|
const typeId = TYPE_TO_ID[typeName].split('-')[0]; // 'adr', 'bdr', or 'edr'
|
|
1829
1871
|
const expectedHeader = `# ${scopeName}-${typeId}-initiative-${number}:`;
|
|
1830
|
-
const firstLine = firstNonEmptyLine(content);
|
|
1872
|
+
const firstLine = firstNonEmptyLine(stripFrontmatter(content));
|
|
1831
1873
|
if (!firstLine.startsWith(expectedHeader)) {
|
|
1832
1874
|
errors.push(`Initiative title must start with "${expectedHeader}": ${toDisplayPath(entryPath)} [_core-adr-policy-007]`);
|
|
1833
1875
|
}
|
|
1876
|
+
lintSyncDateOnlyFrontmatter(content, entryPath, 'Initiative', '_core-adr-policy-007', errors);
|
|
1834
1877
|
|
|
1835
1878
|
lintRequiredSections(content, entryPath, INITIATIVE_REQUIRED_SECTIONS, 'Initiative', errors);
|
|
1836
1879
|
lintInitiativeExpectedEndDate(content, entryPath, errors);
|
|
@@ -2381,6 +2424,7 @@ function extractFrontmatter(content) {
|
|
|
2381
2424
|
const nameMatch = block.match(/^name:\s*(.+)$/m);
|
|
2382
2425
|
const validFromMatch = block.match(/^valid-from:\s*(.+)$/m);
|
|
2383
2426
|
const appliedToMatch = block.match(/^apply-to:\s*(.+)$/m);
|
|
2427
|
+
const syncDateMatch = block.match(/^sync-date:[ \t]*(.*)$/m);
|
|
2384
2428
|
const topLevelKeys = [];
|
|
2385
2429
|
for (const line of block.split('\n')) {
|
|
2386
2430
|
const keyMatch = line.match(/^([a-zA-Z][a-zA-Z0-9_-]*):\s*/);
|
|
@@ -2396,6 +2440,7 @@ function extractFrontmatter(content) {
|
|
|
2396
2440
|
descriptionText,
|
|
2397
2441
|
validFrom: validFromMatch ? validFromMatch[1].trim() : undefined,
|
|
2398
2442
|
appliedTo: appliedToMatch ? appliedToMatch[1].trim() : undefined,
|
|
2443
|
+
syncDate: syncDateMatch ? syncDateMatch[1].trim() : undefined,
|
|
2399
2444
|
freezeReference: freezeReferenceMatch ? freezeReferenceMatch[1].trim() === 'true' : false,
|
|
2400
2445
|
metadata,
|
|
2401
2446
|
topLevelKeys,
|