@descent-vtt/spec-brief 0.1.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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +269 -0
- package/bin/spec-brief.js +19 -0
- package/dist/apply.d.ts +26 -0
- package/dist/apply.js +71 -0
- package/dist/apply.js.map +1 -0
- package/dist/archive.d.ts +81 -0
- package/dist/archive.js +333 -0
- package/dist/archive.js.map +1 -0
- package/dist/brief.d.ts +60 -0
- package/dist/brief.js +152 -0
- package/dist/brief.js.map +1 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.js +411 -0
- package/dist/cli.js.map +1 -0
- package/dist/collisions.d.ts +50 -0
- package/dist/collisions.js +127 -0
- package/dist/collisions.js.map +1 -0
- package/dist/config.d.ts +94 -0
- package/dist/config.js +353 -0
- package/dist/config.js.map +1 -0
- package/dist/corpus.d.ts +41 -0
- package/dist/corpus.js +154 -0
- package/dist/corpus.js.map +1 -0
- package/dist/engine.d.ts +121 -0
- package/dist/engine.js +276 -0
- package/dist/engine.js.map +1 -0
- package/dist/frontmatter.d.ts +68 -0
- package/dist/frontmatter.js +311 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/fs.d.ts +59 -0
- package/dist/fs.js +189 -0
- package/dist/fs.js.map +1 -0
- package/dist/git.d.ts +59 -0
- package/dist/git.js +131 -0
- package/dist/git.js.map +1 -0
- package/dist/glob.d.ts +79 -0
- package/dist/glob.js +465 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/integrity.d.ts +11 -0
- package/dist/integrity.js +20 -0
- package/dist/integrity.js.map +1 -0
- package/dist/links.d.ts +38 -0
- package/dist/links.js +142 -0
- package/dist/links.js.map +1 -0
- package/dist/lint.d.ts +38 -0
- package/dist/lint.js +90 -0
- package/dist/lint.js.map +1 -0
- package/dist/markdown.d.ts +65 -0
- package/dist/markdown.js +274 -0
- package/dist/markdown.js.map +1 -0
- package/dist/plugins.d.ts +16 -0
- package/dist/plugins.js +77 -0
- package/dist/plugins.js.map +1 -0
- package/dist/report.d.ts +38 -0
- package/dist/report.js +244 -0
- package/dist/report.js.map +1 -0
- package/dist/rules.d.ts +58 -0
- package/dist/rules.js +448 -0
- package/dist/rules.js.map +1 -0
- package/dist/scaffold.d.ts +25 -0
- package/dist/scaffold.js +81 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/schema.d.ts +47 -0
- package/dist/schema.js +195 -0
- package/dist/schema.js.map +1 -0
- package/dist/text.d.ts +40 -0
- package/dist/text.js +95 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
- package/schema.json +321 -0
package/dist/report.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering: findings, lists, the collision matrix and plans, for a person at
|
|
3
|
+
* a terminal or for the machine reading the job.
|
|
4
|
+
*
|
|
5
|
+
* `json` is a document with a version, for orchestrators. `sarif` is SARIF
|
|
6
|
+
* 2.1.0, for code-scanning upload. `github` is workflow commands, which put a
|
|
7
|
+
* finding on the line of the pull request with no upload and no permission.
|
|
8
|
+
*/
|
|
9
|
+
import { summarise } from './lint.js';
|
|
10
|
+
import { COLLISION_RULES, RULES } from './rules.js';
|
|
11
|
+
export const FORMATS = ['pretty', 'json', 'sarif', 'github'];
|
|
12
|
+
/** The version of the JSON documents this tool prints. Bumped when a field changes meaning. */
|
|
13
|
+
export const JSON_SCHEMA_VERSION = 1;
|
|
14
|
+
const CODES = {
|
|
15
|
+
red: [31, 39],
|
|
16
|
+
yellow: [33, 39],
|
|
17
|
+
cyan: [36, 39],
|
|
18
|
+
green: [32, 39],
|
|
19
|
+
dim: [2, 22],
|
|
20
|
+
bold: [1, 22],
|
|
21
|
+
};
|
|
22
|
+
export function paint(style, color, text) {
|
|
23
|
+
const code = CODES[color];
|
|
24
|
+
return style.color && code !== undefined ? `\u001b[${code[0]}m${text}\u001b[${code[1]}m` : text;
|
|
25
|
+
}
|
|
26
|
+
const SEVERITY_COLOR = { error: 'red', warning: 'yellow', note: 'cyan' };
|
|
27
|
+
function plural(count, word) {
|
|
28
|
+
return `${count} ${word}${count === 1 ? '' : 's'}`;
|
|
29
|
+
}
|
|
30
|
+
export function summaryLine(findings) {
|
|
31
|
+
const s = summarise(findings);
|
|
32
|
+
return `${plural(s.errors, 'error')}, ${plural(s.warnings, 'warning')}, ${plural(s.notes, 'note')}`;
|
|
33
|
+
}
|
|
34
|
+
export function prettyFindings(findings, style) {
|
|
35
|
+
const out = [];
|
|
36
|
+
let file;
|
|
37
|
+
const width = Math.max(1, ...findings.map((f) => String(f.line).length));
|
|
38
|
+
for (const f of findings) {
|
|
39
|
+
if (f.file !== file) {
|
|
40
|
+
if (file !== undefined)
|
|
41
|
+
out.push('');
|
|
42
|
+
out.push(paint(style, 'bold', f.file));
|
|
43
|
+
file = f.file;
|
|
44
|
+
}
|
|
45
|
+
const severity = paint(style, SEVERITY_COLOR[f.severity], f.severity.padEnd(7));
|
|
46
|
+
out.push(` ${String(f.line).padStart(width)} ${severity} ${f.message} ${paint(style, 'dim', f.rule)}`);
|
|
47
|
+
if (f.hint !== undefined)
|
|
48
|
+
out.push(` ${' '.repeat(width)} ${paint(style, 'dim', f.hint)}`);
|
|
49
|
+
}
|
|
50
|
+
return out.join('\n');
|
|
51
|
+
}
|
|
52
|
+
export function jsonDocument(command, version, body) {
|
|
53
|
+
return `${JSON.stringify({ tool: 'spec-brief', version, schemaVersion: JSON_SCHEMA_VERSION, command, ...body }, null, 2)}\n`;
|
|
54
|
+
}
|
|
55
|
+
export function findingJson(f) {
|
|
56
|
+
const out = { rule: f.rule, severity: f.severity, file: f.file, line: f.line, message: f.message };
|
|
57
|
+
if (f.brief !== undefined)
|
|
58
|
+
out['brief'] = f.brief;
|
|
59
|
+
if (f.hint !== undefined)
|
|
60
|
+
out['hint'] = f.hint;
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
const SARIF_LEVEL = { error: 'error', warning: 'warning', note: 'note' };
|
|
64
|
+
export function sarif(findings, version) {
|
|
65
|
+
const described = new Map([...RULES, ...COLLISION_RULES].map((r) => [r.id, r]));
|
|
66
|
+
const ids = [...new Set(findings.map((f) => f.rule))].sort();
|
|
67
|
+
const document = {
|
|
68
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
69
|
+
version: '2.1.0',
|
|
70
|
+
runs: [
|
|
71
|
+
{
|
|
72
|
+
tool: {
|
|
73
|
+
driver: {
|
|
74
|
+
name: 'spec-brief',
|
|
75
|
+
version,
|
|
76
|
+
informationUri: 'https://github.com/DescentVTT/spec-brief',
|
|
77
|
+
rules: ids.map((id) => {
|
|
78
|
+
const rule = described.get(id);
|
|
79
|
+
return {
|
|
80
|
+
id,
|
|
81
|
+
shortDescription: { text: rule?.description ?? id },
|
|
82
|
+
...(rule !== undefined && rule.severity !== 'off'
|
|
83
|
+
? { defaultConfiguration: { level: SARIF_LEVEL[rule.severity] } }
|
|
84
|
+
: {}),
|
|
85
|
+
};
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
results: findings.map((f) => ({
|
|
90
|
+
ruleId: f.rule,
|
|
91
|
+
level: SARIF_LEVEL[f.severity],
|
|
92
|
+
message: { text: f.hint === undefined ? f.message : `${f.message}. ${f.hint}` },
|
|
93
|
+
locations: [
|
|
94
|
+
{
|
|
95
|
+
physicalLocation: {
|
|
96
|
+
artifactLocation: { uri: f.file, uriBaseId: '%SRCROOT%' },
|
|
97
|
+
region: { startLine: f.line },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
})),
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
return `${JSON.stringify(document, null, 2)}\n`;
|
|
106
|
+
}
|
|
107
|
+
function escapeData(text) {
|
|
108
|
+
return text.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
|
|
109
|
+
}
|
|
110
|
+
function escapeProperty(text) {
|
|
111
|
+
return escapeData(text).replace(/:/g, '%3A').replace(/,/g, '%2C');
|
|
112
|
+
}
|
|
113
|
+
const GITHUB_COMMAND = { error: 'error', warning: 'warning', note: 'notice' };
|
|
114
|
+
export function githubCommands(findings) {
|
|
115
|
+
return findings
|
|
116
|
+
.map((f) => {
|
|
117
|
+
const message = f.hint === undefined ? f.message : `${f.message}. ${f.hint}`;
|
|
118
|
+
const properties = `file=${escapeProperty(f.file)},line=${f.line},title=${escapeProperty(`spec-brief ${f.rule}`)}`;
|
|
119
|
+
return `::${GITHUB_COMMAND[f.severity]} ${properties}::${escapeData(message)}\n`;
|
|
120
|
+
})
|
|
121
|
+
.join('');
|
|
122
|
+
}
|
|
123
|
+
export function briefJson(brief, extra = {}) {
|
|
124
|
+
return {
|
|
125
|
+
id: brief.id,
|
|
126
|
+
file: brief.file,
|
|
127
|
+
title: brief.title,
|
|
128
|
+
phase: brief.phase,
|
|
129
|
+
status: brief.status,
|
|
130
|
+
type: brief.type,
|
|
131
|
+
wave: brief.wave,
|
|
132
|
+
dependsOn: brief.dependsOn,
|
|
133
|
+
affectedFiles: brief.affectedFiles,
|
|
134
|
+
protectedFiles: brief.protectedFiles,
|
|
135
|
+
tasks: { total: brief.tasks.length, checked: brief.tasks.filter((t) => t.checked).length },
|
|
136
|
+
...extra,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Columns padded to their widest cell; every row has the header's length. */
|
|
140
|
+
function table(header, rows) {
|
|
141
|
+
const all = [header, ...rows];
|
|
142
|
+
const widths = header.map((_, c) => Math.max(...all.map((r) => r[c].length)));
|
|
143
|
+
return all.map((r) => r
|
|
144
|
+
.map((cell, c) => (c === r.length - 1 ? cell : cell.padEnd(widths[c])))
|
|
145
|
+
.join(' ')
|
|
146
|
+
.trimEnd());
|
|
147
|
+
}
|
|
148
|
+
export function prettyList(rows, style) {
|
|
149
|
+
if (rows.length === 0)
|
|
150
|
+
return 'no briefs';
|
|
151
|
+
const lines = table(['ID', 'STATUS', 'WAVE', 'TASKS', 'READY', 'TITLE'], rows.map(({ brief, ready, waitingOn }) => [
|
|
152
|
+
brief.id ?? '?',
|
|
153
|
+
brief.status ?? brief.statusWord ?? '?',
|
|
154
|
+
brief.wave === null ? '-' : String(brief.wave),
|
|
155
|
+
`${brief.tasks.filter((t) => t.checked).length}/${brief.tasks.length}`,
|
|
156
|
+
brief.phase === 'archived' ? '-' : ready ? 'yes' : waitingOn.length > 0 ? `after ${waitingOn.join(', ')}` : 'no',
|
|
157
|
+
brief.title ?? brief.name,
|
|
158
|
+
]));
|
|
159
|
+
return [paint(style, 'dim', lines[0]), ...lines.slice(1)].join('\n');
|
|
160
|
+
}
|
|
161
|
+
export function prettyMatrix(report, style) {
|
|
162
|
+
const out = [];
|
|
163
|
+
const label = (b) => b.id ?? b.name;
|
|
164
|
+
for (const wave of report.waves) {
|
|
165
|
+
const title = wave.wave === null ? 'all live briefs' : `wave ${wave.wave}`;
|
|
166
|
+
out.push(paint(style, 'bold', `${title} \u00b7 ${plural(wave.briefs.length, 'brief')}`));
|
|
167
|
+
const names = wave.briefs.map(label);
|
|
168
|
+
const width = Math.max(3, ...names.map((n) => n.length));
|
|
169
|
+
const hits = new Set(wave.collisions.flatMap((c) => [`${label(c.a)}\u0000${label(c.b)}`, `${label(c.b)}\u0000${label(c.a)}`]));
|
|
170
|
+
const near = new Set(wave.shared.flatMap((s) => [`${label(s.a)}\u0000${label(s.b)}`, `${label(s.b)}\u0000${label(s.a)}`]));
|
|
171
|
+
out.push(` ${''.padEnd(width)} ${names.map((n) => n.padStart(width)).join(' ')}`);
|
|
172
|
+
for (const row of names) {
|
|
173
|
+
const cells = names.map((col) => {
|
|
174
|
+
const key = `${row}\u0000${col}`;
|
|
175
|
+
const mark = row === col ? '\u00b7' : hits.has(key) ? paint(style, 'red', 'X') : near.has(key) ? paint(style, 'yellow', '~') : '\u00b7';
|
|
176
|
+
return `${' '.repeat(width - 1)}${mark}`;
|
|
177
|
+
});
|
|
178
|
+
out.push(` ${row.padEnd(width)} ${cells.join(' ')}`);
|
|
179
|
+
}
|
|
180
|
+
for (const c of wave.collisions) {
|
|
181
|
+
out.push(` ${paint(style, 'red', 'X')} ${label(c.a)} "${c.patterns[0]}" and ${label(c.b)} "${c.patterns[1]}" both cover ${c.witness}`);
|
|
182
|
+
}
|
|
183
|
+
for (const s of wave.shared) {
|
|
184
|
+
out.push(` ${paint(style, 'yellow', '~')} ${label(s.a)} and ${label(s.b)} both write into ${s.directory}/`);
|
|
185
|
+
}
|
|
186
|
+
for (const b of wave.unscoped) {
|
|
187
|
+
out.push(` ${paint(style, 'cyan', '?')} ${label(b)} declares no affectedFiles and cannot be checked`);
|
|
188
|
+
}
|
|
189
|
+
out.push('');
|
|
190
|
+
}
|
|
191
|
+
if (report.unscheduled.length > 0) {
|
|
192
|
+
out.push(paint(style, 'dim', `no wave: ${report.unscheduled.map(label).join(', ')}`));
|
|
193
|
+
}
|
|
194
|
+
if (report.waves.length === 0 && report.unscheduled.length === 0)
|
|
195
|
+
out.push('no live briefs');
|
|
196
|
+
return out.join('\n').trimEnd();
|
|
197
|
+
}
|
|
198
|
+
export function prettyPlan(plan, dryRun, style) {
|
|
199
|
+
const out = [];
|
|
200
|
+
const verb = plan.action === 'archive' ? 'archive' : 'unarchive';
|
|
201
|
+
if (plan.done)
|
|
202
|
+
return `${plan.brief.file} is already ${plan.action === 'archive' ? 'archived' : 'live'}; nothing to do`;
|
|
203
|
+
if (plan.blocking.length > 0) {
|
|
204
|
+
out.push(paint(style, 'red', `cannot ${verb} ${plan.brief.file}:`), prettyFindings(plan.blocking, style));
|
|
205
|
+
if (plan.warnings.length > 0)
|
|
206
|
+
out.push('', prettyFindings(plan.warnings, style));
|
|
207
|
+
return out.join('\n');
|
|
208
|
+
}
|
|
209
|
+
out.push(`${dryRun ? 'would move' : 'moved'} ${plan.from} -> ${plan.to}`);
|
|
210
|
+
if (plan.linksRewritten > 0)
|
|
211
|
+
out.push(` ${plural(plan.linksRewritten, 'relative link')} rewritten`);
|
|
212
|
+
for (const file of plan.inboundRewritten)
|
|
213
|
+
out.push(` ${file}: links to it rewritten`);
|
|
214
|
+
for (const f of plan.inboundFrozen)
|
|
215
|
+
out.push(` ${f.file}:${f.line}: links to the old path, and is frozen, so it was left as it is`);
|
|
216
|
+
if (plan.changes.length > 0)
|
|
217
|
+
out.push(` the round changed ${plural(plan.changes.length, 'file')}`);
|
|
218
|
+
if (plan.warnings.length > 0)
|
|
219
|
+
out.push('', prettyFindings(plan.warnings, style));
|
|
220
|
+
if (dryRun && plan.banner.length > 0)
|
|
221
|
+
out.push('', paint(style, 'dim', 'banner:'), ...plan.banner.map((l) => ` ${l}`));
|
|
222
|
+
if (!dryRun)
|
|
223
|
+
out.push('', paint(style, 'dim', 'nothing was committed; review the change and commit it with the round'));
|
|
224
|
+
return out.join('\n');
|
|
225
|
+
}
|
|
226
|
+
export function planJson(plan) {
|
|
227
|
+
return {
|
|
228
|
+
action: plan.action,
|
|
229
|
+
brief: plan.brief.id,
|
|
230
|
+
from: plan.from,
|
|
231
|
+
to: plan.to,
|
|
232
|
+
done: plan.done,
|
|
233
|
+
refused: plan.blocking.length > 0,
|
|
234
|
+
blocking: plan.blocking.map(findingJson),
|
|
235
|
+
warnings: plan.warnings.map(findingJson),
|
|
236
|
+
linksRewritten: plan.linksRewritten,
|
|
237
|
+
inboundRewritten: plan.inboundRewritten,
|
|
238
|
+
inboundFrozen: plan.inboundFrozen,
|
|
239
|
+
operations: plan.ops.map((op) => ({ kind: op.kind, path: op.path })),
|
|
240
|
+
banner: plan.banner,
|
|
241
|
+
changes: plan.changes,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rendering: findings, lists, the collision matrix and plans, for a person at\n * a terminal or for the machine reading the job.\n *\n * `json` is a document with a version, for orchestrators. `sarif` is SARIF\n * 2.1.0, for code-scanning upload. `github` is workflow commands, which put a\n * finding on the line of the pull request with no upload and no permission.\n */\n\nimport type { Plan } from './archive.js';\nimport type { Brief } from './brief.js';\nimport type { CollisionReport } from './collisions.js';\nimport { summarise } from './lint.js';\nimport { COLLISION_RULES, RULES } from './rules.js';\nimport type { Finding, Severity } from './types.js';\n\nexport type Format = 'pretty' | 'json' | 'sarif' | 'github';\n\nexport const FORMATS: readonly Format[] = ['pretty', 'json', 'sarif', 'github'];\n\n/** The version of the JSON documents this tool prints. Bumped when a field changes meaning. */\nexport const JSON_SCHEMA_VERSION = 1;\n\nexport interface Style {\n readonly color: boolean;\n}\n\nconst CODES: Readonly<Record<string, readonly [number, number]>> = {\n red: [31, 39],\n yellow: [33, 39],\n cyan: [36, 39],\n green: [32, 39],\n dim: [2, 22],\n bold: [1, 22],\n};\n\nexport function paint(style: Style, color: keyof typeof CODES, text: string): string {\n const code = CODES[color];\n return style.color && code !== undefined ? `\\u001b[${code[0]}m${text}\\u001b[${code[1]}m` : text;\n}\n\nconst SEVERITY_COLOR: Readonly<Record<Severity, keyof typeof CODES>> = { error: 'red', warning: 'yellow', note: 'cyan' };\n\nfunction plural(count: number, word: string): string {\n return `${count} ${word}${count === 1 ? '' : 's'}`;\n}\n\nexport function summaryLine(findings: readonly Finding[]): string {\n const s = summarise(findings);\n return `${plural(s.errors, 'error')}, ${plural(s.warnings, 'warning')}, ${plural(s.notes, 'note')}`;\n}\n\nexport function prettyFindings(findings: readonly Finding[], style: Style): string {\n const out: string[] = [];\n let file: string | undefined;\n const width = Math.max(1, ...findings.map((f) => String(f.line).length));\n for (const f of findings) {\n if (f.file !== file) {\n if (file !== undefined) out.push('');\n out.push(paint(style, 'bold', f.file));\n file = f.file;\n }\n const severity = paint(style, SEVERITY_COLOR[f.severity], f.severity.padEnd(7));\n out.push(` ${String(f.line).padStart(width)} ${severity} ${f.message} ${paint(style, 'dim', f.rule)}`);\n if (f.hint !== undefined) out.push(` ${' '.repeat(width)} ${paint(style, 'dim', f.hint)}`);\n }\n return out.join('\\n');\n}\n\nexport function jsonDocument(command: string, version: string, body: Record<string, unknown>): string {\n return `${JSON.stringify({ tool: 'spec-brief', version, schemaVersion: JSON_SCHEMA_VERSION, command, ...body }, null, 2)}\\n`;\n}\n\nexport function findingJson(f: Finding): Record<string, unknown> {\n const out: Record<string, unknown> = { rule: f.rule, severity: f.severity, file: f.file, line: f.line, message: f.message };\n if (f.brief !== undefined) out['brief'] = f.brief;\n if (f.hint !== undefined) out['hint'] = f.hint;\n return out;\n}\n\nconst SARIF_LEVEL: Readonly<Record<Severity, string>> = { error: 'error', warning: 'warning', note: 'note' };\n\nexport function sarif(findings: readonly Finding[], version: string): string {\n const described = new Map([...RULES, ...COLLISION_RULES].map((r) => [r.id, r]));\n const ids = [...new Set(findings.map((f) => f.rule))].sort();\n const document = {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [\n {\n tool: {\n driver: {\n name: 'spec-brief',\n version,\n informationUri: 'https://github.com/DescentVTT/spec-brief',\n rules: ids.map((id) => {\n const rule = described.get(id);\n return {\n id,\n shortDescription: { text: rule?.description ?? id },\n ...(rule !== undefined && rule.severity !== 'off'\n ? { defaultConfiguration: { level: SARIF_LEVEL[rule.severity] } }\n : {}),\n };\n }),\n },\n },\n results: findings.map((f) => ({\n ruleId: f.rule,\n level: SARIF_LEVEL[f.severity],\n message: { text: f.hint === undefined ? f.message : `${f.message}. ${f.hint}` },\n locations: [\n {\n physicalLocation: {\n artifactLocation: { uri: f.file, uriBaseId: '%SRCROOT%' },\n region: { startLine: f.line },\n },\n },\n ],\n })),\n },\n ],\n };\n return `${JSON.stringify(document, null, 2)}\\n`;\n}\n\nfunction escapeData(text: string): string {\n return text.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A');\n}\n\nfunction escapeProperty(text: string): string {\n return escapeData(text).replace(/:/g, '%3A').replace(/,/g, '%2C');\n}\n\nconst GITHUB_COMMAND: Readonly<Record<Severity, string>> = { error: 'error', warning: 'warning', note: 'notice' };\n\nexport function githubCommands(findings: readonly Finding[]): string {\n return findings\n .map((f) => {\n const message = f.hint === undefined ? f.message : `${f.message}. ${f.hint}`;\n const properties = `file=${escapeProperty(f.file)},line=${f.line},title=${escapeProperty(`spec-brief ${f.rule}`)}`;\n return `::${GITHUB_COMMAND[f.severity]} ${properties}::${escapeData(message)}\\n`;\n })\n .join('');\n}\n\nexport function briefJson(brief: Brief, extra: Record<string, unknown> = {}): Record<string, unknown> {\n return {\n id: brief.id,\n file: brief.file,\n title: brief.title,\n phase: brief.phase,\n status: brief.status,\n type: brief.type,\n wave: brief.wave,\n dependsOn: brief.dependsOn,\n affectedFiles: brief.affectedFiles,\n protectedFiles: brief.protectedFiles,\n tasks: { total: brief.tasks.length, checked: brief.tasks.filter((t) => t.checked).length },\n ...extra,\n };\n}\n\n/** Columns padded to their widest cell; every row has the header's length. */\nfunction table(header: readonly string[], rows: readonly (readonly string[])[]): string[] {\n const all = [header, ...rows];\n const widths = header.map((_, c) => Math.max(...all.map((r) => (r[c] as string).length)));\n return all.map((r) =>\n r\n .map((cell, c) => (c === r.length - 1 ? cell : cell.padEnd(widths[c] as number)))\n .join(' ')\n .trimEnd(),\n );\n}\n\nexport interface ListRow {\n readonly brief: Brief;\n readonly ready: boolean;\n readonly waitingOn: readonly string[];\n}\n\nexport function prettyList(rows: readonly ListRow[], style: Style): string {\n if (rows.length === 0) return 'no briefs';\n const lines = table(\n ['ID', 'STATUS', 'WAVE', 'TASKS', 'READY', 'TITLE'],\n rows.map(({ brief, ready, waitingOn }) => [\n brief.id ?? '?',\n brief.status ?? brief.statusWord ?? '?',\n brief.wave === null ? '-' : String(brief.wave),\n `${brief.tasks.filter((t) => t.checked).length}/${brief.tasks.length}`,\n brief.phase === 'archived' ? '-' : ready ? 'yes' : waitingOn.length > 0 ? `after ${waitingOn.join(', ')}` : 'no',\n brief.title ?? brief.name,\n ]),\n );\n return [paint(style, 'dim', lines[0] as string), ...lines.slice(1)].join('\\n');\n}\n\nexport function prettyMatrix(report: CollisionReport, style: Style): string {\n const out: string[] = [];\n const label = (b: Brief): string => b.id ?? b.name;\n for (const wave of report.waves) {\n const title = wave.wave === null ? 'all live briefs' : `wave ${wave.wave}`;\n out.push(paint(style, 'bold', `${title} \\u00b7 ${plural(wave.briefs.length, 'brief')}`));\n const names = wave.briefs.map(label);\n const width = Math.max(3, ...names.map((n) => n.length));\n const hits = new Set(wave.collisions.flatMap((c) => [`${label(c.a)}\\u0000${label(c.b)}`, `${label(c.b)}\\u0000${label(c.a)}`]));\n const near = new Set(wave.shared.flatMap((s) => [`${label(s.a)}\\u0000${label(s.b)}`, `${label(s.b)}\\u0000${label(s.a)}`]));\n out.push(` ${''.padEnd(width)} ${names.map((n) => n.padStart(width)).join(' ')}`);\n for (const row of names) {\n const cells = names.map((col) => {\n const key = `${row}\\u0000${col}`;\n const mark = row === col ? '\\u00b7' : hits.has(key) ? paint(style, 'red', 'X') : near.has(key) ? paint(style, 'yellow', '~') : '\\u00b7';\n return `${' '.repeat(width - 1)}${mark}`;\n });\n out.push(` ${row.padEnd(width)} ${cells.join(' ')}`);\n }\n for (const c of wave.collisions) {\n out.push(` ${paint(style, 'red', 'X')} ${label(c.a)} \"${c.patterns[0]}\" and ${label(c.b)} \"${c.patterns[1]}\" both cover ${c.witness}`);\n }\n for (const s of wave.shared) {\n out.push(` ${paint(style, 'yellow', '~')} ${label(s.a)} and ${label(s.b)} both write into ${s.directory}/`);\n }\n for (const b of wave.unscoped) {\n out.push(` ${paint(style, 'cyan', '?')} ${label(b)} declares no affectedFiles and cannot be checked`);\n }\n out.push('');\n }\n if (report.unscheduled.length > 0) {\n out.push(paint(style, 'dim', `no wave: ${report.unscheduled.map(label).join(', ')}`));\n }\n if (report.waves.length === 0 && report.unscheduled.length === 0) out.push('no live briefs');\n return out.join('\\n').trimEnd();\n}\n\nexport function prettyPlan(plan: Plan, dryRun: boolean, style: Style): string {\n const out: string[] = [];\n const verb = plan.action === 'archive' ? 'archive' : 'unarchive';\n if (plan.done) return `${plan.brief.file} is already ${plan.action === 'archive' ? 'archived' : 'live'}; nothing to do`;\n if (plan.blocking.length > 0) {\n out.push(paint(style, 'red', `cannot ${verb} ${plan.brief.file}:`), prettyFindings(plan.blocking, style));\n if (plan.warnings.length > 0) out.push('', prettyFindings(plan.warnings, style));\n return out.join('\\n');\n }\n out.push(`${dryRun ? 'would move' : 'moved'} ${plan.from} -> ${plan.to}`);\n if (plan.linksRewritten > 0) out.push(` ${plural(plan.linksRewritten, 'relative link')} rewritten`);\n for (const file of plan.inboundRewritten) out.push(` ${file}: links to it rewritten`);\n for (const f of plan.inboundFrozen) out.push(` ${f.file}:${f.line}: links to the old path, and is frozen, so it was left as it is`);\n if (plan.changes.length > 0) out.push(` the round changed ${plural(plan.changes.length, 'file')}`);\n if (plan.warnings.length > 0) out.push('', prettyFindings(plan.warnings, style));\n if (dryRun && plan.banner.length > 0) out.push('', paint(style, 'dim', 'banner:'), ...plan.banner.map((l) => ` ${l}`));\n if (!dryRun) out.push('', paint(style, 'dim', 'nothing was committed; review the change and commit it with the round'));\n return out.join('\\n');\n}\n\nexport function planJson(plan: Plan): Record<string, unknown> {\n return {\n action: plan.action,\n brief: plan.brief.id,\n from: plan.from,\n to: plan.to,\n done: plan.done,\n refused: plan.blocking.length > 0,\n blocking: plan.blocking.map(findingJson),\n warnings: plan.warnings.map(findingJson),\n linksRewritten: plan.linksRewritten,\n inboundRewritten: plan.inboundRewritten,\n inboundFrozen: plan.inboundFrozen,\n operations: plan.ops.map((op) => ({ kind: op.kind, path: op.path })),\n banner: plan.banner,\n changes: plan.changes,\n };\n}\n"]}
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in rules. Each is a pure function of one brief and the corpus
|
|
3
|
+
* around it, and returns what it found with 1-based lines.
|
|
4
|
+
*
|
|
5
|
+
* Structure is checked on live briefs only. An archived brief is a frozen
|
|
6
|
+
* record of the tree before a round, and holding it to today's schema would
|
|
7
|
+
* ask somebody to edit what must not be edited; the rules that do reach the
|
|
8
|
+
* archive are about identity and the freeze itself.
|
|
9
|
+
*/
|
|
10
|
+
import { type Brief } from './brief.js';
|
|
11
|
+
import type { Config, SectionRule } from './config.js';
|
|
12
|
+
import { type Corpus } from './corpus.js';
|
|
13
|
+
import { type Section } from './markdown.js';
|
|
14
|
+
import type { Severity, SeveritySetting } from './types.js';
|
|
15
|
+
export interface RuleResult {
|
|
16
|
+
/** 1-based. */
|
|
17
|
+
readonly line: number;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
readonly hint?: string | undefined;
|
|
20
|
+
/** Lower than the rule's severity for this one finding, as a draft's unwritten sections are. */
|
|
21
|
+
readonly severity?: Severity | undefined;
|
|
22
|
+
}
|
|
23
|
+
export interface RuleContext {
|
|
24
|
+
readonly brief: Brief;
|
|
25
|
+
readonly corpus: Corpus;
|
|
26
|
+
readonly config: Config;
|
|
27
|
+
/** The files git sees, or every file without git; `null` when unknown. A rule that needs them stays quiet without them. */
|
|
28
|
+
readonly repoFiles: readonly string[] | null;
|
|
29
|
+
/** Analyses computed once per run and shared by every brief. */
|
|
30
|
+
readonly shared: SharedAnalysis;
|
|
31
|
+
/** A plugin's options from the configuration. */
|
|
32
|
+
readonly options?: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** What a rule is called, how loud it is by default, and what it checks. */
|
|
35
|
+
export interface RuleInfo {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly severity: SeveritySetting;
|
|
38
|
+
readonly description: string;
|
|
39
|
+
}
|
|
40
|
+
export interface Rule extends RuleInfo {
|
|
41
|
+
check(context: RuleContext): readonly RuleResult[] | Promise<readonly RuleResult[]>;
|
|
42
|
+
}
|
|
43
|
+
export interface SharedAnalysis {
|
|
44
|
+
readonly cycles: readonly (readonly Brief[])[];
|
|
45
|
+
readonly duplicates: ReadonlyMap<string, readonly Brief[]>;
|
|
46
|
+
}
|
|
47
|
+
export declare function analyse(corpus: Corpus): SharedAnalysis;
|
|
48
|
+
/** The sections a brief must carry: the configured ones, then its type's. */
|
|
49
|
+
export declare function sectionRules(brief: Brief, config: Config): SectionRule[];
|
|
50
|
+
/** The headings that fill a section rule. */
|
|
51
|
+
export declare function sectionsFilling(brief: Brief, rule: SectionRule): Section[];
|
|
52
|
+
export declare const RULES: readonly Rule[];
|
|
53
|
+
/**
|
|
54
|
+
* The rules the collision analysis reports under. They are not run per brief,
|
|
55
|
+
* so they carry no check: they exist so configuration can set their severity
|
|
56
|
+
* and reports can describe them.
|
|
57
|
+
*/
|
|
58
|
+
export declare const COLLISION_RULES: readonly RuleInfo[];
|