@descent-vtt/spec-brief 0.1.0 → 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 (128) hide show
  1. package/CHANGELOG.md +264 -0
  2. package/README.md +90 -20
  3. package/dist/archive.d.ts +52 -2
  4. package/dist/archive.js +205 -37
  5. package/dist/archive.js.map +1 -1
  6. package/dist/brief.d.ts +2 -0
  7. package/dist/brief.js +5 -1
  8. package/dist/brief.js.map +1 -1
  9. package/dist/cli.d.ts +1 -1
  10. package/dist/cli.js +48 -14
  11. package/dist/cli.js.map +1 -1
  12. package/dist/collisions.d.ts +43 -12
  13. package/dist/collisions.js +90 -58
  14. package/dist/collisions.js.map +1 -1
  15. package/dist/config.d.ts +11 -0
  16. package/dist/config.js +58 -10
  17. package/dist/config.js.map +1 -1
  18. package/dist/corpus.d.ts +21 -1
  19. package/dist/corpus.js +51 -2
  20. package/dist/corpus.js.map +1 -1
  21. package/dist/engine.d.ts +33 -3
  22. package/dist/engine.js +112 -9
  23. package/dist/engine.js.map +1 -1
  24. package/dist/frontmatter.d.ts +18 -61
  25. package/dist/frontmatter.js +31 -300
  26. package/dist/frontmatter.js.map +1 -1
  27. package/dist/glob.d.ts +77 -54
  28. package/dist/glob.js +104 -433
  29. package/dist/glob.js.map +1 -1
  30. package/dist/index.d.ts +10 -8
  31. package/dist/index.js +8 -6
  32. package/dist/index.js.map +1 -1
  33. package/dist/links.d.ts +1 -1
  34. package/dist/links.js +3 -4
  35. package/dist/links.js.map +1 -1
  36. package/dist/lint.d.ts +33 -2
  37. package/dist/lint.js +9 -2
  38. package/dist/lint.js.map +1 -1
  39. package/dist/markdown.d.ts +26 -23
  40. package/dist/markdown.js +60 -237
  41. package/dist/markdown.js.map +1 -1
  42. package/dist/plugins.d.ts +14 -0
  43. package/dist/plugins.js +142 -6
  44. package/dist/plugins.js.map +1 -1
  45. package/dist/report.d.ts +40 -3
  46. package/dist/report.js +227 -11
  47. package/dist/report.js.map +1 -1
  48. package/dist/rules.d.ts +22 -4
  49. package/dist/rules.js +175 -45
  50. package/dist/rules.js.map +1 -1
  51. package/dist/scaffold.d.ts +1 -1
  52. package/dist/scaffold.js +10 -9
  53. package/dist/scaffold.js.map +1 -1
  54. package/dist/schedule.d.ts +133 -0
  55. package/dist/schedule.js +265 -0
  56. package/dist/schedule.js.map +1 -0
  57. package/dist/scope.d.ts +52 -0
  58. package/dist/scope.js +56 -0
  59. package/dist/scope.js.map +1 -0
  60. package/dist/text.d.ts +13 -0
  61. package/dist/text.js +23 -0
  62. package/dist/text.js.map +1 -1
  63. package/dist/trigger.d.ts +17 -0
  64. package/dist/trigger.js +49 -0
  65. package/dist/trigger.js.map +1 -0
  66. package/dist/types.d.ts +14 -5
  67. package/dist/types.js.map +1 -1
  68. package/dist/vendor/spec-core/markdown/frontmatter.d.ts +103 -0
  69. package/dist/vendor/spec-core/markdown/frontmatter.js +459 -0
  70. package/dist/vendor/spec-core/markdown/frontmatter.js.map +1 -0
  71. package/dist/vendor/spec-core/markdown/headings.d.ts +45 -0
  72. package/dist/vendor/spec-core/markdown/headings.js +199 -0
  73. package/dist/vendor/spec-core/markdown/headings.js.map +1 -0
  74. package/dist/vendor/spec-core/markdown/index.d.ts +5 -0
  75. package/dist/vendor/spec-core/markdown/index.js +4 -0
  76. package/dist/vendor/spec-core/markdown/index.js.map +1 -0
  77. package/dist/vendor/spec-core/markdown/layout.d.ts +39 -0
  78. package/dist/vendor/spec-core/markdown/layout.js +28 -0
  79. package/dist/vendor/spec-core/markdown/layout.js.map +1 -0
  80. package/dist/vendor/spec-core/markdown/links.d.ts +22 -0
  81. package/dist/vendor/spec-core/markdown/links.js +364 -0
  82. package/dist/vendor/spec-core/markdown/links.js.map +1 -0
  83. package/dist/vendor/spec-core/markdown/lists.d.ts +22 -0
  84. package/dist/vendor/spec-core/markdown/lists.js +127 -0
  85. package/dist/vendor/spec-core/markdown/lists.js.map +1 -0
  86. package/dist/vendor/spec-core/markdown/scan.d.ts +36 -0
  87. package/dist/vendor/spec-core/markdown/scan.js +439 -0
  88. package/dist/vendor/spec-core/markdown/scan.js.map +1 -0
  89. package/dist/vendor/spec-core/markdown/syntax.d.ts +75 -0
  90. package/dist/vendor/spec-core/markdown/syntax.js +181 -0
  91. package/dist/vendor/spec-core/markdown/syntax.js.map +1 -0
  92. package/dist/vendor/spec-core/markdown/tables.d.ts +18 -0
  93. package/dist/vendor/spec-core/markdown/tables.js +86 -0
  94. package/dist/vendor/spec-core/markdown/tables.js.map +1 -0
  95. package/dist/vendor/spec-core/markdown/types.d.ts +287 -0
  96. package/dist/vendor/spec-core/markdown/types.js +9 -0
  97. package/dist/vendor/spec-core/markdown/types.js.map +1 -0
  98. package/dist/vendor/spec-core/path/index.d.ts +1 -0
  99. package/dist/vendor/spec-core/path/index.js +2 -0
  100. package/dist/vendor/spec-core/path/index.js.map +1 -0
  101. package/dist/vendor/spec-core/path/posix.d.ts +83 -0
  102. package/dist/vendor/spec-core/path/posix.js +150 -0
  103. package/dist/vendor/spec-core/path/posix.js.map +1 -0
  104. package/dist/vendor/spec-core/pattern/automaton.d.ts +129 -0
  105. package/dist/vendor/spec-core/pattern/automaton.js +340 -0
  106. package/dist/vendor/spec-core/pattern/automaton.js.map +1 -0
  107. package/dist/vendor/spec-core/pattern/charset.d.ts +58 -0
  108. package/dist/vendor/spec-core/pattern/charset.js +116 -0
  109. package/dist/vendor/spec-core/pattern/charset.js.map +1 -0
  110. package/dist/vendor/spec-core/pattern/glob.d.ts +121 -0
  111. package/dist/vendor/spec-core/pattern/glob.js +481 -0
  112. package/dist/vendor/spec-core/pattern/glob.js.map +1 -0
  113. package/dist/vendor/spec-core/pattern/index.d.ts +6 -0
  114. package/dist/vendor/spec-core/pattern/index.js +4 -0
  115. package/dist/vendor/spec-core/pattern/index.js.map +1 -0
  116. package/dist/vendor/spec-core/pattern/regex.d.ts +67 -0
  117. package/dist/vendor/spec-core/pattern/regex.js +813 -0
  118. package/dist/vendor/spec-core/pattern/regex.js.map +1 -0
  119. package/dist/vendor/spec-core/text/index.d.ts +2 -0
  120. package/dist/vendor/spec-core/text/index.js +2 -0
  121. package/dist/vendor/spec-core/text/index.js.map +1 -0
  122. package/dist/vendor/spec-core/text/lines.d.ts +72 -0
  123. package/dist/vendor/spec-core/text/lines.js +156 -0
  124. package/dist/vendor/spec-core/text/lines.js.map +1 -0
  125. package/package.json +3 -2
  126. package/schema.json +17 -0
  127. package/src/vendor/spec-core/LICENSE +21 -0
  128. package/src/vendor/spec-core/README.md +8 -0
package/dist/archive.js CHANGED
@@ -9,40 +9,121 @@
9
9
  */
10
10
  import { BANNER_CLOSE, BANNER_OPEN, lineOfField } from './brief.js';
11
11
  import { resolveDependency } from './corpus.js';
12
- import { readFrontMatter, removeEntry, renderScalar, setEntry } from './frontmatter.js';
12
+ import { editable, readFrontMatter, removeEntry, renderScalar, setEntry } from './frontmatter.js';
13
13
  import { matchGlob, parseGlob } from './glob.js';
14
14
  import { INTEGRITY_FIELD, integrityOf } from './integrity.js';
15
15
  import { dirOf, linesLinkingTo, normalisePath, rewriteLinks } from './links.js';
16
- import { fillTemplate, joinLines, labelMatches, lineEnding, templateHoles } from './text.js';
16
+ import { configuredSeverity } from './lint.js';
17
+ import { encodeLike, fillTemplate, joinLines, labelMatches, templateHoles } from './text.js';
17
18
  function problem(brief, rule, severity, line, message, hint) {
18
19
  return { rule, severity, message, file: brief.file, line, brief: brief.id ?? undefined, hint };
19
20
  }
20
21
  function lineOf(brief, key) {
21
22
  return lineOfField(brief, key) + 1;
22
23
  }
24
+ function protectedFinding(brief, path) {
25
+ return {
26
+ ...problem(brief, 'protected-file', 'error', lineOf(brief, 'protectedFiles'), `the round changed ${path}, which this brief protects`, 'revert the change, or record the departure in the brief before archiving it'),
27
+ path,
28
+ };
29
+ }
30
+ /**
31
+ * The files a round changed outside its scope: one finding, since the scope
32
+ * is one defect however many files show it - widened once, or explained once.
33
+ */
34
+ function outOfScopeFinding(brief, paths, strict) {
35
+ return {
36
+ ...problem(brief, 'out-of-scope', strict ? 'error' : 'warning', lineOf(brief, 'affectedFiles'), `the round changed ${paths.length} file(s) outside affectedFiles: ${listed(paths)}`, 'widen affectedFiles if the scope was wrong, or say why in the summary'),
37
+ paths,
38
+ };
39
+ }
40
+ /** Where an archive rule's finding goes: an error refuses, and under --strict a warning refuses as an error, as out-of-scope does. */
41
+ function place(finding, strict, blocking, warnings) {
42
+ if (finding.severity === 'error')
43
+ blocking.push(finding);
44
+ else if (strict && finding.severity === 'warning')
45
+ blocking.push({ ...finding, severity: 'error' });
46
+ else
47
+ warnings.push(finding);
48
+ }
49
+ const UNMEASURED = {
50
+ git: {
51
+ why: 'without git, the files the round changed cannot be read',
52
+ hint: 'archive inside the git work tree without --no-git, and name the round with --commit <rev> or --base <rev>',
53
+ },
54
+ revision: {
55
+ why: 'no commit or base was named, so the files the round changed were not read',
56
+ hint: 'pass --commit <rev> for the commit the round landed as, or --base <rev> for the branch it started from, or set "archiving.base"',
57
+ },
58
+ merged: {
59
+ why: 'the diff from their merge base is empty',
60
+ hint: "archive on the round's branch before it merges, or pass --base <rev> naming the commit the round started from",
61
+ },
62
+ };
63
+ /**
64
+ * The finding for scope checks that had nothing to check. A brief with no
65
+ * scope has nothing to check either way, so it gets none.
66
+ */
67
+ function unmeasuredScope(brief, request, severity) {
68
+ const unmeasured = request.unmeasured ?? (request.changes === undefined ? { reason: 'revision' } : undefined);
69
+ const fields = [
70
+ ...(brief.protectedFiles.length > 0 ? ['protectedFiles'] : []),
71
+ ...(brief.affectedFiles.length > 0 ? ['affectedFiles'] : []),
72
+ ];
73
+ if (unmeasured === undefined || fields.length === 0)
74
+ return null;
75
+ const { why, hint } = UNMEASURED[unmeasured.reason];
76
+ const cause = unmeasured.reason === 'merged' ? `${unmeasured.commit.slice(0, 7)} is already in ${unmeasured.base}, so ${why}` : why;
77
+ return problem(brief, 'scope-unmeasured', severity, lineOf(brief, fields[0]), `${fields.join(' and ')} went unchecked: ${cause}`, hint);
78
+ }
23
79
  /**
24
80
  * Scope patterns, for matching the files a round changed. A changed path is a
25
- * file, and a literal pattern matches its own path whether it is read as a
26
- * file or as a directory, so the tree is not asked.
81
+ * file that exists or existed, so a literal pattern is read as either: its own
82
+ * path, and everything beneath it. Nothing can lie beneath a file, so the
83
+ * reading cannot invent a match, and the tree need not be asked - it has
84
+ * changed since the scope was written, by the round itself.
27
85
  */
28
86
  function globs(patterns) {
29
87
  return patterns.flatMap((p) => {
30
- const parsed = parseGlob(p);
88
+ const parsed = parseGlob(p, { literal: 'either' });
31
89
  return parsed.ok ? [parsed.glob] : [];
32
90
  });
33
91
  }
92
+ /** A list marker opening a line, after its indentation. */
93
+ const ITEM_START = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]+/;
94
+ /** A line that ends a sentence: a stop, then only closing marks. */
95
+ const SENTENCE_END = /[.!?][)\]*_"'`]*[ \t]*$/;
96
+ /** Where a note's text starts: after its indentation and a list marker, if it has one. */
97
+ const NOTE_START = /^[ \t]*(?:(?:[-*+]|\d{1,9}[.)])[ \t]+)?/;
34
98
  /**
35
- * Whether an open box carries a note that closes it: "Delegated to ...",
36
- * "Rejected ...". The note belongs to the box above it, so the search stops at
37
- * the first nested box: a child's note does not close its parent.
99
+ * Whether an open box carries a note that closes it: a line under the box,
100
+ * within its item, that starts with a disposition marker and starts a note -
101
+ * a bullet, "- **Delegated to** 012", a paragraph after a blank line, or a
102
+ * line after one that ends a sentence, "... did not." over "**Rejected** by
103
+ * ...", which is how notes are written where they are written most. A line
104
+ * that continues a sentence is not one: "- [ ] Explain why the" over
105
+ * "**Rejected** designs failed" is the box's own sentence wrapping, and an
106
+ * open task, as a marker in the middle of the box's line is a word in it. The note belongs to the box above
107
+ * it, so the search stops at the first nested box: a child's note does not
108
+ * close its parent.
109
+ *
110
+ * Where the note starts is read from the lines as written, and the marker from
111
+ * the masked line, so a marker in code or a comment, or one after a code span,
112
+ * closes nothing.
38
113
  */
39
114
  function dispositioned(scanned, line, end, markers) {
40
115
  const boxes = new Set(scanned.tasks.map((t) => t.line));
41
- for (let i = line; i < end; i += 1) {
42
- if (i > line && boxes.has(i))
116
+ for (let i = line + 1; i < end; i += 1) {
117
+ if (boxes.has(i))
43
118
  return false;
119
+ const written = scanned.lines[i];
120
+ const above = scanned.lines[i - 1];
121
+ const startsNote = ITEM_START.test(written) || above.trim() === '' || SENTENCE_END.test(above);
122
+ if (!startsNote)
123
+ continue;
124
+ const start = NOTE_START.exec(written)[0].length;
44
125
  const text = scanned.masked[i];
45
- if (markers.some((marker) => text.includes(marker)))
126
+ if (markers.some((marker) => text.startsWith(marker, start)))
46
127
  return true;
47
128
  }
48
129
  return false;
@@ -129,17 +210,32 @@ function withIntegrity(lines) {
129
210
  const hash = integrityOf(joinLines(placed, '\n'));
130
211
  return setEntry(placed, readFrontMatter(placed), INTEGRITY_FIELD, hash);
131
212
  }
132
- /** Lines written back with the source's byte-order mark, line ending and final newline, or lack of one. */
133
- function encodeLike(source, lines) {
134
- const bom = source.charCodeAt(0) === 0xfeff ? '\ufeff' : '';
135
- const eol = lineEnding(source);
136
- const text = joinLines(lines, eol);
137
- const finalNewline = source === '' || source.endsWith('\n');
138
- return `${bom}${finalNewline ? text : text.slice(0, text.length - eol.length)}`;
213
+ /**
214
+ * Whether a plan can write into the brief's front matter: it has none, or one
215
+ * closed and written in YAML. One never closed, or TOML, cannot be written
216
+ * into. The front-matter rule reports both, and a plan that `edits` the front
217
+ * matter and is not refused by it already - the rule lowered, or a reopening,
218
+ * which lint does not gate - is refused here. It leaves the front matter as
219
+ * written rather than half edited.
220
+ */
221
+ function writable(brief, edits, blocking) {
222
+ const frontMatter = brief.frontMatter;
223
+ if (frontMatter === null || editable(frontMatter))
224
+ return true;
225
+ if (edits && !blocking.some((f) => f.rule === 'front-matter')) {
226
+ const open = frontMatter.close < 0;
227
+ blocking.push(problem(brief, 'front-matter', 'error', 1, open ? 'the front matter is never closed, so it cannot be written into' : 'the front matter is TOML, which spec-brief does not write', open ? 'close the front matter with a "---" line' : 'write the front matter as YAML between "---" lines'));
228
+ }
229
+ return false;
139
230
  }
140
- /** Links other briefs hold to a file that is moving: rewritten in live briefs, reported in frozen ones. */
231
+ /**
232
+ * Links other briefs hold to a file that is moving: rewritten in live briefs,
233
+ * reported in frozen ones. `archiving.rewriteLinks` governs these as it does
234
+ * the moving brief's own links, so with it off a live brief is not written,
235
+ * and the links it is left holding are reported instead.
236
+ */
141
237
  function inbound(corpus, moving, to) {
142
- const result = { ops: [], rewritten: [], frozen: [] };
238
+ const result = { ops: [], rewritten: [], frozen: [], stale: [] };
143
239
  for (const other of corpus.briefs) {
144
240
  if (other === moving)
145
241
  continue;
@@ -147,9 +243,10 @@ function inbound(corpus, moving, to) {
147
243
  const lines = linesLinkingTo(other.scan, directory, moving.file);
148
244
  if (lines.length === 0)
149
245
  continue;
150
- if (other.phase === 'archived') {
246
+ if (other.phase === 'archived' || !corpus.config.archiving.rewriteLinks) {
247
+ const left = other.phase === 'archived' ? result.frozen : result.stale;
151
248
  for (const line of lines)
152
- result.frozen.push({ file: other.file, line: line + 1 });
249
+ left.push({ file: other.file, line: line + 1 });
153
250
  continue;
154
251
  }
155
252
  const rewritten = rewriteLinks(other.scan, directory, directory, (p) => (p === moving.file ? to : p));
@@ -158,6 +255,21 @@ function inbound(corpus, moving, to) {
158
255
  }
159
256
  return result;
160
257
  }
258
+ /** Five items, and a count of the rest. */
259
+ function listed(items) {
260
+ return items.slice(0, 5).join(', ') + (items.length > 5 ? `, and ${items.length - 5} more` : '');
261
+ }
262
+ /**
263
+ * Links live briefs will hold to nothing once the brief moves, when rewriting
264
+ * is off: the move is what breaks them, so the move says where they are.
265
+ */
266
+ function staleLinks(corpus, brief, stale, strict, blocking, warnings) {
267
+ const severity = configuredSeverity(corpus, 'stale-link');
268
+ if (severity === null || stale.length === 0)
269
+ return;
270
+ const finding = problem(brief, 'stale-link', severity, 1, `links on ${stale.length} line(s) of other live briefs will stop resolving when it moves: ${listed(stale.map((s) => `${s.file}:${s.line}`))}`, 'turn "archiving.rewriteLinks" on to have them rewritten, or fix them by hand');
271
+ place(finding, strict, blocking, warnings);
272
+ }
161
273
  function donePlan(action, brief) {
162
274
  return {
163
275
  action,
@@ -189,13 +301,16 @@ export function planArchive(corpus, brief, request) {
189
301
  if (finding.file === brief.file && refuses)
190
302
  blocking.push(finding);
191
303
  }
192
- // Only a status field can say "draft", so there is one to point at.
304
+ // Only a status field can say "draft" or "deferred", so there is one to point at.
193
305
  const field = config.status.field;
194
306
  if (brief.status === 'draft') {
195
307
  blocking.push(problem(brief, 'archive-draft', 'error', lineOf(brief, field), 'is a draft, and a draft has not been executed', `set "${field}: ${config.status.active}" once the round runs`));
196
308
  }
309
+ if (brief.status === 'deferred') {
310
+ blocking.push(problem(brief, 'archive-deferred', 'error', lineOf(brief, field), 'is deferred, and deferred work has not been executed', `set "${field}: ${config.status.active}" when its trigger fires and the round runs`));
311
+ }
197
312
  for (const task of openTasks(brief, corpus)) {
198
- blocking.push(problem(brief, 'open-task', 'error', task.line + 1, `"${task.text === '' ? '(empty)' : task.text}" is neither ticked nor dispositioned`, `tick it, or say why under it with a note starting ${config.archiving.dispositions.map((d) => `"${d}"`).join(', ')}`));
313
+ blocking.push(problem(brief, 'open-task', 'error', task.line + 1, `"${task.text === '' ? '(empty)' : task.text}" is neither ticked nor dispositioned`, `tick it, or say why under it, as a bullet or as a paragraph after a blank line, starting ${config.archiving.dispositions.map((d) => `"${d}"`).join(', ')}`));
199
314
  }
200
315
  for (const dependency of brief.dependsOn) {
201
316
  const target = resolveDependency(corpus, dependency);
@@ -207,26 +322,29 @@ export function planArchive(corpus, brief, request) {
207
322
  if (request.dirty !== undefined && request.allowDirty !== true) {
208
323
  const outside = request.dirty.filter((path) => !bookkeeping(path));
209
324
  if (outside.length > 0) {
210
- const shown = outside.slice(0, 5).join(', ') + (outside.length > 5 ? `, and ${outside.length - 5} more` : '');
211
- blocking.push(problem(brief, 'dirty-tree', 'error', 1, `the working tree has uncommitted changes outside the briefs: ${shown}`, 'commit them so the recorded commit holds the round, or pass --allow-dirty'));
325
+ blocking.push(problem(brief, 'dirty-tree', 'error', 1, `the working tree has uncommitted changes outside the briefs: ${listed(outside)}`, 'commit them so the recorded commit holds the round, or pass --allow-dirty'));
212
326
  }
213
327
  }
214
328
  const work = (request.changes ?? []).filter((change) => !bookkeeping(change.path));
215
329
  const protectedGlobs = globs(brief.protectedFiles);
216
330
  const affectedGlobs = globs(brief.affectedFiles);
331
+ // Each protected file changed is its own defect - reverted, or ruled on,
332
+ // one file at a time - so each is a finding that names its path.
217
333
  for (const change of work) {
218
- if (protectedGlobs.some((g) => matchGlob(g, change.path))) {
219
- blocking.push(problem(brief, 'protected-file', 'error', lineOf(brief, 'protectedFiles'), `the round changed ${change.path}, which this brief protects`, 'revert the change, or record the departure in the brief before archiving it'));
220
- }
334
+ if (protectedGlobs.some((g) => matchGlob(g, change.path)))
335
+ blocking.push(protectedFinding(brief, change.path));
221
336
  }
222
337
  // A protected file is reported once, as protected, and not again as out of scope.
223
338
  const unprotected = work.filter((c) => !protectedGlobs.some((g) => matchGlob(g, c.path)));
224
339
  const outside = affectedGlobs.length === 0 ? [] : unprotected.filter((c) => !affectedGlobs.some((g) => matchGlob(g, c.path)));
225
340
  if (outside.length > 0) {
226
- const shown = outside.slice(0, 5).map((c) => c.path).join(', ') + (outside.length > 5 ? `, and ${outside.length - 5} more` : '');
227
- const finding = problem(brief, 'out-of-scope', request.strict === true ? 'error' : 'warning', lineOf(brief, 'affectedFiles'), `the round changed ${outside.length} file(s) outside affectedFiles: ${shown}`, 'widen affectedFiles if the scope was wrong, or say why in the summary');
341
+ const finding = outOfScopeFinding(brief, outside.map((c) => c.path), request.strict === true);
228
342
  (request.strict === true ? blocking : warnings).push(finding);
229
343
  }
344
+ const scopeSeverity = configuredSeverity(corpus, 'scope-unmeasured');
345
+ const unmeasured = scopeSeverity === null ? null : unmeasuredScope(brief, request, scopeSeverity);
346
+ if (unmeasured !== null)
347
+ place(unmeasured, request.strict === true, blocking, warnings);
230
348
  if (corpus.briefs.some((b) => b.file === to)) {
231
349
  blocking.push(problem(brief, 'archive-exists', 'error', 1, `${to} already exists`, 'an archived brief is never overwritten'));
232
350
  }
@@ -255,13 +373,15 @@ export function planArchive(corpus, brief, request) {
255
373
  const banner = renderBanner(config.archiving.banner, values);
256
374
  if (banner.length > 0)
257
375
  lines = withBanner(lines, banner);
258
- if (config.status.field !== null) {
376
+ const writes = writable(brief, config.status.field !== null || config.archiving.freeze, blocking);
377
+ if (writes && config.status.field !== null) {
259
378
  lines = setEntry(lines, readFrontMatter(lines), config.status.field, renderScalar(config.status.archived));
260
379
  }
261
380
  lines = removeEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD);
262
- if (config.archiving.freeze)
381
+ if (writes && config.archiving.freeze)
263
382
  lines = withIntegrity(lines);
264
383
  const incoming = inbound(corpus, brief, to);
384
+ staleLinks(corpus, brief, incoming.stale, request.strict === true, blocking, warnings);
265
385
  return {
266
386
  action: 'archive',
267
387
  brief,
@@ -282,18 +402,56 @@ export function planArchive(corpus, brief, request) {
282
402
  changes: request.changes ?? [],
283
403
  };
284
404
  }
405
+ /**
406
+ * The refusals a plugin may lift. A tool that verifies a ruling can say a
407
+ * protected file was allowed to change, or that a file outside the scope was;
408
+ * whether a round is done is spec-brief's to say.
409
+ */
410
+ export const WAIVABLE = ['protected-file', 'out-of-scope'];
411
+ /**
412
+ * The plan with the refusals plugins waived turned into notes. A waiver
413
+ * matches a blocking finding by rule and path: a protected-file finding is
414
+ * replaced by the note, and an out-of-scope finding loses the path and goes
415
+ * when none is left. A waiver that matches nothing changes nothing, and one
416
+ * for any other rule is ignored with a warning, which refuses under --strict
417
+ * as any warning does.
418
+ */
419
+ export function applyWaivers(plan, waivers, strict = false) {
420
+ const { brief } = plan;
421
+ const blocking = [...plan.blocking];
422
+ const warnings = [...plan.warnings];
423
+ for (const { plugin, waiver } of waivers) {
424
+ if (!WAIVABLE.includes(waiver.rule)) {
425
+ const ignored = problem(brief, 'waiver-ignored', 'warning', 1, `${plugin} asked to waive ${waiver.rule} for ${waiver.path}; a plugin may waive only ${WAIVABLE.join(' and ')}`, `report it to the plugin's authors: ${waiver.rule} is spec-brief's to decide`);
426
+ place(ignored, strict, blocking, warnings);
427
+ continue;
428
+ }
429
+ const at = blocking.findIndex((f) => f.rule === waiver.rule && (f.path === waiver.path || f.paths?.includes(waiver.path) === true));
430
+ if (at < 0)
431
+ continue;
432
+ const found = blocking[at];
433
+ const rest = (found.paths ?? []).filter((p) => p !== waiver.path);
434
+ blocking.splice(at, 1, ...(rest.length > 0 ? [outOfScopeFinding(brief, rest, true)] : []));
435
+ warnings.push({
436
+ ...problem(brief, 'waived', 'note', found.line, `${plugin} waives ${waiver.rule} for ${waiver.path}: ${waiver.reason}`, 'review the waiver with the round; it stands where the refusal was'),
437
+ path: waiver.path,
438
+ });
439
+ }
440
+ return { ...plan, blocking, warnings };
441
+ }
285
442
  /**
286
443
  * Reopens an archived brief: the banner and the freeze come off, the status
287
444
  * goes back to the live word, and the links are rewritten for the directory
288
445
  * it returns to. What the round wrote stays in history, where it belongs.
289
446
  */
290
- export function planUnarchive(corpus, brief) {
447
+ export function planUnarchive(corpus, brief, request = {}) {
291
448
  if (brief.phase === 'live')
292
449
  return donePlan('unarchive', brief);
293
450
  const { config } = corpus;
294
451
  const briefsDir = normalisePath(config.briefs);
295
452
  const to = within(briefsDir, brief.name);
296
453
  const blocking = [];
454
+ const warnings = [];
297
455
  if (corpus.briefs.some((b) => b.file === to)) {
298
456
  blocking.push(problem(brief, 'unarchive-exists', 'error', 1, `${to} already exists`, 'rename one of them first'));
299
457
  }
@@ -306,10 +464,20 @@ export function planUnarchive(corpus, brief) {
306
464
  }
307
465
  lines = withoutBanner(lines, brief.banner);
308
466
  lines = removeEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD);
309
- if (config.status.field !== null) {
310
- lines = setEntry(lines, readFrontMatter(lines), config.status.field, renderScalar(config.status.active));
467
+ const field = config.status.field;
468
+ if (writable(brief, field !== null, blocking) && field !== null) {
469
+ lines = setEntry(lines, readFrontMatter(lines), field, renderScalar(config.status.active));
311
470
  }
471
+ // Archival gives a brief with no front matter a block to hold its hash.
472
+ // A block this reopening emptied goes too, and the brief is the one that
473
+ // was archived. One that was empty before it was archived reads the same
474
+ // once the hash is in it, and goes with it; one empty in the archive stays.
475
+ const left = readFrontMatter(lines);
476
+ const emptied = left !== null && left.kind === 'yaml' && left.close === 1 && (brief.frontMatter?.close ?? 0) > 1;
477
+ if (emptied)
478
+ lines = lines.slice(2);
312
479
  const incoming = inbound(corpus, brief, to);
480
+ staleLinks(corpus, brief, incoming.stale, request.strict === true, blocking, warnings);
313
481
  return {
314
482
  action: 'unarchive',
315
483
  brief,
@@ -322,7 +490,7 @@ export function planUnarchive(corpus, brief) {
322
490
  { kind: 'remove', path: brief.file, before: brief.source },
323
491
  ],
324
492
  blocking,
325
- warnings: [],
493
+ warnings,
326
494
  linksRewritten,
327
495
  inboundRewritten: incoming.rewritten,
328
496
  inboundFrozen: incoming.frozen,
@@ -1 +1 @@
1
- {"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Archival and its reverse, planned.\n *\n * A plan is a pure function of the corpus and a request: what would be\n * refused and why, the text every touched file would hold afterwards, and the\n * operations that get there. Nothing is written here. `--dry-run` prints a\n * plan; `apply.ts` executes one as a transaction. Splitting the two is what\n * makes the ceremony testable without a disk and previewable without risk.\n */\n\nimport { BANNER_CLOSE, BANNER_OPEN, type Brief, lineOfField } from './brief.js';\nimport type { Config } from './config.js';\nimport { type Corpus, resolveDependency } from './corpus.js';\nimport { readFrontMatter, removeEntry, renderScalar, setEntry } from './frontmatter.js';\nimport type { CommitInfo, FileChange } from './git.js';\nimport { type Glob, matchGlob, parseGlob } from './glob.js';\nimport { INTEGRITY_FIELD, integrityOf } from './integrity.js';\nimport { dirOf, linesLinkingTo, normalisePath, rewriteLinks } from './links.js';\nimport type { Scan } from './markdown.js';\nimport { fillTemplate, joinLines, labelMatches, lineEnding, templateHoles } from './text.js';\nimport type { Finding, Severity } from './types.js';\n\nexport interface PullRequest {\n readonly number: number;\n readonly url: string | null;\n}\n\nexport interface ArchiveRequest {\n /** `YYYY-MM-DD`. */\n readonly date: string;\n readonly summary?: string | undefined;\n readonly pr?: PullRequest | undefined;\n readonly commit?: CommitInfo | undefined;\n /** The files the round changed, when a commit is known. */\n readonly changes?: readonly FileChange[] | undefined;\n /** Uncommitted paths, when the working tree was read. */\n readonly dirty?: readonly string[] | undefined;\n readonly allowDirty?: boolean | undefined;\n /** Out-of-scope changes refuse the archival instead of warning. */\n readonly strict?: boolean | undefined;\n /** The corpus's lint findings; errors on the brief refuse the archival. */\n readonly findings?: readonly Finding[] | undefined;\n}\n\nexport type FileOp =\n | {\n readonly kind: 'write';\n readonly path: string;\n readonly content: string;\n /** What the file holds when the plan is made; `null` for a file that must not exist yet. */\n readonly before: string | null;\n }\n | { readonly kind: 'remove'; readonly path: string; readonly before: string };\n\nexport interface Plan {\n readonly action: 'archive' | 'unarchive';\n readonly brief: Brief;\n readonly from: string;\n readonly to: string;\n /** The brief is already where this action would put it. */\n readonly done: boolean;\n readonly ops: readonly FileOp[];\n /** Why the action is refused; empty when it may proceed. */\n readonly blocking: readonly Finding[];\n readonly warnings: readonly Finding[];\n readonly linksRewritten: number;\n readonly inboundRewritten: readonly string[];\n /** Links archived briefs hold to the old path. They are frozen and left as they are. */\n readonly inboundFrozen: readonly { readonly file: string; readonly line: number }[];\n readonly banner: readonly string[];\n readonly changes: readonly FileChange[];\n}\n\nfunction problem(\n brief: Brief,\n rule: string,\n severity: Severity,\n line: number,\n message: string,\n hint?: string,\n): Finding {\n return { rule, severity, message, file: brief.file, line, brief: brief.id ?? undefined, hint };\n}\n\nfunction lineOf(brief: Brief, key: string): number {\n return lineOfField(brief, key) + 1;\n}\n\n/**\n * Scope patterns, for matching the files a round changed. A changed path is a\n * file, and a literal pattern matches its own path whether it is read as a\n * file or as a directory, so the tree is not asked.\n */\nfunction globs(patterns: readonly string[]): Glob[] {\n return patterns.flatMap((p) => {\n const parsed = parseGlob(p);\n return parsed.ok ? [parsed.glob] : [];\n });\n}\n\n/**\n * Whether an open box carries a note that closes it: \"Delegated to ...\",\n * \"Rejected ...\". The note belongs to the box above it, so the search stops at\n * the first nested box: a child's note does not close its parent.\n */\nfunction dispositioned(scanned: Scan, line: number, end: number, markers: readonly string[]): boolean {\n const boxes = new Set(scanned.tasks.map((t) => t.line));\n for (let i = line; i < end; i += 1) {\n if (i > line && boxes.has(i)) return false;\n const text = scanned.masked[i] as string;\n if (markers.some((marker) => text.includes(marker))) return true;\n }\n return false;\n}\n\n/** A path inside a directory that may be the root itself. */\nfunction within(directory: string, name: string): string {\n return directory === '' ? name : `${directory}/${name}`;\n}\n\n/**\n * Whether a path is a brief: a file directly in the live or archive directory\n * whose name the configuration takes for a brief. Edits to briefs are the\n * ceremony, not the round, and are left out of the scope and tree checks.\n * Nothing else in those directories is: a README beside the briefs is work.\n */\nfunction isBriefPath(path: string, config: Config): boolean {\n const directory = dirOf(path);\n if (directory !== normalisePath(config.briefs) && directory !== normalisePath(config.archive)) return false;\n const name = path.slice(path.lastIndexOf('/') + 1);\n const accept = parseGlob(config.files);\n const excluded = config.exclude.some((e) => {\n const parsed = parseGlob(e);\n return parsed.ok && matchGlob(parsed.glob, name);\n });\n return accept.ok && matchGlob(accept.glob, name) && !excluded;\n}\n\n/** Task items that must be closed before a brief can be archived. */\nexport function openTasks(brief: Brief, corpus: Corpus): Brief['tasks'] {\n const { tasks: scope, dispositions } = corpus.config.archiving;\n const inScope =\n scope === 'all'\n ? brief.tasks\n : brief.tasks.filter((task) =>\n brief.sections.some(\n (s) => task.line > s.heading.line && task.line < s.end && scope.some((name) => labelMatches(s.heading.text, name)),\n ),\n );\n return inScope.filter((task) => !task.checked && !dispositioned(brief.scan, task.line, task.end, dispositions));\n}\n\nfunction diffstat(changes: readonly FileChange[]): string {\n const insertions = changes.reduce((sum, c) => sum + (c.insertions ?? 0), 0);\n const deletions = changes.reduce((sum, c) => sum + (c.deletions ?? 0), 0);\n const files = changes.length === 1 ? '1 file changed' : `${changes.length} files changed`;\n return `${files}, +${insertions} \\u2212${deletions}`;\n}\n\n/**\n * The banner's lines, markers included. A template line whose placeholders\n * are not all filled is left out, so a round with no pull request has no\n * pull-request sentence rather than a sentence with a hole in it. A template\n * with nothing left writes no banner at all, markers included.\n */\nexport function renderBanner(template: readonly string[], values: Readonly<Record<string, string>>): string[] {\n const kept = template\n .filter((line) => templateHoles(line).every((hole) => (values[hole] ?? '') !== ''))\n .map((line) => fillTemplate(line, values));\n if (kept.length === 0) return [];\n return [BANNER_OPEN, ...kept.map((line) => (line === '' ? '>' : `> ${line}`)), BANNER_CLOSE];\n}\n\n/**\n * Removes a banner block and the blank line written after it. The exact\n * reverse of {@link withBanner}, so reopening a brief leaves its blank lines\n * as they were.\n */\nfunction withoutBanner(lines: readonly string[], banner: Brief['banner']): string[] {\n if (banner === null) return [...lines];\n const after = lines[banner.end]?.trim() === '' ? banner.end + 1 : banner.end;\n return [...lines.slice(0, banner.start), ...lines.slice(after)];\n}\n\n/**\n * Inserts a banner under the front matter, after the blank line that usually\n * follows it, and writes one blank line after the banner.\n */\nfunction withBanner(lines: readonly string[], banner: readonly string[]): string[] {\n const frontMatter = readFrontMatter(lines);\n let at = frontMatter === null || frontMatter.close < 0 ? 0 : frontMatter.close + 1;\n if (at > 0 && lines[at]?.trim() === '') at += 1;\n return [...lines.slice(0, at), ...banner, '', ...lines.slice(at)];\n}\n\n/**\n * Sets the integrity field to the hash of everything else. The field is put in\n * place before hashing, so the lines hashed are exactly the lines a later\n * check sees once it takes the field back out.\n */\nfunction withIntegrity(lines: readonly string[]): string[] {\n const placed = setEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD, 'pending');\n const hash = integrityOf(joinLines(placed, '\\n'));\n return setEntry(placed, readFrontMatter(placed), INTEGRITY_FIELD, hash);\n}\n\n/** Lines written back with the source's byte-order mark, line ending and final newline, or lack of one. */\nfunction encodeLike(source: string, lines: readonly string[]): string {\n const bom = source.charCodeAt(0) === 0xfeff ? '\\ufeff' : '';\n const eol = lineEnding(source);\n const text = joinLines(lines, eol);\n const finalNewline = source === '' || source.endsWith('\\n');\n return `${bom}${finalNewline ? text : text.slice(0, text.length - eol.length)}`;\n}\n\ninterface Inbound {\n readonly ops: FileOp[];\n readonly rewritten: string[];\n readonly frozen: { file: string; line: number }[];\n}\n\n/** Links other briefs hold to a file that is moving: rewritten in live briefs, reported in frozen ones. */\nfunction inbound(corpus: Corpus, moving: Brief, to: string): Inbound {\n const result: Inbound = { ops: [], rewritten: [], frozen: [] };\n for (const other of corpus.briefs) {\n if (other === moving) continue;\n const directory = dirOf(other.file);\n const lines = linesLinkingTo(other.scan, directory, moving.file);\n if (lines.length === 0) continue;\n if (other.phase === 'archived') {\n for (const line of lines) result.frozen.push({ file: other.file, line: line + 1 });\n continue;\n }\n const rewritten = rewriteLinks(other.scan, directory, directory, (p) => (p === moving.file ? to : p));\n result.ops.push({ kind: 'write', path: other.file, content: encodeLike(other.source, rewritten.lines), before: other.source });\n result.rewritten.push(other.file);\n }\n return result;\n}\n\nfunction donePlan(action: Plan['action'], brief: Brief): Plan {\n return {\n action,\n brief,\n from: brief.file,\n to: brief.file,\n done: true,\n ops: [],\n blocking: [],\n warnings: [],\n linksRewritten: 0,\n inboundRewritten: [],\n inboundFrozen: [],\n banner: [],\n changes: [],\n };\n}\n\nexport function planArchive(corpus: Corpus, brief: Brief, request: ArchiveRequest): Plan {\n if (brief.phase === 'archived') return donePlan('archive', brief);\n const { config } = corpus;\n const archiveDir = normalisePath(config.archive);\n const to = within(archiveDir, brief.name);\n const blocking: Finding[] = [];\n const warnings: Finding[] = [];\n\n // Under --strict a warning is an error, here as in lint.\n for (const finding of request.findings ?? []) {\n const refuses = finding.severity === 'error' || (request.strict === true && finding.severity === 'warning');\n if (finding.file === brief.file && refuses) blocking.push(finding);\n }\n // Only a status field can say \"draft\", so there is one to point at.\n const field = config.status.field as string;\n if (brief.status === 'draft') {\n blocking.push(\n problem(brief, 'archive-draft', 'error', lineOf(brief, field), 'is a draft, and a draft has not been executed', `set \"${field}: ${config.status.active}\" once the round runs`),\n );\n }\n for (const task of openTasks(brief, corpus)) {\n blocking.push(\n problem(\n brief,\n 'open-task',\n 'error',\n task.line + 1,\n `\"${task.text === '' ? '(empty)' : task.text}\" is neither ticked nor dispositioned`,\n `tick it, or say why under it with a note starting ${config.archiving.dispositions.map((d) => `\"${d}\"`).join(', ')}`,\n ),\n );\n }\n for (const dependency of brief.dependsOn) {\n const target = resolveDependency(corpus, dependency);\n if (target !== undefined && target !== brief && target.phase === 'live') {\n blocking.push(\n problem(brief, 'dependency-open', 'error', lineOf(brief, 'dependsOn'), `depends on ${target.id ?? target.name}, which is not archived yet`, `archive ${target.id ?? target.name} first, or drop the dependency if it was never real`),\n );\n }\n }\n const bookkeeping = (path: string): boolean => isBriefPath(path, config);\n if (request.dirty !== undefined && request.allowDirty !== true) {\n const outside = request.dirty.filter((path) => !bookkeeping(path));\n if (outside.length > 0) {\n const shown = outside.slice(0, 5).join(', ') + (outside.length > 5 ? `, and ${outside.length - 5} more` : '');\n blocking.push(\n problem(brief, 'dirty-tree', 'error', 1, `the working tree has uncommitted changes outside the briefs: ${shown}`, 'commit them so the recorded commit holds the round, or pass --allow-dirty'),\n );\n }\n }\n\n const work = (request.changes ?? []).filter((change) => !bookkeeping(change.path));\n const protectedGlobs = globs(brief.protectedFiles);\n const affectedGlobs = globs(brief.affectedFiles);\n for (const change of work) {\n if (protectedGlobs.some((g) => matchGlob(g, change.path))) {\n blocking.push(\n problem(brief, 'protected-file', 'error', lineOf(brief, 'protectedFiles'), `the round changed ${change.path}, which this brief protects`, 'revert the change, or record the departure in the brief before archiving it'),\n );\n }\n }\n // A protected file is reported once, as protected, and not again as out of scope.\n const unprotected = work.filter((c) => !protectedGlobs.some((g) => matchGlob(g, c.path)));\n const outside = affectedGlobs.length === 0 ? [] : unprotected.filter((c) => !affectedGlobs.some((g) => matchGlob(g, c.path)));\n if (outside.length > 0) {\n const shown = outside.slice(0, 5).map((c) => c.path).join(', ') + (outside.length > 5 ? `, and ${outside.length - 5} more` : '');\n const finding = problem(\n brief,\n 'out-of-scope',\n request.strict === true ? 'error' : 'warning',\n lineOf(brief, 'affectedFiles'),\n `the round changed ${outside.length} file(s) outside affectedFiles: ${shown}`,\n 'widen affectedFiles if the scope was wrong, or say why in the summary',\n );\n (request.strict === true ? blocking : warnings).push(finding);\n }\n if (corpus.briefs.some((b) => b.file === to)) {\n blocking.push(problem(brief, 'archive-exists', 'error', 1, `${to} already exists`, 'an archived brief is never overwritten'));\n }\n\n let lines: string[] = [...brief.lines];\n let linksRewritten = 0;\n if (config.archiving.rewriteLinks) {\n const rewritten = rewriteLinks(brief.scan, dirOf(brief.file), archiveDir, (p) => (p === brief.file ? to : p));\n lines = rewritten.lines;\n linksRewritten = rewritten.count;\n }\n lines = withoutBanner(lines, brief.banner);\n\n const pr = request.pr;\n const values: Record<string, string> = {\n date: request.date,\n summary: request.summary?.trim() ?? '',\n pr: pr === undefined ? '' : pr.url === null ? `#${pr.number}` : `[#${pr.number}](${pr.url})`,\n commit: request.commit?.sha.slice(0, 7) ?? '',\n diffstat: request.commit === undefined || request.changes === undefined ? '' : diffstat(request.changes),\n links:\n linksRewritten === 0\n ? ''\n : `Relative links were rewritten to resolve from \\`${archiveDir}/\\` (${linksRewritten}); no other word changed.`,\n id: brief.id ?? '',\n title: brief.title ?? '',\n author: request.commit?.author ?? '',\n };\n const banner = renderBanner(config.archiving.banner, values);\n if (banner.length > 0) lines = withBanner(lines, banner);\n if (config.status.field !== null) {\n lines = setEntry(lines, readFrontMatter(lines), config.status.field, renderScalar(config.status.archived));\n }\n lines = removeEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD);\n if (config.archiving.freeze) lines = withIntegrity(lines);\n\n const incoming = inbound(corpus, brief, to);\n return {\n action: 'archive',\n brief,\n from: brief.file,\n to,\n done: false,\n ops: [\n { kind: 'write', path: to, content: encodeLike(brief.source, lines), before: null },\n ...incoming.ops,\n { kind: 'remove', path: brief.file, before: brief.source },\n ],\n blocking,\n warnings,\n linksRewritten,\n inboundRewritten: incoming.rewritten,\n inboundFrozen: incoming.frozen,\n banner,\n changes: request.changes ?? [],\n };\n}\n\n/**\n * Reopens an archived brief: the banner and the freeze come off, the status\n * goes back to the live word, and the links are rewritten for the directory\n * it returns to. What the round wrote stays in history, where it belongs.\n */\nexport function planUnarchive(corpus: Corpus, brief: Brief): Plan {\n if (brief.phase === 'live') return donePlan('unarchive', brief);\n const { config } = corpus;\n const briefsDir = normalisePath(config.briefs);\n const to = within(briefsDir, brief.name);\n const blocking: Finding[] = [];\n if (corpus.briefs.some((b) => b.file === to)) {\n blocking.push(problem(brief, 'unarchive-exists', 'error', 1, `${to} already exists`, 'rename one of them first'));\n }\n\n let lines: string[] = [...brief.lines];\n let linksRewritten = 0;\n if (config.archiving.rewriteLinks) {\n const rewritten = rewriteLinks(brief.scan, dirOf(brief.file), briefsDir, (p) => (p === brief.file ? to : p));\n lines = rewritten.lines;\n linksRewritten = rewritten.count;\n }\n lines = withoutBanner(lines, brief.banner);\n lines = removeEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD);\n if (config.status.field !== null) {\n lines = setEntry(lines, readFrontMatter(lines), config.status.field, renderScalar(config.status.active));\n }\n\n const incoming = inbound(corpus, brief, to);\n return {\n action: 'unarchive',\n brief,\n from: brief.file,\n to,\n done: false,\n ops: [\n { kind: 'write', path: to, content: encodeLike(brief.source, lines), before: null },\n ...incoming.ops,\n { kind: 'remove', path: brief.file, before: brief.source },\n ],\n blocking,\n warnings: [],\n linksRewritten,\n inboundRewritten: incoming.rewritten,\n inboundFrozen: incoming.frozen,\n banner: [],\n changes: [],\n };\n}\n"]}
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Archival and its reverse, planned.\n *\n * A plan is a pure function of the corpus and a request: what would be\n * refused and why, the text every touched file would hold afterwards, and the\n * operations that get there. Nothing is written here. `--dry-run` prints a\n * plan; `apply.ts` executes one as a transaction. Splitting the two is what\n * makes the ceremony testable without a disk and previewable without risk.\n */\n\nimport { BANNER_CLOSE, BANNER_OPEN, type Brief, lineOfField } from './brief.js';\nimport type { Config } from './config.js';\nimport { type Corpus, resolveDependency } from './corpus.js';\nimport { editable, readFrontMatter, removeEntry, renderScalar, setEntry } from './frontmatter.js';\nimport type { CommitInfo, FileChange } from './git.js';\nimport { type Glob, matchGlob, parseGlob } from './glob.js';\nimport { INTEGRITY_FIELD, integrityOf } from './integrity.js';\nimport { dirOf, linesLinkingTo, normalisePath, rewriteLinks } from './links.js';\nimport { configuredSeverity } from './lint.js';\nimport type { Scan } from './markdown.js';\nimport { encodeLike, fillTemplate, joinLines, labelMatches, templateHoles } from './text.js';\nimport type { Finding, Severity } from './types.js';\n\nexport interface PullRequest {\n readonly number: number;\n readonly url: string | null;\n}\n\n/**\n * Why the files a round changed are unknown. `git`: there was no git to ask.\n * `revision`: no commit was named and no base configured, so no diff was\n * read. `merged`: the commit is already in the base branch, so the diff from\n * their merge base is empty whatever the round changed - archiving on the base\n * branch after the merge.\n */\nexport type Unmeasured =\n | { readonly reason: 'git' }\n | { readonly reason: 'revision' }\n | { readonly reason: 'merged'; readonly base: string; readonly commit: string };\n\nexport interface ArchiveRequest {\n /** `YYYY-MM-DD`. */\n readonly date: string;\n readonly summary?: string | undefined;\n readonly pr?: PullRequest | undefined;\n readonly commit?: CommitInfo | undefined;\n /** The files the round changed, when they were read. */\n readonly changes?: readonly FileChange[] | undefined;\n /**\n * Why `changes` is missing, when the caller knows. Without changes the\n * scope checks have nothing to check, and a plan that said nothing would\n * read as a round that stayed in scope.\n */\n readonly unmeasured?: Unmeasured | undefined;\n /** Uncommitted paths, when the working tree was read. */\n readonly dirty?: readonly string[] | undefined;\n readonly allowDirty?: boolean | undefined;\n /** Out-of-scope changes refuse the archival instead of warning. */\n readonly strict?: boolean | undefined;\n /** The corpus's lint findings; errors on the brief refuse the archival. */\n readonly findings?: readonly Finding[] | undefined;\n}\n\nexport type FileOp =\n | {\n readonly kind: 'write';\n readonly path: string;\n readonly content: string;\n /** What the file holds when the plan is made; `null` for a file that must not exist yet. */\n readonly before: string | null;\n }\n | { readonly kind: 'remove'; readonly path: string; readonly before: string };\n\nexport interface Plan {\n readonly action: 'archive' | 'unarchive';\n readonly brief: Brief;\n readonly from: string;\n readonly to: string;\n /** The brief is already where this action would put it. */\n readonly done: boolean;\n readonly ops: readonly FileOp[];\n /** Why the action is refused; empty when it may proceed. */\n readonly blocking: readonly Finding[];\n readonly warnings: readonly Finding[];\n readonly linksRewritten: number;\n readonly inboundRewritten: readonly string[];\n /** Links archived briefs hold to the old path. They are frozen and left as they are. */\n readonly inboundFrozen: readonly { readonly file: string; readonly line: number }[];\n readonly banner: readonly string[];\n readonly changes: readonly FileChange[];\n}\n\nfunction problem(\n brief: Brief,\n rule: string,\n severity: Severity,\n line: number,\n message: string,\n hint?: string,\n): Finding {\n return { rule, severity, message, file: brief.file, line, brief: brief.id ?? undefined, hint };\n}\n\nfunction lineOf(brief: Brief, key: string): number {\n return lineOfField(brief, key) + 1;\n}\n\nfunction protectedFinding(brief: Brief, path: string): Finding {\n return {\n ...problem(brief, 'protected-file', 'error', lineOf(brief, 'protectedFiles'), `the round changed ${path}, which this brief protects`, 'revert the change, or record the departure in the brief before archiving it'),\n path,\n };\n}\n\n/**\n * The files a round changed outside its scope: one finding, since the scope\n * is one defect however many files show it - widened once, or explained once.\n */\nfunction outOfScopeFinding(brief: Brief, paths: readonly string[], strict: boolean): Finding {\n return {\n ...problem(\n brief,\n 'out-of-scope',\n strict ? 'error' : 'warning',\n lineOf(brief, 'affectedFiles'),\n `the round changed ${paths.length} file(s) outside affectedFiles: ${listed(paths)}`,\n 'widen affectedFiles if the scope was wrong, or say why in the summary',\n ),\n paths,\n };\n}\n\n/** Where an archive rule's finding goes: an error refuses, and under --strict a warning refuses as an error, as out-of-scope does. */\nfunction place(finding: Finding, strict: boolean, blocking: Finding[], warnings: Finding[]): void {\n if (finding.severity === 'error') blocking.push(finding);\n else if (strict && finding.severity === 'warning') blocking.push({ ...finding, severity: 'error' });\n else warnings.push(finding);\n}\n\nconst UNMEASURED: Readonly<Record<Unmeasured['reason'], { readonly why: string; readonly hint: string }>> = {\n git: {\n why: 'without git, the files the round changed cannot be read',\n hint: 'archive inside the git work tree without --no-git, and name the round with --commit <rev> or --base <rev>',\n },\n revision: {\n why: 'no commit or base was named, so the files the round changed were not read',\n hint: 'pass --commit <rev> for the commit the round landed as, or --base <rev> for the branch it started from, or set \"archiving.base\"',\n },\n merged: {\n why: 'the diff from their merge base is empty',\n hint: \"archive on the round's branch before it merges, or pass --base <rev> naming the commit the round started from\",\n },\n};\n\n/**\n * The finding for scope checks that had nothing to check. A brief with no\n * scope has nothing to check either way, so it gets none.\n */\nfunction unmeasuredScope(brief: Brief, request: ArchiveRequest, severity: Severity): Finding | null {\n const unmeasured = request.unmeasured ?? (request.changes === undefined ? { reason: 'revision' as const } : undefined);\n const fields = [\n ...(brief.protectedFiles.length > 0 ? ['protectedFiles'] : []),\n ...(brief.affectedFiles.length > 0 ? ['affectedFiles'] : []),\n ];\n if (unmeasured === undefined || fields.length === 0) return null;\n const { why, hint } = UNMEASURED[unmeasured.reason];\n const cause = unmeasured.reason === 'merged' ? `${unmeasured.commit.slice(0, 7)} is already in ${unmeasured.base}, so ${why}` : why;\n return problem(brief, 'scope-unmeasured', severity, lineOf(brief, fields[0] as string), `${fields.join(' and ')} went unchecked: ${cause}`, hint);\n}\n\n/**\n * Scope patterns, for matching the files a round changed. A changed path is a\n * file that exists or existed, so a literal pattern is read as either: its own\n * path, and everything beneath it. Nothing can lie beneath a file, so the\n * reading cannot invent a match, and the tree need not be asked - it has\n * changed since the scope was written, by the round itself.\n */\nfunction globs(patterns: readonly string[]): Glob[] {\n return patterns.flatMap((p) => {\n const parsed = parseGlob(p, { literal: 'either' });\n return parsed.ok ? [parsed.glob] : [];\n });\n}\n\n/** A list marker opening a line, after its indentation. */\nconst ITEM_START = /^[ \\t]*(?:[-*+]|\\d{1,9}[.)])[ \\t]+/;\n\n/** A line that ends a sentence: a stop, then only closing marks. */\nconst SENTENCE_END = /[.!?][)\\]*_\"'`]*[ \\t]*$/;\n\n/** Where a note's text starts: after its indentation and a list marker, if it has one. */\nconst NOTE_START = /^[ \\t]*(?:(?:[-*+]|\\d{1,9}[.)])[ \\t]+)?/;\n\n/**\n * Whether an open box carries a note that closes it: a line under the box,\n * within its item, that starts with a disposition marker and starts a note -\n * a bullet, \"- **Delegated to** 012\", a paragraph after a blank line, or a\n * line after one that ends a sentence, \"... did not.\" over \"**Rejected** by\n * ...\", which is how notes are written where they are written most. A line\n * that continues a sentence is not one: \"- [ ] Explain why the\" over\n * \"**Rejected** designs failed\" is the box's own sentence wrapping, and an\n * open task, as a marker in the middle of the box's line is a word in it. The note belongs to the box above\n * it, so the search stops at the first nested box: a child's note does not\n * close its parent.\n *\n * Where the note starts is read from the lines as written, and the marker from\n * the masked line, so a marker in code or a comment, or one after a code span,\n * closes nothing.\n */\nfunction dispositioned(scanned: Scan, line: number, end: number, markers: readonly string[]): boolean {\n const boxes = new Set(scanned.tasks.map((t) => t.line));\n for (let i = line + 1; i < end; i += 1) {\n if (boxes.has(i)) return false;\n const written = scanned.lines[i] as string;\n const above = scanned.lines[i - 1] as string;\n const startsNote = ITEM_START.test(written) || above.trim() === '' || SENTENCE_END.test(above);\n if (!startsNote) continue;\n const start = (NOTE_START.exec(written) as RegExpExecArray)[0].length;\n const text = scanned.masked[i] as string;\n if (markers.some((marker) => text.startsWith(marker, start))) return true;\n }\n return false;\n}\n\n/** A path inside a directory that may be the root itself. */\nfunction within(directory: string, name: string): string {\n return directory === '' ? name : `${directory}/${name}`;\n}\n\n/**\n * Whether a path is a brief: a file directly in the live or archive directory\n * whose name the configuration takes for a brief. Edits to briefs are the\n * ceremony, not the round, and are left out of the scope and tree checks.\n * Nothing else in those directories is: a README beside the briefs is work.\n */\nfunction isBriefPath(path: string, config: Config): boolean {\n const directory = dirOf(path);\n if (directory !== normalisePath(config.briefs) && directory !== normalisePath(config.archive)) return false;\n const name = path.slice(path.lastIndexOf('/') + 1);\n const accept = parseGlob(config.files);\n const excluded = config.exclude.some((e) => {\n const parsed = parseGlob(e);\n return parsed.ok && matchGlob(parsed.glob, name);\n });\n return accept.ok && matchGlob(accept.glob, name) && !excluded;\n}\n\n/** Task items that must be closed before a brief can be archived. */\nexport function openTasks(brief: Brief, corpus: Corpus): Brief['tasks'] {\n const { tasks: scope, dispositions } = corpus.config.archiving;\n const inScope =\n scope === 'all'\n ? brief.tasks\n : brief.tasks.filter((task) =>\n brief.sections.some(\n (s) => task.line > s.heading.line && task.line < s.end && scope.some((name) => labelMatches(s.heading.text, name)),\n ),\n );\n return inScope.filter((task) => !task.checked && !dispositioned(brief.scan, task.line, task.end, dispositions));\n}\n\nfunction diffstat(changes: readonly FileChange[]): string {\n const insertions = changes.reduce((sum, c) => sum + (c.insertions ?? 0), 0);\n const deletions = changes.reduce((sum, c) => sum + (c.deletions ?? 0), 0);\n const files = changes.length === 1 ? '1 file changed' : `${changes.length} files changed`;\n return `${files}, +${insertions} \\u2212${deletions}`;\n}\n\n/**\n * The banner's lines, markers included. A template line whose placeholders\n * are not all filled is left out, so a round with no pull request has no\n * pull-request sentence rather than a sentence with a hole in it. A template\n * with nothing left writes no banner at all, markers included.\n */\nexport function renderBanner(template: readonly string[], values: Readonly<Record<string, string>>): string[] {\n const kept = template\n .filter((line) => templateHoles(line).every((hole) => (values[hole] ?? '') !== ''))\n .map((line) => fillTemplate(line, values));\n if (kept.length === 0) return [];\n return [BANNER_OPEN, ...kept.map((line) => (line === '' ? '>' : `> ${line}`)), BANNER_CLOSE];\n}\n\n/**\n * Removes a banner block and the blank line written after it. The exact\n * reverse of {@link withBanner}, so reopening a brief leaves its blank lines\n * as they were.\n */\nfunction withoutBanner(lines: readonly string[], banner: Brief['banner']): string[] {\n if (banner === null) return [...lines];\n const after = lines[banner.end]?.trim() === '' ? banner.end + 1 : banner.end;\n return [...lines.slice(0, banner.start), ...lines.slice(after)];\n}\n\n/**\n * Inserts a banner under the front matter, after the blank line that usually\n * follows it, and writes one blank line after the banner.\n */\nfunction withBanner(lines: readonly string[], banner: readonly string[]): string[] {\n const frontMatter = readFrontMatter(lines);\n let at = frontMatter === null || frontMatter.close < 0 ? 0 : frontMatter.close + 1;\n if (at > 0 && lines[at]?.trim() === '') at += 1;\n return [...lines.slice(0, at), ...banner, '', ...lines.slice(at)];\n}\n\n/**\n * Sets the integrity field to the hash of everything else. The field is put in\n * place before hashing, so the lines hashed are exactly the lines a later\n * check sees once it takes the field back out.\n */\nfunction withIntegrity(lines: readonly string[]): string[] {\n const placed = setEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD, 'pending');\n const hash = integrityOf(joinLines(placed, '\\n'));\n return setEntry(placed, readFrontMatter(placed), INTEGRITY_FIELD, hash);\n}\n\n/**\n * Whether a plan can write into the brief's front matter: it has none, or one\n * closed and written in YAML. One never closed, or TOML, cannot be written\n * into. The front-matter rule reports both, and a plan that `edits` the front\n * matter and is not refused by it already - the rule lowered, or a reopening,\n * which lint does not gate - is refused here. It leaves the front matter as\n * written rather than half edited.\n */\nfunction writable(brief: Brief, edits: boolean, blocking: Finding[]): boolean {\n const frontMatter = brief.frontMatter;\n if (frontMatter === null || editable(frontMatter)) return true;\n if (edits && !blocking.some((f) => f.rule === 'front-matter')) {\n const open = frontMatter.close < 0;\n blocking.push(\n problem(\n brief,\n 'front-matter',\n 'error',\n 1,\n open ? 'the front matter is never closed, so it cannot be written into' : 'the front matter is TOML, which spec-brief does not write',\n open ? 'close the front matter with a \"---\" line' : 'write the front matter as YAML between \"---\" lines',\n ),\n );\n }\n return false;\n}\n\ninterface Place {\n readonly file: string;\n readonly line: number;\n}\n\ninterface Inbound {\n readonly ops: FileOp[];\n readonly rewritten: string[];\n readonly frozen: Place[];\n /** Links live briefs hold to it, left as they are because rewriting is off. */\n readonly stale: Place[];\n}\n\n/**\n * Links other briefs hold to a file that is moving: rewritten in live briefs,\n * reported in frozen ones. `archiving.rewriteLinks` governs these as it does\n * the moving brief's own links, so with it off a live brief is not written,\n * and the links it is left holding are reported instead.\n */\nfunction inbound(corpus: Corpus, moving: Brief, to: string): Inbound {\n const result: Inbound = { ops: [], rewritten: [], frozen: [], stale: [] };\n for (const other of corpus.briefs) {\n if (other === moving) continue;\n const directory = dirOf(other.file);\n const lines = linesLinkingTo(other.scan, directory, moving.file);\n if (lines.length === 0) continue;\n if (other.phase === 'archived' || !corpus.config.archiving.rewriteLinks) {\n const left = other.phase === 'archived' ? result.frozen : result.stale;\n for (const line of lines) left.push({ file: other.file, line: line + 1 });\n continue;\n }\n const rewritten = rewriteLinks(other.scan, directory, directory, (p) => (p === moving.file ? to : p));\n result.ops.push({ kind: 'write', path: other.file, content: encodeLike(other.source, rewritten.lines), before: other.source });\n result.rewritten.push(other.file);\n }\n return result;\n}\n\n/** Five items, and a count of the rest. */\nfunction listed(items: readonly string[]): string {\n return items.slice(0, 5).join(', ') + (items.length > 5 ? `, and ${items.length - 5} more` : '');\n}\n\n/**\n * Links live briefs will hold to nothing once the brief moves, when rewriting\n * is off: the move is what breaks them, so the move says where they are.\n */\nfunction staleLinks(corpus: Corpus, brief: Brief, stale: readonly Place[], strict: boolean, blocking: Finding[], warnings: Finding[]): void {\n const severity = configuredSeverity(corpus, 'stale-link');\n if (severity === null || stale.length === 0) return;\n const finding = problem(\n brief,\n 'stale-link',\n severity,\n 1,\n `links on ${stale.length} line(s) of other live briefs will stop resolving when it moves: ${listed(stale.map((s) => `${s.file}:${s.line}`))}`,\n 'turn \"archiving.rewriteLinks\" on to have them rewritten, or fix them by hand',\n );\n place(finding, strict, blocking, warnings);\n}\n\nfunction donePlan(action: Plan['action'], brief: Brief): Plan {\n return {\n action,\n brief,\n from: brief.file,\n to: brief.file,\n done: true,\n ops: [],\n blocking: [],\n warnings: [],\n linksRewritten: 0,\n inboundRewritten: [],\n inboundFrozen: [],\n banner: [],\n changes: [],\n };\n}\n\nexport function planArchive(corpus: Corpus, brief: Brief, request: ArchiveRequest): Plan {\n if (brief.phase === 'archived') return donePlan('archive', brief);\n const { config } = corpus;\n const archiveDir = normalisePath(config.archive);\n const to = within(archiveDir, brief.name);\n const blocking: Finding[] = [];\n const warnings: Finding[] = [];\n\n // Under --strict a warning is an error, here as in lint.\n for (const finding of request.findings ?? []) {\n const refuses = finding.severity === 'error' || (request.strict === true && finding.severity === 'warning');\n if (finding.file === brief.file && refuses) blocking.push(finding);\n }\n // Only a status field can say \"draft\" or \"deferred\", so there is one to point at.\n const field = config.status.field as string;\n if (brief.status === 'draft') {\n blocking.push(\n problem(brief, 'archive-draft', 'error', lineOf(brief, field), 'is a draft, and a draft has not been executed', `set \"${field}: ${config.status.active}\" once the round runs`),\n );\n }\n if (brief.status === 'deferred') {\n blocking.push(\n problem(\n brief,\n 'archive-deferred',\n 'error',\n lineOf(brief, field),\n 'is deferred, and deferred work has not been executed',\n `set \"${field}: ${config.status.active}\" when its trigger fires and the round runs`,\n ),\n );\n }\n for (const task of openTasks(brief, corpus)) {\n blocking.push(\n problem(\n brief,\n 'open-task',\n 'error',\n task.line + 1,\n `\"${task.text === '' ? '(empty)' : task.text}\" is neither ticked nor dispositioned`,\n `tick it, or say why under it, as a bullet or as a paragraph after a blank line, starting ${config.archiving.dispositions.map((d) => `\"${d}\"`).join(', ')}`,\n ),\n );\n }\n for (const dependency of brief.dependsOn) {\n const target = resolveDependency(corpus, dependency);\n if (target !== undefined && target !== brief && target.phase === 'live') {\n blocking.push(\n problem(brief, 'dependency-open', 'error', lineOf(brief, 'dependsOn'), `depends on ${target.id ?? target.name}, which is not archived yet`, `archive ${target.id ?? target.name} first, or drop the dependency if it was never real`),\n );\n }\n }\n const bookkeeping = (path: string): boolean => isBriefPath(path, config);\n if (request.dirty !== undefined && request.allowDirty !== true) {\n const outside = request.dirty.filter((path) => !bookkeeping(path));\n if (outside.length > 0) {\n blocking.push(\n problem(brief, 'dirty-tree', 'error', 1, `the working tree has uncommitted changes outside the briefs: ${listed(outside)}`, 'commit them so the recorded commit holds the round, or pass --allow-dirty'),\n );\n }\n }\n\n const work = (request.changes ?? []).filter((change) => !bookkeeping(change.path));\n const protectedGlobs = globs(brief.protectedFiles);\n const affectedGlobs = globs(brief.affectedFiles);\n // Each protected file changed is its own defect - reverted, or ruled on,\n // one file at a time - so each is a finding that names its path.\n for (const change of work) {\n if (protectedGlobs.some((g) => matchGlob(g, change.path))) blocking.push(protectedFinding(brief, change.path));\n }\n // A protected file is reported once, as protected, and not again as out of scope.\n const unprotected = work.filter((c) => !protectedGlobs.some((g) => matchGlob(g, c.path)));\n const outside = affectedGlobs.length === 0 ? [] : unprotected.filter((c) => !affectedGlobs.some((g) => matchGlob(g, c.path)));\n if (outside.length > 0) {\n const finding = outOfScopeFinding(brief, outside.map((c) => c.path), request.strict === true);\n (request.strict === true ? blocking : warnings).push(finding);\n }\n const scopeSeverity = configuredSeverity(corpus, 'scope-unmeasured');\n const unmeasured = scopeSeverity === null ? null : unmeasuredScope(brief, request, scopeSeverity);\n if (unmeasured !== null) place(unmeasured, request.strict === true, blocking, warnings);\n if (corpus.briefs.some((b) => b.file === to)) {\n blocking.push(problem(brief, 'archive-exists', 'error', 1, `${to} already exists`, 'an archived brief is never overwritten'));\n }\n\n let lines: string[] = [...brief.lines];\n let linksRewritten = 0;\n if (config.archiving.rewriteLinks) {\n const rewritten = rewriteLinks(brief.scan, dirOf(brief.file), archiveDir, (p) => (p === brief.file ? to : p));\n lines = rewritten.lines;\n linksRewritten = rewritten.count;\n }\n lines = withoutBanner(lines, brief.banner);\n\n const pr = request.pr;\n const values: Record<string, string> = {\n date: request.date,\n summary: request.summary?.trim() ?? '',\n pr: pr === undefined ? '' : pr.url === null ? `#${pr.number}` : `[#${pr.number}](${pr.url})`,\n commit: request.commit?.sha.slice(0, 7) ?? '',\n diffstat: request.commit === undefined || request.changes === undefined ? '' : diffstat(request.changes),\n links:\n linksRewritten === 0\n ? ''\n : `Relative links were rewritten to resolve from \\`${archiveDir}/\\` (${linksRewritten}); no other word changed.`,\n id: brief.id ?? '',\n title: brief.title ?? '',\n author: request.commit?.author ?? '',\n };\n const banner = renderBanner(config.archiving.banner, values);\n if (banner.length > 0) lines = withBanner(lines, banner);\n const writes = writable(brief, config.status.field !== null || config.archiving.freeze, blocking);\n if (writes && config.status.field !== null) {\n lines = setEntry(lines, readFrontMatter(lines), config.status.field, renderScalar(config.status.archived));\n }\n lines = removeEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD);\n if (writes && config.archiving.freeze) lines = withIntegrity(lines);\n\n const incoming = inbound(corpus, brief, to);\n staleLinks(corpus, brief, incoming.stale, request.strict === true, blocking, warnings);\n return {\n action: 'archive',\n brief,\n from: brief.file,\n to,\n done: false,\n ops: [\n { kind: 'write', path: to, content: encodeLike(brief.source, lines), before: null },\n ...incoming.ops,\n { kind: 'remove', path: brief.file, before: brief.source },\n ],\n blocking,\n warnings,\n linksRewritten,\n inboundRewritten: incoming.rewritten,\n inboundFrozen: incoming.frozen,\n banner,\n changes: request.changes ?? [],\n };\n}\n\n/** A plugin's answer to a refusal it may lift: which rule, for which path, and why. */\nexport interface Waiver {\n readonly rule: string;\n readonly path: string;\n readonly reason: string;\n}\n\n/**\n * The refusals a plugin may lift. A tool that verifies a ruling can say a\n * protected file was allowed to change, or that a file outside the scope was;\n * whether a round is done is spec-brief's to say.\n */\nexport const WAIVABLE: readonly string[] = ['protected-file', 'out-of-scope'];\n\n/**\n * The plan with the refusals plugins waived turned into notes. A waiver\n * matches a blocking finding by rule and path: a protected-file finding is\n * replaced by the note, and an out-of-scope finding loses the path and goes\n * when none is left. A waiver that matches nothing changes nothing, and one\n * for any other rule is ignored with a warning, which refuses under --strict\n * as any warning does.\n */\nexport function applyWaivers(plan: Plan, waivers: readonly { readonly plugin: string; readonly waiver: Waiver }[], strict = false): Plan {\n const { brief } = plan;\n const blocking = [...plan.blocking];\n const warnings = [...plan.warnings];\n for (const { plugin, waiver } of waivers) {\n if (!WAIVABLE.includes(waiver.rule)) {\n const ignored = problem(\n brief,\n 'waiver-ignored',\n 'warning',\n 1,\n `${plugin} asked to waive ${waiver.rule} for ${waiver.path}; a plugin may waive only ${WAIVABLE.join(' and ')}`,\n `report it to the plugin's authors: ${waiver.rule} is spec-brief's to decide`,\n );\n place(ignored, strict, blocking, warnings);\n continue;\n }\n const at = blocking.findIndex((f) => f.rule === waiver.rule && (f.path === waiver.path || f.paths?.includes(waiver.path) === true));\n if (at < 0) continue;\n const found = blocking[at] as Finding;\n const rest = (found.paths ?? []).filter((p) => p !== waiver.path);\n blocking.splice(at, 1, ...(rest.length > 0 ? [outOfScopeFinding(brief, rest, true)] : []));\n warnings.push({\n ...problem(brief, 'waived', 'note', found.line, `${plugin} waives ${waiver.rule} for ${waiver.path}: ${waiver.reason}`, 'review the waiver with the round; it stands where the refusal was'),\n path: waiver.path,\n });\n }\n return { ...plan, blocking, warnings };\n}\n\nexport interface UnarchiveRequest {\n /** Warnings refuse the reopening, as they fail lint. */\n readonly strict?: boolean | undefined;\n}\n\n/**\n * Reopens an archived brief: the banner and the freeze come off, the status\n * goes back to the live word, and the links are rewritten for the directory\n * it returns to. What the round wrote stays in history, where it belongs.\n */\nexport function planUnarchive(corpus: Corpus, brief: Brief, request: UnarchiveRequest = {}): Plan {\n if (brief.phase === 'live') return donePlan('unarchive', brief);\n const { config } = corpus;\n const briefsDir = normalisePath(config.briefs);\n const to = within(briefsDir, brief.name);\n const blocking: Finding[] = [];\n const warnings: Finding[] = [];\n if (corpus.briefs.some((b) => b.file === to)) {\n blocking.push(problem(brief, 'unarchive-exists', 'error', 1, `${to} already exists`, 'rename one of them first'));\n }\n\n let lines: string[] = [...brief.lines];\n let linksRewritten = 0;\n if (config.archiving.rewriteLinks) {\n const rewritten = rewriteLinks(brief.scan, dirOf(brief.file), briefsDir, (p) => (p === brief.file ? to : p));\n lines = rewritten.lines;\n linksRewritten = rewritten.count;\n }\n lines = withoutBanner(lines, brief.banner);\n lines = removeEntry(lines, readFrontMatter(lines), INTEGRITY_FIELD);\n const field = config.status.field;\n if (writable(brief, field !== null, blocking) && field !== null) {\n lines = setEntry(lines, readFrontMatter(lines), field, renderScalar(config.status.active));\n }\n // Archival gives a brief with no front matter a block to hold its hash.\n // A block this reopening emptied goes too, and the brief is the one that\n // was archived. One that was empty before it was archived reads the same\n // once the hash is in it, and goes with it; one empty in the archive stays.\n const left = readFrontMatter(lines);\n const emptied = left !== null && left.kind === 'yaml' && left.close === 1 && (brief.frontMatter?.close ?? 0) > 1;\n if (emptied) lines = lines.slice(2);\n\n const incoming = inbound(corpus, brief, to);\n staleLinks(corpus, brief, incoming.stale, request.strict === true, blocking, warnings);\n return {\n action: 'unarchive',\n brief,\n from: brief.file,\n to,\n done: false,\n ops: [\n { kind: 'write', path: to, content: encodeLike(brief.source, lines), before: null },\n ...incoming.ops,\n { kind: 'remove', path: brief.file, before: brief.source },\n ],\n blocking,\n warnings,\n linksRewritten,\n inboundRewritten: incoming.rewritten,\n inboundFrozen: incoming.frozen,\n banner: [],\n changes: [],\n };\n}\n"]}
package/dist/brief.d.ts CHANGED
@@ -43,6 +43,8 @@ export interface Brief {
43
43
  readonly dependsOn: readonly string[];
44
44
  readonly affectedFiles: readonly string[];
45
45
  readonly protectedFiles: readonly string[];
46
+ /** For deferred work: the observable event that brings it back. */
47
+ readonly trigger: string | null;
46
48
  readonly integrity: string | null;
47
49
  /** The frozen banner's lines, markers included, end exclusive. */
48
50
  readonly banner: {
package/dist/brief.js CHANGED
@@ -18,6 +18,7 @@ export const BUILT_IN_FIELDS = [
18
18
  'dependsOn',
19
19
  'affectedFiles',
20
20
  'protectedFiles',
21
+ 'trigger',
21
22
  'integrity',
22
23
  ];
23
24
  function valueText(value) {
@@ -55,6 +56,8 @@ function statusFrom(word, config, phase) {
55
56
  return 'active';
56
57
  if (config.status.draft !== null && lower === config.status.draft.toLowerCase())
57
58
  return 'draft';
59
+ if (config.status.deferred !== null && lower === config.status.deferred.toLowerCase())
60
+ return 'deferred';
58
61
  return null;
59
62
  }
60
63
  export function parseBrief(file, text, config, phase) {
@@ -62,7 +65,7 @@ export function parseBrief(file, text, config, phase) {
62
65
  const lines = splitLines(content);
63
66
  const frontMatter = readFrontMatter(lines);
64
67
  const bodyStart = frontMatter === null || frontMatter.close < 0 ? 0 : frontMatter.close + 1;
65
- const scanned = scan(lines, bodyStart);
68
+ const scanned = scan(lines);
66
69
  const problems = [];
67
70
  const name = file.slice(file.lastIndexOf('/') + 1);
68
71
  const text1 = (field) => {
@@ -133,6 +136,7 @@ export function parseBrief(file, text, config, phase) {
133
136
  dependsOn: list('dependsOn'),
134
137
  affectedFiles: list('affectedFiles'),
135
138
  protectedFiles: list('protectedFiles'),
139
+ trigger: text1('trigger'),
136
140
  integrity: text1('integrity'),
137
141
  banner,
138
142
  problems,