@descent-vtt/spec-guard 0.5.1 → 0.7.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +444 -283
  2. package/README.md +1094 -868
  3. package/dist/cli.d.ts +18 -2
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +136 -7
  6. package/dist/cli.js.map +1 -1
  7. package/dist/glob.d.ts +17 -2
  8. package/dist/glob.d.ts.map +1 -1
  9. package/dist/glob.js +25 -9
  10. package/dist/glob.js.map +1 -1
  11. package/dist/graph.d.ts +124 -0
  12. package/dist/graph.d.ts.map +1 -0
  13. package/dist/graph.js +241 -0
  14. package/dist/graph.js.map +1 -0
  15. package/dist/index.d.ts +19 -7
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +9 -3
  18. package/dist/index.js.map +1 -1
  19. package/dist/layers.d.ts +72 -0
  20. package/dist/layers.d.ts.map +1 -0
  21. package/dist/layers.js +102 -0
  22. package/dist/layers.js.map +1 -0
  23. package/dist/mcp.d.ts +117 -0
  24. package/dist/mcp.d.ts.map +1 -0
  25. package/dist/mcp.js +544 -0
  26. package/dist/mcp.js.map +1 -0
  27. package/dist/parser.d.ts +50 -1
  28. package/dist/parser.d.ts.map +1 -1
  29. package/dist/parser.js +232 -16
  30. package/dist/parser.js.map +1 -1
  31. package/dist/query.d.ts +108 -0
  32. package/dist/query.d.ts.map +1 -0
  33. package/dist/query.js +206 -0
  34. package/dist/query.js.map +1 -0
  35. package/dist/reporter.d.ts.map +1 -1
  36. package/dist/reporter.js +62 -5
  37. package/dist/reporter.js.map +1 -1
  38. package/dist/rules.d.ts +108 -0
  39. package/dist/rules.d.ts.map +1 -0
  40. package/dist/rules.js +153 -0
  41. package/dist/rules.js.map +1 -0
  42. package/dist/runner.d.ts +24 -0
  43. package/dist/runner.d.ts.map +1 -1
  44. package/dist/runner.js +335 -117
  45. package/dist/runner.js.map +1 -1
  46. package/dist/specs.d.ts +44 -0
  47. package/dist/specs.d.ts.map +1 -0
  48. package/dist/specs.js +62 -0
  49. package/dist/specs.js.map +1 -0
  50. package/dist/types.d.ts +60 -2
  51. package/dist/types.d.ts.map +1 -1
  52. package/package.json +73 -73
package/dist/parser.js CHANGED
@@ -14,7 +14,7 @@
14
14
  * error rather than silently skipped - a typo must never turn into a
15
15
  * silently-passing invariant.
16
16
  */
17
- import { lineStarts, locate } from './text.js';
17
+ import { lineStarts, locate, maskRanges } from './text.js';
18
18
  /** Every directive spec-guard understands. */
19
19
  export const KINDS = new Set([
20
20
  'assert-absence',
@@ -22,6 +22,8 @@ export const KINDS = new Set([
22
22
  'assert-present',
23
23
  'assert-import-absence',
24
24
  'assert-import-count',
25
+ 'assert-import-cycle',
26
+ 'assert-layers',
25
27
  ]);
26
28
  /**
27
29
  * Which attributes each directive accepts.
@@ -87,7 +89,208 @@ export const ALLOWED_ATTRIBUTES = {
87
89
  'allow-empty',
88
90
  'reason',
89
91
  ]),
92
+ // No baseline: a baseline lists files, and a cycle is not a file. Exempting
93
+ // the files of today's cycle would exempt a new one among the same files.
94
+ 'assert-import-cycle': new Set(['target', 'exclude', 'types', 'expected', 'max', 'allow-empty', 'reason']),
95
+ 'assert-layers': new Set([
96
+ 'target',
97
+ 'order',
98
+ 'exclude',
99
+ 'types',
100
+ 'expected',
101
+ 'max',
102
+ 'allow-empty',
103
+ 'baseline',
104
+ 'ratchet',
105
+ 'reason',
106
+ ]),
90
107
  };
108
+ /* --------------------------------------------------------------- the status */
109
+ /**
110
+ * Status words that withhold a document's directives from execution.
111
+ *
112
+ * The words that mean "not in force" in Nygard's ADR template and in MADR's
113
+ * between them, plus `draft`, which is the one people actually type. The list
114
+ * is closed and short on purpose: every word added to it is another way for a
115
+ * document to go dark, and a rule that stops being enforced without anyone
116
+ * deciding so is the failure this tool exists to prevent. Anything else - an
117
+ * unrecognised word, a misspelling, no status at all - stays in force. See
118
+ * ADR-0010.
119
+ */
120
+ export const INACTIVE_STATUSES = new Set([
121
+ 'draft',
122
+ 'proposed',
123
+ 'rejected',
124
+ 'deprecated',
125
+ 'superseded',
126
+ ]);
127
+ /** `---\n...\n---` at the very top of the file, and nowhere else. */
128
+ const FRONTMATTER_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
129
+ /**
130
+ * The three ways the key is spelled: `**Status**:`, `**Status:**`, `Status:`.
131
+ *
132
+ * Written as alternatives rather than as optional emphasis on either side of
133
+ * the colon, which is what it used to be - and an optional `**` after the colon
134
+ * cannot tell the key's closing marker from the value's opening one, so
135
+ * `Status: **Draft**` reached the report as "Draft**". Here emphasis after the
136
+ * colon is only consumed when it closes emphasis that opened the key. The
137
+ * colon itself is mandatory, which keeps "Status reports are..." from being
138
+ * read as metadata. Leading whitespace in the value is left to `toStatus`,
139
+ * which trims it anyway.
140
+ */
141
+ const STATUS_LABEL_RE = /^[ \t]{0,3}(?:(\*\*|__)status(?:\1[ \t]*:|[ \t]*:\1)|status[ \t]*:)(.*)/i;
142
+ /**
143
+ * The value a YAML scalar holds: a quoted one up to its closing quote, a plain
144
+ * one up to a comment.
145
+ *
146
+ * Not a YAML parser, and it does not need to be one - it reads one line. But
147
+ * it has to read that line the way YAML does. MADR's own template quotes the
148
+ * status, and a quote left in leaves no first word to read; a trailing
149
+ * `# decided at review` left in is part of the label; and a regex anchored at
150
+ * both ends of a quoted value read `"proposed" # a comment` as no status at
151
+ * all, which keeps a withdrawn proposal in force.
152
+ */
153
+ function yamlScalar(raw) {
154
+ const text = raw.trim();
155
+ const quoted = /^(["'])(.*?)\1/.exec(text);
156
+ return quoted ? quoted[2] : text.replace(/\s#.*/, '');
157
+ }
158
+ /** An ATX heading whose entire text is "Status". */
159
+ const STATUS_HEADING_RE = /^[ \t]{0,3}#{1,6}[ \t]+status[ \t]*#*[ \t]*$/i;
160
+ /** Any ATX heading of level 2 or deeper - where a document's preamble ends. */
161
+ const SECTION_HEADING_RE = /^[ \t]{0,3}#{2,6}[ \t]/;
162
+ /**
163
+ * Turns a status line into a status, or into nothing.
164
+ *
165
+ * The value is the first run of letters, so "Accepted (0.3.0)." and
166
+ * "Superseded by ADR-0007" normalise to one word while the line as written
167
+ * survives for the report - a reader shown "superseded" learns much less than
168
+ * one shown what it was superseded by.
169
+ */
170
+ function toStatus(raw, source) {
171
+ const trimmed = raw.trim();
172
+ // The word is read through any leading emphasis, so `**Superseded** by
173
+ // ADR-0007` is superseded rather than nothing at all - which is what a rule
174
+ // that required the markers to balance made of it.
175
+ const word = /^[*_]*([a-zA-Z]+)/.exec(trimmed);
176
+ if (!word)
177
+ return undefined;
178
+ // The label loses emphasis only when it wraps the whole line. `**Draft**` is
179
+ // a status written in bold and reads better without the markers; "Superseded
180
+ // by *ADR-0007*" is a sentence with emphasis inside it, and taking one marker
181
+ // off each end would put a half-mangled line in the report.
182
+ const label = trimmed.replace(/^(\*{1,2}|_{1,2})(.*)\1$/, '$2');
183
+ const value = word[1].toLowerCase();
184
+ return { value, label, source, active: !INACTIVE_STATUSES.has(value) };
185
+ }
186
+ /**
187
+ * Lines, whichever way the file ends them.
188
+ *
189
+ * Splitting on "\n" alone leaves a carriage return on every line of a CRLF
190
+ * document, and a trailing "\r" defeats every `$` below - so a Windows-checkout
191
+ * ADR declared no status at all, silently, which is the one failure mode this
192
+ * feature must not have. Found by running the parser over this repository's own
193
+ * ADR-0003, which happened to be CRLF on disk.
194
+ */
195
+ function toLines(masked) {
196
+ return masked.split(/\r?\n/);
197
+ }
198
+ function fromFrontmatter(masked) {
199
+ const block = FRONTMATTER_RE.exec(masked);
200
+ if (!block)
201
+ return undefined;
202
+ for (const line of toLines(block[1])) {
203
+ const found = STATUS_LABEL_RE.exec(line);
204
+ // Read as YAML here and nowhere else: quotes and `#` are syntax in
205
+ // front-matter and characters in a sentence.
206
+ if (found)
207
+ return toStatus(yamlScalar(found[2]), 'frontmatter');
208
+ }
209
+ return undefined;
210
+ }
211
+ /**
212
+ * A `## Status` section, whose value is the first line of prose under it.
213
+ *
214
+ * A section with nothing but the next heading under it declares no status.
215
+ * There is no guard for that here: every ATX heading begins with `#`, and a
216
+ * value that does not begin with a letter is already no status at all. The
217
+ * guard this used to carry could not decide anything, which is a different
218
+ * thing from a guard that is merely never hit.
219
+ */
220
+ function fromHeading(lines) {
221
+ const start = lines.findIndex((line) => STATUS_HEADING_RE.test(line));
222
+ if (start === -1)
223
+ return undefined;
224
+ for (let index = start + 1; index < lines.length; index++) {
225
+ const candidate = lines[index].trim();
226
+ if (candidate.length === 0)
227
+ continue;
228
+ return toStatus(candidate, 'heading');
229
+ }
230
+ return undefined;
231
+ }
232
+ /**
233
+ * A `**Status:** accepted` line in the document's preamble.
234
+ *
235
+ * Bounded to the preamble - everything before the first `##` - because this
236
+ * form is a line of prose with a colon in it, and a tool that accepts one
237
+ * anywhere in a long document will eventually find one in a sentence.
238
+ */
239
+ function fromLabel(lines) {
240
+ for (const line of lines) {
241
+ if (SECTION_HEADING_RE.test(line))
242
+ return undefined;
243
+ const found = STATUS_LABEL_RE.exec(line);
244
+ if (found)
245
+ return toStatus(found[2], 'label');
246
+ }
247
+ return undefined;
248
+ }
249
+ /**
250
+ * The lifecycle status a Markdown document declares about itself.
251
+ *
252
+ * Three spellings are recognised because three are in use, including two in
253
+ * this repository's own ADRs: YAML front-matter (MADR), a `## Status` section
254
+ * (Nygard), and a bold `**Status:**` label. Front-matter wins when present -
255
+ * it is machine-readable metadata rather than a convention read out of prose.
256
+ *
257
+ * Code is masked first, so a document that documents this syntax inside a
258
+ * fence - this project's README does - is not read as declaring a status.
259
+ */
260
+ export function parseStatus(source) {
261
+ return statusOf(maskCode(source));
262
+ }
263
+ function statusOf(masked) {
264
+ const lines = toLines(masked);
265
+ return fromFrontmatter(masked) ?? fromHeading(lines) ?? fromLabel(lines);
266
+ }
267
+ /** An ATX heading of level 1, with any closing sequence taken off. */
268
+ const TITLE_RE = /^[ \t]{0,3}#[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$/;
269
+ /**
270
+ * The document's title: its first level-one heading.
271
+ *
272
+ * Read so that a rule can be shown with the decision it belongs to - "ADR-0011:
273
+ * Layering constraints and import cycles" says more to someone about to edit a
274
+ * file than `docs/adr/0011-layers-and-cycles.md` does. Front-matter is skipped
275
+ * and code is masked, for the same reasons as the status: a README that shows
276
+ * an example ADR inside a fence has not titled itself with the example.
277
+ *
278
+ * Setext headings (a line underlined with `===`) are not read. Nobody writes an
279
+ * ADR that way, and a reader that guessed at underlines would find titles in
280
+ * tables.
281
+ */
282
+ export function parseTitle(source) {
283
+ return titleOf(maskCode(source));
284
+ }
285
+ function titleOf(masked) {
286
+ const body = masked.slice(FRONTMATTER_RE.exec(masked)?.[0].length ?? 0);
287
+ for (const line of toLines(body)) {
288
+ const title = TITLE_RE.exec(line)?.[1];
289
+ if (title)
290
+ return title;
291
+ }
292
+ return undefined;
293
+ }
91
294
  const DIRECTIVE_RE = /<!--\s*@([a-zA-Z][\w-]*)([\s\S]*?)-->/g;
92
295
  const ATTRIBUTE_RE = /([a-zA-Z][\w-]*)(?:\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s"'=<>`]+)))?/g;
93
296
  /**
@@ -95,15 +298,13 @@ const ATTRIBUTE_RE = /([a-zA-Z][\w-]*)(?:\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|
95
298
  * offset and newline so that reported line/column numbers stay exact.
96
299
  */
97
300
  export function maskCode(source) {
98
- // split('') keeps UTF-16 index parity with the original string, which
99
- // [...source] would break on astral characters (and break line numbers).
100
- const chars = source.split('');
101
- const blank = (start, end) => {
102
- for (let i = start; i < end && i < chars.length; i++) {
103
- if (chars[i] !== '\n')
104
- chars[i] = ' ';
105
- }
106
- };
301
+ // Ranges, blanked by `maskRanges`, rather than a character array blanked in
302
+ // place. The array was `source.split('')` - one string per UTF-16 unit - and
303
+ // measured at 7.2ms over this repository's 183KB of specs against 0.8ms for
304
+ // the ranges, with identical output on every one of them and on 50,000
305
+ // random inputs. It became worth measuring when `spec-guard query` put a
306
+ // budget on reading specs; see ADR-0012. Offsets still survive, for the reason
307
+ // `maskRanges` gives.
107
308
  // Fenced code blocks: ``` or ~~~ (3+ markers), optionally indented up to 3 spaces.
108
309
  const fenceRe = /^[ \t]{0,3}(`{3,}|~{3,})[^\n]*$/gm;
109
310
  let match;
@@ -126,13 +327,13 @@ export function maskCode(source) {
126
327
  }
127
328
  }
128
329
  consumed.push([open.index, closeEnd]);
129
- blank(open.index, closeEnd);
130
330
  }
131
331
  // Inline code spans, using CommonMark's rule: a run of N backticks is closed
132
332
  // by the next run of EXACTLY N backticks. Runs of a different length are
133
333
  // skipped rather than treated as a closer - otherwise a stray ``` inside a
134
334
  // sentence shifts every later pairing by one and un-masks real prose.
135
- const masked = chars.join('');
335
+ const masked = maskRanges(source, consumed);
336
+ const spans = [];
136
337
  const runs = [];
137
338
  const runRe = /`+/g;
138
339
  let run;
@@ -145,10 +346,10 @@ export function maskCode(source) {
145
346
  if (closeIndex === -1)
146
347
  continue;
147
348
  const close = runs[closeIndex];
148
- blank(open.index, close.index + close.length);
349
+ spans.push([open.index, close.index + close.length]);
149
350
  index = closeIndex;
150
351
  }
151
- return chars.join('');
352
+ return maskRanges(masked, spans);
152
353
  }
153
354
  function unescape(value) {
154
355
  return value.replace(/\\(.)/g, '$1');
@@ -167,9 +368,23 @@ export function parseAttributes(input) {
167
368
  }
168
369
  /** Extracts every spec-guard directive from a Markdown source string. */
169
370
  export function parseDirectives(source, context) {
371
+ return directivesOf(source, maskCode(source), context);
372
+ }
373
+ /**
374
+ * Everything a spec document declares, read with one pass of `maskCode`.
375
+ *
376
+ * `parseDirectives` and `parseTitle` each mask the source, and masking is most
377
+ * of what reading a spec costs; a caller that wants both should not pay twice.
378
+ */
379
+ export function parseDocument(source, context) {
380
+ const masked = maskCode(source);
381
+ const parsed = directivesOf(source, masked, context);
382
+ const title = titleOf(masked);
383
+ return title === undefined ? parsed : { ...parsed, title };
384
+ }
385
+ function directivesOf(source, masked, context) {
170
386
  const directives = [];
171
387
  const errors = [];
172
- const masked = maskCode(source);
173
388
  const starts = lineStarts(source);
174
389
  DIRECTIVE_RE.lastIndex = 0;
175
390
  let match;
@@ -210,6 +425,7 @@ export function parseDirectives(source, context) {
210
425
  }
211
426
  directives.push({ kind: kind, attributes, location, raw });
212
427
  }
213
- return { directives, errors };
428
+ const status = statusOf(masked);
429
+ return status ? { directives, errors, status } : { directives, errors };
214
430
  }
215
431
  //# sourceMappingURL=parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `spec-guard query`: the rules in force for a path, without running any.
3
+ *
4
+ * The question an agent - or a person - asks before touching a file is not
5
+ * "does the codebase pass" but "what am I allowed to do here". Answering it
6
+ * from CI means writing the code first and finding out afterwards, which for a
7
+ * model is a loop of generate, fail, read the log, regenerate. This answers it
8
+ * up front, from the specs alone, in the time it takes to read them. ADR-0012.
9
+ */
10
+ import { type DocumentView, type QueryPath, type RuleView } from './rules.js';
11
+ import { type SpecDocument } from './specs.js';
12
+ import type { Assertion, DirectiveError } from './types.js';
13
+ export interface RuleSetOptions {
14
+ /** Globs or paths of the Markdown specs. */
15
+ patterns: readonly string[];
16
+ /** Absolute root the specs' paths are resolved against. */
17
+ root: string;
18
+ /** Count matches inside the spec files themselves, as `--include-specs` does. */
19
+ includeSpecs?: boolean;
20
+ /** Skip `.git`, `.hg`, `.svn` and `node_modules`, as a run does by default. */
21
+ defaultSkips?: boolean;
22
+ }
23
+ /** Every rule the specs state, resolved, with the document that states it. */
24
+ export interface RuleSet {
25
+ root: string;
26
+ /** Root-relative paths of every spec file the patterns matched. */
27
+ specFiles: string[];
28
+ documents: SpecDocument[];
29
+ rules: Array<{
30
+ assertion: Assertion;
31
+ document: DocumentView;
32
+ }>;
33
+ /** Unreadable specs, malformed directives and directives that do not resolve. */
34
+ errors: DirectiveError[];
35
+ }
36
+ export declare function viewDocument(document: SpecDocument): DocumentView;
37
+ /**
38
+ * Reads and resolves every rule, in force or not.
39
+ *
40
+ * Resolution is the runner's own, so a query and a run cannot disagree about
41
+ * what a directive means - including about which directives are errors.
42
+ */
43
+ export declare function loadRuleSet(options: RuleSetOptions): Promise<RuleSet>;
44
+ /** A path argument that cannot be asked about. */
45
+ export declare class QueryPathError extends Error {
46
+ }
47
+ /**
48
+ * Turns a path as someone typed it into the path a rule is written against.
49
+ *
50
+ * Absolute paths are accepted as long as they are inside the root, because that
51
+ * is what an agent's file tools hand around. A path that does not exist is
52
+ * accepted too: "what rules will govern the file I am about to create" is the
53
+ * question this command exists for. It is a file unless it exists as a
54
+ * directory or is written with a trailing slash.
55
+ */
56
+ export declare function resolveQueryPath(input: string, root: string): Promise<QueryPath & {
57
+ exists: boolean;
58
+ }>;
59
+ export interface PathRules {
60
+ path: string;
61
+ shape: QueryPath['shape'];
62
+ exists: boolean;
63
+ /** The rules that govern the path, in document order. */
64
+ rules: RuleView[];
65
+ /**
66
+ * Rules in documents not in force that would govern the path, and where.
67
+ *
68
+ * Counted whether or not they are listed. A rule that has stopped applying is
69
+ * the thing ADR-0010 says a report must never be quiet about, and an agent
70
+ * told "nothing governs this file" while a draft is about to govern it has
71
+ * been told something false by omission.
72
+ */
73
+ withheld: {
74
+ rules: number;
75
+ documents: string[];
76
+ };
77
+ }
78
+ export interface QueryReport {
79
+ root: string;
80
+ specFiles: string[];
81
+ results: PathRules[];
82
+ /** The documents behind the listed rules and the withheld counts, by path. */
83
+ documents: DocumentView[];
84
+ errors: Array<{
85
+ file: string;
86
+ line: number;
87
+ message: string;
88
+ }>;
89
+ durationMs: number;
90
+ }
91
+ export interface QueryOptions extends RuleSetOptions {
92
+ paths: readonly string[];
93
+ /** List rules from documents that are not in force, as `--ignore-status` does. */
94
+ includeInactive?: boolean;
95
+ }
96
+ /** The rules governing each path. */
97
+ export declare function answerQuery(ruleSet: RuleSet, paths: ReadonlyArray<QueryPath & {
98
+ exists: boolean;
99
+ }>, includeInactive: boolean): Omit<QueryReport, 'durationMs'>;
100
+ /** Loads the rules once and answers for every path. */
101
+ export declare function queryRules(options: QueryOptions): Promise<QueryReport>;
102
+ /** The human-readable form of a query. */
103
+ export declare function formatQuery(report: QueryReport): string;
104
+ /** The JSON form of a query, stable enough for a script to depend on. */
105
+ export declare function formatQueryJson(report: QueryReport): string;
106
+ /** Whether a location lies in any of the queried paths. */
107
+ export declare function inQueriedPaths(file: string, paths: readonly QueryPath[]): boolean;
108
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
package/dist/query.js ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * `spec-guard query`: the rules in force for a path, without running any.
3
+ *
4
+ * The question an agent - or a person - asks before touching a file is not
5
+ * "does the codebase pass" but "what am I allowed to do here". Answering it
6
+ * from CI means writing the code first and finding out afterwards, which for a
7
+ * model is a loop of generate, fail, read the log, regenerate. This answers it
8
+ * up front, from the specs alone, in the time it takes to read them. ADR-0012.
9
+ */
10
+ import path from 'node:path';
11
+ import { comparePaths } from './engine.js';
12
+ import { statOrNull, toPosix } from './glob.js';
13
+ import { elapsed, resolveDirective, specExclusions } from './runner.js';
14
+ import { createScope } from './scope.js';
15
+ import { governs, viewRule, within } from './rules.js';
16
+ import { readSpecs, specPath } from './specs.js';
17
+ export function viewDocument(document) {
18
+ return {
19
+ file: document.relativeFile,
20
+ title: document.title ?? null,
21
+ status: document.status?.value ?? null,
22
+ label: document.status?.label ?? null,
23
+ inForce: document.inForce,
24
+ };
25
+ }
26
+ /**
27
+ * Reads and resolves every rule, in force or not.
28
+ *
29
+ * Resolution is the runner's own, so a query and a run cannot disagree about
30
+ * what a directive means - including about which directives are errors.
31
+ */
32
+ export async function loadRuleSet(options) {
33
+ const specs = await readSpecs(options.patterns, options.root);
34
+ const context = {
35
+ root: options.root,
36
+ excludeFiles: specExclusions(specs.files, options.includeSpecs ?? false),
37
+ scope: createScope(options.defaultSkips ?? true),
38
+ };
39
+ const rules = [];
40
+ const errors = [...specs.errors];
41
+ for (const document of specs.documents) {
42
+ const view = viewDocument(document);
43
+ for (const directive of document.directives) {
44
+ const resolved = resolveDirective(directive, context);
45
+ if ('error' in resolved)
46
+ errors.push(resolved.error);
47
+ else
48
+ rules.push({ assertion: resolved.assertion, document: view });
49
+ }
50
+ }
51
+ errors.sort((a, b) => a.location.relativeFile === b.location.relativeFile
52
+ ? a.location.line - b.location.line
53
+ : comparePaths(a.location.relativeFile, b.location.relativeFile));
54
+ return {
55
+ root: options.root,
56
+ specFiles: specs.files.map((file) => specPath(options.root, file)),
57
+ documents: specs.documents,
58
+ rules,
59
+ errors,
60
+ };
61
+ }
62
+ /** A path argument that cannot be asked about. */
63
+ export class QueryPathError extends Error {
64
+ }
65
+ /**
66
+ * Turns a path as someone typed it into the path a rule is written against.
67
+ *
68
+ * Absolute paths are accepted as long as they are inside the root, because that
69
+ * is what an agent's file tools hand around. A path that does not exist is
70
+ * accepted too: "what rules will govern the file I am about to create" is the
71
+ * question this command exists for. It is a file unless it exists as a
72
+ * directory or is written with a trailing slash.
73
+ */
74
+ export async function resolveQueryPath(input, root) {
75
+ if (input.trim().length === 0)
76
+ throw new QueryPathError('A path to query must not be empty.');
77
+ // Separators normalised before resolving, so the path, the absolute path and
78
+ // whether it exists all describe the same file on every platform.
79
+ const absolutePath = path.resolve(root, toPosix(input));
80
+ const relative = path.relative(root, absolutePath);
81
+ const posix = toPosix(relative);
82
+ if (posix === '..' || posix.startsWith('../') || path.isAbsolute(relative)) {
83
+ throw new QueryPathError(`"${input}" is outside the root ${toPosix(root)}.`);
84
+ }
85
+ const stats = await statOrNull(absolutePath);
86
+ const shape = stats ? (stats.isDirectory() ? 'directory' : 'file') : /[\\/]$/.test(input) ? 'directory' : 'file';
87
+ return { path: posix || '.', shape, absolutePath, exists: stats !== null };
88
+ }
89
+ /** The rules governing each path. */
90
+ export function answerQuery(ruleSet, paths, includeInactive) {
91
+ const cited = new Map();
92
+ const results = paths.map((query) => {
93
+ const governing = ruleSet.rules.filter(({ assertion }) => governs(assertion, query));
94
+ const listed = governing.filter(({ document }) => document.inForce || includeInactive);
95
+ for (const { document } of governing)
96
+ cited.set(document.file, document);
97
+ const withheld = governing.filter(({ document }) => !document.inForce);
98
+ return {
99
+ path: query.path,
100
+ shape: query.shape,
101
+ exists: query.exists,
102
+ rules: listed.map(({ assertion, document }) => viewRule(assertion, document, query)),
103
+ withheld: {
104
+ rules: withheld.length,
105
+ documents: [...new Set(withheld.map(({ document }) => document.file))],
106
+ },
107
+ };
108
+ });
109
+ return {
110
+ root: toPosix(ruleSet.root),
111
+ specFiles: ruleSet.specFiles,
112
+ results,
113
+ documents: [...cited.values()].sort((a, b) => comparePaths(a.file, b.file)),
114
+ errors: ruleSet.errors.map((error) => ({
115
+ file: error.location.relativeFile,
116
+ line: error.location.line,
117
+ message: error.message,
118
+ })),
119
+ };
120
+ }
121
+ /** Loads the rules once and answers for every path. */
122
+ export async function queryRules(options) {
123
+ const startedAt = performance.now();
124
+ const paths = await Promise.all(options.paths.map((input) => resolveQueryPath(input, options.root)));
125
+ const ruleSet = await loadRuleSet(options);
126
+ return { ...answerQuery(ruleSet, paths, options.includeInactive ?? false), durationMs: elapsed(startedAt) };
127
+ }
128
+ /* --------------------------------------------------------------- formatting */
129
+ function plural(count, one, many) {
130
+ return `${count} ${count === 1 ? one : many}`;
131
+ }
132
+ /** The lines under a rule that say what it asks of this path. */
133
+ function ruleDetails(rule) {
134
+ const lines = [];
135
+ if (rule.position) {
136
+ const { layer, position, matches, mayImport, mustNotImport } = rule.position;
137
+ const order = rule.order;
138
+ if (layer === null) {
139
+ lines.push(matches.length === 0
140
+ ? 'layer: none - no layer matches this path'
141
+ : `layer: ambiguous - ${matches.map((match) => `"${match}"`).join(' and ')} all match, and the rule fails until one does`);
142
+ }
143
+ else {
144
+ lines.push(`layer: ${layer} (${position} of ${order.length})`);
145
+ lines.push(`may import: ${mayImport.join(', ')}`);
146
+ if (mustNotImport.length > 0)
147
+ lines.push(`must not import: ${mustNotImport.join(', ')}`);
148
+ }
149
+ }
150
+ if (rule.baseline && rule.baseline.length > 0) {
151
+ lines.push(`baseline: ${rule.baseline.map((entry) => `${entry.path} (${entry.declared})`).join(', ')}`);
152
+ }
153
+ if (rule.reason !== null)
154
+ lines.push(`reason: ${rule.reason}`);
155
+ return lines;
156
+ }
157
+ /** The human-readable form of a query. */
158
+ export function formatQuery(report) {
159
+ const out = [];
160
+ const documents = new Map(report.documents.map((document) => [document.file, document]));
161
+ for (const result of report.results) {
162
+ const note = result.exists ? '' : result.shape === 'directory' ? ' (directory, does not exist yet)' : ' (does not exist yet)';
163
+ out.push(`${result.path}${note}`);
164
+ const byDocument = new Map();
165
+ for (const rule of result.rules)
166
+ byDocument.set(rule.document, [...(byDocument.get(rule.document) ?? []), rule]);
167
+ out.push(result.rules.length === 0
168
+ ? ' no rules in force govern this path'
169
+ : ` ${plural(result.rules.length, 'rule', 'rules')} from ${plural(byDocument.size, 'document', 'documents')}`);
170
+ for (const [file, rules] of byDocument) {
171
+ const document = documents.get(file);
172
+ const status = document.label === null ? '' : `, ${document.inForce ? document.status : `${document.status} - not in force`}`;
173
+ out.push('', ` ${document.title ?? file} (${file}${status})`);
174
+ for (const rule of rules) {
175
+ out.push(` :${rule.line} @${rule.kind} ${rule.description}`);
176
+ for (const detail of ruleDetails(rule))
177
+ out.push(` ${detail}`);
178
+ }
179
+ }
180
+ const unlisted = result.withheld.rules - result.rules.filter((rule) => !rule.inForce).length;
181
+ if (unlisted > 0) {
182
+ const where = result.withheld.documents
183
+ .map((file) => `${file} (${documents.get(file).status})`)
184
+ .join(', ');
185
+ out.push('', ` ${plural(unlisted, 'more rule', 'more rules')} would govern this path if ${where} were in force; --ignore-status lists ${unlisted === 1 ? 'it' : 'them'}`);
186
+ }
187
+ out.push('');
188
+ }
189
+ if (report.errors.length > 0) {
190
+ out.push(`${plural(report.errors.length, 'directive', 'directives')} could not be read, so ${report.errors.length === 1 ? 'its rule governs' : 'their rules govern'} nothing:`);
191
+ for (const error of report.errors)
192
+ out.push(` ${error.file}:${error.line} ${error.message}`);
193
+ out.push('');
194
+ }
195
+ out.push(`${plural(report.specFiles.length, 'spec file', 'spec files')} read in ${report.durationMs.toFixed(1)}ms`);
196
+ return out.join('\n');
197
+ }
198
+ /** The JSON form of a query, stable enough for a script to depend on. */
199
+ export function formatQueryJson(report) {
200
+ return JSON.stringify({ ...report, durationMs: Math.round(report.durationMs * 1000) / 1000 }, null, 2);
201
+ }
202
+ /** Whether a location lies in any of the queried paths. */
203
+ export function inQueriedPaths(file, paths) {
204
+ return paths.some((query) => within(file, query.path));
205
+ }
206
+ //# sourceMappingURL=query.js.map