xdrs-core 0.14.5 → 0.15.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/principles/001-xdrs-core.md +6 -0
- package/.xdrs/_core/adrs/principles/002-xdr-standards.md +50 -16
- package/.xdrs/_core/adrs/principles/003-skill-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/004-article-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/005-semantic-versioning-for-xdr-packages.md +5 -0
- package/.xdrs/_core/adrs/principles/006-research-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/007-plan-standards.md +5 -0
- package/.xdrs/_core/adrs/principles/skills/001-lint/SKILL.md +3 -3
- package/.xdrs/_core/adrs/principles/skills/003-write-skill/003-write-skill.test.int.report +1 -2
- package/.xdrs/_core/adrs/principles/skills/004-write-article/004-write-article.test.int.report +2 -6
- package/.xdrs/_core/adrs/principles/skills/005-write-research/005-write-research.test.int.js +1 -1
- package/lib/lint.js +60 -121
- package/lib/testPrompt.js +1 -0
- package/package.json +1 -1
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-001-xdrs-core
|
|
3
|
+
description: Defines the core XDR framework including types (ADR, BDR, EDR), folder structure, scopes, subjects, and index requirements. Use when structuring or navigating decision records.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# _core-adr-001: XDRs core
|
|
2
7
|
|
|
3
8
|
## Context and Problem Statement
|
|
@@ -31,6 +36,7 @@ Collectively, these are referred to as XDRs.
|
|
|
31
36
|
- ALWAYS use the following folder structure for XDR documents:
|
|
32
37
|
`.xdrs/[scope]/[type]/[subject]/[number]-[short-title].md`
|
|
33
38
|
- ALWAYS ignore symlinks paths. NEVER create or update documents inside symlinked folders.
|
|
39
|
+
- **Readonly files are external XDRs.** A file with read-only permissions (e.g., `444` set by a distribution tool such as filedist) was distributed from an external source repository. It must NEVER be modified locally. To change it, submit the change to the source repository and re-extract the updated package.
|
|
34
40
|
- Optional supporting artifacts under the same subject:
|
|
35
41
|
- `.xdrs/[scope]/[type]/[subject]/researches/[number]-[short-title].md`
|
|
36
42
|
- `.xdrs/[scope]/[type]/[subject]/skills/[number]-[skill-name]/SKILL.md`
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-002-xdr-standards
|
|
3
|
+
description: Defines how XDR decision documents should be written, including template, frontmatter, applicability fields, and conflict handling. Use when writing or reviewing any XDR document.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# _core-adr-002: XDR standards
|
|
2
7
|
|
|
3
8
|
## Context and Problem Statement
|
|
@@ -14,21 +19,46 @@ XDR documents are the authoritative policy for their scope, type, and subject. T
|
|
|
14
19
|
|
|
15
20
|
- XDRs MUST contain a clear decision about a certain problem or situation. Avoid being too verbose and focus on explaining clearly the context and the decision. Avoid adding contents that are not original. If you have other references that are important to understand the document, add links and references. Always cite sources.
|
|
16
21
|
- XDRs are the central artifact of the framework and the authoritative policy for their scope, type, and subject. Supporting artifacts may explain, justify, or operationalize the decision (like articles, researches and skills), but they do not replace it.
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
- XDR documents MUST include a YAML frontmatter block at the very beginning of the file. The supported fields are:
|
|
23
|
+
|
|
24
|
+
| Field | Required | Constraints |
|
|
25
|
+
|---|---|---|
|
|
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
|
+
| `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`. |
|
|
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
|
+
| `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
|
+
| `metadata` | No | Arbitrary key-value map for additional properties not defined by this spec. |
|
|
32
|
+
|
|
33
|
+
- Minimal example:
|
|
34
|
+
```yaml
|
|
35
|
+
---
|
|
36
|
+
name: _core-adr-002-xdr-standards
|
|
37
|
+
description: Defines how XDR documents should be written. Use when writing or reviewing any XDR.
|
|
38
|
+
---
|
|
39
|
+
```
|
|
40
|
+
- Example with optional fields:
|
|
41
|
+
```yaml
|
|
42
|
+
---
|
|
43
|
+
name: _core-adr-002-xdr-standards
|
|
44
|
+
description: Defines how XDR documents should be written. Use when writing or reviewing any XDR.
|
|
45
|
+
applied-to: All XDR scopes
|
|
46
|
+
valid-from: 2026-06-01
|
|
47
|
+
metadata:
|
|
48
|
+
author: example-org
|
|
49
|
+
---
|
|
50
|
+
```
|
|
21
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.
|
|
22
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.
|
|
23
|
-
- Check `
|
|
24
|
-
- 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 `applied-to:` next to determine whether the decision fits the current codebase, system, workflow, or audience.
|
|
25
55
|
- Check the decision context and implementation details last to determine any additional boundaries, exceptions, or qualifiers that metadata alone cannot express.
|
|
26
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.
|
|
27
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)
|
|
28
58
|
- Types in IDs: `adr`, `bdr`, `edr`
|
|
29
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.
|
|
30
60
|
- Decisions MUST be concise and reference other XDRs to avoid duplication.
|
|
31
|
-
- The `### Implementation Details` section SHOULD state relevant boundaries or exceptions and what a reader should do or avoid in common cases. Use
|
|
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.
|
|
32
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.
|
|
33
63
|
- Conflict handling applies to XDR documents:
|
|
34
64
|
- For cross-scope overrides, document the decision conflict in the XDR `## Conflicts` section of the XDR that overrides another scope.
|
|
@@ -47,13 +77,17 @@ XDR documents are the authoritative policy for their scope, type, and subject. T
|
|
|
47
77
|
All XDRs MUST follow this template
|
|
48
78
|
|
|
49
79
|
```markdown
|
|
50
|
-
|
|
80
|
+
---
|
|
81
|
+
name: [scope]-[type]-[number]-[short-title]
|
|
82
|
+
description: [What this decision is about and when to use it]
|
|
83
|
+
applied-to: [Optional. Contexts this decision applies to, under 40 words]
|
|
84
|
+
valid-from: [Optional. ISO date YYYY-MM-DD from when enforcement begins]
|
|
85
|
+
license: [Optional. SPDX license expression]
|
|
86
|
+
metadata:
|
|
87
|
+
[optional-key]: [optional-value]
|
|
88
|
+
---
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
[Optional section. Omit the entire section when none of `Valid:` or `Applied to:` is defined. Readers decide whether to use the XDR by checking `Valid:` first, then `Applied to:`, and finally the decision text itself.]
|
|
55
|
-
Valid: [Optional. Use `from YYYY-MM-DD` to set a convergence date for adoption]
|
|
56
|
-
Applied to: [Optional short applicability scope, under 40 words]
|
|
90
|
+
# [scope]-[type]-[number]: [Short Title]
|
|
57
91
|
|
|
58
92
|
## Context and Problem Statement
|
|
59
93
|
|
|
@@ -93,9 +127,9 @@ Question: In the end, state explicitly the question that needs to be answered. E
|
|
|
93
127
|
```
|
|
94
128
|
|
|
95
129
|
**Examples:**
|
|
96
|
-
-
|
|
97
|
-
- `
|
|
98
|
-
- `
|
|
130
|
+
- Frontmatter examples:
|
|
131
|
+
- `valid-from: 2026-03-01`
|
|
132
|
+
- `applied-to: JavaScript projects`
|
|
99
133
|
|
|
100
134
|
**XDR ID Examples:**
|
|
101
135
|
- `business-x-adr-001` (not `ADR-business-x-001` or `business-x-adr-1`)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-003-skill-standards
|
|
3
|
+
description: Defines skill package standards including structure, SKILL.md format, and co-location with XDRs. Use when creating or reviewing skills.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# _core-adr-003: Skill standards
|
|
2
7
|
|
|
3
8
|
## Context and Problem Statement
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-004-article-standards
|
|
3
|
+
description: Defines article document standards for synthesizing and linking XDRs, research, and skills. Use when creating or reviewing articles.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# _core-adr-004: Article standards
|
|
2
7
|
|
|
3
8
|
## Context and Problem Statement
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-005-semantic-versioning-for-xdr-packages
|
|
3
|
+
description: Defines how semantic versioning applies to XDR packages. Use when publishing or versioning a package containing XDRs, research, skills, or articles.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# _core-adr-005: Semantic versioning for XDR packages
|
|
2
7
|
|
|
3
8
|
## Context and Problem Statement
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: _core-adr-006-research-standards
|
|
3
|
+
description: Defines research document standards including IMRAD structure and traceability to XDRs. Use when creating or reviewing research documents.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# _core-adr-006: Research standards
|
|
2
7
|
|
|
3
8
|
## Context and Problem Statement
|
|
@@ -24,10 +24,10 @@ Performs a structured review of code changes or files against the XDRs in the re
|
|
|
24
24
|
|
|
25
25
|
1. Gather all Decision Records from `.xdrs/index.md` starting from the working directory.
|
|
26
26
|
- XDR scopes are controlled by nested folders; some are broad, others domain-specific.
|
|
27
|
-
- Extract
|
|
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 `applied-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
|
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
"Create a skill with instructions on how -a7079882": {
|
|
3
3
|
"result": "success",
|
|
4
4
|
"contextFiles": [
|
|
5
|
-
".xdrs/_core/adrs/index.md",
|
|
6
5
|
".xdrs/_core/adrs/principles/001-xdrs-core.md",
|
|
7
6
|
".xdrs/_core/adrs/principles/003-skill-standards.md",
|
|
8
7
|
".xdrs/_core/adrs/principles/skills/003-write-skill/SKILL.md",
|
|
9
8
|
".xdrs/index.md",
|
|
10
9
|
"AGENTS.md"
|
|
11
10
|
],
|
|
12
|
-
"contextHash": "
|
|
11
|
+
"contextHash": "30c68e62fee8f74be079e7df7f6c10d1"
|
|
13
12
|
}
|
|
14
13
|
}
|
package/.xdrs/_core/adrs/principles/skills/004-write-article/004-write-article.test.int.report
CHANGED
|
@@ -5,16 +5,12 @@
|
|
|
5
5
|
".xdrs/_core/adrs/index.md",
|
|
6
6
|
".xdrs/_core/adrs/principles/001-xdrs-core.md",
|
|
7
7
|
".xdrs/_core/adrs/principles/002-xdr-standards.md",
|
|
8
|
-
".xdrs/_core/adrs/principles/003-skill-standards.md",
|
|
9
8
|
".xdrs/_core/adrs/principles/004-article-standards.md",
|
|
10
|
-
".xdrs/_core/adrs/principles/006-research-standards.md",
|
|
11
|
-
".xdrs/_core/adrs/principles/007-plan-standards.md",
|
|
12
9
|
".xdrs/_core/adrs/principles/articles/001-xdrs-overview.md",
|
|
13
|
-
".xdrs/
|
|
14
|
-
".xdrs/_local/adrs/principles/researches/001-research-and-decision-lifecycle.md",
|
|
10
|
+
".xdrs/_core/adrs/principles/skills/004-write-article/SKILL.md",
|
|
15
11
|
".xdrs/index.md",
|
|
16
12
|
"AGENTS.md"
|
|
17
13
|
],
|
|
18
|
-
"contextHash": "
|
|
14
|
+
"contextHash": "21d52973f1313bfbbf238f69e080de81"
|
|
19
15
|
}
|
|
20
16
|
}
|
package/.xdrs/_core/adrs/principles/skills/005-write-research/005-write-research.test.int.js
CHANGED
|
@@ -14,7 +14,7 @@ test('005-write-research creates an IMRAD research document in copy mode', async
|
|
|
14
14
|
workspaceMode: 'copy',
|
|
15
15
|
...copilotCmd(REPO_ROOT),
|
|
16
16
|
},
|
|
17
|
-
'Create a very small research document with the following data: Java vs Python for datascience projects. Java has much less libraries and community momentum than Python. Java is faster, but in general our DS applications doesn\'t require high performance. Is some cases we could use Spark for data and very specific ETL and transformations, but in general Python should be the norm for Data Science projects, especially in the early phases.',
|
|
17
|
+
'Create a very small research document in principles subject with the following data: Java vs Python for datascience projects. Java has much less libraries and community momentum than Python. Java is faster, but in general our DS applications doesn\'t require high performance. Is some cases we could use Spark for data and very specific ETL and transformations, but in general Python should be the norm for Data Science projects, especially in the early phases.',
|
|
18
18
|
'Verify that a research file was created under .xdrs/_local/edrs/application/researches/, that it contains the sections Abstract, Introduction, Methods, Results, Discussion, Conclusion, and References, have the right ratio of words on the sections (not worse than 50% in deviation) and that the content contains all the provided data in input prompt, and doesn\'t contain more than 20% of additional information outside the central topic. Check also if there is at least one comparison table between the options, ',
|
|
19
19
|
null,
|
|
20
20
|
true
|
package/lib/lint.js
CHANGED
|
@@ -105,11 +105,11 @@ function lintRootIndex(rootIndexPath, xdrsRoot, actualTypeIndexes, errors) {
|
|
|
105
105
|
|
|
106
106
|
const linkedTypeIndexes = links.filter((linkPath) => isCanonicalTypeIndex(linkPath, xdrsRoot));
|
|
107
107
|
const linkedSet = new Set(linkedTypeIndexes.map(normalizePath));
|
|
108
|
+
const localScopePath = normalizePath(path.join(xdrsRoot, '_local'));
|
|
108
109
|
|
|
109
|
-
for (const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
errors.push(`Root index must not link _local canonical index: ${displayPath(rootIndexPath, indexPath)}`);
|
|
110
|
+
for (const linkPath of links) {
|
|
111
|
+
if (isPathInside(localScopePath, linkPath) || normalizePath(linkPath) === localScopePath) {
|
|
112
|
+
errors.push(`Root index must not link into _local scope: ${displayPath(rootIndexPath, linkPath)}`);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -235,6 +235,7 @@ function lintXdrFile(xdrsRoot, scopeName, typeName, filePath, xdrNumbers, errors
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
const number = match[1];
|
|
238
|
+
const shortTitle = match[2];
|
|
238
239
|
const previous = xdrNumbers.get(number);
|
|
239
240
|
if (previous) {
|
|
240
241
|
errors.push(`Duplicate XDR number ${number} in ${scopeName}/${typeName}: ${toDisplayPath(previous)} and ${toDisplayPath(filePath)}`);
|
|
@@ -248,124 +249,42 @@ function lintXdrFile(xdrsRoot, scopeName, typeName, filePath, xdrNumbers, errors
|
|
|
248
249
|
|
|
249
250
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
250
251
|
const expectedHeader = `# ${scopeName}-${TYPE_TO_ID[typeName]}-${number}:`;
|
|
251
|
-
const firstLine = firstNonEmptyLine(content);
|
|
252
|
+
const firstLine = firstNonEmptyLine(stripFrontmatter(content));
|
|
252
253
|
if (!firstLine.startsWith(expectedHeader)) {
|
|
253
254
|
errors.push(`XDR title must start with "${expectedHeader}": ${toDisplayPath(filePath)}`);
|
|
254
255
|
}
|
|
255
256
|
|
|
256
|
-
|
|
257
|
+
const expectedName = `${scopeName}-${TYPE_TO_ID[typeName]}-${number}-${shortTitle}`;
|
|
258
|
+
lintXdrFrontmatter(content, expectedName, filePath, errors);
|
|
257
259
|
lintDocumentResourceLinks(filePath, errors);
|
|
258
260
|
}
|
|
259
261
|
|
|
260
|
-
function
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const contextLine = findHeadingLine(lines, ignoredLines, '## Context and Problem Statement');
|
|
265
|
-
const headingLines = findHeadingLines(lines, ignoredLines);
|
|
266
|
-
|
|
267
|
-
if (metadataLine !== -1) {
|
|
268
|
-
const nextHeadingLine = headingLines.find((lineIndex) => lineIndex > metadataLine);
|
|
269
|
-
if (nextHeadingLine !== contextLine) {
|
|
270
|
-
errors.push(`XDR metadata section must appear immediately before Context and Problem Statement: ${toDisplayPath(filePath)}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (metadataLine !== -1 && contextLine !== -1 && metadataLine > contextLine) {
|
|
275
|
-
errors.push(`XDR metadata section must appear before Context and Problem Statement: ${toDisplayPath(filePath)}`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const metadataRange = metadataLine === -1
|
|
279
|
-
? null
|
|
280
|
-
: {
|
|
281
|
-
start: metadataLine + 1,
|
|
282
|
-
end: (headingLines.find((lineIndex) => lineIndex > metadataLine) ?? lines.length) - 1
|
|
283
|
-
};
|
|
284
|
-
const appliedLineNumbers = findFieldLines(lines, ignoredLines, 'Applied to:');
|
|
285
|
-
const validLineNumbers = findFieldLines(lines, ignoredLines, 'Valid:');
|
|
286
|
-
|
|
287
|
-
if (!metadataRange && (appliedLineNumbers.length > 0 || validLineNumbers.length > 0)) {
|
|
288
|
-
errors.push(`XDR metadata fields require a ## Metadata section immediately before Context and Problem Statement: ${toDisplayPath(filePath)}`);
|
|
262
|
+
function lintXdrFrontmatter(content, expectedName, filePath, errors) {
|
|
263
|
+
const fm = extractFrontmatter(content);
|
|
264
|
+
if (!fm.present) {
|
|
265
|
+
errors.push(`XDR must start with a YAML frontmatter block: ${toDisplayPath(filePath)}`);
|
|
289
266
|
return;
|
|
290
267
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const metadataApplied = appliedLineNumbers.filter((lineIndex) => isLineInsideRange(lineIndex, metadataRange));
|
|
297
|
-
const metadataValid = validLineNumbers.filter((lineIndex) => isLineInsideRange(lineIndex, metadataRange));
|
|
298
|
-
|
|
299
|
-
if (metadataApplied.length === 0 && metadataValid.length === 0) {
|
|
300
|
-
errors.push(`XDR metadata section must be omitted when no metadata fields are defined: ${toDisplayPath(filePath)}`);
|
|
268
|
+
if (!fm.name) {
|
|
269
|
+
errors.push(`XDR frontmatter must include a non-empty name field: ${toDisplayPath(filePath)}`);
|
|
270
|
+
} else if (fm.name !== expectedName) {
|
|
271
|
+
errors.push(`XDR frontmatter name must be "${expectedName}": ${toDisplayPath(filePath)}`);
|
|
301
272
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
errors.push(`XDR metadata must not repeat Applied to: ${toDisplayPath(filePath)}`);
|
|
273
|
+
if (!fm.description) {
|
|
274
|
+
errors.push(`XDR frontmatter must include a non-empty description field: ${toDisplayPath(filePath)}`);
|
|
305
275
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
for (const lineIndex of [...appliedLineNumbers, ...validLineNumbers]) {
|
|
312
|
-
if (!isLineInsideRange(lineIndex, metadataRange)) {
|
|
313
|
-
errors.push(`XDR metadata fields must be declared inside ## Metadata: ${toDisplayPath(filePath)}`);
|
|
314
|
-
break;
|
|
276
|
+
if (fm.validFrom !== undefined) {
|
|
277
|
+
if (!isIsoDate(fm.validFrom)) {
|
|
278
|
+
errors.push(`XDR frontmatter valid-from must be a valid ISO date YYYY-MM-DD: ${toDisplayPath(filePath)}`);
|
|
315
279
|
}
|
|
316
280
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
281
|
+
if (fm.appliedTo !== undefined) {
|
|
282
|
+
const words = countWords(fm.appliedTo);
|
|
283
|
+
if (words === 0) {
|
|
284
|
+
errors.push(`XDR frontmatter applied-to must not be empty: ${toDisplayPath(filePath)}`);
|
|
285
|
+
} else if (words >= 40) {
|
|
286
|
+
errors.push(`XDR frontmatter applied-to must be under 40 words: ${toDisplayPath(filePath)}`);
|
|
322
287
|
}
|
|
323
|
-
const trimmed = lines[lineIndex].trim();
|
|
324
|
-
if (trimmed === '') {
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
const currentFieldOrder = trimmed.startsWith('Valid:')
|
|
328
|
-
? 0
|
|
329
|
-
: trimmed.startsWith('Applied to:')
|
|
330
|
-
? 1
|
|
331
|
-
: -1;
|
|
332
|
-
if (currentFieldOrder === -1) {
|
|
333
|
-
errors.push(`XDR metadata section only supports Valid: and Applied to: fields: ${toDisplayPath(filePath)}`);
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
if (currentFieldOrder < previousFieldOrder) {
|
|
337
|
-
errors.push(`XDR metadata fields must be ordered as Valid:, Applied to: ${toDisplayPath(filePath)}`);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
previousFieldOrder = currentFieldOrder;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (metadataApplied.length === 1) {
|
|
344
|
-
const value = lines[metadataApplied[0]].slice('Applied to:'.length).trim();
|
|
345
|
-
if (value === '') {
|
|
346
|
-
errors.push(`XDR Applied to: must not be empty: ${toDisplayPath(filePath)}`);
|
|
347
|
-
} else if (countWords(value) >= 40) {
|
|
348
|
-
errors.push(`XDR Applied to: must be under 40 words: ${toDisplayPath(filePath)}`);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (metadataValid.length === 1) {
|
|
353
|
-
lintValidField(lines[metadataValid[0]], filePath, errors);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function lintValidField(line, filePath, errors) {
|
|
358
|
-
const value = line.slice('Valid:'.length).trim().replace(/\.$/, '');
|
|
359
|
-
|
|
360
|
-
const validMatch = value.match(/^from\s+(\d{4}-\d{2}-\d{2})$/);
|
|
361
|
-
if (!validMatch) {
|
|
362
|
-
errors.push(`XDR Valid: must be from YYYY-MM-DD: ${toDisplayPath(filePath)}`);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const fromDate = validMatch[1];
|
|
367
|
-
if (!isIsoDate(fromDate)) {
|
|
368
|
-
errors.push(`XDR Valid: must use ISO dates in YYYY-MM-DD format: ${toDisplayPath(filePath)}`);
|
|
369
288
|
}
|
|
370
289
|
}
|
|
371
290
|
|
|
@@ -408,11 +327,18 @@ function lintSkillsDirectory(xdrsRoot, scopeName, typeName, subjectName, skillsP
|
|
|
408
327
|
}
|
|
409
328
|
|
|
410
329
|
const skillContent = fs.readFileSync(skillFilePath, 'utf8');
|
|
411
|
-
const
|
|
412
|
-
if (!
|
|
413
|
-
errors.push(`SKILL.md
|
|
414
|
-
} else
|
|
415
|
-
|
|
330
|
+
const skillFm = extractFrontmatter(skillContent);
|
|
331
|
+
if (!skillFm.present) {
|
|
332
|
+
errors.push(`SKILL.md must start with a YAML frontmatter block: ${toDisplayPath(skillFilePath)}`);
|
|
333
|
+
} else {
|
|
334
|
+
if (!skillFm.name) {
|
|
335
|
+
errors.push(`SKILL.md frontmatter must include a non-empty name field: ${toDisplayPath(skillFilePath)}`);
|
|
336
|
+
} else if (skillFm.name !== entry.name) {
|
|
337
|
+
errors.push(`Skill frontmatter name must match package directory "${entry.name}": ${toDisplayPath(skillFilePath)}`);
|
|
338
|
+
}
|
|
339
|
+
if (!skillFm.description) {
|
|
340
|
+
errors.push(`SKILL.md frontmatter must include a non-empty description field: ${toDisplayPath(skillFilePath)}`);
|
|
341
|
+
}
|
|
416
342
|
}
|
|
417
343
|
|
|
418
344
|
lintDocumentResourceLinks(skillFilePath, errors);
|
|
@@ -677,14 +603,27 @@ function shouldValidateResourceLink(rawTarget) {
|
|
|
677
603
|
|| IMAGE_EXTENSIONS.has(extension);
|
|
678
604
|
}
|
|
679
605
|
|
|
680
|
-
function
|
|
681
|
-
const
|
|
682
|
-
if (!
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
const nameMatch =
|
|
687
|
-
|
|
606
|
+
function extractFrontmatter(content) {
|
|
607
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/);
|
|
608
|
+
if (!match) {
|
|
609
|
+
return { present: false, name: null, description: false, validFrom: undefined, appliedTo: undefined };
|
|
610
|
+
}
|
|
611
|
+
const block = match[1];
|
|
612
|
+
const nameMatch = block.match(/^name:\s*(.+)$/m);
|
|
613
|
+
const validFromMatch = block.match(/^valid-from:\s*(.+)$/m);
|
|
614
|
+
const appliedToMatch = block.match(/^applied-to:\s*(.+)$/m);
|
|
615
|
+
return {
|
|
616
|
+
present: true,
|
|
617
|
+
name: nameMatch ? nameMatch[1].trim() || null : null,
|
|
618
|
+
description: /^description:\s*\S/m.test(block),
|
|
619
|
+
validFrom: validFromMatch ? validFromMatch[1].trim() : undefined,
|
|
620
|
+
appliedTo: appliedToMatch ? appliedToMatch[1].trim() : undefined,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function stripFrontmatter(content) {
|
|
625
|
+
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/);
|
|
626
|
+
return match ? content.slice(match[0].length) : content;
|
|
688
627
|
}
|
|
689
628
|
|
|
690
629
|
function findIgnoredMarkdownLines(lines) {
|
package/lib/testPrompt.js
CHANGED
|
@@ -242,6 +242,7 @@ async function runJudgePhase({ judgePrompt, commandTemplate, continueFlag, works
|
|
|
242
242
|
'Use target="output" when the issue is in the final task output and target="workspace" when it is not tied to a specific file.',
|
|
243
243
|
'Include 1-based line numbers when you cite a file or the output text. Include the exact judge-prompt phrase that triggered each finding in assertionRef.',
|
|
244
244
|
'NEVER change any file during judge evaluation. If you identify an issue that would require a file change to fix, report it as a finding instead.',
|
|
245
|
+
'</INSTRUCTIONS>',
|
|
245
246
|
'',
|
|
246
247
|
judgePrompt,
|
|
247
248
|
].join('\n');
|
package/package.json
CHANGED