@descent-vtt/spec-graph 0.2.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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +627 -0
  3. package/bin/spec-graph.js +24 -0
  4. package/dist/baseline.d.ts +93 -0
  5. package/dist/baseline.js +187 -0
  6. package/dist/baseline.js.map +1 -0
  7. package/dist/cli.d.ts +66 -0
  8. package/dist/cli.js +565 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +66 -0
  11. package/dist/config.js +183 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/directives.d.ts +56 -0
  14. package/dist/directives.js +125 -0
  15. package/dist/directives.js.map +1 -0
  16. package/dist/extract.d.ts +116 -0
  17. package/dist/extract.js +1043 -0
  18. package/dist/extract.js.map +1 -0
  19. package/dist/glob.d.ts +73 -0
  20. package/dist/glob.js +284 -0
  21. package/dist/glob.js.map +1 -0
  22. package/dist/graph.d.ts +55 -0
  23. package/dist/graph.js +201 -0
  24. package/dist/graph.js.map +1 -0
  25. package/dist/identity.d.ts +87 -0
  26. package/dist/identity.js +294 -0
  27. package/dist/identity.js.map +1 -0
  28. package/dist/index.d.ts +63 -0
  29. package/dist/index.js +45 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/lifecycle.d.ts +57 -0
  32. package/dist/lifecycle.js +258 -0
  33. package/dist/lifecycle.js.map +1 -0
  34. package/dist/markdown.d.ts +152 -0
  35. package/dist/markdown.js +820 -0
  36. package/dist/markdown.js.map +1 -0
  37. package/dist/paths.d.ts +22 -0
  38. package/dist/paths.js +58 -0
  39. package/dist/paths.js.map +1 -0
  40. package/dist/report.d.ts +89 -0
  41. package/dist/report.js +326 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/resolve.d.ts +61 -0
  44. package/dist/resolve.js +340 -0
  45. package/dist/resolve.js.map +1 -0
  46. package/dist/rules.d.ts +61 -0
  47. package/dist/rules.js +557 -0
  48. package/dist/rules.js.map +1 -0
  49. package/dist/runner.d.ts +108 -0
  50. package/dist/runner.js +186 -0
  51. package/dist/runner.js.map +1 -0
  52. package/dist/sections.d.ts +94 -0
  53. package/dist/sections.js +372 -0
  54. package/dist/sections.js.map +1 -0
  55. package/dist/select.d.ts +96 -0
  56. package/dist/select.js +413 -0
  57. package/dist/select.js.map +1 -0
  58. package/dist/source.d.ts +43 -0
  59. package/dist/source.js +109 -0
  60. package/dist/source.js.map +1 -0
  61. package/dist/state.d.ts +45 -0
  62. package/dist/state.js +330 -0
  63. package/dist/state.js.map +1 -0
  64. package/dist/types.d.ts +294 -0
  65. package/dist/types.js +52 -0
  66. package/dist/types.js.map +1 -0
  67. package/dist/yaml.d.ts +37 -0
  68. package/dist/yaml.js +240 -0
  69. package/dist/yaml.js.map +1 -0
  70. package/package.json +71 -0
package/dist/select.js ADDED
@@ -0,0 +1,413 @@
1
+ /**
2
+ * The selector language.
3
+ *
4
+ * Rules and users ask the graph the same kinds of question, so they use the same
5
+ * machinery. Every built-in diagnostic is a path query plus a message; anything
6
+ * a rule can find, `spec-graph query` can find too, which means a team can
7
+ * express a convention spec-graph never anticipated without writing a plugin.
8
+ *
9
+ * ```text
10
+ * item[openness=open] -delegates-to-> document[phase=retired]
11
+ * document[phase=active] -assumes,depends-on-> document[phase=retired]
12
+ * document =supersedes=> document[phase=active]
13
+ * item[state=obviated] <-assumes- *
14
+ * *[path^=docs/adr] -blocked-by-> item[openness=open]
15
+ * ```
16
+ *
17
+ * A query evaluates to **paths**, not endpoints. A rule that has the whole path
18
+ * can say "ADR-0004 line 31 delegates this to ADR-0002, which was retired in
19
+ * March" instead of naming two documents and leaving the reader to reconstruct
20
+ * the relationship.
21
+ */
22
+ import { receptivityOf } from './lifecycle.js';
23
+ import { documentOf } from './resolve.js';
24
+ import { EDGE_KINDS } from './types.js';
25
+ export class QueryError extends Error {
26
+ /** Offset into the query string where the problem starts. */
27
+ offset;
28
+ constructor(message, offset) {
29
+ super(message);
30
+ this.name = 'QueryError';
31
+ this.offset = offset;
32
+ }
33
+ }
34
+ /** Guards against a pathological query fanning out over a huge corpus. */
35
+ export const DEFAULT_MATCH_LIMIT = 10_000;
36
+ /* -------------------------------------------------------------------------- */
37
+ /* Parsing */
38
+ /* -------------------------------------------------------------------------- */
39
+ const NODE_KINDS = {
40
+ document: 'document',
41
+ documents: 'document',
42
+ doc: 'document',
43
+ docs: 'document',
44
+ item: 'item',
45
+ items: 'item',
46
+ node: null,
47
+ nodes: null,
48
+ any: null,
49
+ '*': null,
50
+ };
51
+ /** Compiles a selector string into a {@link QuerySpec}. */
52
+ export function parseQuery(source) {
53
+ const parser = new Parser(source);
54
+ const start = parser.parseNodeMatcher();
55
+ const steps = [];
56
+ parser.skipSpace();
57
+ while (!parser.atEnd()) {
58
+ steps.push(parser.parseStep());
59
+ parser.skipSpace();
60
+ }
61
+ return { start, steps };
62
+ }
63
+ class Parser {
64
+ source;
65
+ position = 0;
66
+ constructor(source) {
67
+ this.source = source;
68
+ }
69
+ atEnd() {
70
+ return this.position >= this.source.length;
71
+ }
72
+ skipSpace() {
73
+ while (this.position < this.source.length && /\s/.test(this.source[this.position]))
74
+ this.position += 1;
75
+ }
76
+ parseNodeMatcher() {
77
+ this.skipSpace();
78
+ let kind = null;
79
+ const word = /^[A-Za-z]+|^\*/.exec(this.source.slice(this.position));
80
+ if (word) {
81
+ const text = word[0].toLowerCase();
82
+ if (!(text in NODE_KINDS)) {
83
+ throw new QueryError(`unknown node type "${word[0]}", expected one of: document, item, * (any)`, this.position);
84
+ }
85
+ kind = NODE_KINDS[text];
86
+ this.position += word[0].length;
87
+ }
88
+ else if (this.source[this.position] !== '[') {
89
+ throw new QueryError('expected a node type (document, item or *) or a [predicate]', this.position);
90
+ }
91
+ const predicates = [];
92
+ while (this.source[this.position] === '[')
93
+ predicates.push(this.parsePredicate());
94
+ return { kind, predicates };
95
+ }
96
+ parsePredicate() {
97
+ this.position += 1;
98
+ const keyMatch = /^[A-Za-z_][A-Za-z0-9_.:-]*/.exec(this.source.slice(this.position));
99
+ if (!keyMatch)
100
+ throw new QueryError('expected an attribute name after "["', this.position);
101
+ const key = keyMatch[0].toLowerCase();
102
+ this.position += keyMatch[0].length;
103
+ if (this.source[this.position] === ']') {
104
+ this.position += 1;
105
+ return { key, operator: 'exists', value: '' };
106
+ }
107
+ const operatorMatch = /^(!=|\^=|\$=|\*=|~=|=)/.exec(this.source.slice(this.position));
108
+ if (!operatorMatch) {
109
+ throw new QueryError(`expected an operator (=, !=, ^=, $=, *=, ~=) or "]" after "${key}"`, this.position);
110
+ }
111
+ const operator = operatorMatch[0];
112
+ this.position += operator.length;
113
+ const value = this.parseValue();
114
+ if (this.source[this.position] !== ']')
115
+ throw new QueryError('expected "]" to close the predicate', this.position);
116
+ this.position += 1;
117
+ return { key, operator, value };
118
+ }
119
+ parseValue() {
120
+ const quote = this.source[this.position];
121
+ if (quote === '"' || quote === "'") {
122
+ this.position += 1;
123
+ let out = '';
124
+ while (this.position < this.source.length && this.source[this.position] !== quote) {
125
+ if (this.source[this.position] === '\\')
126
+ this.position += 1;
127
+ out += this.source[this.position] ?? '';
128
+ this.position += 1;
129
+ }
130
+ if (this.source[this.position] !== quote)
131
+ throw new QueryError('unterminated quoted value', this.position);
132
+ this.position += 1;
133
+ return out;
134
+ }
135
+ const match = /^[^\]\s]*/.exec(this.source.slice(this.position));
136
+ const value = (match?.[0] ?? '');
137
+ this.position += value.length;
138
+ return value;
139
+ }
140
+ parseStep() {
141
+ const rest = this.source.slice(this.position);
142
+ // The forward form is lazy because its terminator is two characters (`->`),
143
+ // which cannot be confused with the hyphen inside `delegates-to`. The
144
+ // backward terminator is a single `-`, so it must be greedy and backtrack:
145
+ // matching lazily would read `<-delegates-to-` as the relation `delegates`.
146
+ const forward = /^(-|=)([A-Za-z,*-]*?)(->|=>)/.exec(rest);
147
+ const backward = /^(<-|<=)([A-Za-z,*-]*)(-|=)(?=\s|$|[A-Za-z*[])/.exec(rest);
148
+ if (forward && (!backward || (forward.index ?? 0) <= (backward.index ?? 0))) {
149
+ const transitive = forward[1] === '=';
150
+ const closing = forward[3];
151
+ if ((closing === '=>') !== transitive) {
152
+ throw new QueryError('mismatched arrow: use -kind-> for one hop or =kind=> for transitive', this.position);
153
+ }
154
+ const kinds = this.parseKinds(forward[2], this.position + 1);
155
+ this.position += forward[0].length;
156
+ return { direction: 'out', kinds, transitive, node: this.parseNodeMatcher() };
157
+ }
158
+ if (backward) {
159
+ const transitive = backward[1] === '<=';
160
+ const closing = backward[3];
161
+ if ((closing === '=') !== transitive) {
162
+ throw new QueryError('mismatched arrow: use <-kind- for one hop or <=kind= for transitive', this.position);
163
+ }
164
+ const kinds = this.parseKinds(backward[2], this.position + 2);
165
+ this.position += backward[0].length;
166
+ return { direction: 'in', kinds, transitive, node: this.parseNodeMatcher() };
167
+ }
168
+ throw new QueryError('expected a relation step such as -delegates-to->, <-assumes- or =supersedes=>', this.position);
169
+ }
170
+ parseKinds(raw, offset) {
171
+ const trimmed = raw.trim();
172
+ if (trimmed.length === 0 || trimmed === '*')
173
+ return null;
174
+ const kinds = [];
175
+ for (const part of trimmed.split(',')) {
176
+ const name = part.trim().toLowerCase();
177
+ if (name.length === 0)
178
+ continue;
179
+ if (!EDGE_KINDS.includes(name)) {
180
+ throw new QueryError(`unknown relation "${name}", expected one of: ${EDGE_KINDS.join(', ')}`, offset);
181
+ }
182
+ kinds.push(name);
183
+ }
184
+ return kinds.length > 0 ? kinds : null;
185
+ }
186
+ }
187
+ /* -------------------------------------------------------------------------- */
188
+ /* Attributes */
189
+ /* -------------------------------------------------------------------------- */
190
+ /**
191
+ * The values a predicate key sees on a node.
192
+ *
193
+ * Multi-valued keys (`alias`, `section`) match when *any* value matches, which
194
+ * is what makes `[section=Open Questions]` read the way people expect.
195
+ */
196
+ export function attributesOf(node, key, graph) {
197
+ switch (key) {
198
+ case 'id':
199
+ return [node.id];
200
+ case 'kind':
201
+ return [node.kind];
202
+ case 'title':
203
+ return [node.title];
204
+ case 'file':
205
+ return [node.at.file];
206
+ case 'line':
207
+ return [String(node.at.span.start.line)];
208
+ case 'document':
209
+ return [node.kind === 'item' ? node.document : node.id];
210
+ default:
211
+ break;
212
+ }
213
+ if (node.kind === 'document') {
214
+ switch (key) {
215
+ case 'path':
216
+ return [node.path];
217
+ case 'phase':
218
+ return [node.phase];
219
+ case 'status':
220
+ return node.rawStatus === null ? [] : [node.rawStatus];
221
+ case 'receptivity':
222
+ return [receptivityOf(node.phase)];
223
+ case 'alias':
224
+ return [...node.aliases];
225
+ default:
226
+ break;
227
+ }
228
+ if (key.startsWith('fm.')) {
229
+ const value = node.frontMatter[key.slice(3)];
230
+ if (value === undefined)
231
+ return [];
232
+ return typeof value === 'string' ? [value] : [...value];
233
+ }
234
+ return [];
235
+ }
236
+ // An item inherits its document's lifecycle. Without this, the two rules that
237
+ // matter most could not be written as queries at all, because the question
238
+ // they ask - "is the document holding this obligation still open?" - is about
239
+ // the owner, not the item.
240
+ if (key === 'phase' || key === 'receptivity' || key === 'status' || key === 'path' || key === 'alias') {
241
+ const owner = graph?.owningDocument(node.id);
242
+ if (owner)
243
+ return attributesOf(owner, key, graph);
244
+ return key === 'path' ? [node.at.file] : [];
245
+ }
246
+ switch (key) {
247
+ case 'state':
248
+ case 'disposition':
249
+ return [node.disposition];
250
+ case 'openness':
251
+ return [node.openness];
252
+ case 'section':
253
+ return [...node.section];
254
+ case 'text':
255
+ return [node.text];
256
+ case 'body':
257
+ return [node.body];
258
+ case 'evidence':
259
+ return [node.evidence.source];
260
+ case 'conflicted':
261
+ return [node.conflicts.length > 0 ? 'true' : 'false'];
262
+ default:
263
+ return [];
264
+ }
265
+ }
266
+ function testPredicate(node, predicate, graph) {
267
+ const values = attributesOf(node, predicate.key, graph);
268
+ const needle = predicate.value.toLowerCase();
269
+ if (predicate.operator === 'exists')
270
+ return values.some((value) => value.length > 0);
271
+ if (predicate.operator === '!=')
272
+ return !values.some((value) => value.toLowerCase() === needle);
273
+ return values.some((raw) => {
274
+ const value = raw.toLowerCase();
275
+ switch (predicate.operator) {
276
+ case '=':
277
+ return value === needle;
278
+ case '^=':
279
+ return value.startsWith(needle);
280
+ case '$=':
281
+ return value.endsWith(needle);
282
+ case '*=':
283
+ return value.includes(needle);
284
+ case '~=':
285
+ return safeRegExp(predicate.value).test(raw);
286
+ default:
287
+ return false;
288
+ }
289
+ });
290
+ }
291
+ const regexCache = new Map();
292
+ function safeRegExp(source) {
293
+ const cached = regexCache.get(source);
294
+ if (cached)
295
+ return cached;
296
+ let expression;
297
+ try {
298
+ expression = new RegExp(source, 'i');
299
+ }
300
+ catch {
301
+ // An invalid pattern matches nothing rather than crashing a whole run.
302
+ expression = /(?!)/;
303
+ }
304
+ regexCache.set(source, expression);
305
+ return expression;
306
+ }
307
+ /** True when a node satisfies a matcher. */
308
+ export function matches(node, matcher, graph) {
309
+ if (matcher.kind !== null && node.kind !== matcher.kind)
310
+ return false;
311
+ return matcher.predicates.every((predicate) => testPredicate(node, predicate, graph));
312
+ }
313
+ /** Runs a compiled query and returns every matching path. */
314
+ export function execute(graph, spec, options = {}) {
315
+ const limit = options.limit ?? DEFAULT_MATCH_LIMIT;
316
+ const allowReflexive = options.allowReflexive ?? false;
317
+ let frontier = [];
318
+ for (const node of graph.nodes.values()) {
319
+ if (matches(node, spec.start, graph))
320
+ frontier.push({ nodes: [node], edges: [] });
321
+ }
322
+ for (const step of spec.steps) {
323
+ const next = [];
324
+ const seen = new Set();
325
+ for (const match of frontier) {
326
+ const tail = match.nodes[match.nodes.length - 1];
327
+ for (const { node, path } of expand(graph, tail, step, allowReflexive)) {
328
+ if (!matches(node, step.node, graph))
329
+ continue;
330
+ const key = `${match.nodes.map((n) => n.id).join('>')}>${node.id}`;
331
+ if (seen.has(key))
332
+ continue;
333
+ seen.add(key);
334
+ next.push({ nodes: [...match.nodes, node], edges: [...match.edges, ...path] });
335
+ if (next.length >= limit)
336
+ break;
337
+ }
338
+ if (next.length >= limit)
339
+ break;
340
+ }
341
+ frontier = next;
342
+ if (frontier.length === 0)
343
+ break;
344
+ }
345
+ return frontier;
346
+ }
347
+ function expand(graph, from, step, allowReflexive) {
348
+ const kinds = step.kinds ?? [];
349
+ const out = [];
350
+ if (!step.transitive) {
351
+ const edges = step.direction === 'out' ? graph.out(from.id, kinds) : graph.in(from.id, kinds);
352
+ for (const edge of edges) {
353
+ if (!allowReflexive && edge.reflexive)
354
+ continue;
355
+ const targetId = step.direction === 'out' ? edge.to : edge.from;
356
+ const node = graph.node(targetId);
357
+ if (node)
358
+ out.push({ node, path: [edge] });
359
+ }
360
+ return out;
361
+ }
362
+ // Transitive steps take the shortest path to each reachable node, which keeps
363
+ // the result deterministic and the reported evidence minimal.
364
+ const reached = step.direction === 'out'
365
+ ? graph.reach(from.id, kinds)
366
+ : reachIncoming(graph, from.id, kinds);
367
+ for (const [id, path] of reached) {
368
+ if (!allowReflexive && path.every((edge) => edge.reflexive))
369
+ continue;
370
+ const node = graph.node(id);
371
+ if (node)
372
+ out.push({ node, path });
373
+ }
374
+ return out;
375
+ }
376
+ function reachIncoming(graph, id, kinds) {
377
+ const paths = new Map();
378
+ const pathTo = new Map([[id, []]]);
379
+ const queue = [id];
380
+ while (queue.length > 0) {
381
+ const current = queue.shift();
382
+ const prefix = pathTo.get(current);
383
+ for (const edge of graph.in(current, kinds)) {
384
+ if (pathTo.has(edge.from) && edge.from !== id)
385
+ continue;
386
+ const path = [...prefix, edge];
387
+ if (edge.from === id) {
388
+ if (!paths.has(id))
389
+ paths.set(id, path);
390
+ continue;
391
+ }
392
+ pathTo.set(edge.from, path);
393
+ paths.set(edge.from, path);
394
+ queue.push(edge.from);
395
+ }
396
+ }
397
+ return paths;
398
+ }
399
+ /** Parses and runs a selector in one call. */
400
+ export function query(graph, selector, options) {
401
+ return execute(graph, parseQuery(selector), options);
402
+ }
403
+ /** Renders a match as `A -kind-> B`, for reports and for `--explain`. */
404
+ export function renderMatch(match) {
405
+ const parts = [match.nodes[0]?.id ?? ''];
406
+ for (let i = 0; i < match.edges.length; i += 1) {
407
+ const edge = match.edges[i];
408
+ const node = match.nodes[i + 1];
409
+ parts.push(`-${edge.kind}->`, node?.id ?? documentOf(edge.to));
410
+ }
411
+ return parts.join(' ');
412
+ }
413
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../src/select.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The selector language.\n *\n * Rules and users ask the graph the same kinds of question, so they use the same\n * machinery. Every built-in diagnostic is a path query plus a message; anything\n * a rule can find, `spec-graph query` can find too, which means a team can\n * express a convention spec-graph never anticipated without writing a plugin.\n *\n * ```text\n * item[openness=open] -delegates-to-> document[phase=retired]\n * document[phase=active] -assumes,depends-on-> document[phase=retired]\n * document =supersedes=> document[phase=active]\n * item[state=obviated] <-assumes- *\n * *[path^=docs/adr] -blocked-by-> item[openness=open]\n * ```\n *\n * A query evaluates to **paths**, not endpoints. A rule that has the whole path\n * can say \"ADR-0004 line 31 delegates this to ADR-0002, which was retired in\n * March\" instead of naming two documents and leaving the reader to reconstruct\n * the relationship.\n */\n\nimport { receptivityOf } from './lifecycle.js';\nimport { documentOf } from './resolve.js';\nimport type { SpecGraph } from './graph.js';\nimport { EDGE_KINDS, type Edge, type EdgeKind, type NodeKind, type SpecNode } from './types.js';\n\n/* -------------------------------------------------------------------------- */\n/* Shapes */\n/* -------------------------------------------------------------------------- */\n\nexport type Operator = '=' | '!=' | '^=' | '$=' | '*=' | '~=' | 'exists';\n\nexport interface Predicate {\n readonly key: string;\n readonly operator: Operator;\n readonly value: string;\n}\n\nexport interface NodeMatcher {\n /** `null` matches both documents and items. */\n readonly kind: NodeKind | null;\n readonly predicates: readonly Predicate[];\n}\n\nexport interface StepSpec {\n readonly direction: 'out' | 'in';\n /** `null` matches any edge kind. */\n readonly kinds: readonly EdgeKind[] | null;\n /** Follow the relation transitively, taking the shortest path to each node. */\n readonly transitive: boolean;\n readonly node: NodeMatcher;\n}\n\nexport interface QuerySpec {\n readonly start: NodeMatcher;\n readonly steps: readonly StepSpec[];\n}\n\n/** One path through the graph. `edges` always has one fewer entry than `nodes`. */\nexport interface Match {\n readonly nodes: readonly SpecNode[];\n readonly edges: readonly Edge[];\n}\n\nexport class QueryError extends Error {\n /** Offset into the query string where the problem starts. */\n readonly offset: number;\n\n constructor(message: string, offset: number) {\n super(message);\n this.name = 'QueryError';\n this.offset = offset;\n }\n}\n\n/** Guards against a pathological query fanning out over a huge corpus. */\nexport const DEFAULT_MATCH_LIMIT = 10_000;\n\n/* -------------------------------------------------------------------------- */\n/* Parsing */\n/* -------------------------------------------------------------------------- */\n\nconst NODE_KINDS: Readonly<Record<string, NodeKind | null>> = {\n document: 'document',\n documents: 'document',\n doc: 'document',\n docs: 'document',\n item: 'item',\n items: 'item',\n node: null,\n nodes: null,\n any: null,\n '*': null,\n};\n\n/** Compiles a selector string into a {@link QuerySpec}. */\nexport function parseQuery(source: string): QuerySpec {\n const parser = new Parser(source);\n const start = parser.parseNodeMatcher();\n const steps: StepSpec[] = [];\n parser.skipSpace();\n while (!parser.atEnd()) {\n steps.push(parser.parseStep());\n parser.skipSpace();\n }\n return { start, steps };\n}\n\nclass Parser {\n private readonly source: string;\n private position = 0;\n\n constructor(source: string) {\n this.source = source;\n }\n\n atEnd(): boolean {\n return this.position >= this.source.length;\n }\n\n skipSpace(): void {\n while (this.position < this.source.length && /\\s/.test(this.source[this.position] as string)) this.position += 1;\n }\n\n parseNodeMatcher(): NodeMatcher {\n this.skipSpace();\n let kind: NodeKind | null = null;\n const word = /^[A-Za-z]+|^\\*/.exec(this.source.slice(this.position));\n if (word) {\n const text = (word[0] as string).toLowerCase();\n if (!(text in NODE_KINDS)) {\n throw new QueryError(\n `unknown node type \"${word[0]}\", expected one of: document, item, * (any)`,\n this.position,\n );\n }\n kind = NODE_KINDS[text] as NodeKind | null;\n this.position += (word[0] as string).length;\n } else if (this.source[this.position] !== '[') {\n throw new QueryError('expected a node type (document, item or *) or a [predicate]', this.position);\n }\n\n const predicates: Predicate[] = [];\n while (this.source[this.position] === '[') predicates.push(this.parsePredicate());\n return { kind, predicates };\n }\n\n private parsePredicate(): Predicate {\n this.position += 1;\n const keyMatch = /^[A-Za-z_][A-Za-z0-9_.:-]*/.exec(this.source.slice(this.position));\n if (!keyMatch) throw new QueryError('expected an attribute name after \"[\"', this.position);\n const key = (keyMatch[0] as string).toLowerCase();\n this.position += (keyMatch[0] as string).length;\n\n if (this.source[this.position] === ']') {\n this.position += 1;\n return { key, operator: 'exists', value: '' };\n }\n\n const operatorMatch = /^(!=|\\^=|\\$=|\\*=|~=|=)/.exec(this.source.slice(this.position));\n if (!operatorMatch) {\n throw new QueryError(\n `expected an operator (=, !=, ^=, $=, *=, ~=) or \"]\" after \"${key}\"`,\n this.position,\n );\n }\n const operator = operatorMatch[0] as Operator;\n this.position += operator.length;\n\n const value = this.parseValue();\n if (this.source[this.position] !== ']') throw new QueryError('expected \"]\" to close the predicate', this.position);\n this.position += 1;\n return { key, operator, value };\n }\n\n private parseValue(): string {\n const quote = this.source[this.position];\n if (quote === '\"' || quote === \"'\") {\n this.position += 1;\n let out = '';\n while (this.position < this.source.length && this.source[this.position] !== quote) {\n if (this.source[this.position] === '\\\\') this.position += 1;\n out += this.source[this.position] ?? '';\n this.position += 1;\n }\n if (this.source[this.position] !== quote) throw new QueryError('unterminated quoted value', this.position);\n this.position += 1;\n return out;\n }\n const match = /^[^\\]\\s]*/.exec(this.source.slice(this.position));\n const value = (match?.[0] ?? '') as string;\n this.position += value.length;\n return value;\n }\n\n parseStep(): StepSpec {\n const rest = this.source.slice(this.position);\n // The forward form is lazy because its terminator is two characters (`->`),\n // which cannot be confused with the hyphen inside `delegates-to`. The\n // backward terminator is a single `-`, so it must be greedy and backtrack:\n // matching lazily would read `<-delegates-to-` as the relation `delegates`.\n const forward = /^(-|=)([A-Za-z,*-]*?)(->|=>)/.exec(rest);\n const backward = /^(<-|<=)([A-Za-z,*-]*)(-|=)(?=\\s|$|[A-Za-z*[])/.exec(rest);\n\n if (forward && (!backward || (forward.index ?? 0) <= (backward.index ?? 0))) {\n const transitive = (forward[1] as string) === '=';\n const closing = forward[3] as string;\n if ((closing === '=>') !== transitive) {\n throw new QueryError('mismatched arrow: use -kind-> for one hop or =kind=> for transitive', this.position);\n }\n const kinds = this.parseKinds(forward[2] as string, this.position + 1);\n this.position += (forward[0] as string).length;\n return { direction: 'out', kinds, transitive, node: this.parseNodeMatcher() };\n }\n\n if (backward) {\n const transitive = (backward[1] as string) === '<=';\n const closing = backward[3] as string;\n if ((closing === '=') !== transitive) {\n throw new QueryError('mismatched arrow: use <-kind- for one hop or <=kind= for transitive', this.position);\n }\n const kinds = this.parseKinds(backward[2] as string, this.position + 2);\n this.position += (backward[0] as string).length;\n return { direction: 'in', kinds, transitive, node: this.parseNodeMatcher() };\n }\n\n throw new QueryError(\n 'expected a relation step such as -delegates-to->, <-assumes- or =supersedes=>',\n this.position,\n );\n }\n\n private parseKinds(raw: string, offset: number): readonly EdgeKind[] | null {\n const trimmed = raw.trim();\n if (trimmed.length === 0 || trimmed === '*') return null;\n const kinds: EdgeKind[] = [];\n for (const part of trimmed.split(',')) {\n const name = part.trim().toLowerCase();\n if (name.length === 0) continue;\n if (!(EDGE_KINDS as readonly string[]).includes(name)) {\n throw new QueryError(`unknown relation \"${name}\", expected one of: ${EDGE_KINDS.join(', ')}`, offset);\n }\n kinds.push(name as EdgeKind);\n }\n return kinds.length > 0 ? kinds : null;\n }\n}\n\n/* -------------------------------------------------------------------------- */\n/* Attributes */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The values a predicate key sees on a node.\n *\n * Multi-valued keys (`alias`, `section`) match when *any* value matches, which\n * is what makes `[section=Open Questions]` read the way people expect.\n */\nexport function attributesOf(node: SpecNode, key: string, graph?: SpecGraph): string[] {\n switch (key) {\n case 'id':\n return [node.id];\n case 'kind':\n return [node.kind];\n case 'title':\n return [node.title];\n case 'file':\n return [node.at.file];\n case 'line':\n return [String(node.at.span.start.line)];\n case 'document':\n return [node.kind === 'item' ? node.document : node.id];\n default:\n break;\n }\n\n if (node.kind === 'document') {\n switch (key) {\n case 'path':\n return [node.path];\n case 'phase':\n return [node.phase];\n case 'status':\n return node.rawStatus === null ? [] : [node.rawStatus];\n case 'receptivity':\n return [receptivityOf(node.phase)];\n case 'alias':\n return [...node.aliases];\n default:\n break;\n }\n if (key.startsWith('fm.')) {\n const value = node.frontMatter[key.slice(3)];\n if (value === undefined) return [];\n return typeof value === 'string' ? [value] : [...value];\n }\n return [];\n }\n\n // An item inherits its document's lifecycle. Without this, the two rules that\n // matter most could not be written as queries at all, because the question\n // they ask - \"is the document holding this obligation still open?\" - is about\n // the owner, not the item.\n if (key === 'phase' || key === 'receptivity' || key === 'status' || key === 'path' || key === 'alias') {\n const owner = graph?.owningDocument(node.id);\n if (owner) return attributesOf(owner, key, graph);\n return key === 'path' ? [node.at.file] : [];\n }\n\n switch (key) {\n case 'state':\n case 'disposition':\n return [node.disposition];\n case 'openness':\n return [node.openness];\n case 'section':\n return [...node.section];\n case 'text':\n return [node.text];\n case 'body':\n return [node.body];\n case 'evidence':\n return [node.evidence.source];\n case 'conflicted':\n return [node.conflicts.length > 0 ? 'true' : 'false'];\n default:\n return [];\n }\n}\n\nfunction testPredicate(node: SpecNode, predicate: Predicate, graph?: SpecGraph): boolean {\n const values = attributesOf(node, predicate.key, graph);\n const needle = predicate.value.toLowerCase();\n\n if (predicate.operator === 'exists') return values.some((value) => value.length > 0);\n if (predicate.operator === '!=') return !values.some((value) => value.toLowerCase() === needle);\n\n return values.some((raw) => {\n const value = raw.toLowerCase();\n switch (predicate.operator) {\n case '=':\n return value === needle;\n case '^=':\n return value.startsWith(needle);\n case '$=':\n return value.endsWith(needle);\n case '*=':\n return value.includes(needle);\n case '~=':\n return safeRegExp(predicate.value).test(raw);\n default:\n return false;\n }\n });\n}\n\nconst regexCache = new Map<string, RegExp>();\n\nfunction safeRegExp(source: string): RegExp {\n const cached = regexCache.get(source);\n if (cached) return cached;\n let expression: RegExp;\n try {\n expression = new RegExp(source, 'i');\n } catch {\n // An invalid pattern matches nothing rather than crashing a whole run.\n expression = /(?!)/;\n }\n regexCache.set(source, expression);\n return expression;\n}\n\n/** True when a node satisfies a matcher. */\nexport function matches(node: SpecNode, matcher: NodeMatcher, graph?: SpecGraph): boolean {\n if (matcher.kind !== null && node.kind !== matcher.kind) return false;\n return matcher.predicates.every((predicate) => testPredicate(node, predicate, graph));\n}\n\n/* -------------------------------------------------------------------------- */\n/* Execution */\n/* -------------------------------------------------------------------------- */\n\nexport interface ExecuteOptions {\n /**\n * Cap on the paths produced by each traversal step.\n *\n * It bounds *expansion*, not the initial selection: a query with no steps\n * returns every matching node. That asymmetry is deliberate. The limit exists\n * so a query across a dense graph cannot fan out unboundedly, and the rules\n * run through this engine - truncating the set of nodes a rule starts from\n * would silently drop findings, whereas truncating a runaway expansion only\n * ever drops paths that were already beyond reading.\n */\n readonly limit?: number | undefined;\n /**\n * Whether a step may stay inside one document.\n *\n * Off by default for reflexive edges: a document that links to itself is a\n * formatting quirk, not a relationship, and letting those through turns every\n * table of contents into a finding.\n */\n readonly allowReflexive?: boolean | undefined;\n}\n\n/** Runs a compiled query and returns every matching path. */\nexport function execute(graph: SpecGraph, spec: QuerySpec, options: ExecuteOptions = {}): Match[] {\n const limit = options.limit ?? DEFAULT_MATCH_LIMIT;\n const allowReflexive = options.allowReflexive ?? false;\n\n let frontier: Match[] = [];\n for (const node of graph.nodes.values()) {\n if (matches(node, spec.start, graph)) frontier.push({ nodes: [node], edges: [] });\n }\n\n for (const step of spec.steps) {\n const next: Match[] = [];\n const seen = new Set<string>();\n\n for (const match of frontier) {\n const tail = match.nodes[match.nodes.length - 1] as SpecNode;\n for (const { node, path } of expand(graph, tail, step, allowReflexive)) {\n if (!matches(node, step.node, graph)) continue;\n const key = `${match.nodes.map((n) => n.id).join('>')}>${node.id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n next.push({ nodes: [...match.nodes, node], edges: [...match.edges, ...path] });\n if (next.length >= limit) break;\n }\n if (next.length >= limit) break;\n }\n\n frontier = next;\n if (frontier.length === 0) break;\n }\n\n return frontier;\n}\n\ninterface Expansion {\n readonly node: SpecNode;\n readonly path: readonly Edge[];\n}\n\nfunction expand(graph: SpecGraph, from: SpecNode, step: StepSpec, allowReflexive: boolean): Expansion[] {\n const kinds = step.kinds ?? [];\n const out: Expansion[] = [];\n\n if (!step.transitive) {\n const edges = step.direction === 'out' ? graph.out(from.id, kinds) : graph.in(from.id, kinds);\n for (const edge of edges) {\n if (!allowReflexive && edge.reflexive) continue;\n const targetId = step.direction === 'out' ? edge.to : edge.from;\n const node = graph.node(targetId);\n if (node) out.push({ node, path: [edge] });\n }\n return out;\n }\n\n // Transitive steps take the shortest path to each reachable node, which keeps\n // the result deterministic and the reported evidence minimal.\n const reached =\n step.direction === 'out'\n ? graph.reach(from.id, kinds)\n : reachIncoming(graph, from.id, kinds);\n\n for (const [id, path] of reached) {\n if (!allowReflexive && path.every((edge) => edge.reflexive)) continue;\n const node = graph.node(id);\n if (node) out.push({ node, path });\n }\n return out;\n}\n\nfunction reachIncoming(graph: SpecGraph, id: string, kinds: readonly EdgeKind[]): Map<string, readonly Edge[]> {\n const paths = new Map<string, readonly Edge[]>();\n const pathTo = new Map<string, readonly Edge[]>([[id, []]]);\n const queue: string[] = [id];\n\n while (queue.length > 0) {\n const current = queue.shift() as string;\n const prefix = pathTo.get(current) as readonly Edge[];\n for (const edge of graph.in(current, kinds)) {\n if (pathTo.has(edge.from) && edge.from !== id) continue;\n const path = [...prefix, edge];\n if (edge.from === id) {\n if (!paths.has(id)) paths.set(id, path);\n continue;\n }\n pathTo.set(edge.from, path);\n paths.set(edge.from, path);\n queue.push(edge.from);\n }\n }\n return paths;\n}\n\n/** Parses and runs a selector in one call. */\nexport function query(graph: SpecGraph, selector: string, options?: ExecuteOptions): Match[] {\n return execute(graph, parseQuery(selector), options);\n}\n\n/** Renders a match as `A -kind-> B`, for reports and for `--explain`. */\nexport function renderMatch(match: Match): string {\n const parts: string[] = [match.nodes[0]?.id ?? ''];\n for (let i = 0; i < match.edges.length; i += 1) {\n const edge = match.edges[i] as Edge;\n const node = match.nodes[i + 1];\n parts.push(`-${edge.kind}->`, node?.id ?? documentOf(edge.to));\n }\n return parts.join(' ');\n}\n"]}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Offset arithmetic.
3
+ *
4
+ * Every finding spec-graph reports points at a line and a column, and every
5
+ * intermediate structure carries raw offsets instead of copies of the text.
6
+ * Converting between the two is therefore on the hot path, so the line table is
7
+ * built once per file and searched with a binary search rather than by counting
8
+ * newlines from the start each time.
9
+ */
10
+ import type { Position, SourceRef, Span } from './types.js';
11
+ export interface LineIndex {
12
+ /** Number of lines. A file ending in a newline does not gain a trailing line. */
13
+ readonly lineCount: number;
14
+ /** Offset at which the given 1-based line starts. */
15
+ lineStart(line: number): number;
16
+ /** Offset just past the given 1-based line, excluding its terminator. */
17
+ lineEnd(line: number): number;
18
+ /** Text of the given 1-based line, without its terminator. */
19
+ lineText(line: number): string;
20
+ /** Convert an absolute offset to a `{ line, column }` position. */
21
+ positionAt(offset: number): Position;
22
+ }
23
+ /**
24
+ * Builds the line table for `text`.
25
+ *
26
+ * Recognises all three line terminators. `\r\n` counts once: a Windows checkout
27
+ * of a spec must produce the same line numbers as a POSIX one, or half the
28
+ * team gets diagnostics pointing at the wrong place.
29
+ */
30
+ export declare function createLineIndex(text: string): LineIndex;
31
+ /** Builds a {@link Span} from two offsets. */
32
+ export declare function spanOf(index: LineIndex, start: number, end: number): Span;
33
+ /** Builds a {@link SourceRef} from two offsets. */
34
+ export declare function refOf(file: string, index: LineIndex, start: number, end: number): SourceRef;
35
+ /** `path:line:column`, the form every editor and terminal knows how to open. */
36
+ export declare function formatRef(ref: SourceRef): string;
37
+ /**
38
+ * Sort key for deterministic report ordering.
39
+ *
40
+ * CI output that reorders between runs is output nobody diffs, so ordering is
41
+ * total: file, then line, then column, then length.
42
+ */
43
+ export declare function compareRefs(a: SourceRef, b: SourceRef): number;
package/dist/source.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Offset arithmetic.
3
+ *
4
+ * Every finding spec-graph reports points at a line and a column, and every
5
+ * intermediate structure carries raw offsets instead of copies of the text.
6
+ * Converting between the two is therefore on the hot path, so the line table is
7
+ * built once per file and searched with a binary search rather than by counting
8
+ * newlines from the start each time.
9
+ */
10
+ /**
11
+ * Builds the line table for `text`.
12
+ *
13
+ * Recognises all three line terminators. `\r\n` counts once: a Windows checkout
14
+ * of a spec must produce the same line numbers as a POSIX one, or half the
15
+ * team gets diagnostics pointing at the wrong place.
16
+ */
17
+ export function createLineIndex(text) {
18
+ const starts = [0];
19
+ for (let i = 0; i < text.length; i += 1) {
20
+ const ch = text.charCodeAt(i);
21
+ if (ch === 10 /* \n */) {
22
+ starts.push(i + 1);
23
+ }
24
+ else if (ch === 13 /* \r */) {
25
+ if (text.charCodeAt(i + 1) === 10)
26
+ i += 1;
27
+ starts.push(i + 1);
28
+ }
29
+ }
30
+ // A terminator at the very end opens a line that contains nothing. Drop it so
31
+ // `lineCount` matches what an editor shows.
32
+ if (starts.length > 1 && starts[starts.length - 1] === text.length)
33
+ starts.pop();
34
+ const lineCount = starts.length;
35
+ const startOf = (line) => {
36
+ const clamped = line < 1 ? 1 : line > lineCount ? lineCount : line;
37
+ return starts[clamped - 1];
38
+ };
39
+ const endOf = (line) => {
40
+ const clamped = line < 1 ? 1 : line > lineCount ? lineCount : line;
41
+ if (clamped === lineCount) {
42
+ let end = text.length;
43
+ while (end > starts[clamped - 1]) {
44
+ const ch = text.charCodeAt(end - 1);
45
+ if (ch === 10 || ch === 13)
46
+ end -= 1;
47
+ else
48
+ break;
49
+ }
50
+ return end;
51
+ }
52
+ let end = starts[clamped];
53
+ while (end > starts[clamped - 1]) {
54
+ const ch = text.charCodeAt(end - 1);
55
+ if (ch === 10 || ch === 13)
56
+ end -= 1;
57
+ else
58
+ break;
59
+ }
60
+ return end;
61
+ };
62
+ return {
63
+ lineCount,
64
+ lineStart: startOf,
65
+ lineEnd: endOf,
66
+ lineText: (line) => text.slice(startOf(line), endOf(line)),
67
+ positionAt(offset) {
68
+ const clamped = offset < 0 ? 0 : offset > text.length ? text.length : offset;
69
+ let lo = 0;
70
+ let hi = starts.length - 1;
71
+ while (lo < hi) {
72
+ const mid = (lo + hi + 1) >> 1;
73
+ if (starts[mid] <= clamped)
74
+ lo = mid;
75
+ else
76
+ hi = mid - 1;
77
+ }
78
+ return { offset: clamped, line: lo + 1, column: clamped - starts[lo] + 1 };
79
+ },
80
+ };
81
+ }
82
+ /** Builds a {@link Span} from two offsets. */
83
+ export function spanOf(index, start, end) {
84
+ return { start: index.positionAt(start), end: index.positionAt(end) };
85
+ }
86
+ /** Builds a {@link SourceRef} from two offsets. */
87
+ export function refOf(file, index, start, end) {
88
+ return { file, span: spanOf(index, start, end) };
89
+ }
90
+ /** `path:line:column`, the form every editor and terminal knows how to open. */
91
+ export function formatRef(ref) {
92
+ return `${ref.file}:${ref.span.start.line}:${ref.span.start.column}`;
93
+ }
94
+ /**
95
+ * Sort key for deterministic report ordering.
96
+ *
97
+ * CI output that reorders between runs is output nobody diffs, so ordering is
98
+ * total: file, then line, then column, then length.
99
+ */
100
+ export function compareRefs(a, b) {
101
+ if (a.file !== b.file)
102
+ return a.file < b.file ? -1 : 1;
103
+ if (a.span.start.line !== b.span.start.line)
104
+ return a.span.start.line - b.span.start.line;
105
+ if (a.span.start.column !== b.span.start.column)
106
+ return a.span.start.column - b.span.start.column;
107
+ return a.span.end.offset - b.span.end.offset;
108
+ }
109
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiBH;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAa,CAAC,CAAC,CAAC,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrB,CAAC;aAAM,IAAI,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE;gBAAE,CAAC,IAAI,CAAC,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,8EAA8E;IAC9E,4CAA4C;IAC5C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,MAAM,CAAC,GAAG,EAAE,CAAC;IAEjF,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;IAEhC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE;QACvC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,OAAO,MAAM,CAAC,OAAO,GAAG,CAAC,CAAW,CAAC;IACvC,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAU,EAAE;QACrC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QACnE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;YACtB,OAAO,GAAG,GAAI,MAAM,CAAC,OAAO,GAAG,CAAC,CAAY,EAAE,CAAC;gBAC7C,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;gBACpC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE;oBAAE,GAAG,IAAI,CAAC,CAAC;;oBAChC,MAAM;YACb,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QACD,IAAI,GAAG,GAAG,MAAM,CAAC,OAAO,CAAW,CAAC;QACpC,OAAO,GAAG,GAAI,MAAM,CAAC,OAAO,GAAG,CAAC,CAAY,EAAE,CAAC;YAC7C,MAAM,EAAE,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACpC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE;gBAAE,GAAG,IAAI,CAAC,CAAC;;gBAChC,MAAM;QACb,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF,OAAO;QACL,SAAS;QACT,SAAS,EAAE,OAAO;QAClB,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1D,UAAU,CAAC,MAAc;YACvB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;YAC7E,IAAI,EAAE,GAAG,CAAC,CAAC;YACX,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAC3B,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;gBACf,MAAM,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAK,MAAM,CAAC,GAAG,CAAY,IAAI,OAAO;oBAAE,EAAE,GAAG,GAAG,CAAC;;oBAC5C,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC;YACpB,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,GAAI,MAAM,CAAC,EAAE,CAAY,GAAG,CAAC,EAAE,CAAC;QACzF,CAAC;KACF,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,MAAM,CAAC,KAAgB,EAAE,KAAa,EAAE,GAAW;IACjE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,KAAgB,EAAE,KAAa,EAAE,GAAW;IAC9E,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;AACnD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,GAAc;IACtC,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACvE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,CAAY,EAAE,CAAY;IACpD,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IAC1F,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAClG,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;AAC/C,CAAC","sourcesContent":["/**\n * Offset arithmetic.\n *\n * Every finding spec-graph reports points at a line and a column, and every\n * intermediate structure carries raw offsets instead of copies of the text.\n * Converting between the two is therefore on the hot path, so the line table is\n * built once per file and searched with a binary search rather than by counting\n * newlines from the start each time.\n */\n\nimport type { Position, SourceRef, Span } from './types.js';\n\nexport interface LineIndex {\n /** Number of lines. A file ending in a newline does not gain a trailing line. */\n readonly lineCount: number;\n /** Offset at which the given 1-based line starts. */\n lineStart(line: number): number;\n /** Offset just past the given 1-based line, excluding its terminator. */\n lineEnd(line: number): number;\n /** Text of the given 1-based line, without its terminator. */\n lineText(line: number): string;\n /** Convert an absolute offset to a `{ line, column }` position. */\n positionAt(offset: number): Position;\n}\n\n/**\n * Builds the line table for `text`.\n *\n * Recognises all three line terminators. `\\r\\n` counts once: a Windows checkout\n * of a spec must produce the same line numbers as a POSIX one, or half the\n * team gets diagnostics pointing at the wrong place.\n */\nexport function createLineIndex(text: string): LineIndex {\n const starts: number[] = [0];\n for (let i = 0; i < text.length; i += 1) {\n const ch = text.charCodeAt(i);\n if (ch === 10 /* \\n */) {\n starts.push(i + 1);\n } else if (ch === 13 /* \\r */) {\n if (text.charCodeAt(i + 1) === 10) i += 1;\n starts.push(i + 1);\n }\n }\n // A terminator at the very end opens a line that contains nothing. Drop it so\n // `lineCount` matches what an editor shows.\n if (starts.length > 1 && starts[starts.length - 1] === text.length) starts.pop();\n\n const lineCount = starts.length;\n\n const startOf = (line: number): number => {\n const clamped = line < 1 ? 1 : line > lineCount ? lineCount : line;\n return starts[clamped - 1] as number;\n };\n\n const endOf = (line: number): number => {\n const clamped = line < 1 ? 1 : line > lineCount ? lineCount : line;\n if (clamped === lineCount) {\n let end = text.length;\n while (end > (starts[clamped - 1] as number)) {\n const ch = text.charCodeAt(end - 1);\n if (ch === 10 || ch === 13) end -= 1;\n else break;\n }\n return end;\n }\n let end = starts[clamped] as number;\n while (end > (starts[clamped - 1] as number)) {\n const ch = text.charCodeAt(end - 1);\n if (ch === 10 || ch === 13) end -= 1;\n else break;\n }\n return end;\n };\n\n return {\n lineCount,\n lineStart: startOf,\n lineEnd: endOf,\n lineText: (line) => text.slice(startOf(line), endOf(line)),\n positionAt(offset: number): Position {\n const clamped = offset < 0 ? 0 : offset > text.length ? text.length : offset;\n let lo = 0;\n let hi = starts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if ((starts[mid] as number) <= clamped) lo = mid;\n else hi = mid - 1;\n }\n return { offset: clamped, line: lo + 1, column: clamped - (starts[lo] as number) + 1 };\n },\n };\n}\n\n/** Builds a {@link Span} from two offsets. */\nexport function spanOf(index: LineIndex, start: number, end: number): Span {\n return { start: index.positionAt(start), end: index.positionAt(end) };\n}\n\n/** Builds a {@link SourceRef} from two offsets. */\nexport function refOf(file: string, index: LineIndex, start: number, end: number): SourceRef {\n return { file, span: spanOf(index, start, end) };\n}\n\n/** `path:line:column`, the form every editor and terminal knows how to open. */\nexport function formatRef(ref: SourceRef): string {\n return `${ref.file}:${ref.span.start.line}:${ref.span.start.column}`;\n}\n\n/**\n * Sort key for deterministic report ordering.\n *\n * CI output that reorders between runs is output nobody diffs, so ordering is\n * total: file, then line, then column, then length.\n */\nexport function compareRefs(a: SourceRef, b: SourceRef): number {\n if (a.file !== b.file) return a.file < b.file ? -1 : 1;\n if (a.span.start.line !== b.span.start.line) return a.span.start.line - b.span.start.line;\n if (a.span.start.column !== b.span.start.column) return a.span.start.column - b.span.start.column;\n return a.span.end.offset - b.span.end.offset;\n}\n"]}