@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +269 -0
  4. package/bin/spec-brief.js +19 -0
  5. package/dist/apply.d.ts +26 -0
  6. package/dist/apply.js +71 -0
  7. package/dist/apply.js.map +1 -0
  8. package/dist/archive.d.ts +81 -0
  9. package/dist/archive.js +333 -0
  10. package/dist/archive.js.map +1 -0
  11. package/dist/brief.d.ts +60 -0
  12. package/dist/brief.js +152 -0
  13. package/dist/brief.js.map +1 -0
  14. package/dist/cli.d.ts +35 -0
  15. package/dist/cli.js +411 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/collisions.d.ts +50 -0
  18. package/dist/collisions.js +127 -0
  19. package/dist/collisions.js.map +1 -0
  20. package/dist/config.d.ts +94 -0
  21. package/dist/config.js +353 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/corpus.d.ts +41 -0
  24. package/dist/corpus.js +154 -0
  25. package/dist/corpus.js.map +1 -0
  26. package/dist/engine.d.ts +121 -0
  27. package/dist/engine.js +276 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/frontmatter.d.ts +68 -0
  30. package/dist/frontmatter.js +311 -0
  31. package/dist/frontmatter.js.map +1 -0
  32. package/dist/fs.d.ts +59 -0
  33. package/dist/fs.js +189 -0
  34. package/dist/fs.js.map +1 -0
  35. package/dist/git.d.ts +59 -0
  36. package/dist/git.js +131 -0
  37. package/dist/git.js.map +1 -0
  38. package/dist/glob.d.ts +79 -0
  39. package/dist/glob.js +465 -0
  40. package/dist/glob.js.map +1 -0
  41. package/dist/index.d.ts +24 -0
  42. package/dist/index.js +26 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/integrity.d.ts +11 -0
  45. package/dist/integrity.js +20 -0
  46. package/dist/integrity.js.map +1 -0
  47. package/dist/links.d.ts +38 -0
  48. package/dist/links.js +142 -0
  49. package/dist/links.js.map +1 -0
  50. package/dist/lint.d.ts +38 -0
  51. package/dist/lint.js +90 -0
  52. package/dist/lint.js.map +1 -0
  53. package/dist/markdown.d.ts +65 -0
  54. package/dist/markdown.js +274 -0
  55. package/dist/markdown.js.map +1 -0
  56. package/dist/plugins.d.ts +16 -0
  57. package/dist/plugins.js +77 -0
  58. package/dist/plugins.js.map +1 -0
  59. package/dist/report.d.ts +38 -0
  60. package/dist/report.js +244 -0
  61. package/dist/report.js.map +1 -0
  62. package/dist/rules.d.ts +58 -0
  63. package/dist/rules.js +448 -0
  64. package/dist/rules.js.map +1 -0
  65. package/dist/scaffold.d.ts +25 -0
  66. package/dist/scaffold.js +81 -0
  67. package/dist/scaffold.js.map +1 -0
  68. package/dist/schema.d.ts +47 -0
  69. package/dist/schema.js +195 -0
  70. package/dist/schema.js.map +1 -0
  71. package/dist/text.d.ts +40 -0
  72. package/dist/text.js +95 -0
  73. package/dist/text.js.map +1 -0
  74. package/dist/types.d.ts +30 -0
  75. package/dist/types.js +5 -0
  76. package/dist/types.js.map +1 -0
  77. package/package.json +76 -0
  78. package/schema.json +321 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief.js","sourceRoot":"","sources":["../src/brief.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One brief, read: its identity, its front matter, its sections and its task\n * items. A pure function of a file's path and text.\n */\n\nimport type { Config } from './config.js';\nimport { findEntry, isNull, keyName, readFrontMatter, type FrontMatter, type YamlValue } from './frontmatter.js';\nimport { scan, sectionsOf, titleOf, type Scan, type Section, type TaskItem } from './markdown.js';\nimport { splitLines, stripBom } from './text.js';\nimport type { Phase, Status } from './types.js';\n\nexport const BANNER_OPEN = '<!-- spec-brief:banner -->';\nexport const BANNER_CLOSE = '<!-- /spec-brief:banner -->';\n\n/** Front-matter keys spec-brief itself reads, in the spelling it documents. */\nexport const BUILT_IN_FIELDS: readonly string[] = [\n 'id',\n 'title',\n 'type',\n 'status',\n 'date',\n 'wave',\n 'dependsOn',\n 'affectedFiles',\n 'protectedFiles',\n 'integrity',\n];\n\nexport interface FieldProblem {\n readonly field: string;\n /** 0-based line. */\n readonly line: number;\n readonly message: string;\n}\n\nexport interface Brief {\n /** `null` when no id can be read, which a rule reports. */\n readonly id: string | null;\n /** Repository-relative path. */\n readonly file: string;\n /** The file name. */\n readonly name: string;\n readonly phase: Phase;\n /** The file exactly as read, byte-order mark and line endings included. */\n readonly source: string;\n /** The text parsed: the source without a byte-order mark. */\n readonly text: string;\n readonly lines: readonly string[];\n readonly frontMatter: FrontMatter | null;\n /** 0-based first line after the front matter. */\n readonly bodyStart: number;\n readonly scan: Scan;\n readonly sections: readonly Section[];\n readonly tasks: readonly TaskItem[];\n readonly title: string | null;\n readonly status: Status | null;\n readonly statusWord: string | null;\n readonly type: string | null;\n readonly wave: number | null;\n readonly dependsOn: readonly string[];\n readonly affectedFiles: readonly string[];\n readonly protectedFiles: readonly string[];\n readonly integrity: string | null;\n /** The frozen banner's lines, markers included, end exclusive. */\n readonly banner: { readonly start: number; readonly end: number } | null;\n readonly problems: readonly FieldProblem[];\n}\n\nfunction valueText(value: YamlValue): string | null | { readonly problem: string } {\n if (value.kind === 'unsupported') return { problem: value.reason };\n if (value.kind === 'list') return { problem: 'must be a single value, not a list' };\n return isNull(value.scalar) ? null : value.scalar.text;\n}\n\nfunction valueList(value: YamlValue): string[] | { readonly problem: string } {\n if (value.kind === 'unsupported') return { problem: value.reason };\n if (value.kind === 'scalar') return isNull(value.scalar) ? [] : [value.scalar.text];\n const empty = value.items.find((item) => isNull(item) || item.text.trim() === '');\n if (empty !== undefined) return { problem: 'must not contain an empty item' };\n return value.items.map((item) => item.text.trim());\n}\n\n/** The id a file name carries: everything before the separator, or the whole stem. */\nexport function idFromName(name: string, separator: string): string {\n const stem = name.replace(/\\.md$/i, '');\n const at = stem.indexOf(separator);\n return at > 0 ? stem.slice(0, at) : stem;\n}\n\nfunction statusFrom(word: string | null, config: Config, phase: Phase): Status | null {\n if (config.status.field === null) return phase === 'archived' ? 'archived' : 'active';\n if (word === null) return null;\n const lower = word.toLowerCase();\n if (lower === config.status.archived.toLowerCase()) return 'archived';\n if (lower === config.status.active.toLowerCase()) return 'active';\n if (config.status.draft !== null && lower === config.status.draft.toLowerCase()) return 'draft';\n return null;\n}\n\nexport function parseBrief(file: string, text: string, config: Config, phase: Phase): Brief {\n const content = stripBom(text);\n const lines = splitLines(content);\n const frontMatter = readFrontMatter(lines);\n const bodyStart = frontMatter === null || frontMatter.close < 0 ? 0 : frontMatter.close + 1;\n const scanned = scan(lines, bodyStart);\n const problems: FieldProblem[] = [];\n const name = file.slice(file.lastIndexOf('/') + 1);\n\n const text1 = (field: string): string | null => {\n const entry = findEntry(frontMatter, field);\n if (entry === undefined) return null;\n const value = valueText(entry.value);\n if (value !== null && typeof value === 'object') {\n problems.push({ field, line: entry.line, message: `\"${entry.key}\" ${value.problem}` });\n return null;\n }\n return value;\n };\n const list = (field: string): string[] => {\n const entry = findEntry(frontMatter, field);\n if (entry === undefined) return [];\n const value = valueList(entry.value);\n if (!Array.isArray(value)) {\n problems.push({ field, line: entry.line, message: `\"${entry.key}\" ${value.problem}` });\n return [];\n }\n return value;\n };\n\n const waveText = text1('wave');\n let wave: number | null = null;\n const waveEntry = findEntry(frontMatter, 'wave');\n if (waveText !== null && waveEntry !== undefined) {\n const quoted = waveEntry.value.kind === 'scalar' && waveEntry.value.scalar.quoted;\n if (!quoted && /^\\d{1,9}$/.test(waveText)) wave = Number(waveText);\n else problems.push({ field: 'wave', line: waveEntry.line, message: `\"wave\" must be a whole number, not \"${waveText}\"` });\n }\n\n const statusWord = config.status.field === null ? null : text1(config.status.field);\n const frontTitle = text1('title');\n const heading = titleOf(scanned);\n const id = config.id.source === 'filename' ? idFromName(name, config.id.separator) : text1('id');\n\n // A marker is a comment on a line of its own. Quoted in a code block it is\n // text: the prose mask keeps code and blanks comments, so a real marker is\n // a line whose prose is empty.\n const marker = (line: number, text: string): boolean =>\n (lines[line] as string).trim() === text && (scanned.prose[line] as string).trim() === '';\n let banner: Brief['banner'] = null;\n const open = lines.findIndex((_, i) => i >= bodyStart && marker(i, BANNER_OPEN));\n if (open >= 0) {\n const close = lines.findIndex((_, i) => i > open && marker(i, BANNER_CLOSE));\n if (close > open) banner = { start: open, end: close + 1 };\n }\n\n return {\n id: id === null || id.trim() === '' ? null : id.trim(),\n file,\n name,\n phase,\n source: text,\n text: content,\n lines,\n frontMatter,\n bodyStart,\n scan: scanned,\n sections: sectionsOf(scanned),\n tasks: scanned.tasks,\n title: frontTitle ?? (heading === undefined || heading.text === '' ? null : heading.text),\n status: statusFrom(statusWord, config, phase),\n statusWord,\n type: text1('type'),\n wave,\n dependsOn: list('dependsOn'),\n affectedFiles: list('affectedFiles'),\n protectedFiles: list('protectedFiles'),\n integrity: text1('integrity'),\n banner,\n problems,\n };\n}\n\n/** The 0-based line a front-matter key is declared on, or the first line when it is absent. */\nexport function lineOfField(brief: Brief, field: string): number {\n return findEntry(brief.frontMatter, field)?.line ?? 0;\n}\n\n/** Keys spec-brief knows, compared the way front matter compares them. */\nexport function knownFields(config: Config): Set<string> {\n const names = [...BUILT_IN_FIELDS, ...config.fields];\n if (config.status.field !== null) names.push(config.status.field);\n return new Set(names.map(keyName));\n}\n"]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The command line: argument parsing, dispatch, output and exit codes.
3
+ *
4
+ * Exit 0: done, nothing found. Exit 1: findings, collisions, or an action
5
+ * refused. Exit 2: the run could not be trusted - a bad flag, a configuration
6
+ * that does not load, a brief that does not exist. A run that checked nothing
7
+ * must never exit as though it found nothing.
8
+ */
9
+ export declare const EXIT_OK = 0;
10
+ export declare const EXIT_FAILED = 1;
11
+ export declare const EXIT_ERROR = 2;
12
+ export declare class UsageError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ export interface CliIO {
16
+ readonly stdout?: {
17
+ write(text: string): unknown;
18
+ readonly isTTY?: boolean;
19
+ };
20
+ readonly stderr?: {
21
+ write(text: string): unknown;
22
+ };
23
+ readonly cwd?: string;
24
+ readonly env?: Readonly<Record<string, string | undefined>>;
25
+ }
26
+ export declare const HELP = "spec-brief - lint, schedule and archive task briefs\n\nUsage:\n spec-brief <command> [options]\n\nCommands:\n init write .spec-brief.json and the brief directories\n new <title> scaffold a brief with the next free id\n lint [brief...] check briefs against the configuration\n list briefs with their status, wave and readiness\n matrix scope collisions between briefs in the same wave\n archive <brief> close a round: check it, stamp it, freeze it, move it\n unarchive <brief> reopen an archived brief\n\nOptions for every command:\n --root <dir> run from this directory instead of the current one\n --config <file> use this configuration file\n --no-config use the defaults, ignoring any configuration file\n --format <format> pretty (default), json, sarif or github\n --strict treat warnings as errors\n --no-git leave git out; read the files on disk instead\n --color, --no-color force colour on or off\n -h, --help show this help\n -v, --version show the version\n\ninit:\n --briefs <dir> the directory of live briefs; default briefs\n --archive <dir> the directory of archived briefs; default <briefs>/archive\n\nnew:\n --id <id> use this id instead of the next free one\n --type <type> a brief type from the configuration\n --wave <n> the wave it runs in\n --depends-on <ids> comma-separated ids it runs after\n --date <YYYY-MM-DD> the date in its front matter; default today\n\nlist:\n --ready only live briefs whose dependencies are archived\n --archived include archived briefs\n\nmatrix:\n --all-waves compare every live brief, whatever its wave\n\narchive:\n --pr <number> the pull request the round merged in\n --commit <rev> the commit the round landed as\n --base <rev> the branch it started from; the diff runs from the merge base\n --summary <text> what the round did, for the banner\n --date <YYYY-MM-DD> the archival date; default today\n --allow-dirty archive although the tree has uncommitted work\n --dry-run print the plan and change nothing\n\nunarchive:\n --dry-run print the plan and change nothing\n\nExit codes: 0 clean, 1 findings or refused, 2 the run could not be trusted.\n";
27
+ type Values = Record<string, string | boolean | undefined>;
28
+ interface Parsed {
29
+ readonly command: string | undefined;
30
+ readonly values: Values;
31
+ readonly positionals: readonly string[];
32
+ }
33
+ export declare function parse(argv: readonly string[]): Parsed;
34
+ export declare function main(argv?: readonly string[], io?: CliIO): Promise<number>;
35
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,411 @@
1
+ /**
2
+ * The command line: argument parsing, dispatch, output and exit codes.
3
+ *
4
+ * Exit 0: done, nothing found. Exit 1: findings, collisions, or an action
5
+ * refused. Exit 2: the run could not be trusted - a bad flag, a configuration
6
+ * that does not load, a brief that does not exist. A run that checked nothing
7
+ * must never exit as though it found nothing.
8
+ */
9
+ import { readFile } from 'node:fs/promises';
10
+ import { parseArgs } from 'node:util';
11
+ import { TransactionError, ConflictError } from './apply.js';
12
+ import { CONFIG_FILES, ConfigError, initialConfig } from './config.js';
13
+ import { BriefEngine, EngineError, today } from './engine.js';
14
+ import { NodeFileSystem } from './fs.js';
15
+ import { failing, sortFindings, summarise } from './lint.js';
16
+ import { normalisePath } from './links.js';
17
+ import { briefJson, findingJson, FORMATS, githubCommands, jsonDocument, planJson, prettyFindings, prettyList, prettyMatrix, prettyPlan, sarif, summaryLine, } from './report.js';
18
+ export const EXIT_OK = 0;
19
+ export const EXIT_FAILED = 1;
20
+ export const EXIT_ERROR = 2;
21
+ export class UsageError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = 'UsageError';
25
+ }
26
+ }
27
+ export const HELP = `spec-brief - lint, schedule and archive task briefs
28
+
29
+ Usage:
30
+ spec-brief <command> [options]
31
+
32
+ Commands:
33
+ init write .spec-brief.json and the brief directories
34
+ new <title> scaffold a brief with the next free id
35
+ lint [brief...] check briefs against the configuration
36
+ list briefs with their status, wave and readiness
37
+ matrix scope collisions between briefs in the same wave
38
+ archive <brief> close a round: check it, stamp it, freeze it, move it
39
+ unarchive <brief> reopen an archived brief
40
+
41
+ Options for every command:
42
+ --root <dir> run from this directory instead of the current one
43
+ --config <file> use this configuration file
44
+ --no-config use the defaults, ignoring any configuration file
45
+ --format <format> pretty (default), json, sarif or github
46
+ --strict treat warnings as errors
47
+ --no-git leave git out; read the files on disk instead
48
+ --color, --no-color force colour on or off
49
+ -h, --help show this help
50
+ -v, --version show the version
51
+
52
+ init:
53
+ --briefs <dir> the directory of live briefs; default briefs
54
+ --archive <dir> the directory of archived briefs; default <briefs>/archive
55
+
56
+ new:
57
+ --id <id> use this id instead of the next free one
58
+ --type <type> a brief type from the configuration
59
+ --wave <n> the wave it runs in
60
+ --depends-on <ids> comma-separated ids it runs after
61
+ --date <YYYY-MM-DD> the date in its front matter; default today
62
+
63
+ list:
64
+ --ready only live briefs whose dependencies are archived
65
+ --archived include archived briefs
66
+
67
+ matrix:
68
+ --all-waves compare every live brief, whatever its wave
69
+
70
+ archive:
71
+ --pr <number> the pull request the round merged in
72
+ --commit <rev> the commit the round landed as
73
+ --base <rev> the branch it started from; the diff runs from the merge base
74
+ --summary <text> what the round did, for the banner
75
+ --date <YYYY-MM-DD> the archival date; default today
76
+ --allow-dirty archive although the tree has uncommitted work
77
+ --dry-run print the plan and change nothing
78
+
79
+ unarchive:
80
+ --dry-run print the plan and change nothing
81
+
82
+ Exit codes: 0 clean, 1 findings or refused, 2 the run could not be trusted.
83
+ `;
84
+ const GLOBAL = {
85
+ root: { type: 'string' },
86
+ config: { type: 'string' },
87
+ 'no-config': { type: 'boolean' },
88
+ format: { type: 'string' },
89
+ strict: { type: 'boolean' },
90
+ 'no-git': { type: 'boolean' },
91
+ color: { type: 'boolean' },
92
+ 'no-color': { type: 'boolean' },
93
+ help: { type: 'boolean', short: 'h' },
94
+ version: { type: 'boolean', short: 'v' },
95
+ };
96
+ const COMMANDS = {
97
+ init: { options: { briefs: { type: 'string' }, archive: { type: 'string' } }, formats: ['pretty', 'json'] },
98
+ new: {
99
+ options: {
100
+ id: { type: 'string' },
101
+ type: { type: 'string' },
102
+ wave: { type: 'string' },
103
+ 'depends-on': { type: 'string' },
104
+ date: { type: 'string' },
105
+ },
106
+ formats: ['pretty', 'json'],
107
+ },
108
+ lint: { options: {}, formats: FORMATS },
109
+ list: { options: { ready: { type: 'boolean' }, archived: { type: 'boolean' } }, formats: ['pretty', 'json'] },
110
+ matrix: { options: { 'all-waves': { type: 'boolean' } }, formats: FORMATS },
111
+ archive: {
112
+ options: {
113
+ pr: { type: 'string' },
114
+ commit: { type: 'string' },
115
+ base: { type: 'string' },
116
+ summary: { type: 'string' },
117
+ date: { type: 'string' },
118
+ 'allow-dirty': { type: 'boolean' },
119
+ 'dry-run': { type: 'boolean' },
120
+ },
121
+ formats: ['pretty', 'json'],
122
+ },
123
+ unarchive: { options: { 'dry-run': { type: 'boolean' } }, formats: ['pretty', 'json'] },
124
+ };
125
+ /** Global options that take a value, so the value is not mistaken for the command. */
126
+ const VALUED = new Set(['--root', '--config', '--format']);
127
+ function commandOf(argv) {
128
+ for (let i = 0; i < argv.length; i += 1) {
129
+ const arg = argv[i];
130
+ if (arg === '--')
131
+ return argv[i + 1];
132
+ if (VALUED.has(arg)) {
133
+ i += 1;
134
+ continue;
135
+ }
136
+ if (!arg.startsWith('-'))
137
+ return arg;
138
+ }
139
+ return undefined;
140
+ }
141
+ export function parse(argv) {
142
+ const command = commandOf(argv);
143
+ const spec = command === undefined ? undefined : COMMANDS[command];
144
+ if (command !== undefined && spec === undefined) {
145
+ throw new UsageError(`"${command}" is not a command; run spec-brief --help`);
146
+ }
147
+ try {
148
+ const { values, positionals } = parseArgs({
149
+ args: [...argv],
150
+ options: { ...GLOBAL, ...(spec?.options ?? {}) },
151
+ allowPositionals: true,
152
+ strict: true,
153
+ });
154
+ return { command, values: values, positionals: positionals.slice(1) };
155
+ }
156
+ catch (error) {
157
+ throw new UsageError(error.message);
158
+ }
159
+ }
160
+ function text(values, key) {
161
+ const value = values[key];
162
+ return typeof value === 'string' ? value : undefined;
163
+ }
164
+ function flag(values, key) {
165
+ return values[key] === true;
166
+ }
167
+ function integer(values, key, minimum) {
168
+ const raw = text(values, key);
169
+ if (raw === undefined)
170
+ return undefined;
171
+ if (!/^\d+$/.test(raw) || Number(raw) < minimum)
172
+ throw new UsageError(`--${key} must be a whole number of at least ${minimum}, not "${raw}"`);
173
+ return Number(raw);
174
+ }
175
+ async function version() {
176
+ const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
177
+ return pkg.version;
178
+ }
179
+ function useColor(values, io) {
180
+ if (flag(values, 'no-color'))
181
+ return false;
182
+ if (flag(values, 'color'))
183
+ return true;
184
+ const env = io.env ?? process.env;
185
+ if ((env['NO_COLOR'] ?? '') !== '')
186
+ return false;
187
+ const force = env['FORCE_COLOR'];
188
+ if (force !== undefined && force !== '')
189
+ return force !== '0';
190
+ return (io.stdout ?? process.stdout).isTTY === true;
191
+ }
192
+ async function openEngine(run) {
193
+ const root = text(run.values, 'root');
194
+ const config = text(run.values, 'config');
195
+ return BriefEngine.open({
196
+ cwd: root === undefined ? run.cwd : root,
197
+ ...(config === undefined ? {} : { config }),
198
+ ...(flag(run.values, 'no-git') ? { git: null } : {}),
199
+ noConfig: flag(run.values, 'no-config'),
200
+ });
201
+ }
202
+ function emitFindings(run, command, findings, checked, extra = {}) {
203
+ const failed = failing(findings, run.strict);
204
+ switch (run.format) {
205
+ case 'json':
206
+ run.out(jsonDocument(command, run.version, { ok: !failed, checked, summary: summarise(findings), findings: findings.map(findingJson), ...extra }));
207
+ break;
208
+ case 'sarif':
209
+ run.out(sarif(findings, run.version));
210
+ break;
211
+ case 'github':
212
+ run.out(githubCommands(findings));
213
+ break;
214
+ case 'pretty':
215
+ if (findings.length > 0)
216
+ run.out(`${prettyFindings(findings, run.style)}\n\n`);
217
+ run.out(`${findings.length === 0 ? `${checked} brief(s) checked, no findings` : `${summaryLine(findings)} in ${checked} brief(s)`}\n`);
218
+ break;
219
+ }
220
+ return failed ? EXIT_FAILED : EXIT_OK;
221
+ }
222
+ async function runInit(run) {
223
+ const root = text(run.values, 'root') ?? run.cwd;
224
+ const fs = new NodeFileSystem(root);
225
+ for (const name of CONFIG_FILES) {
226
+ if (await fs.exists(name))
227
+ throw new UsageError(`${name} already exists here; edit it instead`);
228
+ }
229
+ const briefs = normalisePath(text(run.values, 'briefs') ?? 'briefs');
230
+ const archive = normalisePath(text(run.values, 'archive') ?? `${briefs}/archive`);
231
+ if (briefs === '' || archive === '' || briefs === archive)
232
+ throw new UsageError('--briefs and --archive must be two directories');
233
+ const config = `${JSON.stringify(initialConfig(briefs, archive), null, 2)}\n`;
234
+ await fs.write('.spec-brief.json', config);
235
+ const written = ['.spec-brief.json'];
236
+ for (const dir of [briefs, archive]) {
237
+ const keep = `${dir}/.gitkeep`;
238
+ if ((await fs.list(dir)) === null) {
239
+ await fs.write(keep, '');
240
+ written.push(keep);
241
+ }
242
+ }
243
+ if (run.format === 'json')
244
+ run.out(jsonDocument('init', run.version, { ok: true, written }));
245
+ else
246
+ run.out(`${written.map((w) => `wrote ${w}`).join('\n')}\nnext: spec-brief new "<title>"\n`);
247
+ return EXIT_OK;
248
+ }
249
+ async function runNew(run) {
250
+ const title = run.positionals.join(' ').trim();
251
+ if (title === '')
252
+ throw new UsageError('new needs a title: spec-brief new "<title>"');
253
+ const engine = await openEngine(run);
254
+ const dependsOn = text(run.values, 'depends-on')
255
+ ?.split(',')
256
+ .map((d) => d.trim())
257
+ .filter((d) => d !== '');
258
+ const created = await engine.create({
259
+ title,
260
+ id: text(run.values, 'id'),
261
+ type: text(run.values, 'type'),
262
+ wave: integer(run.values, 'wave', 0),
263
+ dependsOn,
264
+ date: text(run.values, 'date') ?? run.today,
265
+ });
266
+ if (run.format === 'json')
267
+ run.out(jsonDocument('new', run.version, { ok: true, file: created.file }));
268
+ else
269
+ run.out(`wrote ${created.file}\n`);
270
+ return EXIT_OK;
271
+ }
272
+ async function runLint(run) {
273
+ const engine = await openEngine(run);
274
+ engine.requireBriefs();
275
+ const findings = await engine.lint(run.positionals);
276
+ const checked = run.positionals.length === 0 ? engine.corpus.briefs.length : run.positionals.length;
277
+ return emitFindings(run, 'lint', findings, checked);
278
+ }
279
+ async function runList(run) {
280
+ const engine = await openEngine(run);
281
+ engine.requireBriefs();
282
+ const briefs = flag(run.values, 'archived') ? engine.corpus.briefs : engine.corpus.live;
283
+ const ready = new Set(engine.ready());
284
+ const rows = briefs
285
+ .map((brief) => ({ brief, ready: ready.has(brief), waitingOn: engine.waitingOn(brief) }))
286
+ .filter((row) => !flag(run.values, 'ready') || row.ready);
287
+ if (run.format === 'json') {
288
+ run.out(jsonDocument('list', run.version, { ok: true, briefs: rows.map((r) => briefJson(r.brief, { ready: r.ready, waitingOn: r.waitingOn })) }));
289
+ }
290
+ else {
291
+ run.out(`${prettyList(rows, run.style)}\n`);
292
+ }
293
+ return EXIT_OK;
294
+ }
295
+ async function runMatrix(run) {
296
+ const engine = await openEngine(run);
297
+ engine.requireBriefs();
298
+ const { report, findings } = await engine.collisions({ all: flag(run.values, 'all-waves') });
299
+ const sorted = sortFindings(findings);
300
+ if (run.format === 'pretty') {
301
+ run.out(`${prettyMatrix(report, run.style)}\n`);
302
+ return failing(sorted, run.strict) ? EXIT_FAILED : EXIT_OK;
303
+ }
304
+ const waves = report.waves.map((w) => ({
305
+ wave: w.wave,
306
+ briefs: w.briefs.map((b) => b.id),
307
+ collisions: w.collisions.map((c) => ({ a: c.a.id, b: c.b.id, patterns: c.patterns, witness: c.witness })),
308
+ sharedDirectories: w.shared.map((s) => ({ a: s.a.id, b: s.b.id, directory: s.directory })),
309
+ unscoped: w.unscoped.map((b) => b.id),
310
+ }));
311
+ return emitFindings(run, 'matrix', sorted, engine.corpus.live.length, { waves, unscheduled: report.unscheduled.map((b) => b.id) });
312
+ }
313
+ async function runTransition(run, action) {
314
+ const reference = run.positionals[0];
315
+ if (reference === undefined || run.positionals.length > 1)
316
+ throw new UsageError(`${action} takes one brief: spec-brief ${action} <brief>`);
317
+ const engine = await openEngine(run);
318
+ const dryRun = flag(run.values, 'dry-run');
319
+ const plan = action === 'archive'
320
+ ? await engine.planArchive(reference, {
321
+ pr: integer(run.values, 'pr', 1),
322
+ commit: text(run.values, 'commit'),
323
+ base: text(run.values, 'base'),
324
+ summary: text(run.values, 'summary'),
325
+ date: text(run.values, 'date') ?? run.today,
326
+ allowDirty: flag(run.values, 'allow-dirty'),
327
+ strict: run.strict,
328
+ noGit: flag(run.values, 'no-git'),
329
+ })
330
+ : engine.planUnarchive(reference);
331
+ const refused = plan.blocking.length > 0;
332
+ if (!refused && !dryRun)
333
+ await engine.apply(plan);
334
+ if (run.format === 'json')
335
+ run.out(jsonDocument(action, run.version, { ok: !refused, dryRun, plan: planJson(plan) }));
336
+ else
337
+ run.out(`${prettyPlan(plan, dryRun, run.style)}\n`);
338
+ return refused ? EXIT_FAILED : EXIT_OK;
339
+ }
340
+ export async function main(argv = process.argv.slice(2), io = {}) {
341
+ const stdout = io.stdout ?? process.stdout;
342
+ const stderr = io.stderr ?? process.stderr;
343
+ const err = (message) => {
344
+ stderr.write(`spec-brief: ${message}\n`);
345
+ };
346
+ let parsed;
347
+ try {
348
+ parsed = parse(argv);
349
+ }
350
+ catch (error) {
351
+ err(error.message);
352
+ return EXIT_ERROR;
353
+ }
354
+ const { command, values, positionals } = parsed;
355
+ if (flag(values, 'version')) {
356
+ stdout.write(`${await version()}\n`);
357
+ return EXIT_OK;
358
+ }
359
+ if (flag(values, 'help') || command === undefined) {
360
+ stdout.write(HELP);
361
+ return command === undefined && !flag(values, 'help') ? EXIT_ERROR : EXIT_OK;
362
+ }
363
+ const spec = COMMANDS[command];
364
+ const format = (text(values, 'format') ?? 'pretty');
365
+ if (!spec.formats.includes(format)) {
366
+ err(`--format for ${command} is one of ${spec.formats.join(', ')}, not "${format}"`);
367
+ return EXIT_ERROR;
368
+ }
369
+ const run = {
370
+ today: today(io.env ?? process.env),
371
+ out: (t) => {
372
+ stdout.write(t);
373
+ },
374
+ err,
375
+ style: { color: format === 'pretty' && useColor(values, io) },
376
+ format,
377
+ strict: flag(values, 'strict'),
378
+ version: await version(),
379
+ values,
380
+ positionals,
381
+ cwd: io.cwd ?? process.cwd(),
382
+ };
383
+ try {
384
+ switch (command) {
385
+ case 'init':
386
+ return await runInit(run);
387
+ case 'new':
388
+ return await runNew(run);
389
+ case 'lint':
390
+ return await runLint(run);
391
+ case 'list':
392
+ return await runList(run);
393
+ case 'matrix':
394
+ return await runMatrix(run);
395
+ case 'archive':
396
+ return await runTransition(run, 'archive');
397
+ default:
398
+ return await runTransition(run, 'unarchive');
399
+ }
400
+ }
401
+ catch (error) {
402
+ const expected = [ConfigError, UsageError, EngineError, ConflictError, TransactionError];
403
+ if (expected.some((kind) => error instanceof kind)) {
404
+ err(error.message);
405
+ return EXIT_ERROR;
406
+ }
407
+ err(`unexpected error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
408
+ return EXIT_ERROR;
409
+ }
410
+ }
411
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The command line: argument parsing, dispatch, output and exit codes.\n *\n * Exit 0: done, nothing found. Exit 1: findings, collisions, or an action\n * refused. Exit 2: the run could not be trusted - a bad flag, a configuration\n * that does not load, a brief that does not exist. A run that checked nothing\n * must never exit as though it found nothing.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { parseArgs, type ParseArgsConfig } from 'node:util';\n\nimport { TransactionError, ConflictError } from './apply.js';\nimport type { Plan } from './archive.js';\nimport { CONFIG_FILES, ConfigError, initialConfig } from './config.js';\nimport { BriefEngine, EngineError, today } from './engine.js';\nimport { NodeFileSystem } from './fs.js';\nimport { failing, sortFindings, summarise } from './lint.js';\nimport { normalisePath } from './links.js';\nimport {\n briefJson,\n findingJson,\n FORMATS,\n type Format,\n githubCommands,\n jsonDocument,\n type ListRow,\n planJson,\n prettyFindings,\n prettyList,\n prettyMatrix,\n prettyPlan,\n sarif,\n type Style,\n summaryLine,\n} from './report.js';\nimport type { Finding } from './types.js';\n\nexport const EXIT_OK = 0;\nexport const EXIT_FAILED = 1;\nexport const EXIT_ERROR = 2;\n\nexport class UsageError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UsageError';\n }\n}\n\nexport interface CliIO {\n readonly stdout?: { write(text: string): unknown; readonly isTTY?: boolean };\n readonly stderr?: { write(text: string): unknown };\n readonly cwd?: string;\n readonly env?: Readonly<Record<string, string | undefined>>;\n}\n\nexport const HELP = `spec-brief - lint, schedule and archive task briefs\n\nUsage:\n spec-brief <command> [options]\n\nCommands:\n init write .spec-brief.json and the brief directories\n new <title> scaffold a brief with the next free id\n lint [brief...] check briefs against the configuration\n list briefs with their status, wave and readiness\n matrix scope collisions between briefs in the same wave\n archive <brief> close a round: check it, stamp it, freeze it, move it\n unarchive <brief> reopen an archived brief\n\nOptions for every command:\n --root <dir> run from this directory instead of the current one\n --config <file> use this configuration file\n --no-config use the defaults, ignoring any configuration file\n --format <format> pretty (default), json, sarif or github\n --strict treat warnings as errors\n --no-git leave git out; read the files on disk instead\n --color, --no-color force colour on or off\n -h, --help show this help\n -v, --version show the version\n\ninit:\n --briefs <dir> the directory of live briefs; default briefs\n --archive <dir> the directory of archived briefs; default <briefs>/archive\n\nnew:\n --id <id> use this id instead of the next free one\n --type <type> a brief type from the configuration\n --wave <n> the wave it runs in\n --depends-on <ids> comma-separated ids it runs after\n --date <YYYY-MM-DD> the date in its front matter; default today\n\nlist:\n --ready only live briefs whose dependencies are archived\n --archived include archived briefs\n\nmatrix:\n --all-waves compare every live brief, whatever its wave\n\narchive:\n --pr <number> the pull request the round merged in\n --commit <rev> the commit the round landed as\n --base <rev> the branch it started from; the diff runs from the merge base\n --summary <text> what the round did, for the banner\n --date <YYYY-MM-DD> the archival date; default today\n --allow-dirty archive although the tree has uncommitted work\n --dry-run print the plan and change nothing\n\nunarchive:\n --dry-run print the plan and change nothing\n\nExit codes: 0 clean, 1 findings or refused, 2 the run could not be trusted.\n`;\n\ntype Values = Record<string, string | boolean | undefined>;\n\nconst GLOBAL: NonNullable<ParseArgsConfig['options']> = {\n root: { type: 'string' },\n config: { type: 'string' },\n 'no-config': { type: 'boolean' },\n format: { type: 'string' },\n strict: { type: 'boolean' },\n 'no-git': { type: 'boolean' },\n color: { type: 'boolean' },\n 'no-color': { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean', short: 'v' },\n};\n\nconst COMMANDS: Readonly<Record<string, { options: NonNullable<ParseArgsConfig['options']>; formats: readonly Format[] }>> = {\n init: { options: { briefs: { type: 'string' }, archive: { type: 'string' } }, formats: ['pretty', 'json'] },\n new: {\n options: {\n id: { type: 'string' },\n type: { type: 'string' },\n wave: { type: 'string' },\n 'depends-on': { type: 'string' },\n date: { type: 'string' },\n },\n formats: ['pretty', 'json'],\n },\n lint: { options: {}, formats: FORMATS },\n list: { options: { ready: { type: 'boolean' }, archived: { type: 'boolean' } }, formats: ['pretty', 'json'] },\n matrix: { options: { 'all-waves': { type: 'boolean' } }, formats: FORMATS },\n archive: {\n options: {\n pr: { type: 'string' },\n commit: { type: 'string' },\n base: { type: 'string' },\n summary: { type: 'string' },\n date: { type: 'string' },\n 'allow-dirty': { type: 'boolean' },\n 'dry-run': { type: 'boolean' },\n },\n formats: ['pretty', 'json'],\n },\n unarchive: { options: { 'dry-run': { type: 'boolean' } }, formats: ['pretty', 'json'] },\n};\n\ninterface Parsed {\n readonly command: string | undefined;\n readonly values: Values;\n readonly positionals: readonly string[];\n}\n\n/** Global options that take a value, so the value is not mistaken for the command. */\nconst VALUED = new Set(['--root', '--config', '--format']);\n\nfunction commandOf(argv: readonly string[]): string | undefined {\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i] as string;\n if (arg === '--') return argv[i + 1];\n if (VALUED.has(arg)) {\n i += 1;\n continue;\n }\n if (!arg.startsWith('-')) return arg;\n }\n return undefined;\n}\n\nexport function parse(argv: readonly string[]): Parsed {\n const command = commandOf(argv);\n const spec = command === undefined ? undefined : COMMANDS[command];\n if (command !== undefined && spec === undefined) {\n throw new UsageError(`\"${command}\" is not a command; run spec-brief --help`);\n }\n try {\n const { values, positionals } = parseArgs({\n args: [...argv],\n options: { ...GLOBAL, ...(spec?.options ?? {}) },\n allowPositionals: true,\n strict: true,\n });\n return { command, values: values as Values, positionals: positionals.slice(1) };\n } catch (error) {\n throw new UsageError((error as Error).message);\n }\n}\n\nfunction text(values: Values, key: string): string | undefined {\n const value = values[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction flag(values: Values, key: string): boolean {\n return values[key] === true;\n}\n\nfunction integer(values: Values, key: string, minimum: number): number | undefined {\n const raw = text(values, key);\n if (raw === undefined) return undefined;\n if (!/^\\d+$/.test(raw) || Number(raw) < minimum) throw new UsageError(`--${key} must be a whole number of at least ${minimum}, not \"${raw}\"`);\n return Number(raw);\n}\n\nasync function version(): Promise<string> {\n const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')) as { version: string };\n return pkg.version;\n}\n\ninterface Run {\n /** Today, as the environment the run was given says. */\n readonly today: string;\n readonly out: (text: string) => void;\n readonly err: (text: string) => void;\n readonly style: Style;\n readonly format: Format;\n readonly strict: boolean;\n readonly version: string;\n readonly values: Values;\n readonly positionals: readonly string[];\n readonly cwd: string;\n}\n\nfunction useColor(values: Values, io: CliIO): boolean {\n if (flag(values, 'no-color')) return false;\n if (flag(values, 'color')) return true;\n const env = io.env ?? process.env;\n if ((env['NO_COLOR'] ?? '') !== '') return false;\n const force = env['FORCE_COLOR'];\n if (force !== undefined && force !== '') return force !== '0';\n return (io.stdout ?? process.stdout).isTTY === true;\n}\n\nasync function openEngine(run: Run): Promise<BriefEngine> {\n const root = text(run.values, 'root');\n const config = text(run.values, 'config');\n return BriefEngine.open({\n cwd: root === undefined ? run.cwd : root,\n ...(config === undefined ? {} : { config }),\n ...(flag(run.values, 'no-git') ? { git: null } : {}),\n noConfig: flag(run.values, 'no-config'),\n });\n}\n\nfunction emitFindings(run: Run, command: string, findings: readonly Finding[], checked: number, extra: Record<string, unknown> = {}): number {\n const failed = failing(findings, run.strict);\n switch (run.format) {\n case 'json':\n run.out(jsonDocument(command, run.version, { ok: !failed, checked, summary: summarise(findings), findings: findings.map(findingJson), ...extra }));\n break;\n case 'sarif':\n run.out(sarif(findings, run.version));\n break;\n case 'github':\n run.out(githubCommands(findings));\n break;\n case 'pretty':\n if (findings.length > 0) run.out(`${prettyFindings(findings, run.style)}\\n\\n`);\n run.out(`${findings.length === 0 ? `${checked} brief(s) checked, no findings` : `${summaryLine(findings)} in ${checked} brief(s)`}\\n`);\n break;\n }\n return failed ? EXIT_FAILED : EXIT_OK;\n}\n\nasync function runInit(run: Run): Promise<number> {\n const root = text(run.values, 'root') ?? run.cwd;\n const fs = new NodeFileSystem(root);\n for (const name of CONFIG_FILES) {\n if (await fs.exists(name)) throw new UsageError(`${name} already exists here; edit it instead`);\n }\n const briefs = normalisePath(text(run.values, 'briefs') ?? 'briefs');\n const archive = normalisePath(text(run.values, 'archive') ?? `${briefs}/archive`);\n if (briefs === '' || archive === '' || briefs === archive) throw new UsageError('--briefs and --archive must be two directories');\n const config = `${JSON.stringify(initialConfig(briefs, archive), null, 2)}\\n`;\n await fs.write('.spec-brief.json', config);\n const written = ['.spec-brief.json'];\n for (const dir of [briefs, archive]) {\n const keep = `${dir}/.gitkeep`;\n if ((await fs.list(dir)) === null) {\n await fs.write(keep, '');\n written.push(keep);\n }\n }\n if (run.format === 'json') run.out(jsonDocument('init', run.version, { ok: true, written }));\n else run.out(`${written.map((w) => `wrote ${w}`).join('\\n')}\\nnext: spec-brief new \"<title>\"\\n`);\n return EXIT_OK;\n}\n\nasync function runNew(run: Run): Promise<number> {\n const title = run.positionals.join(' ').trim();\n if (title === '') throw new UsageError('new needs a title: spec-brief new \"<title>\"');\n const engine = await openEngine(run);\n const dependsOn = text(run.values, 'depends-on')\n ?.split(',')\n .map((d) => d.trim())\n .filter((d) => d !== '');\n const created = await engine.create({\n title,\n id: text(run.values, 'id'),\n type: text(run.values, 'type'),\n wave: integer(run.values, 'wave', 0),\n dependsOn,\n date: text(run.values, 'date') ?? run.today,\n });\n if (run.format === 'json') run.out(jsonDocument('new', run.version, { ok: true, file: created.file }));\n else run.out(`wrote ${created.file}\\n`);\n return EXIT_OK;\n}\n\nasync function runLint(run: Run): Promise<number> {\n const engine = await openEngine(run);\n engine.requireBriefs();\n const findings = await engine.lint(run.positionals);\n const checked = run.positionals.length === 0 ? engine.corpus.briefs.length : run.positionals.length;\n return emitFindings(run, 'lint', findings, checked);\n}\n\nasync function runList(run: Run): Promise<number> {\n const engine = await openEngine(run);\n engine.requireBriefs();\n const briefs = flag(run.values, 'archived') ? engine.corpus.briefs : engine.corpus.live;\n const ready = new Set(engine.ready());\n const rows: ListRow[] = briefs\n .map((brief) => ({ brief, ready: ready.has(brief), waitingOn: engine.waitingOn(brief) }))\n .filter((row) => !flag(run.values, 'ready') || row.ready);\n if (run.format === 'json') {\n run.out(jsonDocument('list', run.version, { ok: true, briefs: rows.map((r) => briefJson(r.brief, { ready: r.ready, waitingOn: r.waitingOn })) }));\n } else {\n run.out(`${prettyList(rows, run.style)}\\n`);\n }\n return EXIT_OK;\n}\n\nasync function runMatrix(run: Run): Promise<number> {\n const engine = await openEngine(run);\n engine.requireBriefs();\n const { report, findings } = await engine.collisions({ all: flag(run.values, 'all-waves') });\n const sorted = sortFindings(findings);\n if (run.format === 'pretty') {\n run.out(`${prettyMatrix(report, run.style)}\\n`);\n return failing(sorted, run.strict) ? EXIT_FAILED : EXIT_OK;\n }\n const waves = report.waves.map((w) => ({\n wave: w.wave,\n briefs: w.briefs.map((b) => b.id),\n collisions: w.collisions.map((c) => ({ a: c.a.id, b: c.b.id, patterns: c.patterns, witness: c.witness })),\n sharedDirectories: w.shared.map((s) => ({ a: s.a.id, b: s.b.id, directory: s.directory })),\n unscoped: w.unscoped.map((b) => b.id),\n }));\n return emitFindings(run, 'matrix', sorted, engine.corpus.live.length, { waves, unscheduled: report.unscheduled.map((b) => b.id) });\n}\n\nasync function runTransition(run: Run, action: 'archive' | 'unarchive'): Promise<number> {\n const reference = run.positionals[0];\n if (reference === undefined || run.positionals.length > 1) throw new UsageError(`${action} takes one brief: spec-brief ${action} <brief>`);\n const engine = await openEngine(run);\n const dryRun = flag(run.values, 'dry-run');\n const plan: Plan =\n action === 'archive'\n ? await engine.planArchive(reference, {\n pr: integer(run.values, 'pr', 1),\n commit: text(run.values, 'commit'),\n base: text(run.values, 'base'),\n summary: text(run.values, 'summary'),\n date: text(run.values, 'date') ?? run.today,\n allowDirty: flag(run.values, 'allow-dirty'),\n strict: run.strict,\n noGit: flag(run.values, 'no-git'),\n })\n : engine.planUnarchive(reference);\n const refused = plan.blocking.length > 0;\n if (!refused && !dryRun) await engine.apply(plan);\n if (run.format === 'json') run.out(jsonDocument(action, run.version, { ok: !refused, dryRun, plan: planJson(plan) }));\n else run.out(`${prettyPlan(plan, dryRun, run.style)}\\n`);\n return refused ? EXIT_FAILED : EXIT_OK;\n}\n\nexport async function main(argv: readonly string[] = process.argv.slice(2), io: CliIO = {}): Promise<number> {\n const stdout = io.stdout ?? process.stdout;\n const stderr = io.stderr ?? process.stderr;\n const err = (message: string): void => {\n stderr.write(`spec-brief: ${message}\\n`);\n };\n let parsed: Parsed;\n try {\n parsed = parse(argv);\n } catch (error) {\n err((error as Error).message);\n return EXIT_ERROR;\n }\n const { command, values, positionals } = parsed;\n if (flag(values, 'version')) {\n stdout.write(`${await version()}\\n`);\n return EXIT_OK;\n }\n if (flag(values, 'help') || command === undefined) {\n stdout.write(HELP);\n return command === undefined && !flag(values, 'help') ? EXIT_ERROR : EXIT_OK;\n }\n const spec = COMMANDS[command] as (typeof COMMANDS)[string];\n const format = (text(values, 'format') ?? 'pretty') as Format;\n if (!spec.formats.includes(format)) {\n err(`--format for ${command} is one of ${spec.formats.join(', ')}, not \"${format}\"`);\n return EXIT_ERROR;\n }\n const run: Run = {\n today: today(io.env ?? process.env),\n out: (t) => {\n stdout.write(t);\n },\n err,\n style: { color: format === 'pretty' && useColor(values, io) },\n format,\n strict: flag(values, 'strict'),\n version: await version(),\n values,\n positionals,\n cwd: io.cwd ?? process.cwd(),\n };\n try {\n switch (command) {\n case 'init':\n return await runInit(run);\n case 'new':\n return await runNew(run);\n case 'lint':\n return await runLint(run);\n case 'list':\n return await runList(run);\n case 'matrix':\n return await runMatrix(run);\n case 'archive':\n return await runTransition(run, 'archive');\n default:\n return await runTransition(run, 'unarchive');\n }\n } catch (error) {\n const expected = [ConfigError, UsageError, EngineError, ConflictError, TransactionError];\n if (expected.some((kind) => error instanceof kind)) {\n err((error as Error).message);\n return EXIT_ERROR;\n }\n err(`unexpected error: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);\n return EXIT_ERROR;\n }\n}\n"]}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The collision matrix: which briefs scheduled to run side by side declare
3
+ * scopes that can name the same file.
4
+ *
5
+ * Scopes are compared as globs, not as strings. `src/auth/**` and
6
+ * `src/**\/session.ts` share no prefix a string comparison would see and both
7
+ * cover `src/auth/session.ts`; the intersection finds that, and names the file.
8
+ * A brief that declares no scope cannot be proven apart from anything, so it
9
+ * is reported as unscoped rather than silently counted as safe.
10
+ */
11
+ import type { Brief } from './brief.js';
12
+ import type { Corpus } from './corpus.js';
13
+ import type { Finding } from './types.js';
14
+ export interface Collision {
15
+ readonly a: Brief;
16
+ readonly b: Brief;
17
+ readonly patterns: readonly [string, string];
18
+ /** A path both scopes cover. */
19
+ readonly witness: string;
20
+ }
21
+ export interface SharedDirectory {
22
+ readonly a: Brief;
23
+ readonly b: Brief;
24
+ readonly directory: string;
25
+ }
26
+ export interface WaveMatrix {
27
+ /** `null` when every live brief is compared regardless of wave. */
28
+ readonly wave: number | null;
29
+ readonly briefs: readonly Brief[];
30
+ readonly collisions: readonly Collision[];
31
+ readonly shared: readonly SharedDirectory[];
32
+ readonly unscoped: readonly Brief[];
33
+ }
34
+ export interface CollisionReport {
35
+ readonly waves: readonly WaveMatrix[];
36
+ /** Live briefs with no wave, which the matrix does not place. */
37
+ readonly unscheduled: readonly Brief[];
38
+ }
39
+ export interface CollisionOptions {
40
+ /** Compare every live brief with every other, whatever its wave. */
41
+ readonly all?: boolean;
42
+ /** Tracked files, which tell a literal file path from a directory. */
43
+ readonly repoFiles?: readonly string[] | null;
44
+ }
45
+ export declare function collisions(corpus: Corpus, options?: CollisionOptions): CollisionReport;
46
+ /**
47
+ * The report as findings. One collision is one finding, placed on the later
48
+ * brief of the pair, which is usually the one still being written.
49
+ */
50
+ export declare function collisionFindings(corpus: Corpus, report: CollisionReport): Finding[];