@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.
- package/LICENSE +21 -0
- package/README.md +627 -0
- package/bin/spec-graph.js +24 -0
- package/dist/baseline.d.ts +93 -0
- package/dist/baseline.js +187 -0
- package/dist/baseline.js.map +1 -0
- package/dist/cli.d.ts +66 -0
- package/dist/cli.js +565 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +66 -0
- package/dist/config.js +183 -0
- package/dist/config.js.map +1 -0
- package/dist/directives.d.ts +56 -0
- package/dist/directives.js +125 -0
- package/dist/directives.js.map +1 -0
- package/dist/extract.d.ts +116 -0
- package/dist/extract.js +1043 -0
- package/dist/extract.js.map +1 -0
- package/dist/glob.d.ts +73 -0
- package/dist/glob.js +284 -0
- package/dist/glob.js.map +1 -0
- package/dist/graph.d.ts +55 -0
- package/dist/graph.js +201 -0
- package/dist/graph.js.map +1 -0
- package/dist/identity.d.ts +87 -0
- package/dist/identity.js +294 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +57 -0
- package/dist/lifecycle.js +258 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/markdown.d.ts +152 -0
- package/dist/markdown.js +820 -0
- package/dist/markdown.js.map +1 -0
- package/dist/paths.d.ts +22 -0
- package/dist/paths.js +58 -0
- package/dist/paths.js.map +1 -0
- package/dist/report.d.ts +89 -0
- package/dist/report.js +326 -0
- package/dist/report.js.map +1 -0
- package/dist/resolve.d.ts +61 -0
- package/dist/resolve.js +340 -0
- package/dist/resolve.js.map +1 -0
- package/dist/rules.d.ts +61 -0
- package/dist/rules.js +557 -0
- package/dist/rules.js.map +1 -0
- package/dist/runner.d.ts +108 -0
- package/dist/runner.js +186 -0
- package/dist/runner.js.map +1 -0
- package/dist/sections.d.ts +94 -0
- package/dist/sections.js +372 -0
- package/dist/sections.js.map +1 -0
- package/dist/select.d.ts +96 -0
- package/dist/select.js +413 -0
- package/dist/select.js.map +1 -0
- package/dist/source.d.ts +43 -0
- package/dist/source.js +109 -0
- package/dist/source.js.map +1 -0
- package/dist/state.d.ts +45 -0
- package/dist/state.js +330 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.js +52 -0
- package/dist/types.js.map +1 -0
- package/dist/yaml.d.ts +37 -0
- package/dist/yaml.js +240 -0
- package/dist/yaml.js.map +1 -0
- package/package.json +71 -0
package/dist/sections.js
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Specification regions: the part of a file that one specification occupies.
|
|
3
|
+
*
|
|
4
|
+
* `spec-graph` began by assuming one specification per file, because that is
|
|
5
|
+
* how MADR, KEPs and Rust RFCs are laid out. Plenty of organisations do not
|
|
6
|
+
* work that way. A register, a whitepaper or a living architecture document
|
|
7
|
+
* holds dozens of decisions in one file, each with its own identifier, its own
|
|
8
|
+
* status and its own dependencies - and to an engine that binds a document to a
|
|
9
|
+
* file, every one of them is invisible.
|
|
10
|
+
*
|
|
11
|
+
* The fix is small once stated: **a specification is a region of a file, not a
|
|
12
|
+
* file.** A file yields at least one region - itself - and may yield more.
|
|
13
|
+
* Everything downstream is unchanged, because a region produces exactly the
|
|
14
|
+
* same `DocumentNode` a whole file produced.
|
|
15
|
+
*
|
|
16
|
+
* What this module decides is where those regions begin and end, and it is
|
|
17
|
+
* deliberately hard to convince. A heading has to carry an identifier *and* the
|
|
18
|
+
* section has to declare a status before it counts. One signal is not enough:
|
|
19
|
+
* `## Q3 2026 Roadmap` parses as family `Q`, number 3, and `## v1.2.0` in a
|
|
20
|
+
* changelog parses as family `v`, number 1. Neither is a specification, and
|
|
21
|
+
* neither declares a status.
|
|
22
|
+
*/
|
|
23
|
+
import { attr } from './directives.js';
|
|
24
|
+
import { isExternal } from './identity.js';
|
|
25
|
+
import { isStatusHeading } from './lifecycle.js';
|
|
26
|
+
/** Column headers that name the row's identifier. */
|
|
27
|
+
const ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;
|
|
28
|
+
/** Column headers that name the row's status. */
|
|
29
|
+
const STATUS_COLUMNS = /^(?:status|state|stage|lifecycle|phase)$/;
|
|
30
|
+
/** Column headers that name the row's title. */
|
|
31
|
+
const TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic)$/;
|
|
32
|
+
/**
|
|
33
|
+
* Column headers that declare a relation.
|
|
34
|
+
*
|
|
35
|
+
* Written the way a header is written - "Depends on", "Superseded by" - rather
|
|
36
|
+
* than the way a front-matter key is, because that is what an author types at
|
|
37
|
+
* the top of a column.
|
|
38
|
+
*/
|
|
39
|
+
const RELATION_COLUMNS = {
|
|
40
|
+
'depends on': { kind: 'depends-on', inverted: false },
|
|
41
|
+
'depends-on': { kind: 'depends-on', inverted: false },
|
|
42
|
+
dependencies: { kind: 'depends-on', inverted: false },
|
|
43
|
+
requires: { kind: 'depends-on', inverted: false },
|
|
44
|
+
supersedes: { kind: 'supersedes', inverted: false },
|
|
45
|
+
replaces: { kind: 'supersedes', inverted: false },
|
|
46
|
+
'superseded by': { kind: 'supersedes', inverted: true },
|
|
47
|
+
'replaced by': { kind: 'supersedes', inverted: true },
|
|
48
|
+
'blocked by': { kind: 'blocked-by', inverted: false },
|
|
49
|
+
'blocked on': { kind: 'blocked-by', inverted: false },
|
|
50
|
+
blocks: { kind: 'blocked-by', inverted: true },
|
|
51
|
+
amends: { kind: 'amends', inverted: false },
|
|
52
|
+
extends: { kind: 'amends', inverted: false },
|
|
53
|
+
assumes: { kind: 'assumes', inverted: false },
|
|
54
|
+
'delegates to': { kind: 'delegates-to', inverted: false },
|
|
55
|
+
'delegated to': { kind: 'delegates-to', inverted: false },
|
|
56
|
+
'tracked in': { kind: 'delegates-to', inverted: false },
|
|
57
|
+
related: { kind: 'relates-to', inverted: false },
|
|
58
|
+
'related to': { kind: 'relates-to', inverted: false },
|
|
59
|
+
'see also': { kind: 'relates-to', inverted: false },
|
|
60
|
+
references: { kind: 'references', inverted: false },
|
|
61
|
+
};
|
|
62
|
+
/** Cell values that mean "nothing here". */
|
|
63
|
+
const MARKDOWN_LINK = /!?\[([^\]]*)\]\(([^)]*)\)/g;
|
|
64
|
+
const EMPTY_CELL = /^(?:-+|—|–|n\/?a|none|nil|tbd|\.|_+)$/i;
|
|
65
|
+
/** `## ADR-0007: Sharding` - an identifier at the very start of a heading. */
|
|
66
|
+
const HEADING_ID = /^\s*([A-Za-z]{1,15}[\s._-]?\d{1,6})\b/;
|
|
67
|
+
/** `**Status:** Accepted`, `Status: Accepted`, `*Status* : Accepted`. */
|
|
68
|
+
const INLINE_STATUS = /^[ \t>]*[*_]{0,2}\s*status\s*[*_]{0,2}\s*[::]\s*(.+?)\s*$/im;
|
|
69
|
+
/**
|
|
70
|
+
* Finds every specification in a file.
|
|
71
|
+
*
|
|
72
|
+
* The first entry is always the file itself, so a caller that ignores the rest
|
|
73
|
+
* behaves exactly as it did before regions existed.
|
|
74
|
+
*/
|
|
75
|
+
export function findSpecificationRegions(scanned, directives,
|
|
76
|
+
/** The file's own identifier, so its title heading is not read as a sub-region. */
|
|
77
|
+
fileId) {
|
|
78
|
+
const whole = {
|
|
79
|
+
heading: null,
|
|
80
|
+
row: null,
|
|
81
|
+
start: scanned.bodyStart,
|
|
82
|
+
end: scanned.text.length,
|
|
83
|
+
declaredId: null,
|
|
84
|
+
title: null,
|
|
85
|
+
status: null,
|
|
86
|
+
directive: null,
|
|
87
|
+
relations: [],
|
|
88
|
+
claimed: [],
|
|
89
|
+
};
|
|
90
|
+
const regions = [];
|
|
91
|
+
const folded = fold(fileId);
|
|
92
|
+
for (let i = 0; i < scanned.headings.length; i += 1) {
|
|
93
|
+
const heading = scanned.headings[i];
|
|
94
|
+
const declaredId = HEADING_ID.exec(heading.text)?.[1];
|
|
95
|
+
if (declaredId === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
// The file's own title heading names the file, not a region inside it.
|
|
98
|
+
if (fold(declaredId) === folded)
|
|
99
|
+
continue;
|
|
100
|
+
const end = regionEnd(scanned, i);
|
|
101
|
+
const directive = directiveIn(directives, heading.end, end);
|
|
102
|
+
const status = readRegionStatus(scanned, heading.end, end);
|
|
103
|
+
// Two independent signals are required. An identifier alone matches
|
|
104
|
+
// `## Q3 2026 Roadmap` and `## v1.2.0`; a status alone matches every
|
|
105
|
+
// ordinary `## Status` section in a normal ADR.
|
|
106
|
+
if (status === null && directive === null)
|
|
107
|
+
continue;
|
|
108
|
+
regions.push({
|
|
109
|
+
heading,
|
|
110
|
+
row: null,
|
|
111
|
+
start: heading.start,
|
|
112
|
+
end,
|
|
113
|
+
declaredId: (directive ? attr(directive, 'id')?.value : null) ?? declaredId,
|
|
114
|
+
title: heading.text,
|
|
115
|
+
status,
|
|
116
|
+
directive,
|
|
117
|
+
relations: [],
|
|
118
|
+
claimed: [],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return [whole, ...regions, ...findTableRegions(scanned)];
|
|
122
|
+
}
|
|
123
|
+
/* -------------------------------------------------------------------------- */
|
|
124
|
+
/* Registers kept as tables */
|
|
125
|
+
/* -------------------------------------------------------------------------- */
|
|
126
|
+
/**
|
|
127
|
+
* Reads a table whose columns describe specifications.
|
|
128
|
+
*
|
|
129
|
+
* A table qualifies when it has an identifier column *and* either a status
|
|
130
|
+
* column or at least one relation column. Two signals again, for the same
|
|
131
|
+
* reason: a table of identifiers and prose is a citation list, and promoting
|
|
132
|
+
* every row of it to a specification would invent a register nobody wrote.
|
|
133
|
+
*/
|
|
134
|
+
export function findTableRegions(scanned) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const table of scanned.tables) {
|
|
137
|
+
const schema = readSchema(table);
|
|
138
|
+
if (schema === null)
|
|
139
|
+
continue;
|
|
140
|
+
for (const row of table.rows) {
|
|
141
|
+
const idCell = row.cells[schema.id];
|
|
142
|
+
if (!idCell)
|
|
143
|
+
continue;
|
|
144
|
+
const id = flatten(idCell.text);
|
|
145
|
+
if (id.length === 0 || EMPTY_CELL.test(id))
|
|
146
|
+
continue;
|
|
147
|
+
const statusCell = schema.status === null ? undefined : row.cells[schema.status];
|
|
148
|
+
const statusText = statusCell ? flatten(statusCell.text) : '';
|
|
149
|
+
const status = statusCell && statusText.length > 0 && !EMPTY_CELL.test(statusText)
|
|
150
|
+
? { text: statusText, start: statusCell.start, end: statusCell.end }
|
|
151
|
+
: null;
|
|
152
|
+
const titleCell = schema.title === null ? undefined : row.cells[schema.title];
|
|
153
|
+
const title = titleCell ? flatten(titleCell.text) : null;
|
|
154
|
+
const relations = [];
|
|
155
|
+
const claimed = [{ start: idCell.start, end: idCell.end }];
|
|
156
|
+
if (statusCell)
|
|
157
|
+
claimed.push({ start: statusCell.start, end: statusCell.end });
|
|
158
|
+
for (const [column, relation] of schema.relations) {
|
|
159
|
+
const cell = row.cells[column];
|
|
160
|
+
if (!cell)
|
|
161
|
+
continue;
|
|
162
|
+
claimed.push({ start: cell.start, end: cell.end });
|
|
163
|
+
for (const target of splitTargets(cell)) {
|
|
164
|
+
relations.push({
|
|
165
|
+
kind: relation.kind,
|
|
166
|
+
inverted: relation.inverted,
|
|
167
|
+
target: target.text,
|
|
168
|
+
start: target.start,
|
|
169
|
+
end: target.end,
|
|
170
|
+
column: table.headers[column].text,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
out.push({
|
|
175
|
+
heading: null,
|
|
176
|
+
row,
|
|
177
|
+
start: row.start,
|
|
178
|
+
end: row.end,
|
|
179
|
+
declaredId: id,
|
|
180
|
+
title: title !== null && title.length > 0 ? title : null,
|
|
181
|
+
status,
|
|
182
|
+
directive: null,
|
|
183
|
+
relations,
|
|
184
|
+
claimed,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
function readSchema(table) {
|
|
191
|
+
let id = null;
|
|
192
|
+
let status = null;
|
|
193
|
+
let title = null;
|
|
194
|
+
const relations = [];
|
|
195
|
+
table.headers.forEach((header, column) => {
|
|
196
|
+
const name = normaliseHeader(header.text);
|
|
197
|
+
if (id === null && ID_COLUMNS.test(name)) {
|
|
198
|
+
id = column;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (status === null && STATUS_COLUMNS.test(name)) {
|
|
202
|
+
status = column;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const relation = RELATION_COLUMNS[name];
|
|
206
|
+
if (relation) {
|
|
207
|
+
relations.push([column, relation]);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (title === null && TITLE_COLUMNS.test(name))
|
|
211
|
+
title = column;
|
|
212
|
+
});
|
|
213
|
+
if (id === null)
|
|
214
|
+
return null;
|
|
215
|
+
if (status === null && relations.length === 0)
|
|
216
|
+
return null;
|
|
217
|
+
return { id, status, title, relations };
|
|
218
|
+
}
|
|
219
|
+
function normaliseHeader(text) {
|
|
220
|
+
return flatten(text)
|
|
221
|
+
.toLowerCase()
|
|
222
|
+
.replace(/[^a-z0-9/#\s-]/g, '')
|
|
223
|
+
.replace(/\s+/g, ' ')
|
|
224
|
+
.trim();
|
|
225
|
+
}
|
|
226
|
+
/** Splits a cell into the identifiers it lists, keeping each one's offset. */
|
|
227
|
+
function splitTargets(cell) {
|
|
228
|
+
const out = [];
|
|
229
|
+
// Every target is reported at the cell's span. A cell is short, and an offset
|
|
230
|
+
// recovered back through a flattening is an offset that can drift.
|
|
231
|
+
const push = (raw) => {
|
|
232
|
+
const target = raw.trim();
|
|
233
|
+
if (target.length === 0 || EMPTY_CELL.test(target))
|
|
234
|
+
return;
|
|
235
|
+
out.push({ text: target, start: cell.start, end: cell.end });
|
|
236
|
+
};
|
|
237
|
+
const prose = (segment) => {
|
|
238
|
+
for (const piece of flatten(segment).split(/\s*(?:[,;]|\band\b)\s*/i))
|
|
239
|
+
push(piece);
|
|
240
|
+
};
|
|
241
|
+
// Links are taken out whole rather than flattened with everything else: a
|
|
242
|
+
// path would otherwise be cut in half by its own slash, and its underscores
|
|
243
|
+
// stripped as emphasis.
|
|
244
|
+
// A fresh matcher per cell. `prose` calls `flatten`, which drives the shared
|
|
245
|
+
// pattern; one /g regex advanced from inside its own scan resets its cursor
|
|
246
|
+
// and never terminates.
|
|
247
|
+
const links = new RegExp(MARKDOWN_LINK.source, 'g');
|
|
248
|
+
let last = 0;
|
|
249
|
+
for (let match = links.exec(cell.text); match !== null; match = links.exec(cell.text)) {
|
|
250
|
+
prose(cell.text.slice(last, match.index));
|
|
251
|
+
const destination = (match[2] ?? '').trim();
|
|
252
|
+
// A link states where its target lives; its label states only what the
|
|
253
|
+
// target is called (ADR-0008). In a column the author has already typed,
|
|
254
|
+
// the destination is the claim - `[see](adr/0050.md)` is a dependency on
|
|
255
|
+
// that file, not on the word "see". An external or in-page destination
|
|
256
|
+
// names no document here, so the label is what is left to try.
|
|
257
|
+
const local = destination.length > 0 && !destination.startsWith('#') && !isExternal(destination);
|
|
258
|
+
push(local ? destination : (match[1] ?? ''));
|
|
259
|
+
last = match.index + match[0].length;
|
|
260
|
+
}
|
|
261
|
+
prose(cell.text.slice(last));
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
/** Reduces a cell to its text: link labels rather than link syntax. */
|
|
265
|
+
function flatten(text) {
|
|
266
|
+
return text
|
|
267
|
+
.replace(MARKDOWN_LINK, '$1')
|
|
268
|
+
.replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g, '$1')
|
|
269
|
+
.replace(/[`*_~]/g, '')
|
|
270
|
+
.replace(/\s+/g, ' ')
|
|
271
|
+
.trim();
|
|
272
|
+
}
|
|
273
|
+
/** Where a heading's section ends: the next heading at the same level or above. */
|
|
274
|
+
function regionEnd(scanned, index) {
|
|
275
|
+
const heading = scanned.headings[index];
|
|
276
|
+
for (let i = index + 1; i < scanned.headings.length; i += 1) {
|
|
277
|
+
const next = scanned.headings[i];
|
|
278
|
+
if (next.level <= heading.level)
|
|
279
|
+
return next.start;
|
|
280
|
+
}
|
|
281
|
+
return scanned.text.length;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Reads a status declared inside a region.
|
|
285
|
+
*
|
|
286
|
+
* Two forms, both common: a labelled line (`**Status:** Accepted`) and a
|
|
287
|
+
* sub-heading (`### Status` with the value beneath it). Only the region is
|
|
288
|
+
* searched, so one decision in a register cannot inherit its neighbour's.
|
|
289
|
+
*/
|
|
290
|
+
export function readRegionStatus(scanned, start, end) {
|
|
291
|
+
const labelled = readLabelledStatus(scanned, start, end);
|
|
292
|
+
if (labelled)
|
|
293
|
+
return labelled;
|
|
294
|
+
return readHeadedStatus(scanned, start, end);
|
|
295
|
+
}
|
|
296
|
+
function readLabelledStatus(scanned, start, end) {
|
|
297
|
+
for (const line of scanned.lines) {
|
|
298
|
+
if (line.contentStart < start)
|
|
299
|
+
continue;
|
|
300
|
+
if (line.contentStart >= end)
|
|
301
|
+
break;
|
|
302
|
+
if (line.code || line.blank)
|
|
303
|
+
continue;
|
|
304
|
+
const match = INLINE_STATUS.exec(line.content);
|
|
305
|
+
if (!match)
|
|
306
|
+
continue;
|
|
307
|
+
// `**Status:** Accepted` closes its emphasis *after* the colon, so the
|
|
308
|
+
// closing marker lands in the captured value. Strip it rather than let it
|
|
309
|
+
// reach the report as part of the status a reader is shown.
|
|
310
|
+
const value = match[1].replace(/^[*_\s]+/, '').replace(/[*_\s]+$/, '');
|
|
311
|
+
if (value.length === 0)
|
|
312
|
+
continue;
|
|
313
|
+
const offset = line.contentStart + line.content.lastIndexOf(value);
|
|
314
|
+
return { text: value, start: offset, end: offset + value.length };
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
function readHeadedStatus(scanned, start, end) {
|
|
319
|
+
const heading = scanned.headings.find((h) => h.start >= start && h.start < end && isStatusHeading(h.text));
|
|
320
|
+
if (!heading)
|
|
321
|
+
return null;
|
|
322
|
+
for (const line of scanned.lines) {
|
|
323
|
+
if (line.line <= heading.line)
|
|
324
|
+
continue;
|
|
325
|
+
if (line.contentStart >= end)
|
|
326
|
+
break;
|
|
327
|
+
if (line.blank)
|
|
328
|
+
continue;
|
|
329
|
+
if (line.code)
|
|
330
|
+
return null;
|
|
331
|
+
const trimmed = line.content.trim();
|
|
332
|
+
if (trimmed.startsWith('#'))
|
|
333
|
+
return null;
|
|
334
|
+
// A bullet list under Status is a status history; the first entry is current.
|
|
335
|
+
const cleaned = trimmed.replace(/^[-*+]\s+/, '');
|
|
336
|
+
const offset = line.contentStart + line.content.indexOf(trimmed) + (trimmed.length - cleaned.length);
|
|
337
|
+
return { text: cleaned, start: offset, end: offset + cleaned.length };
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
/** The last `@spec-node` directive inside a region. */
|
|
342
|
+
function directiveIn(directives, start, end) {
|
|
343
|
+
let found = null;
|
|
344
|
+
for (const directive of directives) {
|
|
345
|
+
if (directive.name !== 'spec-node')
|
|
346
|
+
continue;
|
|
347
|
+
if (directive.start < start || directive.end > end)
|
|
348
|
+
continue;
|
|
349
|
+
if (found === null || directive.start > found.start)
|
|
350
|
+
found = directive;
|
|
351
|
+
}
|
|
352
|
+
return found;
|
|
353
|
+
}
|
|
354
|
+
/** Case- and separator-insensitive comparison, matching reference folding. */
|
|
355
|
+
function fold(value) {
|
|
356
|
+
return value.trim().toLowerCase().replace(/[\s._-]+/g, '');
|
|
357
|
+
}
|
|
358
|
+
/** The region containing an offset, innermost first. `null` when only the file does. */
|
|
359
|
+
export function regionAt(regions, offset) {
|
|
360
|
+
let best = null;
|
|
361
|
+
for (const region of regions) {
|
|
362
|
+
// The whole-file region owns nothing in particular; it is the fallback.
|
|
363
|
+
if (region.heading === null && region.row === null)
|
|
364
|
+
continue;
|
|
365
|
+
if (offset < region.start || offset >= region.end)
|
|
366
|
+
continue;
|
|
367
|
+
if (best === null || region.start > best.start)
|
|
368
|
+
best = region;
|
|
369
|
+
}
|
|
370
|
+
return best;
|
|
371
|
+
}
|
|
372
|
+
//# sourceMappingURL=sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections.js","sourceRoot":"","sources":["../src/sections.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Specification regions: the part of a file that one specification occupies.\n *\n * `spec-graph` began by assuming one specification per file, because that is\n * how MADR, KEPs and Rust RFCs are laid out. Plenty of organisations do not\n * work that way. A register, a whitepaper or a living architecture document\n * holds dozens of decisions in one file, each with its own identifier, its own\n * status and its own dependencies - and to an engine that binds a document to a\n * file, every one of them is invisible.\n *\n * The fix is small once stated: **a specification is a region of a file, not a\n * file.** A file yields at least one region - itself - and may yield more.\n * Everything downstream is unchanged, because a region produces exactly the\n * same `DocumentNode` a whole file produced.\n *\n * What this module decides is where those regions begin and end, and it is\n * deliberately hard to convince. A heading has to carry an identifier *and* the\n * section has to declare a status before it counts. One signal is not enough:\n * `## Q3 2026 Roadmap` parses as family `Q`, number 3, and `## v1.2.0` in a\n * changelog parses as family `v`, number 1. Neither is a specification, and\n * neither declares a status.\n */\n\nimport { attr, type Directive } from './directives.js';\nimport { isExternal } from './identity.js';\nimport { isStatusHeading } from './lifecycle.js';\nimport type { Heading, Range, ScannedDocument, Table, TableCell, TableRow } from './markdown.js';\nimport type { EdgeKind } from './types.js';\n\n/** A status as written, and where it was written. */\nexport interface RegionStatus {\n readonly text: string;\n readonly start: number;\n readonly end: number;\n}\n\n/** A relation read from a table column rather than from prose. */\nexport interface TableRelation {\n readonly kind: EdgeKind;\n readonly inverted: boolean;\n readonly target: string;\n readonly start: number;\n readonly end: number;\n /** The column header that gave it its meaning, for the diagnostic. */\n readonly column: string;\n}\n\nexport interface SpecificationRegion {\n /** The heading that opens the region, or `null` for the file or a table row. */\n readonly heading: Heading | null;\n /** The table row that is the region, when it is one. */\n readonly row: TableRow | null;\n /** Offset the region starts at. */\n readonly start: number;\n /** Offset just past the region. */\n readonly end: number;\n /** Identifier read from the heading or the identifier column. */\n readonly declaredId: string | null;\n readonly title: string | null;\n readonly status: RegionStatus | null;\n /** A `@spec-node` directive inside the region, when there is one. */\n readonly directive: Directive | null;\n /** Relations declared by column, empty for a heading region. */\n readonly relations: readonly TableRelation[];\n /**\n * Cells whose content is already accounted for as a typed relation.\n *\n * Prose scanning skips these, so a link in a `Depends on` column produces one\n * typed edge rather than a typed edge and a neutral citation beside it.\n */\n readonly claimed: readonly Range[];\n}\n\n/** Column headers that name the row's identifier. */\nconst ID_COLUMNS = /^(?:id|ids|ref|reference|key|no|number|#|adr|rfc|kep|oi|decision|spec|specification)$/;\n\n/** Column headers that name the row's status. */\nconst STATUS_COLUMNS = /^(?:status|state|stage|lifecycle|phase)$/;\n\n/** Column headers that name the row's title. */\nconst TITLE_COLUMNS = /^(?:title|name|summary|description|subject|decision|topic)$/;\n\n/**\n * Column headers that declare a relation.\n *\n * Written the way a header is written - \"Depends on\", \"Superseded by\" - rather\n * than the way a front-matter key is, because that is what an author types at\n * the top of a column.\n */\nconst RELATION_COLUMNS: Readonly<Record<string, { kind: EdgeKind; inverted: boolean }>> = {\n 'depends on': { kind: 'depends-on', inverted: false },\n 'depends-on': { kind: 'depends-on', inverted: false },\n dependencies: { kind: 'depends-on', inverted: false },\n requires: { kind: 'depends-on', inverted: false },\n supersedes: { kind: 'supersedes', inverted: false },\n replaces: { kind: 'supersedes', inverted: false },\n 'superseded by': { kind: 'supersedes', inverted: true },\n 'replaced by': { kind: 'supersedes', inverted: true },\n 'blocked by': { kind: 'blocked-by', inverted: false },\n 'blocked on': { kind: 'blocked-by', inverted: false },\n blocks: { kind: 'blocked-by', inverted: true },\n amends: { kind: 'amends', inverted: false },\n extends: { kind: 'amends', inverted: false },\n assumes: { kind: 'assumes', inverted: false },\n 'delegates to': { kind: 'delegates-to', inverted: false },\n 'delegated to': { kind: 'delegates-to', inverted: false },\n 'tracked in': { kind: 'delegates-to', inverted: false },\n related: { kind: 'relates-to', inverted: false },\n 'related to': { kind: 'relates-to', inverted: false },\n 'see also': { kind: 'relates-to', inverted: false },\n references: { kind: 'references', inverted: false },\n};\n\n/** Cell values that mean \"nothing here\". */\nconst MARKDOWN_LINK = /!?\\[([^\\]]*)\\]\\(([^)]*)\\)/g;\nconst EMPTY_CELL = /^(?:-+|—|–|n\\/?a|none|nil|tbd|\\.|_+)$/i;\n\n/** `## ADR-0007: Sharding` - an identifier at the very start of a heading. */\nconst HEADING_ID = /^\\s*([A-Za-z]{1,15}[\\s._-]?\\d{1,6})\\b/;\n\n/** `**Status:** Accepted`, `Status: Accepted`, `*Status* : Accepted`. */\nconst INLINE_STATUS = /^[ \\t>]*[*_]{0,2}\\s*status\\s*[*_]{0,2}\\s*[::]\\s*(.+?)\\s*$/im;\n\n/**\n * Finds every specification in a file.\n *\n * The first entry is always the file itself, so a caller that ignores the rest\n * behaves exactly as it did before regions existed.\n */\nexport function findSpecificationRegions(\n scanned: ScannedDocument,\n directives: readonly Directive[],\n /** The file's own identifier, so its title heading is not read as a sub-region. */\n fileId: string,\n): SpecificationRegion[] {\n const whole: SpecificationRegion = {\n heading: null,\n row: null,\n start: scanned.bodyStart,\n end: scanned.text.length,\n declaredId: null,\n title: null,\n status: null,\n directive: null,\n relations: [],\n claimed: [],\n };\n\n const regions: SpecificationRegion[] = [];\n const folded = fold(fileId);\n\n for (let i = 0; i < scanned.headings.length; i += 1) {\n const heading = scanned.headings[i] as Heading;\n const declaredId = HEADING_ID.exec(heading.text)?.[1];\n if (declaredId === undefined) continue;\n // The file's own title heading names the file, not a region inside it.\n if (fold(declaredId) === folded) continue;\n\n const end = regionEnd(scanned, i);\n const directive = directiveIn(directives, heading.end, end);\n const status = readRegionStatus(scanned, heading.end, end);\n\n // Two independent signals are required. An identifier alone matches\n // `## Q3 2026 Roadmap` and `## v1.2.0`; a status alone matches every\n // ordinary `## Status` section in a normal ADR.\n if (status === null && directive === null) continue;\n\n regions.push({\n heading,\n row: null,\n start: heading.start,\n end,\n declaredId: (directive ? attr(directive, 'id')?.value : null) ?? declaredId,\n title: heading.text,\n status,\n directive,\n relations: [],\n claimed: [],\n });\n }\n\n return [whole, ...regions, ...findTableRegions(scanned)];\n}\n\n/* -------------------------------------------------------------------------- */\n/* Registers kept as tables */\n/* -------------------------------------------------------------------------- */\n\n/**\n * Reads a table whose columns describe specifications.\n *\n * A table qualifies when it has an identifier column *and* either a status\n * column or at least one relation column. Two signals again, for the same\n * reason: a table of identifiers and prose is a citation list, and promoting\n * every row of it to a specification would invent a register nobody wrote.\n */\nexport function findTableRegions(scanned: ScannedDocument): SpecificationRegion[] {\n const out: SpecificationRegion[] = [];\n\n for (const table of scanned.tables) {\n const schema = readSchema(table);\n if (schema === null) continue;\n\n for (const row of table.rows) {\n const idCell = row.cells[schema.id];\n if (!idCell) continue;\n const id = flatten(idCell.text);\n if (id.length === 0 || EMPTY_CELL.test(id)) continue;\n\n const statusCell = schema.status === null ? undefined : row.cells[schema.status];\n const statusText = statusCell ? flatten(statusCell.text) : '';\n const status: RegionStatus | null =\n statusCell && statusText.length > 0 && !EMPTY_CELL.test(statusText)\n ? { text: statusText, start: statusCell.start, end: statusCell.end }\n : null;\n\n const titleCell = schema.title === null ? undefined : row.cells[schema.title];\n const title = titleCell ? flatten(titleCell.text) : null;\n\n const relations: TableRelation[] = [];\n const claimed: Range[] = [{ start: idCell.start, end: idCell.end }];\n if (statusCell) claimed.push({ start: statusCell.start, end: statusCell.end });\n\n for (const [column, relation] of schema.relations) {\n const cell = row.cells[column];\n if (!cell) continue;\n claimed.push({ start: cell.start, end: cell.end });\n for (const target of splitTargets(cell)) {\n relations.push({\n kind: relation.kind,\n inverted: relation.inverted,\n target: target.text,\n start: target.start,\n end: target.end,\n column: (table.headers[column] as TableCell).text,\n });\n }\n }\n\n out.push({\n heading: null,\n row,\n start: row.start,\n end: row.end,\n declaredId: id,\n title: title !== null && title.length > 0 ? title : null,\n status,\n directive: null,\n relations,\n claimed,\n });\n }\n }\n\n return out;\n}\n\ninterface TableSchema {\n readonly id: number;\n readonly status: number | null;\n readonly title: number | null;\n readonly relations: readonly (readonly [number, { kind: EdgeKind; inverted: boolean }])[];\n}\n\nfunction readSchema(table: Table): TableSchema | null {\n let id: number | null = null;\n let status: number | null = null;\n let title: number | null = null;\n const relations: [number, { kind: EdgeKind; inverted: boolean }][] = [];\n\n table.headers.forEach((header, column) => {\n const name = normaliseHeader(header.text);\n if (id === null && ID_COLUMNS.test(name)) {\n id = column;\n return;\n }\n if (status === null && STATUS_COLUMNS.test(name)) {\n status = column;\n return;\n }\n const relation = RELATION_COLUMNS[name];\n if (relation) {\n relations.push([column, relation]);\n return;\n }\n if (title === null && TITLE_COLUMNS.test(name)) title = column;\n });\n\n if (id === null) return null;\n if (status === null && relations.length === 0) return null;\n return { id, status, title, relations };\n}\n\nfunction normaliseHeader(text: string): string {\n return flatten(text)\n .toLowerCase()\n .replace(/[^a-z0-9/#\\s-]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Splits a cell into the identifiers it lists, keeping each one's offset. */\nfunction splitTargets(cell: TableCell): { text: string; start: number; end: number }[] {\n const out: { text: string; start: number; end: number }[] = [];\n // Every target is reported at the cell's span. A cell is short, and an offset\n // recovered back through a flattening is an offset that can drift.\n const push = (raw: string): void => {\n const target = raw.trim();\n if (target.length === 0 || EMPTY_CELL.test(target)) return;\n out.push({ text: target, start: cell.start, end: cell.end });\n };\n const prose = (segment: string): void => {\n for (const piece of flatten(segment).split(/\\s*(?:[,;]|\\band\\b)\\s*/i)) push(piece);\n };\n\n // Links are taken out whole rather than flattened with everything else: a\n // path would otherwise be cut in half by its own slash, and its underscores\n // stripped as emphasis.\n // A fresh matcher per cell. `prose` calls `flatten`, which drives the shared\n // pattern; one /g regex advanced from inside its own scan resets its cursor\n // and never terminates.\n const links = new RegExp(MARKDOWN_LINK.source, 'g');\n let last = 0;\n for (let match = links.exec(cell.text); match !== null; match = links.exec(cell.text)) {\n prose(cell.text.slice(last, match.index));\n const destination = (match[2] ?? '').trim();\n // A link states where its target lives; its label states only what the\n // target is called (ADR-0008). In a column the author has already typed,\n // the destination is the claim - `[see](adr/0050.md)` is a dependency on\n // that file, not on the word \"see\". An external or in-page destination\n // names no document here, so the label is what is left to try.\n const local = destination.length > 0 && !destination.startsWith('#') && !isExternal(destination);\n push(local ? destination : (match[1] ?? ''));\n last = match.index + match[0].length;\n }\n prose(cell.text.slice(last));\n return out;\n}\n\n/** Reduces a cell to its text: link labels rather than link syntax. */\nfunction flatten(text: string): string {\n return text\n .replace(MARKDOWN_LINK, '$1')\n .replace(/\\[\\[(?:[^\\]|]*\\|)?([^\\]]*)\\]\\]/g, '$1')\n .replace(/[`*_~]/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** Where a heading's section ends: the next heading at the same level or above. */\nfunction regionEnd(scanned: ScannedDocument, index: number): number {\n const heading = scanned.headings[index] as Heading;\n for (let i = index + 1; i < scanned.headings.length; i += 1) {\n const next = scanned.headings[i] as Heading;\n if (next.level <= heading.level) return next.start;\n }\n return scanned.text.length;\n}\n\n/**\n * Reads a status declared inside a region.\n *\n * Two forms, both common: a labelled line (`**Status:** Accepted`) and a\n * sub-heading (`### Status` with the value beneath it). Only the region is\n * searched, so one decision in a register cannot inherit its neighbour's.\n */\nexport function readRegionStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const labelled = readLabelledStatus(scanned, start, end);\n if (labelled) return labelled;\n return readHeadedStatus(scanned, start, end);\n}\n\nfunction readLabelledStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n for (const line of scanned.lines) {\n if (line.contentStart < start) continue;\n if (line.contentStart >= end) break;\n if (line.code || line.blank) continue;\n const match = INLINE_STATUS.exec(line.content);\n if (!match) continue;\n // `**Status:** Accepted` closes its emphasis *after* the colon, so the\n // closing marker lands in the captured value. Strip it rather than let it\n // reach the report as part of the status a reader is shown.\n const value = (match[1] as string).replace(/^[*_\\s]+/, '').replace(/[*_\\s]+$/, '');\n if (value.length === 0) continue;\n const offset = line.contentStart + line.content.lastIndexOf(value);\n return { text: value, start: offset, end: offset + value.length };\n }\n return null;\n}\n\nfunction readHeadedStatus(scanned: ScannedDocument, start: number, end: number): RegionStatus | null {\n const heading = scanned.headings.find((h) => h.start >= start && h.start < end && isStatusHeading(h.text));\n if (!heading) return null;\n\n for (const line of scanned.lines) {\n if (line.line <= heading.line) continue;\n if (line.contentStart >= end) break;\n if (line.blank) continue;\n if (line.code) return null;\n const trimmed = line.content.trim();\n if (trimmed.startsWith('#')) return null;\n // A bullet list under Status is a status history; the first entry is current.\n const cleaned = trimmed.replace(/^[-*+]\\s+/, '');\n const offset = line.contentStart + line.content.indexOf(trimmed) + (trimmed.length - cleaned.length);\n return { text: cleaned, start: offset, end: offset + cleaned.length };\n }\n return null;\n}\n\n/** The last `@spec-node` directive inside a region. */\nfunction directiveIn(directives: readonly Directive[], start: number, end: number): Directive | null {\n let found: Directive | null = null;\n for (const directive of directives) {\n if (directive.name !== 'spec-node') continue;\n if (directive.start < start || directive.end > end) continue;\n if (found === null || directive.start > found.start) found = directive;\n }\n return found;\n}\n\n/** Case- and separator-insensitive comparison, matching reference folding. */\nfunction fold(value: string): string {\n return value.trim().toLowerCase().replace(/[\\s._-]+/g, '');\n}\n\n/** The region containing an offset, innermost first. `null` when only the file does. */\nexport function regionAt(regions: readonly SpecificationRegion[], offset: number): SpecificationRegion | null {\n let best: SpecificationRegion | null = null;\n for (const region of regions) {\n // The whole-file region owns nothing in particular; it is the fallback.\n if (region.heading === null && region.row === null) continue;\n if (offset < region.start || offset >= region.end) continue;\n if (best === null || region.start > best.start) best = region;\n }\n return best;\n}\n"]}
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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 type { SpecGraph } from './graph.js';
|
|
23
|
+
import { type Edge, type EdgeKind, type NodeKind, type SpecNode } from './types.js';
|
|
24
|
+
export type Operator = '=' | '!=' | '^=' | '$=' | '*=' | '~=' | 'exists';
|
|
25
|
+
export interface Predicate {
|
|
26
|
+
readonly key: string;
|
|
27
|
+
readonly operator: Operator;
|
|
28
|
+
readonly value: string;
|
|
29
|
+
}
|
|
30
|
+
export interface NodeMatcher {
|
|
31
|
+
/** `null` matches both documents and items. */
|
|
32
|
+
readonly kind: NodeKind | null;
|
|
33
|
+
readonly predicates: readonly Predicate[];
|
|
34
|
+
}
|
|
35
|
+
export interface StepSpec {
|
|
36
|
+
readonly direction: 'out' | 'in';
|
|
37
|
+
/** `null` matches any edge kind. */
|
|
38
|
+
readonly kinds: readonly EdgeKind[] | null;
|
|
39
|
+
/** Follow the relation transitively, taking the shortest path to each node. */
|
|
40
|
+
readonly transitive: boolean;
|
|
41
|
+
readonly node: NodeMatcher;
|
|
42
|
+
}
|
|
43
|
+
export interface QuerySpec {
|
|
44
|
+
readonly start: NodeMatcher;
|
|
45
|
+
readonly steps: readonly StepSpec[];
|
|
46
|
+
}
|
|
47
|
+
/** One path through the graph. `edges` always has one fewer entry than `nodes`. */
|
|
48
|
+
export interface Match {
|
|
49
|
+
readonly nodes: readonly SpecNode[];
|
|
50
|
+
readonly edges: readonly Edge[];
|
|
51
|
+
}
|
|
52
|
+
export declare class QueryError extends Error {
|
|
53
|
+
/** Offset into the query string where the problem starts. */
|
|
54
|
+
readonly offset: number;
|
|
55
|
+
constructor(message: string, offset: number);
|
|
56
|
+
}
|
|
57
|
+
/** Guards against a pathological query fanning out over a huge corpus. */
|
|
58
|
+
export declare const DEFAULT_MATCH_LIMIT = 10000;
|
|
59
|
+
/** Compiles a selector string into a {@link QuerySpec}. */
|
|
60
|
+
export declare function parseQuery(source: string): QuerySpec;
|
|
61
|
+
/**
|
|
62
|
+
* The values a predicate key sees on a node.
|
|
63
|
+
*
|
|
64
|
+
* Multi-valued keys (`alias`, `section`) match when *any* value matches, which
|
|
65
|
+
* is what makes `[section=Open Questions]` read the way people expect.
|
|
66
|
+
*/
|
|
67
|
+
export declare function attributesOf(node: SpecNode, key: string, graph?: SpecGraph): string[];
|
|
68
|
+
/** True when a node satisfies a matcher. */
|
|
69
|
+
export declare function matches(node: SpecNode, matcher: NodeMatcher, graph?: SpecGraph): boolean;
|
|
70
|
+
export interface ExecuteOptions {
|
|
71
|
+
/**
|
|
72
|
+
* Cap on the paths produced by each traversal step.
|
|
73
|
+
*
|
|
74
|
+
* It bounds *expansion*, not the initial selection: a query with no steps
|
|
75
|
+
* returns every matching node. That asymmetry is deliberate. The limit exists
|
|
76
|
+
* so a query across a dense graph cannot fan out unboundedly, and the rules
|
|
77
|
+
* run through this engine - truncating the set of nodes a rule starts from
|
|
78
|
+
* would silently drop findings, whereas truncating a runaway expansion only
|
|
79
|
+
* ever drops paths that were already beyond reading.
|
|
80
|
+
*/
|
|
81
|
+
readonly limit?: number | undefined;
|
|
82
|
+
/**
|
|
83
|
+
* Whether a step may stay inside one document.
|
|
84
|
+
*
|
|
85
|
+
* Off by default for reflexive edges: a document that links to itself is a
|
|
86
|
+
* formatting quirk, not a relationship, and letting those through turns every
|
|
87
|
+
* table of contents into a finding.
|
|
88
|
+
*/
|
|
89
|
+
readonly allowReflexive?: boolean | undefined;
|
|
90
|
+
}
|
|
91
|
+
/** Runs a compiled query and returns every matching path. */
|
|
92
|
+
export declare function execute(graph: SpecGraph, spec: QuerySpec, options?: ExecuteOptions): Match[];
|
|
93
|
+
/** Parses and runs a selector in one call. */
|
|
94
|
+
export declare function query(graph: SpecGraph, selector: string, options?: ExecuteOptions): Match[];
|
|
95
|
+
/** Renders a match as `A -kind-> B`, for reports and for `--explain`. */
|
|
96
|
+
export declare function renderMatch(match: Match): string;
|