xdrs-core 0.52.2 → 0.53.0
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-standards.md +3 -3
- package/.xdrs/_core/adrs/principles/002-policy-standards.md +1 -1
- package/.xdrs/_core/adrs/principles/003-skill-standards.md +12 -1
- package/.xdrs/_core/adrs/principles/006-research-standards.md +6 -6
- package/.xdrs/_core/adrs/principles/007-initiative-standards.md +9 -9
- package/.xdrs/_core/adrs/principles/008-policy-structured-standards.md +1 -1
- package/.xdrs/_core/adrs/principles/010-scope-governance.md +5 -5
- package/.xdrs/_core/adrs/principles/011-core-scope-type.md +1 -1
- package/.xdrs/_core/adrs/principles/022-scope-activation.md +12 -9
- package/.xdrs/_core/adrs/principles/skills/.assets/lint-verification.md +0 -1
- package/.xdrs/_core/adrs/principles/skills/compile-scope/SKILL.md +10 -8
- package/.xdrs/_core/adrs/principles/skills/review/SKILL.md +10 -8
- package/.xdrs/_core/adrs/principles/skills/write-article/SKILL.md +12 -10
- package/.xdrs/_core/adrs/principles/skills/write-initiative/SKILL.md +16 -14
- package/.xdrs/_core/adrs/principles/skills/write-policy/SKILL.md +12 -11
- package/.xdrs/_core/adrs/principles/skills/write-presentation/SKILL.md +5 -3
- package/.xdrs/_core/adrs/principles/skills/write-research/SKILL.md +17 -15
- package/.xdrs/_core/adrs/principles/skills/write-skill/SKILL.md +20 -12
- package/.xdrs/_core/adrs/principles/skills/write-xdrs-doc/SKILL.md +4 -2
- package/README.md +155 -59
- package/lib/lint.js +37 -73
- package/lib/lint.test.js +89 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ After preparation those elements can be downloaded anywhere and used to compose
|
|
|
12
12
|
|
|
13
13
|
Policies capture Architectural (ADR), Business (BDR), and Engineering (EDR) decisions. As organizations grow, hundreds of decisions accumulate across teams, levels, and domains. Without a consistent structure, AI agents cannot efficiently locate the right decisions for a given context, and humans cannot maintain or evolve them sustainably.
|
|
14
14
|
|
|
15
|
-
This project defines a standard for organizing XDRS that
|
|
15
|
+
This project defines a standard for organizing XDRS that addresses this problem (see [Features](#features)).
|
|
16
16
|
|
|
17
17
|
## Overview
|
|
18
18
|
|
|
@@ -35,6 +35,20 @@ The XDRS framework separates these concerns into different document types, each
|
|
|
35
35
|
- **Articles** — Synthetic explanatory texts that combine information from multiple Policies, Research documents, and Skills around a specific topic or audience. They never replace Policies as source of truth.
|
|
36
36
|
- **Initiatives** — Ephemeral execution documents that describe a problem, proposed solution, and the approach and activities needed to solve it. Initiatives have a clear start and end and must be deleted after full implementation. Lasting outputs are captured as Policies, Skills, Articles, or other artifacts.
|
|
37
37
|
|
|
38
|
+
```mermaid
|
|
39
|
+
flowchart LR
|
|
40
|
+
DR["Raw Decision Record<br/>ADR / BDR / EDR"]
|
|
41
|
+
DR -- why --> R[Research]
|
|
42
|
+
DR -- what --> P["Policy<br/>source of truth"]
|
|
43
|
+
DR -- how --> S[Skill]
|
|
44
|
+
DR -- when --> I["Initiative<br/>ephemeral"]
|
|
45
|
+
DR -- view --> A[Article]
|
|
46
|
+
R -. backs .-> P
|
|
47
|
+
S -. complies with .-> P
|
|
48
|
+
I -. implements .-> P
|
|
49
|
+
A -. explains .-> P
|
|
50
|
+
```
|
|
51
|
+
|
|
38
52
|
The compilation process of a raw Decision Record is to distribute it into those different documents and create links between them. You can also use the framework standalone, generating these elements individually directly during the writing process without starting from a raw Decision Record.
|
|
39
53
|
|
|
40
54
|
## Getting started
|
|
@@ -43,9 +57,11 @@ The compilation process of a raw Decision Record is to distribute it into those
|
|
|
43
57
|
|
|
44
58
|
2. On the workspace root folder, run `npx xdrs-core`
|
|
45
59
|
|
|
46
|
-
|
|
47
|
-
- AGENTS.md
|
|
48
|
-
-
|
|
60
|
+
This installs:
|
|
61
|
+
- `AGENTS.md` instructing agents to consult Policies before every request
|
|
62
|
+
- `.xdrs/_core/` with the framework Policies, skills and articles
|
|
63
|
+
- `.xdrs/index.md` root index, installed unmanaged so you own it and list your scopes there
|
|
64
|
+
- `.agents/skills/review` and `.agents/skills/write-xdrs-doc` symlinks so agents discover the main skills
|
|
49
65
|
|
|
50
66
|
3. Run a prompt such as:
|
|
51
67
|
|
|
@@ -53,10 +69,28 @@ The compilation process of a raw Decision Record is to distribute it into those
|
|
|
53
69
|
|
|
54
70
|
> Compile ADR 043-python-package-manager into the XDRS structure
|
|
55
71
|
|
|
72
|
+
> Review my changes against our Policies
|
|
73
|
+
|
|
74
|
+
4. Validate the result with `npx xdrs-core lint .`
|
|
75
|
+
|
|
76
|
+
### Bundled skills
|
|
77
|
+
|
|
78
|
+
| Skill | Purpose |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `write-xdrs-doc` | Router: infers the document type from your request and delegates to the right skill below |
|
|
81
|
+
| `write-policy`, `write-skill`, `write-article`, `write-research`, `write-initiative` | Author each XDRS element type |
|
|
82
|
+
| `write-presentation` | Create Marp slide decks backing XDRS documents |
|
|
83
|
+
| `review` | Review code or documents against applicable Policies and report violations |
|
|
84
|
+
| `compile-scope` | Fetch and compile a `compiled`-type scope from external sources |
|
|
85
|
+
|
|
86
|
+
See [.xdrs/_core/adrs/index.md](.xdrs/_core/adrs/index.md) for the full list of `_core` Policies, skills and articles.
|
|
87
|
+
|
|
56
88
|
## Examples
|
|
57
89
|
|
|
58
90
|
- [examples/basic-usage](examples/basic-usage) shows the minimal consumer flow for installing the packaged `xdrs-core` tarball, extracting files, checking drift, and linting the resulting tree.
|
|
59
91
|
- [examples/mydevkit](examples/mydevkit) shows a reusable extension package that uses `.filedistrc` as its package config source, composes `xdrs-core`, and ships its own named scope.
|
|
92
|
+
- [examples/typed-scope](examples/typed-scope) shows `core`, `reference`, `platform` and `_local` scopes, a custom `business-area` scope type, and `follows`.
|
|
93
|
+
- [examples/load-test](examples/load-test) generates a large synthetic XDRS tree to exercise the linter at scale.
|
|
60
94
|
- For a fuller real-world package built on the same distribution model, see [flaviostutz/agentme](https://github.com/flaviostutz/agentme).
|
|
61
95
|
|
|
62
96
|
|
|
@@ -66,7 +100,7 @@ The compilation process of a raw Decision Record is to distribute it into those
|
|
|
66
100
|
|
|
67
101
|
Different teams at different organizational levels make decisions that apply to different audiences. XDRS are organized by scope (e.g. `_core`, `business-x`, `business-y-mobileapp`) so that each team owns its own decision space. Scopes can extend or override policies from broader scopes, with explicit precedence rules: scopes listed later in an index override those listed earlier.
|
|
68
102
|
|
|
69
|
-
### Scope types
|
|
103
|
+
### Scope types
|
|
70
104
|
|
|
71
105
|
Every scope declares a `scope-type` in its `index.md` frontmatter. The six built-in types are `core`, `reference`, `platform`, `compiled`, `standard`, and `_local`, each with different governance rules. Custom types (e.g. `business-area`) can be introduced by adding a `{type}-scope-type` policy to any `core`-type scope.
|
|
72
106
|
|
|
@@ -79,11 +113,7 @@ Every scope declares a `scope-type` in its `index.md` frontmatter. The six built
|
|
|
79
113
|
| `standard` | Business areas, products, teams, or any general-purpose scope. Default type when nothing else fits. | Any valid scope name (e.g. `checkout`, `mobile-app`) | 4th |
|
|
80
114
|
| `_local` | Workspace-local overrides. Reserved exclusively for the `_local` scope. Never distributed. | `_local` (reserved) | last |
|
|
81
115
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
**`reference` scopes** hold read-only material to adopt or map against: industry frameworks, regulatory requirements, vendor best-practice patterns, business procedure standards. Content describes how something should be done — not what is currently running.
|
|
85
|
-
|
|
86
|
-
**`platform` scopes** document what already exists and is ready to use. A scope is of type `platform` when its content describes a live operational capability — what is available, how to access it, and what constraints apply.
|
|
116
|
+
`_core` is the built-in `core` scope; teams can add their own (e.g. `myorg-core`) with organisation-level authoring standards.
|
|
87
117
|
|
|
88
118
|
**`compiled` scopes** hold policies compiled directly from external authoritative sources. All content must trace back to the source; no invented content is allowed. A `compiled` scope requires a local meta-policy with source URLs and compilation configuration, and uses the `compile-scope` skill to fetch, compile, and keep content up to date. The `compiled` type may be combined with another type (e.g. `scope-type: compiled, reference`) when the combined type's governance also applies.
|
|
89
119
|
|
|
@@ -102,9 +132,7 @@ Include A01–A10 risk entries only.
|
|
|
102
132
|
|
|
103
133
|
By default, fetched and selected source content persists under `.assets/sources/[name]/` so later compilations can re-sync incrementally instead of always refetching; set `Source storage: temporary` in `## Sync Settings` to discard the bulk content after each run instead (only the per-source tracking file persists in that mode). Either way, `.assets/sources/` content is never authoritative on its own — only the compiled policies are.
|
|
104
134
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
**`_local` scopes** hold workspace-specific content such as local tooling decisions, transition overrides, or temporary decisions. This content is never packaged or distributed.
|
|
135
|
+
### Scope relationships: `follows` and `extends`
|
|
108
136
|
|
|
109
137
|
The `follows` field in a scope's `index.md` links it to one or more `core` scopes whose standards it must respect (beyond `_core`). This models organisational hierarchies: a team scope can follow a business-area `core` scope, which in turn follows `_core`.
|
|
110
138
|
|
|
@@ -118,11 +146,37 @@ description: Checkout team decisions
|
|
|
118
146
|
---
|
|
119
147
|
```
|
|
120
148
|
|
|
121
|
-
The
|
|
149
|
+
The `extends` field makes a scope inherit all **policy documents** (not skills, articles, research or initiatives) of other scopes, as if authored locally. Use it to adopt a `reference` scope and override only what differs. When scopes are related by `extends`, the chain decides conflicts instead of root index ordering. A scope cannot list the same scope in both `follows` and `extends`, cannot extend `_core`/`_local` or itself, and cannot form cycles.
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
# .xdrs/payments/index.md
|
|
153
|
+
---
|
|
154
|
+
scope-type: standard
|
|
155
|
+
name: payments
|
|
156
|
+
extends: ecomm-ref-payments # inherit its policies, override locally when needed
|
|
157
|
+
---
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A scope can also hold its own authoring rules in a local meta-policy (`[type]/principles/NNN-core.md`) without creating a separate `-core` scope. See [_core-adr-policy-010](.xdrs/_core/adrs/principles/010-scope-governance.md) for the full precedence chain.
|
|
161
|
+
|
|
162
|
+
```mermaid
|
|
163
|
+
flowchart LR
|
|
164
|
+
ecore["ecomm-core<br/>core"]
|
|
165
|
+
ref["ecomm-ref-payments<br/>reference, extends-only"]
|
|
166
|
+
checkout["checkout<br/>standard"]
|
|
167
|
+
payments["payments<br/>standard"]
|
|
168
|
+
local["_local<br/>workspace only"]
|
|
169
|
+
|
|
170
|
+
checkout -- "follows: respects authoring rules" --> ecore
|
|
171
|
+
payments -- follows --> ecore
|
|
172
|
+
payments -- "extends: inherits policies" --> ref
|
|
173
|
+
local -- overrides --> checkout
|
|
174
|
+
local -- overrides --> payments
|
|
175
|
+
```
|
|
122
176
|
|
|
123
|
-
|
|
177
|
+
`_core` applies implicitly to every scope, so it is never listed in `follows` or `extends`.
|
|
124
178
|
|
|
125
|
-
|
|
179
|
+
The `checkout` scope in [examples/typed-scope](examples/typed-scope) uses a custom `business-area` type defined in `ecomm-core` and declares `follows: ecomm-core`.
|
|
126
180
|
|
|
127
181
|
### Scope activation
|
|
128
182
|
|
|
@@ -158,13 +212,27 @@ XDRS packages are versioned and distributed via the npm registry. This allows te
|
|
|
158
212
|
|
|
159
213
|
The folder layout, file naming, and document format are designed so that AI agents can efficiently work with hundreds of decisions:
|
|
160
214
|
|
|
161
|
-
- Each Policy is a small, focused Markdown file (target under 1300 words), covering a set of rules or statements.
|
|
215
|
+
- Each Policy is a small, focused Markdown file (target under 1300 words, hard limit 2600), covering a set of rules or statements.
|
|
216
|
+
- Policies that need external citation can expose individually numbered rules (e.g. `_core-adr-policy-010.28-extends-disjoint`), see [_core-adr-policy-008](.xdrs/_core/adrs/principles/008-policy-structured-standards.md).
|
|
162
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.
|
|
163
218
|
- The root index at `.xdrs/index.md` provides a single entry point for discovery.
|
|
164
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.
|
|
165
220
|
- Decisions cross-reference each other by Policy ID rather than duplicating content, keeping individual files concise.
|
|
166
221
|
- Subject folders reduce the search space when a query maps to a known domain.
|
|
167
222
|
|
|
223
|
+
How an agent finds the Policies that apply to a request:
|
|
224
|
+
|
|
225
|
+
```mermaid
|
|
226
|
+
flowchart LR
|
|
227
|
+
Q([Request]) --> AG[AGENTS.md]
|
|
228
|
+
AG --> RI[".xdrs/index.md<br/>scope order and activation"]
|
|
229
|
+
RI --> TI["scope type indexes<br/>one line per document"]
|
|
230
|
+
TI --> F{"apply-to and<br/>valid-from match?"}
|
|
231
|
+
F -- yes --> P["Read Policy<br/>and linked Skills"]
|
|
232
|
+
F -- no --> X[Skip]
|
|
233
|
+
P --> W([Act and verify])
|
|
234
|
+
```
|
|
235
|
+
|
|
168
236
|
### Multi-agent framework support
|
|
169
237
|
|
|
170
238
|
Policies and skills must be usable by any type of AI agent, not only coding agents (e.g. GitHub Copilot, Cursor, Cline). General-purpose agent frameworks such as LangGraph, CrewAI, AutoGen, and similar orchestration runtimes must be able to consume Policies without relying on IDE-specific tooling or conventions.
|
|
@@ -197,15 +265,13 @@ This is especially important for BDRs: because business rules govern decisions t
|
|
|
197
265
|
.assets/
|
|
198
266
|
```
|
|
199
267
|
|
|
200
|
-
|
|
268
|
+
Types:
|
|
201
269
|
|
|
202
270
|
- **ADR** - Architectural Decision Record: architectural and technical decisions
|
|
203
271
|
- **BDR** - Business Decision Record: business process and strategy decisions
|
|
204
272
|
- **EDR** - Engineering Decision Record: engineering workflow and tooling decisions
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- **Articles** - Synthetic views that explain concepts or combine information from multiple Policies, Research documents, and Skills into a coherent text for a specific topic or audience. Articles are not the source of truth; Policies take precedence when there is a conflict. Useful as navigational indexes that link related artifacts around a specific aspect.
|
|
208
|
-
- **Initiatives** - Ephemeral execution documents that describe a problem, proposed solution, and the approach and activities needed to solve it. Initiatives have a clear start and end and must be deleted after full implementation. Lasting outputs are captured as Policies, Skills, Articles, or other artifacts. Co-located with Policies inside `initiatives/` sub-directories.
|
|
273
|
+
|
|
274
|
+
Each type has a fixed set of allowed subjects (e.g. `principles`, `application`, `data` for ADRs), see [_core-adr-policy-016](.xdrs/_core/adrs/principles/016-policy-subjects.md). Element types are described in [XDRS elements](#xdrs-elements).
|
|
209
275
|
|
|
210
276
|
See [.xdrs/index.md](.xdrs/index.md) for the full list of active policies.
|
|
211
277
|
|
|
@@ -232,38 +298,37 @@ A project that wants to follow a scope's decisions adds the corresponding npm pa
|
|
|
232
298
|
|
|
233
299
|
Multiple scope packages can be combined in the same workspace by listing them as separate dependencies. Scope precedence (defined in `.xdrs/index.md`) determines which decisions take effect when scopes overlap.
|
|
234
300
|
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
301
|
+
```mermaid
|
|
302
|
+
flowchart LR
|
|
303
|
+
subgraph owners[Scope owner repositories]
|
|
304
|
+
A[".xdrs/ecomm-core/"]
|
|
305
|
+
B[".xdrs/checkout/"]
|
|
306
|
+
end
|
|
307
|
+
A -- pack and publish --> NPM[("npm registry<br/>versioned packages")]
|
|
308
|
+
B -- pack and publish --> NPM
|
|
309
|
+
NPM -- "install (filedist)" --> WS
|
|
310
|
+
subgraph WS[Project workspace]
|
|
311
|
+
M[".xdrs/_core, ecomm-core, checkout<br/>managed, pinned in .filedist.lock"]
|
|
312
|
+
X[".xdrs/index.md<br/>owned by the project"]
|
|
313
|
+
L[".xdrs/_local<br/>never published"]
|
|
314
|
+
end
|
|
315
|
+
WS -- "xdrs-core lint / check" --> CI([CI])
|
|
244
316
|
```
|
|
245
317
|
|
|
246
318
|
## CLI
|
|
247
319
|
|
|
248
320
|
The published package exposes the `xdrs-core` CLI.
|
|
249
321
|
|
|
250
|
-
-
|
|
251
|
-
-
|
|
322
|
+
- `npx -y xdrs-core` (or `install`) installs or updates the managed XDRS files; `npx -y xdrs-core check` fails if managed files drifted from the package. Both are delegated to [filedist](https://github.com/flaviostutz/filedist) (run `--help` for all options).
|
|
323
|
+
- `npx -y xdrs-core lint [path]` validates the XDRS tree. Scopes listed in the workspace `.filedist.lock` are treated as external and skipped; use `--all` to include them.
|
|
252
324
|
|
|
253
|
-
The `lint` command reads
|
|
325
|
+
The `lint` command reads `.xdrs/**` (or `[path]` directly when it contains an `index.md`) and reports errors citing the violated Policy rule. It checks:
|
|
254
326
|
|
|
255
|
-
- allowed scope, type
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
- initiative `Expected end date:` field presence and ISO date format
|
|
261
|
-
- canonical index presence and link consistency
|
|
262
|
-
- root index coverage for all discovered canonical indexes
|
|
263
|
-
- root index completeness: every scope folder linked exactly once, activation tags (`extends-only`, `disabled`) valid, coherent with the scope's `extends-only` field, and explained by the legend line when used
|
|
264
|
-
- Policy metadata section placement and `valid-from` / `apply-to` field format
|
|
265
|
-
- local markdown links between Policy documents, skills, articles, researches, and initiatives (excluding fenced code blocks)
|
|
266
|
-
- local image and `.assets/` links resolving inside the sibling `.assets/` folder for each document
|
|
327
|
+
- **Structure**: allowed scope, type and subject folders; canonical indexes present and linking every document; root index linking every scope exactly once with valid activation tags
|
|
328
|
+
- **Numbering**: unique numbers per element type, subject-based numbering ranges ([_core-adr-policy-017](.xdrs/_core/adrs/principles/017-policy-numbering-ranges.md))
|
|
329
|
+
- **Document format**: Policy metadata (`valid-from`, `apply-to`), structured rule syntax, initiative `Expected end date`, skill `README.md`, word limits per element type
|
|
330
|
+
- **Scope governance**: scope-type definitions and naming conventions, `follows`/`extends` targets, cycles and conflicts, local meta-policies, `compiled` scope meta-policy
|
|
331
|
+
- **Links**: local markdown links and `.assets/` references resolve (fenced code blocks are ignored)
|
|
267
332
|
|
|
268
333
|
Examples:
|
|
269
334
|
|
|
@@ -279,30 +344,61 @@ The package also exposes a reusable behavior-test library for Jest or any other
|
|
|
279
344
|
|
|
280
345
|
Main exports:
|
|
281
346
|
|
|
282
|
-
- `testPrompt(config, inputPrompt, judgePrompt)` runs the task prompt, evaluates the result
|
|
283
|
-
- `runPromptTest(config, inputPrompt, judgePrompt)`
|
|
284
|
-
- `copilotCmd(workspaceRoot)` returns a
|
|
285
|
-
|
|
347
|
+
- `async testPrompt(config, inputPrompt, judgePrompt, id?, verbose?)` runs the task prompt, evaluates the result with a judge prompt, and resolves to an empty string on success or a markdown bullet list of findings on failure. Passing results are cached (see below).
|
|
348
|
+
- `async runPromptTest(config, inputPrompt, judgePrompt, verbose?)` resolves to the structured result (`passed`, `findings`, `taskOutput`, `agentReportedChanges`, `contextFiles`, `workspace`), without caching.
|
|
349
|
+
- `copilotCmd(workspaceRoot?)` returns a partial config for the Copilot CLI in headless mode (`promptCmd`, `promptCmdContinueFlag`, `workspaceFilter`, `workspaceExclude`). Spread it into your config. `workspaceRoot` defaults to the current git repository root.
|
|
350
|
+
|
|
351
|
+
Main `config` fields:
|
|
352
|
+
|
|
353
|
+
| Field | Description |
|
|
354
|
+
|---|---|
|
|
355
|
+
| `promptCmd` | Command as string array or JSON array string; must include a `{PROMPT}` placeholder |
|
|
356
|
+
| `promptCmdContinueFlag` | Flag inserted before the prompt to continue the previous session (e.g. `--continue`) |
|
|
357
|
+
| `workspaceRoot` | Workspace under test (default: current git repository root) |
|
|
358
|
+
| `workspaceMode` | `copy` (default, runs in a temp copy honoring `.gitignore`, without git metadata) or `in-place` |
|
|
359
|
+
| `workspaceFilter` / `workspaceExclude` | Globs selecting which files are copied |
|
|
360
|
+
| `taskTimeoutMs` / `judgeTimeoutMs` | Timeouts per phase |
|
|
361
|
+
| `env` | Extra environment variables for the command |
|
|
362
|
+
| `reportFile` | Cache file of passing tests (default: `<test-file>.report` next to the calling test); `null` disables caching |
|
|
363
|
+
| `checkOnly` | Do not run prompts; fail unless a matching passing entry exists in `reportFile` (useful in CI) |
|
|
364
|
+
| `model` | Model name, included in the cache hash |
|
|
286
365
|
|
|
287
366
|
Execution model:
|
|
288
367
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
368
|
+
1. **Task**: runs the task prompt and captures the final output
|
|
369
|
+
2. **Files**: continues the same session to collect the files the agent read and changed
|
|
370
|
+
3. **Judge**: continues the same session and evaluates the task output, changed files and workspace state against the judge prompt
|
|
371
|
+
|
|
372
|
+
A passing result is stored in `reportFile` with a hash of the model, prompts and the files the agent read. Later runs are skipped while that hash is unchanged, so tests re-run only when relevant context changes.
|
|
373
|
+
|
|
374
|
+
```mermaid
|
|
375
|
+
sequenceDiagram
|
|
376
|
+
participant T as Test
|
|
377
|
+
participant L as testPrompt
|
|
378
|
+
participant A as Agent CLI
|
|
379
|
+
T->>L: testPrompt(config, task, judge)
|
|
380
|
+
alt hash matches reportFile entry
|
|
381
|
+
L-->>T: empty string (cached pass)
|
|
382
|
+
else run
|
|
383
|
+
L->>A: 1. task prompt (temp workspace copy)
|
|
384
|
+
L->>A: 2. continue: list files read and changed
|
|
385
|
+
L->>A: 3. continue: judge prompt
|
|
386
|
+
A-->>L: pass and findings
|
|
387
|
+
L->>L: store hash when passed
|
|
388
|
+
L-->>T: empty string or findings markdown
|
|
389
|
+
end
|
|
390
|
+
```
|
|
295
391
|
|
|
296
392
|
Example with Jest:
|
|
297
393
|
|
|
298
394
|
```js
|
|
299
395
|
const { copilotCmd, testPrompt } = require('xdrs-core');
|
|
300
396
|
|
|
301
|
-
test('creates hello.md', () => {
|
|
302
|
-
const err = testPrompt(
|
|
397
|
+
test('creates hello.md', async () => {
|
|
398
|
+
const err = await testPrompt(
|
|
303
399
|
{
|
|
400
|
+
...copilotCmd(process.cwd()),
|
|
304
401
|
workspaceRoot: process.cwd(),
|
|
305
|
-
promptCmd: copilotCmd(process.cwd()),
|
|
306
402
|
workspaceMode: 'copy'
|
|
307
403
|
},
|
|
308
404
|
"Create a nice markdown file at hello.md saying 'hello!'",
|
package/lib/lint.js
CHANGED
|
@@ -99,18 +99,6 @@ function runLintCli(args) {
|
|
|
99
99
|
const targetPath = pathArgs[0] || '.';
|
|
100
100
|
const result = lintWorkspace(targetPath, { ignoreExternal: !all });
|
|
101
101
|
|
|
102
|
-
if (result.warnings.length > 0) {
|
|
103
|
-
for (const warning of result.warnings) {
|
|
104
|
-
console.warn(`- [warning] ${warning}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
for (const [scopeName, causes] of result.readOnlyScopes) {
|
|
109
|
-
for (const cause of causes) {
|
|
110
|
-
console.log(`- READ-ONLY scope "${scopeName}": ${cause}; no changes must be made to this scope [_core-adr-policy-022.06-read-only-dependencies]`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
102
|
if (result.errors.length === 0) {
|
|
115
103
|
console.log(`Lint passed for ${toDisplayPath(result.xdrsRoot)}`);
|
|
116
104
|
return 0;
|
|
@@ -147,12 +135,10 @@ function lintWorkspace(targetPath, options = {}) {
|
|
|
147
135
|
const resolvedTarget = path.resolve(targetPath);
|
|
148
136
|
const xdrsRoot = resolveXdrsRoot(resolvedTarget);
|
|
149
137
|
const errors = [];
|
|
150
|
-
const warnings = [];
|
|
151
|
-
const readOnlyScopes = new Map(); // scopeName -> [cause, ...] per _core-adr-policy-022.06
|
|
152
138
|
|
|
153
139
|
if (!existsDirectory(xdrsRoot)) {
|
|
154
140
|
errors.push(`Missing XDRS root directory: ${toDisplayPath(xdrsRoot)} [_core-adr-policy-001]`);
|
|
155
|
-
return { xdrsRoot, errors
|
|
141
|
+
return { xdrsRoot, errors };
|
|
156
142
|
}
|
|
157
143
|
|
|
158
144
|
const repoRoot = path.dirname(xdrsRoot);
|
|
@@ -247,13 +233,13 @@ function lintWorkspace(targetPath, options = {}) {
|
|
|
247
233
|
}
|
|
248
234
|
}
|
|
249
235
|
|
|
250
|
-
|
|
236
|
+
lintLocalReadOnlyDependencies(xdrsRoot, scopeMeta, activation, externalScopes, knownScopeTypes, scopeTypeDefiningScopes, errors);
|
|
251
237
|
|
|
252
238
|
// Populated during lintScopeIndexFrontmatter: scopeName -> [extendedScopeNames...]
|
|
253
239
|
const extendsRelationships = new Map();
|
|
254
240
|
|
|
255
241
|
for (const scopeEntry of scopeEntries) {
|
|
256
|
-
lintScopeDirectory(xdrsRoot, scopeEntry.name, errors,
|
|
242
|
+
lintScopeDirectory(xdrsRoot, scopeEntry.name, errors, actualTypeIndexes, ignoreExternal, effectiveExternalScopes, knownScopeTypes, extendsRelationships, externalScopes);
|
|
257
243
|
}
|
|
258
244
|
|
|
259
245
|
// Detect cycles in extends: chains (DFS)
|
|
@@ -283,13 +269,13 @@ function lintWorkspace(targetPath, options = {}) {
|
|
|
283
269
|
if (!existsFile(rootIndexPath)) {
|
|
284
270
|
errors.push('Missing required root index: .xdrs/index.md [_core-adr-policy-001]');
|
|
285
271
|
} else {
|
|
286
|
-
lintRootIndex(rootIndexPath, xdrsRoot, errors,
|
|
272
|
+
lintRootIndex(rootIndexPath, xdrsRoot, errors, effectiveExternalScopes, { activation, scopeMeta, knownScopeTypes });
|
|
287
273
|
}
|
|
288
274
|
|
|
289
|
-
return { xdrsRoot, errors
|
|
275
|
+
return { xdrsRoot, errors };
|
|
290
276
|
}
|
|
291
277
|
|
|
292
|
-
function lintRootIndex(rootIndexPath, xdrsRoot, errors,
|
|
278
|
+
function lintRootIndex(rootIndexPath, xdrsRoot, errors, externalScopes, ctx) {
|
|
293
279
|
const content = fs.readFileSync(rootIndexPath, 'utf8');
|
|
294
280
|
const repoRoot = path.dirname(xdrsRoot);
|
|
295
281
|
|
|
@@ -360,7 +346,7 @@ function lintRootIndex(rootIndexPath, xdrsRoot, errors, warnings, externalScopes
|
|
|
360
346
|
}
|
|
361
347
|
for (const [scopeName, entry] of activation) {
|
|
362
348
|
if (entry.tag === 'extends-only' && !reached.has(scopeName)) {
|
|
363
|
-
|
|
349
|
+
errors.push(`Scope "${scopeName}" is tagged \`extends-only\` but no active scope extends it, so its policies never apply; add it to the extends: of an active scope or tag it \`disabled\`: ${display} [_core-adr-policy-022.04-extends-only]`);
|
|
364
350
|
}
|
|
365
351
|
}
|
|
366
352
|
}
|
|
@@ -443,30 +429,19 @@ function readScopeMeta(scopeIndexPath) {
|
|
|
443
429
|
};
|
|
444
430
|
}
|
|
445
431
|
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
function
|
|
432
|
+
// Reports local scopes that would be READ-ONLY per _core-adr-policy-022.06 because of a disabled or
|
|
433
|
+
// invalid dependency. Missing dependencies are reported by lintScopeIndexFrontmatter; external scopes are never reported.
|
|
434
|
+
function lintLocalReadOnlyDependencies(xdrsRoot, scopeMeta, activation, externalScopes, knownScopeTypes, scopeTypeDefiningScopes, errors) {
|
|
449
435
|
const isDisabled = (scopeName) => (activation.get(scopeName) || {}).tag === 'disabled';
|
|
450
436
|
for (const [scopeName, meta] of scopeMeta) {
|
|
451
|
-
if (!meta) continue;
|
|
452
|
-
const isExternal = externalScopes.has(scopeName);
|
|
437
|
+
if (!meta || externalScopes.has(scopeName)) continue;
|
|
453
438
|
const causes = [];
|
|
454
|
-
if (isExternal && !meta.hasFrontmatter) {
|
|
455
|
-
causes.push('its scope index has no frontmatter; fix it in the source package');
|
|
456
|
-
}
|
|
457
|
-
if (isExternal && meta.extendsOnly !== null && meta.extendsOnly !== 'true' && meta.extendsOnly !== 'false') {
|
|
458
|
-
causes.push(`its scope index has an invalid extends-only value "${meta.extendsOnly}" (treated as absent); fix it in the source package`);
|
|
459
|
-
}
|
|
460
|
-
if (isExternal && meta.hasFrontmatter && meta.scopeTypes.length === 0) {
|
|
461
|
-
causes.push('its scope index has no scope-type; fix it in the source package');
|
|
462
|
-
}
|
|
463
439
|
for (const [field, targets] of [['follows', meta.follows], ['extends', meta.extends]]) {
|
|
464
440
|
for (const target of targets) {
|
|
465
441
|
if (target === scopeName || RESERVED_SCOPES.has(target)) continue;
|
|
466
442
|
const targetMeta = scopeMeta.get(target);
|
|
467
|
-
if (!targetMeta)
|
|
468
|
-
|
|
469
|
-
} else if (isDisabled(target)) {
|
|
443
|
+
if (!targetMeta) continue;
|
|
444
|
+
if (isDisabled(target)) {
|
|
470
445
|
causes.push(`${field}: scope "${target}" is tagged \`disabled\` in the root index; enable it or remove the ${field}: reference`);
|
|
471
446
|
} else if (!targetMeta.hasFrontmatter) {
|
|
472
447
|
causes.push(`${field}: scope "${target}" has an invalid scope index; fix it`);
|
|
@@ -474,18 +449,15 @@ function collectReadOnlyScopes(scopeMeta, activation, externalScopes, knownScope
|
|
|
474
449
|
}
|
|
475
450
|
}
|
|
476
451
|
for (const scopeType of resolveScopeTypeChain(meta.scopeTypes, knownScopeTypes)) {
|
|
477
|
-
if (scopeType === '_local') continue;
|
|
478
|
-
if (!knownScopeTypes.has(scopeType)) {
|
|
479
|
-
if (isExternal) causes.push(`scope type "${scopeType}" has no ${scopeType}-scope-type policy in this workspace; install the scope that defines it`);
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
452
|
+
if (scopeType === '_local' || !knownScopeTypes.has(scopeType)) continue;
|
|
482
453
|
const definers = [...scopeTypeDefiningScopes.get(scopeType)].filter((s) => s !== scopeName);
|
|
483
454
|
if (definers.length > 0 && definers.every(isDisabled)) {
|
|
484
455
|
causes.push(`scope type "${scopeType}" is defined only in disabled scope(s) ${definers.map((s) => `"${s}"`).join(', ')}; enable one of them`);
|
|
485
456
|
}
|
|
486
457
|
}
|
|
487
|
-
|
|
488
|
-
|
|
458
|
+
const display = toDisplayPath(path.join(xdrsRoot, scopeName, 'index.md'));
|
|
459
|
+
for (const cause of causes) {
|
|
460
|
+
errors.push(`Local scope "${scopeName}" is frozen (read-only) because ${cause}: ${display} [_core-adr-policy-022.06-read-only-dependencies]`);
|
|
489
461
|
}
|
|
490
462
|
}
|
|
491
463
|
}
|
|
@@ -787,8 +759,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
787
759
|
}
|
|
788
760
|
const followedIndexPath = path.join(xdrsRoot, entry, 'index.md');
|
|
789
761
|
if (!existsFile(followedIndexPath)) {
|
|
790
|
-
//
|
|
791
|
-
// (collected by collectReadOnlyScopes per _core-adr-policy-022.06), not a lint error.
|
|
762
|
+
// A missing dependency of an external scope makes it READ-ONLY (_core-adr-policy-022.06); lint does not report it.
|
|
792
763
|
if (!isExternal) {
|
|
793
764
|
errors.push(`Scope index frontmatter follows references scope "${entry}" which does not exist in the workspace: ${toDisplayPath(scopeIndexPath)} [_core-adr-policy-001]`);
|
|
794
765
|
}
|
|
@@ -858,7 +829,7 @@ function lintScopeIndexFrontmatter(scopeIndexPath, scopeName, errors, xdrsRoot,
|
|
|
858
829
|
}
|
|
859
830
|
|
|
860
831
|
|
|
861
|
-
function lintScopeDirectory(xdrsRoot, scopeName, errors,
|
|
832
|
+
function lintScopeDirectory(xdrsRoot, scopeName, errors, actualTypeIndexes, ignoreExternal, externalScopes, knownScopeTypes = new Map(), extendsRelationships = new Map(), trackedExternalScopes = new Set()) {
|
|
862
833
|
const scopePath = path.join(xdrsRoot, scopeName);
|
|
863
834
|
|
|
864
835
|
if (ignoreExternal && externalScopes.has(scopeName)) {
|
|
@@ -897,7 +868,7 @@ function lintScopeDirectory(xdrsRoot, scopeName, errors, warnings, actualTypeInd
|
|
|
897
868
|
errors.push(`Unexpected directory under scope ${scopeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001]`);
|
|
898
869
|
continue;
|
|
899
870
|
}
|
|
900
|
-
lintTypeDirectory(xdrsRoot, scopeName, entry.name, errors,
|
|
871
|
+
lintTypeDirectory(xdrsRoot, scopeName, entry.name, errors, actualTypeIndexes, externalScopes, isCoreTypeNonExempt, isCompiledScope);
|
|
901
872
|
const typeIndexPath = path.join(entryPath, 'index.md');
|
|
902
873
|
if (existsFile(typeIndexPath)) {
|
|
903
874
|
typeIndexesInScope.push(typeIndexPath);
|
|
@@ -1028,7 +999,7 @@ function lintLocalMetaPolicies(xdrsRoot, scopeName, errors, isCompiledScope = fa
|
|
|
1028
999
|
}
|
|
1029
1000
|
}
|
|
1030
1001
|
|
|
1031
|
-
function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors,
|
|
1002
|
+
function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, actualTypeIndexes, externalScopes = new Set(), enforceStructured = false, isCompiledScope = false) {
|
|
1032
1003
|
const typePath = path.join(xdrsRoot, scopeName, typeName);
|
|
1033
1004
|
const indexPath = path.join(typePath, 'index.md');
|
|
1034
1005
|
const xdrsNumbers = new Map();
|
|
@@ -1055,11 +1026,10 @@ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actu
|
|
|
1055
1026
|
errors.push(`Invalid subject folder for ${typeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001]`);
|
|
1056
1027
|
continue;
|
|
1057
1028
|
}
|
|
1058
|
-
// All policies are frozen:
|
|
1059
|
-
warnings.push(`Invalid subject folder for ${typeName}: ${toDisplayPath(entryPath)} [_core-adr-policy-001] [exempt: freeze-reference]`);
|
|
1029
|
+
// All policies are frozen (_core-adr-policy-002.01): skip the check, keep content-level checks.
|
|
1060
1030
|
}
|
|
1061
1031
|
|
|
1062
|
-
artifacts.push(...lintSubjectDirectory(xdrsRoot, scopeName, typeName, entry.name, xdrsNumbers, errors,
|
|
1032
|
+
artifacts.push(...lintSubjectDirectory(xdrsRoot, scopeName, typeName, entry.name, xdrsNumbers, errors, externalScopes, enforceStructured, isCompiledScope));
|
|
1063
1033
|
}
|
|
1064
1034
|
|
|
1065
1035
|
if (existsFile(indexPath)) {
|
|
@@ -1067,7 +1037,7 @@ function lintTypeDirectory(xdrsRoot, scopeName, typeName, errors, warnings, actu
|
|
|
1067
1037
|
}
|
|
1068
1038
|
}
|
|
1069
1039
|
|
|
1070
|
-
function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNumbers, errors,
|
|
1040
|
+
function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNumbers, errors, externalScopes = new Set(), enforceStructured = false, isCompiledScope = false) {
|
|
1071
1041
|
const subjectPath = path.join(xdrsRoot, scopeName, typeName, subjectName);
|
|
1072
1042
|
const artifacts = [];
|
|
1073
1043
|
const entries = safeReadDir(subjectPath, errors, `read subject directory ${scopeName}/${typeName}/${subjectName}`);
|
|
@@ -1106,7 +1076,7 @@ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNu
|
|
|
1106
1076
|
}
|
|
1107
1077
|
|
|
1108
1078
|
artifacts.push(entryPath);
|
|
1109
|
-
lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, entryPath, xdrsNumbers, errors,
|
|
1079
|
+
lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, entryPath, xdrsNumbers, errors, externalScopes, enforceStructured, isCompiledScope);
|
|
1110
1080
|
}
|
|
1111
1081
|
|
|
1112
1082
|
const subjectAssetsDir = path.join(subjectPath, RESOURCE_DIR_NAME);
|
|
@@ -1116,7 +1086,7 @@ function lintSubjectDirectory(xdrsRoot, scopeName, typeName, subjectName, xdrsNu
|
|
|
1116
1086
|
return artifacts;
|
|
1117
1087
|
}
|
|
1118
1088
|
|
|
1119
|
-
function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePath, xdrsNumbers, errors,
|
|
1089
|
+
function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePath, xdrsNumbers, errors, externalScopes = new Set(), enforceStructured = false, isCompiledScope = false) {
|
|
1120
1090
|
const baseName = path.basename(filePath);
|
|
1121
1091
|
const match = baseName.match(NUMBERED_FILE_RE);
|
|
1122
1092
|
if (!match) {
|
|
@@ -1136,26 +1106,23 @@ function lintXdrsElementFile(xdrsRoot, scopeName, typeName, subjectName, filePat
|
|
|
1136
1106
|
}
|
|
1137
1107
|
|
|
1138
1108
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
1139
|
-
lintPolicyNumberRange(number, subjectName, content, filePath, errors
|
|
1109
|
+
lintPolicyNumberRange(number, subjectName, content, filePath, errors);
|
|
1140
1110
|
const { freezeReference } = extractFrontmatter(content);
|
|
1141
1111
|
const expectedHeader = `# ${scopeName}-${TYPE_TO_ID[typeName]}-${number}:`;
|
|
1142
1112
|
const firstLine = firstNonEmptyLine(stripFrontmatter(content));
|
|
1143
1113
|
if (!freezeReference && !firstLine.startsWith(expectedHeader)) {
|
|
1144
1114
|
errors.push(`Policy title must start with "${expectedHeader}": ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
1145
|
-
} else if (freezeReference && !firstLine.startsWith(expectedHeader)) {
|
|
1146
|
-
warnings.push(`Policy title must start with "${expectedHeader}": ${toDisplayPath(filePath)} [_core-adr-policy-002] [exempt: freeze-reference]`);
|
|
1147
1115
|
}
|
|
1148
1116
|
|
|
1149
1117
|
// Enforce 'core' word reservation: only allowed as first word of filename title in principles/
|
|
1150
1118
|
const titleParts = match[2].split('-');
|
|
1151
|
-
if (titleParts.includes('core') && !(subjectName === 'principles' && titleParts[0] === 'core')) {
|
|
1152
|
-
|
|
1153
|
-
if (freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
|
|
1119
|
+
if (!freezeReference && titleParts.includes('core') && !(subjectName === 'principles' && titleParts[0] === 'core')) {
|
|
1120
|
+
errors.push(`Policy filename must not use "core" as a name word unless it is a local meta-policy in "principles/" starting with "core" (e.g., "NNN-core.md" or "NNN-core-{qualifier}.md"): ${toDisplayPath(filePath)} [_core-adr-policy-010.13-local-naming]`);
|
|
1154
1121
|
}
|
|
1155
1122
|
|
|
1156
1123
|
const expectedName = extractExpectedXdrsNameFromHeading(firstLine)
|
|
1157
1124
|
|| `${scopeName}-${TYPE_TO_ID[typeName]}-${number}-${match[2]}`;
|
|
1158
|
-
lintXdrsElementFrontmatter(content, expectedName, filePath, errors,
|
|
1125
|
+
lintXdrsElementFrontmatter(content, expectedName, filePath, errors, freezeReference);
|
|
1159
1126
|
if (enforceStructured) {
|
|
1160
1127
|
validateStructuredFormatContent(content, filePath, errors, '_core-adr-policy-010.12-def-core-scope-policies-must-be-structured');
|
|
1161
1128
|
} else if (isCompiledScope) {
|
|
@@ -1198,7 +1165,7 @@ function slugifyTitle(title) {
|
|
|
1198
1165
|
.replace(/--+/g, '-');
|
|
1199
1166
|
}
|
|
1200
1167
|
|
|
1201
|
-
function lintXdrsElementFrontmatter(content, expectedName, filePath, errors,
|
|
1168
|
+
function lintXdrsElementFrontmatter(content, expectedName, filePath, errors, freezeReference = false) {
|
|
1202
1169
|
const fm = extractFrontmatter(content);
|
|
1203
1170
|
if (!fm.present) {
|
|
1204
1171
|
errors.push(`Policy must start with a YAML frontmatter block: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
@@ -1207,9 +1174,8 @@ function lintXdrsElementFrontmatter(content, expectedName, filePath, errors, war
|
|
|
1207
1174
|
if (!fm.name) {
|
|
1208
1175
|
errors.push(`Policy frontmatter must include a non-empty name field: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
1209
1176
|
} else {
|
|
1210
|
-
if (fm.name !== expectedName) {
|
|
1211
|
-
|
|
1212
|
-
if (freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
|
|
1177
|
+
if (fm.name !== expectedName && !freezeReference) {
|
|
1178
|
+
errors.push(`Policy frontmatter name must be "${expectedName}": ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
1213
1179
|
}
|
|
1214
1180
|
if (fm.name.endsWith('-')) {
|
|
1215
1181
|
errors.push(`Policy frontmatter name must not end with a hyphen: ${toDisplayPath(filePath)} [_core-adr-policy-002]`);
|
|
@@ -1915,19 +1881,17 @@ function hasAnyNonFrozenPolicy(dirPath) {
|
|
|
1915
1881
|
return false;
|
|
1916
1882
|
}
|
|
1917
1883
|
|
|
1918
|
-
function lintPolicyNumberRange(number, subjectName, content, filePath, errors
|
|
1919
|
-
|
|
1884
|
+
function lintPolicyNumberRange(number, subjectName, content, filePath, errors) {
|
|
1885
|
+
if (extractFrontmatter(content).freezeReference) return;
|
|
1920
1886
|
const num = parseInt(number, 10);
|
|
1921
1887
|
if (num >= RESERVED_NUMBER_RANGE_MIN && num <= RESERVED_NUMBER_RANGE_MAX) {
|
|
1922
|
-
|
|
1923
|
-
if (fm.freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
|
|
1888
|
+
errors.push(`Policy number ${number} is in the reserved range (801–900) which MUST NOT be assigned: ${toDisplayPath(filePath)} [_core-adr-policy-017.05-reserved-gap-must-not-be-assigned]`);
|
|
1924
1889
|
return;
|
|
1925
1890
|
}
|
|
1926
1891
|
if (num < OVERFLOW_RANGE_MIN) {
|
|
1927
1892
|
const range = SUBJECT_NUMBER_RANGES[subjectName];
|
|
1928
1893
|
if (range && (num < range[0] || num > range[1])) {
|
|
1929
|
-
|
|
1930
|
-
if (fm.freezeReference) { warnings.push(`${msg} [exempt: freeze-reference]`); } else { errors.push(msg); }
|
|
1894
|
+
errors.push(`Policy number ${number} is outside the ${range[0]}–${range[1]} block reserved for subject "${subjectName}": ${toDisplayPath(filePath)} [_core-adr-policy-017.01-numbering-blocks-per-subject]`);
|
|
1931
1895
|
}
|
|
1932
1896
|
}
|
|
1933
1897
|
}
|