xdrs-core 0.52.0 → 0.52.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.
|
@@ -164,7 +164,7 @@ Rules:
|
|
|
164
164
|
- `description` SHOULD stay near 250 characters when the activation trigger can be stated that tightly, even though the hard cap remains 1024 characters — it is loaded into context on every activation scan.
|
|
165
165
|
- `## Overview` SHOULD state the task objective, expected outcome, and relevant prerequisites or tools when they matter, and MUST contain nested subsections, in order, for `### Inputs`, `### Outputs`, `### Halt Conditions`, then the optional `### User Interaction` and `### Runtime Requirements`.
|
|
166
166
|
- `### Inputs` MUST contain `#### Required` (the bare minimum needed to invoke the skill) then `#### Optional` (helpful extra context), in that order.
|
|
167
|
-
- `## Instructions` SHOULD state one clearly recommended approach first when multiple are viable, noting alternatives briefly afterward, and SHOULD include verification steps or acceptance criteria at the end of the task or major phases.
|
|
167
|
+
- `## Instructions` is REQUIRED. It SHOULD state one clearly recommended approach first when multiple are viable, noting alternatives briefly afterward, and SHOULD include verification steps or acceptance criteria at the end of the task or major phases.
|
|
168
168
|
- `## Examples` SHOULD include 2-3 example prompts a user could give as input, alongside a short description of what to expect during execution and as output for each.
|
|
169
169
|
- `### Outputs` covers only end-objective results, not intermediary items, and MUST contain `#### Contents` (generated files or chat-delivered results) then `#### Changes` (external system mutations that are part of the main objective), in that order.
|
|
170
170
|
- `### Halt Conditions` MUST list this skill's specific stop-before-completing triggers, grounded in at least missing required input, dubious/ambiguous input, and insufficient agent confidence — distinct from `## Edge Cases` (activation/boundary conditions) and `## Anti-Patterns` (execution mistakes). A partial or empty result the skill still finishes and returns belongs in `### Outputs`, not `### Halt Conditions`.
|
|
@@ -49,7 +49,7 @@ A rule title SHOULD include a normative keyword when the rule can be stated clea
|
|
|
49
49
|
|
|
50
50
|
#### 08-rule-block-must-use-standard-syntax
|
|
51
51
|
|
|
52
|
-
Each rule MUST be written as a level-4 heading with a two-digit zero-padded sequence number followed by a kebab-case title, and a body paragraph:
|
|
52
|
+
Each rule MUST be written as a level-4 heading with a two-digit zero-padded sequence number followed by a kebab-case title of 12 words or fewer, and a body paragraph:
|
|
53
53
|
|
|
54
54
|
```markdown
|
|
55
55
|
#### [NN]-[short-rule-title-in-kebab-case]
|
package/lib/lint.js
CHANGED
|
@@ -2025,6 +2025,14 @@ function lintInitiativeExpectedEndDate(content, filePath, errors) {
|
|
|
2025
2025
|
errors.push(`Initiative must not repeat Expected end date: ${toDisplayPath(filePath)} [_core-adr-policy-007]`);
|
|
2026
2026
|
}
|
|
2027
2027
|
|
|
2028
|
+
const sectionStart = findHeadingLine(lines, ignoredLines, '## Proposed Solution');
|
|
2029
|
+
const sectionEnd = sectionStart === -1
|
|
2030
|
+
? -1
|
|
2031
|
+
: (findHeadingLines(lines, ignoredLines).find((index) => index > sectionStart) ?? lines.length);
|
|
2032
|
+
if (expectedEndDateLines.some((index) => index <= sectionStart || index >= sectionEnd)) {
|
|
2033
|
+
errors.push(`Initiative Expected end date: field must be inside the "## Proposed Solution" section: ${toDisplayPath(filePath)} [_core-adr-policy-007]`);
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2028
2036
|
const value = lines[expectedEndDateLines[0]].slice('Expected end date:'.length).trim().replace(/\.$/, '');
|
|
2029
2037
|
if (!isIsoDate(value)) {
|
|
2030
2038
|
errors.push(`Initiative Expected end date: must be a valid ISO date in YYYY-MM-DD format: ${toDisplayPath(filePath)} [_core-adr-policy-007]`);
|
|
@@ -2123,7 +2131,7 @@ function lintSourceSnapshotTracking(sourcesDir, errors) {
|
|
|
2123
2131
|
}
|
|
2124
2132
|
const trackingFilePath = path.join(sourcesDir, entry.name, SOURCE_TRACKING_FILE_NAME);
|
|
2125
2133
|
if (!existsFile(trackingFilePath)) {
|
|
2126
|
-
errors.push(`Compiled source snapshot folder must contain a "${SOURCE_TRACKING_FILE_NAME}" tracking file: ${toDisplayPath(trackingFilePath)} [_core-adr-policy-019.12-source-storage-tracking]`);
|
|
2134
|
+
errors.push(`Compiled source snapshot folder must contain a "${SOURCE_TRACKING_FILE_NAME}" tracking file: ${toDisplayPath(trackingFilePath)} [_core-adr-policy-019.12-source-storage-and-tracking]`);
|
|
2127
2135
|
continue;
|
|
2128
2136
|
}
|
|
2129
2137
|
validateSourceTrackingFile(trackingFilePath, errors);
|
|
@@ -2135,19 +2143,19 @@ function validateSourceTrackingFile(filePath, errors) {
|
|
|
2135
2143
|
const lines = content.split(/\r?\n/);
|
|
2136
2144
|
|
|
2137
2145
|
if (firstNonEmptyLine(content) !== '# Source') {
|
|
2138
|
-
errors.push(`Source tracking file must start with a "# Source" heading: ${toDisplayPath(filePath)} [_core-adr-policy-019.12-source-storage-tracking]`);
|
|
2146
|
+
errors.push(`Source tracking file must start with a "# Source" heading: ${toDisplayPath(filePath)} [_core-adr-policy-019.12-source-storage-and-tracking]`);
|
|
2139
2147
|
}
|
|
2140
2148
|
|
|
2141
2149
|
const timestampRe = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
|
2142
2150
|
for (const field of ['last-fetch-timestamp', 'last-compilation-timestamp']) {
|
|
2143
2151
|
const fieldLine = lines.find((line) => line.trim().startsWith(`${field}:`));
|
|
2144
2152
|
if (!fieldLine) {
|
|
2145
|
-
errors.push(`Source tracking file must include a "${field}:" line: ${toDisplayPath(filePath)} [_core-adr-policy-019.12-source-storage-tracking]`);
|
|
2153
|
+
errors.push(`Source tracking file must include a "${field}:" line: ${toDisplayPath(filePath)} [_core-adr-policy-019.12-source-storage-and-tracking]`);
|
|
2146
2154
|
continue;
|
|
2147
2155
|
}
|
|
2148
2156
|
const value = fieldLine.trim().slice(`${field}:`.length).trim();
|
|
2149
2157
|
if (!timestampRe.test(value)) {
|
|
2150
|
-
errors.push(`Source tracking file "${field}" must match "YYYY-MM-DD HH:MM:SS": ${toDisplayPath(filePath)} [_core-adr-policy-019.12-source-storage-tracking]`);
|
|
2158
|
+
errors.push(`Source tracking file "${field}" must match "YYYY-MM-DD HH:MM:SS": ${toDisplayPath(filePath)} [_core-adr-policy-019.12-source-storage-and-tracking]`);
|
|
2151
2159
|
}
|
|
2152
2160
|
}
|
|
2153
2161
|
}
|
|
@@ -2264,7 +2272,7 @@ function lintDocumentLinks(documentPath, xdrsRoot, scopeName, errors, externalSc
|
|
|
2264
2272
|
}
|
|
2265
2273
|
|
|
2266
2274
|
if (isSourceSnapshotPath(link.resolvedPath)) {
|
|
2267
|
-
errors.push(`Document must not link directly to compiled-source snapshot content, which is not authoritative (see _core-adr-policy-019 rule 14) in ${toDisplayPath(documentPath)}:${index + 1}: ${link.rawTarget} [_core-adr-policy-019.14-source-not-authoritative]`);
|
|
2275
|
+
errors.push(`Document must not link directly to compiled-source snapshot content, which is not authoritative (see _core-adr-policy-019 rule 14) in ${toDisplayPath(documentPath)}:${index + 1}: ${link.rawTarget} [_core-adr-policy-019.14-source-documents-not-authoritative]`);
|
|
2268
2276
|
}
|
|
2269
2277
|
}
|
|
2270
2278
|
}
|
|
@@ -2724,20 +2732,20 @@ function lintStructuredRuleBlocks(content, filePath, errors) {
|
|
|
2724
2732
|
const { lineIndex, number, title } = ruleEntries[r];
|
|
2725
2733
|
|
|
2726
2734
|
if (!/^\d{2}$/.test(number)) {
|
|
2727
|
-
errors.push(`Policy structured rule number must be exactly two digits ("${number}" in "${lines[lineIndex].trim()}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.
|
|
2735
|
+
errors.push(`Policy structured rule number must be exactly two digits ("${number}" in "${lines[lineIndex].trim()}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.08-rule-block-must-use-standard-syntax]`);
|
|
2728
2736
|
}
|
|
2729
2737
|
|
|
2730
2738
|
if (title.length === 0 || !/^[a-z][a-z0-9-]*$/.test(title)) {
|
|
2731
2739
|
if (/[A-Z]/.test(title)) {
|
|
2732
2740
|
errors.push(`Policy structured rule title must be all lowercase ("${title}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.05-rule-title-must-be-all-lowercase]`);
|
|
2733
2741
|
} else {
|
|
2734
|
-
errors.push(`Policy structured rule title must be in kebab-case ("${title}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.
|
|
2742
|
+
errors.push(`Policy structured rule title must be in kebab-case ("${title}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.08-rule-block-must-use-standard-syntax]`);
|
|
2735
2743
|
}
|
|
2736
2744
|
}
|
|
2737
2745
|
|
|
2738
2746
|
const wordCount = title.split('-').filter(Boolean).length;
|
|
2739
2747
|
if (wordCount > 12) {
|
|
2740
|
-
errors.push(`Policy structured rule title must be 12 words or fewer ("${title}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.
|
|
2748
|
+
errors.push(`Policy structured rule title must be 12 words or fewer ("${title}"): ${toDisplayPath(filePath)} [_core-adr-policy-008.08-rule-block-must-use-standard-syntax]`);
|
|
2741
2749
|
}
|
|
2742
2750
|
|
|
2743
2751
|
if (ruleNumbers.has(number)) {
|
package/lib/lint.test.js
CHANGED
|
@@ -3860,7 +3860,7 @@ test('does not report orphan asset errors for files under .assets/sources in a c
|
|
|
3860
3860
|
const result = lintWorkspace(workspaceRoot);
|
|
3861
3861
|
|
|
3862
3862
|
expect(result.errors.join('\n')).not.toContain('raw-standard.md');
|
|
3863
|
-
expect(result.errors.join('\n')).not.toContain('019.12-source-storage-tracking');
|
|
3863
|
+
expect(result.errors.join('\n')).not.toContain('019.12-source-storage-and-tracking');
|
|
3864
3864
|
expect(result.errors.join('\n')).toContain('Orphan asset file');
|
|
3865
3865
|
expect(result.errors.join('\n')).toContain('unused.png');
|
|
3866
3866
|
});
|
|
@@ -3879,7 +3879,7 @@ test('reports missing source.md tracking file in a compiled source snapshot fold
|
|
|
3879
3879
|
const result = lintWorkspace(workspaceRoot);
|
|
3880
3880
|
|
|
3881
3881
|
expect(result.errors.join('\n')).toContain('must contain a "source.md" tracking file');
|
|
3882
|
-
expect(result.errors.join('\n')).toContain('019.12-source-storage-tracking');
|
|
3882
|
+
expect(result.errors.join('\n')).toContain('019.12-source-storage-and-tracking');
|
|
3883
3883
|
});
|
|
3884
3884
|
|
|
3885
3885
|
test('reports malformed source.md tracking file content', () => {
|
|
@@ -3901,7 +3901,7 @@ test('reports malformed source.md tracking file content', () => {
|
|
|
3901
3901
|
expect(result.errors.join('\n')).toContain('must start with a "# Source" heading');
|
|
3902
3902
|
expect(result.errors.join('\n')).toContain('must include a "last-compilation-timestamp:" line');
|
|
3903
3903
|
expect(result.errors.join('\n')).toContain('must match "YYYY-MM-DD HH:MM:SS"');
|
|
3904
|
-
expect(result.errors.join('\n')).toContain('019.12-source-storage-tracking');
|
|
3904
|
+
expect(result.errors.join('\n')).toContain('019.12-source-storage-and-tracking');
|
|
3905
3905
|
});
|
|
3906
3906
|
|
|
3907
3907
|
test('reports document linking directly into a compiled-source snapshot path', () => {
|
|
@@ -3917,7 +3917,7 @@ test('reports document linking directly into a compiled-source snapshot path', (
|
|
|
3917
3917
|
const result = lintWorkspace(workspaceRoot);
|
|
3918
3918
|
|
|
3919
3919
|
expect(result.errors.join('\n')).toContain('must not link directly to compiled-source snapshot content');
|
|
3920
|
-
expect(result.errors.join('\n')).toContain('019.14-source-not-authoritative');
|
|
3920
|
+
expect(result.errors.join('\n')).toContain('019.14-source-documents-not-authoritative');
|
|
3921
3921
|
});
|
|
3922
3922
|
|
|
3923
3923
|
test('enforces compiled scope checks when scope-type is comma-separated with compiled', () => {
|
|
@@ -4603,6 +4603,37 @@ test('reports invalid Expected end date format in initiative document', () => {
|
|
|
4603
4603
|
expect(result.errors.join('\n')).toContain('Initiative Expected end date: must be a valid ISO date');
|
|
4604
4604
|
});
|
|
4605
4605
|
|
|
4606
|
+
test('reports Expected end date outside Proposed Solution in initiative document', () => {
|
|
4607
|
+
const workspaceRoot = createWorkspace('initiative-misplaced-end-date', {
|
|
4608
|
+
'.xdrs/index.md': rootIndex(),
|
|
4609
|
+
'.xdrs/_local/adrs/index.md': localAdrIndex([
|
|
4610
|
+
'- [001-myinitiative](principles/initiatives/001-myinitiative.md) - My initiative'
|
|
4611
|
+
]),
|
|
4612
|
+
'.xdrs/_local/adrs/principles/initiatives/001-myinitiative.md': [
|
|
4613
|
+
'# _local-adr-initiative-001: My initiative',
|
|
4614
|
+
'',
|
|
4615
|
+
'## Executive Summary',
|
|
4616
|
+
'',
|
|
4617
|
+
'Summary.',
|
|
4618
|
+
'',
|
|
4619
|
+
'Expected end date: 2026-12-31',
|
|
4620
|
+
'',
|
|
4621
|
+
'## Context and Problem Statement',
|
|
4622
|
+
'',
|
|
4623
|
+
'Body.',
|
|
4624
|
+
'',
|
|
4625
|
+
'## Proposed Solution',
|
|
4626
|
+
'',
|
|
4627
|
+
'We will fix it.',
|
|
4628
|
+
''
|
|
4629
|
+
].join('\n'),
|
|
4630
|
+
});
|
|
4631
|
+
|
|
4632
|
+
const result = lintWorkspace(workspaceRoot);
|
|
4633
|
+
|
|
4634
|
+
expect(result.errors.join('\n')).toContain('Expected end date: field must be inside the "## Proposed Solution" section');
|
|
4635
|
+
});
|
|
4636
|
+
|
|
4606
4637
|
// ─── Name / description length ────────────────────────────────────────────────
|
|
4607
4638
|
|
|
4608
4639
|
test('reports XDRS element frontmatter name exceeding 64 characters', () => {
|
|
@@ -5175,7 +5206,7 @@ test('reports structured rule block with non-two-digit number', () => {
|
|
|
5175
5206
|
const result = lintWorkspace(workspaceRoot);
|
|
5176
5207
|
|
|
5177
5208
|
expect(result.errors.join('\n')).toContain('structured rule number must be exactly two digits');
|
|
5178
|
-
expect(result.errors.join('\n')).toContain('[_core-adr-policy-008.
|
|
5209
|
+
expect(result.errors.join('\n')).toContain('[_core-adr-policy-008.08-rule-block-must-use-standard-syntax]');
|
|
5179
5210
|
});
|
|
5180
5211
|
|
|
5181
5212
|
test('reports structured rule block with non-kebab-case title', () => {
|
|
@@ -5196,7 +5227,7 @@ test('reports structured rule block with non-kebab-case title', () => {
|
|
|
5196
5227
|
const result = lintWorkspace(workspaceRoot);
|
|
5197
5228
|
|
|
5198
5229
|
expect(result.errors.join('\n')).toContain('structured rule title must be in kebab-case');
|
|
5199
|
-
expect(result.errors.join('\n')).toContain('[_core-adr-policy-008.
|
|
5230
|
+
expect(result.errors.join('\n')).toContain('[_core-adr-policy-008.08-rule-block-must-use-standard-syntax]');
|
|
5200
5231
|
});
|
|
5201
5232
|
|
|
5202
5233
|
test('reports structured rule block with duplicate rule number', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xdrs-core",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.1",
|
|
4
4
|
"description": "XDRS (eXtensible Decision Record System): a framework to structure, compile and distribute Architectural (ADR), Business (BDR), and Engineering (EDR) decision records contents so that AI agents and humans can reliably find and use them with hierarchical scopes and controlled rollout in the format of distributable versioned packages.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|