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.02-rule-numbering-must-be-stable]`);
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.01-always-use-numbered-rules-for-strong-or-referenceable-policies]`);
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.01-always-use-numbered-rules-for-strong-or-referenceable-policies]`);
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.02-rule-numbering-must-be-stable]');
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.01-always-use-numbered-rules-for-strong-or-referenceable-policies]');
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.0",
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",