@descent-vtt/spec-brief 0.10.0 → 0.11.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 +23 -0
- package/README.md +1 -0
- package/dist/apply.d.ts +3 -2
- package/dist/apply.js +12 -3
- package/dist/apply.js.map +1 -1
- package/dist/engine.js +16 -9
- package/dist/engine.js.map +1 -1
- package/dist/fs.d.ts +25 -3
- package/dist/fs.js +39 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins.js +19 -4
- package/dist/plugins.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,29 @@ release that can turn a run red. This is the family's policy, recorded in
|
|
|
11
11
|
releases up to 0.2.7 came before it, and several of their patches changed
|
|
12
12
|
what a run reports.
|
|
13
13
|
|
|
14
|
+
## 0.11.0
|
|
15
|
+
|
|
16
|
+
A template, a plugin, or what stands where `archive` or `unarchive`
|
|
17
|
+
would write a brief is read only where it is a file, or a link to one:
|
|
18
|
+
a named pipe there had a run wait without end, and a directory or a
|
|
19
|
+
socket ended it on a stack. Each is now refused, exit 2, before it is
|
|
20
|
+
opened.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **A template, a plugin, or what stands where a brief would move, is read
|
|
25
|
+
only where it is a file.** A named pipe as the `template`, where
|
|
26
|
+
`archive` or `unarchive` would write the brief, or as a plugin's module or
|
|
27
|
+
the `package.json` its package is found by, had the run wait without end;
|
|
28
|
+
a link to `/dev/null` as the template wrote a brief of no bytes; a
|
|
29
|
+
directory or a socket ended the run on Node's error and a stack. Each is
|
|
30
|
+
now exit 2 with one line before it is opened, nothing written, as a
|
|
31
|
+
configuration has been since 0.10.0. A link is read as what it names
|
|
32
|
+
([An input that names nothing](README.md#an-input-that-names-nothing);
|
|
33
|
+
ADR-0003, amended 2026-10-24). Upgrading: nothing to change where each is
|
|
34
|
+
a file, or a link to one; a template that is a link to `/dev/null` now
|
|
35
|
+
refuses, so point `template` at a file, or set it to `null`.
|
|
36
|
+
|
|
14
37
|
## 0.10.0
|
|
15
38
|
|
|
16
39
|
One value written two ways is taken once in three more cases, each of
|
package/README.md
CHANGED
|
@@ -306,6 +306,7 @@ An option, an argument or a path that is given and names nothing is refused: exi
|
|
|
306
306
|
- **An argument a command does not take.** `init`, `list`, `matrix` and `schedule` take none and refuse one by name, with their usage. `init mydir` set up the current directory; `init --root mydir` is what sets up another.
|
|
307
307
|
- **A `--root` that is no directory.** A file, or a path that does not exist, is refused: the search for a configuration would go on from its parent, and the run would report on, or `new` write into, the tree above. A directory below the configuration's is a place to start from, as the current directory is.
|
|
308
308
|
- **A configuration that is not a file**, named by `--config` or found under the configuration's name, does not load, and is said so in a line before it is opened, as one that is not JSON is: a directory, a named pipe, which was read without end, a socket or a device. A link is read as what it names.
|
|
309
|
+
- **A template, a plugin or what stands where a brief would move that is not a file** is refused in a line before it is opened, and nothing is written: the `template` a configuration names, a plugin's module and each `package.json` spec-brief reads to find one, and what stands where `archive` or `unarchive` would write the brief, which a plan made from the files a listing names cannot have seen. A named pipe there had the run wait without end, a link to `/dev/null` as the template wrote a brief of no bytes, and a directory or a socket ended the run on a stack. A link is read as what it names.
|
|
309
310
|
|
|
310
311
|
### An option a command does not read
|
|
311
312
|
|
package/dist/apply.d.ts
CHANGED
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
* which `lint` reports by name.
|
|
13
13
|
*/
|
|
14
14
|
import type { FileOp } from './archive.js';
|
|
15
|
-
import type
|
|
15
|
+
import { type FileSystem, type NotAFile } from './fs.js';
|
|
16
16
|
export declare class ConflictError extends Error {
|
|
17
17
|
readonly path: string;
|
|
18
|
-
|
|
18
|
+
/** `found` is what stands at the path where it is not a file, which is not read. */
|
|
19
|
+
constructor(path: string, found?: NotAFile);
|
|
19
20
|
}
|
|
20
21
|
export declare class TransactionError extends Error {
|
|
21
22
|
/** Whether every completed operation was undone. */
|
package/dist/apply.js
CHANGED
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
* operation and the last, and a crash inside it leaves two briefs with one id,
|
|
12
12
|
* which `lint` reports by name.
|
|
13
13
|
*/
|
|
14
|
+
import { NotAFileError } from './fs.js';
|
|
14
15
|
export class ConflictError extends Error {
|
|
15
16
|
path;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/** `found` is what stands at the path where it is not a file, which is not read. */
|
|
18
|
+
constructor(path, found) {
|
|
19
|
+
super(found === undefined
|
|
20
|
+
? `${path} changed after the plan was made; nothing was written. Run the command again.`
|
|
21
|
+
: `${path} is ${found}; nothing was written. Move it out of the way, then run the command again.`);
|
|
18
22
|
this.name = 'ConflictError';
|
|
19
23
|
this.path = path;
|
|
20
24
|
}
|
|
@@ -35,7 +39,12 @@ export class TransactionError extends Error {
|
|
|
35
39
|
}
|
|
36
40
|
export async function applyPlan(fs, ops) {
|
|
37
41
|
for (const op of ops) {
|
|
38
|
-
const current = await fs.read(op.path)
|
|
42
|
+
const current = await fs.read(op.path).catch((error) => {
|
|
43
|
+
// A plan is made from the files a listing names, so what stands where
|
|
44
|
+
// it would write a new one can be a named pipe, read without end, or a
|
|
45
|
+
// directory. Nothing is written yet, and the refusal can say so.
|
|
46
|
+
throw error instanceof NotAFileError ? new ConflictError(op.path, error.what) : error;
|
|
47
|
+
});
|
|
39
48
|
if (current !== op.before)
|
|
40
49
|
throw new ConflictError(op.path);
|
|
41
50
|
}
|
package/dist/apply.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../src/apply.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Executing a plan as a transaction over files.\n *\n * Three phases. First every file is read back and compared with what the plan\n * saw, so a file somebody edited in the meantime stops the transaction before\n * anything changes. Then the operations run in order - the new file first, the\n * old one removed last - and each one that completes is recorded. If any\n * fails, the recorded ones are undone in reverse from the contents the plan\n * already holds. Each write is itself atomic (a rename over the target), so\n * the only window in which the tree shows both copies is between the first\n * operation and the last, and a crash inside it leaves two briefs with one id,\n * which `lint` reports by name.\n */\n\nimport type { FileOp } from './archive.js';\nimport { type FileSystem, type NotAFile, NotAFileError } from './fs.js';\n\nexport class ConflictError extends Error {\n readonly path: string;\n\n /** `found` is what stands at the path where it is not a file, which is not read. */\n constructor(path: string, found?: NotAFile) {\n super(\n found === undefined\n ? `${path} changed after the plan was made; nothing was written. Run the command again.`\n : `${path} is ${found}; nothing was written. Move it out of the way, then run the command again.`,\n );\n this.name = 'ConflictError';\n this.path = path;\n }\n}\n\nexport class TransactionError extends Error {\n /** Whether every completed operation was undone. */\n readonly rolledBack: boolean;\n readonly failures: readonly string[];\n\n constructor(cause: unknown, rolledBack: boolean, failures: readonly string[]) {\n const reason = cause instanceof Error ? cause.message : String(cause);\n super(\n rolledBack\n ? `${reason}; every change was rolled back`\n : `${reason}; the rollback also failed (${failures.join('; ')}), so check these files by hand`,\n );\n this.name = 'TransactionError';\n this.rolledBack = rolledBack;\n this.failures = failures;\n }\n}\n\nexport async function applyPlan(fs: FileSystem, ops: readonly FileOp[]): Promise<void> {\n for (const op of ops) {\n const current = await fs.read(op.path).catch((error: unknown) => {\n // A plan is made from the files a listing names, so what stands where\n // it would write a new one can be a named pipe, read without end, or a\n // directory. Nothing is written yet, and the refusal can say so.\n throw error instanceof NotAFileError ? new ConflictError(op.path, error.what) : error;\n });\n if (current !== op.before) throw new ConflictError(op.path);\n }\n\n const done: FileOp[] = [];\n try {\n for (const op of ops) {\n if (op.kind === 'write') await fs.write(op.path, op.content);\n else await fs.remove(op.path);\n done.push(op);\n }\n } catch (error) {\n const failures: string[] = [];\n for (const op of [...done].reverse()) {\n // Undoing is putting back what was there: a file that was absent is\n // removed, and one that was present - written over or removed - is\n // written again.\n try {\n if (op.before === null) await fs.remove(op.path);\n else await fs.write(op.path, op.before);\n } catch (undo) {\n failures.push(`${op.path}: ${undo instanceof Error ? undo.message : String(undo)}`);\n }\n }\n throw new TransactionError(error, failures.length === 0, failures);\n }\n}\n"]}
|
package/dist/engine.js
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
* Everything they compose is a pure function that takes those as arguments,
|
|
9
9
|
* so an engine can equally run over a `MemoryFileSystem` and no git at all.
|
|
10
10
|
*/
|
|
11
|
-
import { access
|
|
11
|
+
import { access } from 'node:fs/promises';
|
|
12
12
|
import { basename, dirname, relative, resolve, sep } from 'node:path';
|
|
13
13
|
import { applyPlan } from './apply.js';
|
|
14
14
|
import { applyWaivers, planArchive, planUnarchive, WAIVABLE, } from './archive.js';
|
|
15
15
|
import { collisionFindings, collisions } from './collisions.js';
|
|
16
16
|
import { ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig, typeSections } from './config.js';
|
|
17
17
|
import { buildCorpus, findBriefs, isReady, pendingDependencies } from './corpus.js';
|
|
18
|
-
import { canonicalPath, NodeFileSystem } from './fs.js';
|
|
18
|
+
import { canonicalPath, NodeFileSystem, NotAFileError } from './fs.js';
|
|
19
19
|
import { NodeGit, pullRequestUrl } from './git.js';
|
|
20
20
|
import { matchGlob, parseGlob } from './glob.js';
|
|
21
21
|
import { normalisePath } from './links.js';
|
|
@@ -158,11 +158,9 @@ export class BriefEngine {
|
|
|
158
158
|
// read without end, by every command; and a directory, found or named
|
|
159
159
|
// with --config, ended the run on Node's EISDIR. Each is a
|
|
160
160
|
// configuration that does not load, and no defect to report a stack for.
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
const text = await new NodeFileSystem(root, reads).read(basename(configPath));
|
|
161
|
+
const text = await new NodeFileSystem(root, reads).read(basename(configPath)).catch((error) => {
|
|
162
|
+
throw error instanceof NotAFileError ? new ConfigError(relative(cwd, configPath), [`is ${error.what}, not a configuration file`]) : error;
|
|
163
|
+
});
|
|
166
164
|
if (text === null)
|
|
167
165
|
throw new EngineError('not-found', `${configPath} does not exist`);
|
|
168
166
|
config = parseConfig(text, relative(cwd, configPath));
|
|
@@ -211,7 +209,8 @@ export class BriefEngine {
|
|
|
211
209
|
if (!isBrief(name))
|
|
212
210
|
continue;
|
|
213
211
|
const path = dir === '' ? name : `${dir}/${name}`;
|
|
214
|
-
|
|
212
|
+
// The listing named it a file, so the read does not ask again.
|
|
213
|
+
const text = await this.fs.read(path, true);
|
|
215
214
|
// A file listed a moment ago and gone now was removed under the run; there is nothing to read.
|
|
216
215
|
if (text !== null)
|
|
217
216
|
files.push({ path, text, phase });
|
|
@@ -458,7 +457,15 @@ export class BriefEngine {
|
|
|
458
457
|
const date = options.date ?? today();
|
|
459
458
|
if (!isDate(date))
|
|
460
459
|
throw new EngineError('invalid', `"${date}" is not a date written YYYY-MM-DD`);
|
|
461
|
-
const
|
|
460
|
+
const named = this.config.template;
|
|
461
|
+
const template = named === null
|
|
462
|
+
? null
|
|
463
|
+
: await this.fs.read(named).catch((error) => {
|
|
464
|
+
// A named pipe was read without end, and a directory or a socket
|
|
465
|
+
// ended the run on Node's error and a stack: each is a template
|
|
466
|
+
// the configuration names wrongly, and said so in a line.
|
|
467
|
+
throw error instanceof NotAFileError ? new EngineError('invalid', `the template ${named} is ${error.what}; point "template" at a file, or set it to null`) : error;
|
|
468
|
+
});
|
|
462
469
|
if (this.config.template !== null && template === null) {
|
|
463
470
|
throw new EngineError('not-found', `the template ${this.config.template} does not exist`);
|
|
464
471
|
}
|
package/dist/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The engine: one repository's briefs, with the operations the CLI offers and\n * the same ones for a harness or an orchestrator that would rather call a\n * function than parse a terminal.\n *\n * This module and the CLI are where the pieces meet their implementations -\n * the real filesystem, the real git, the plugins a configuration names.\n * Everything they compose is a pure function that takes those as arguments,\n * so an engine can equally run over a `MemoryFileSystem` and no git at all.\n */\n\nimport { access, stat } from 'node:fs/promises';\nimport { basename, dirname, relative, resolve, sep } from 'node:path';\n\nimport { applyPlan } from './apply.js';\nimport {\n applyWaivers,\n planArchive,\n type Plan,\n planUnarchive,\n type PullRequest,\n type UnarchiveRequest,\n type Unmeasured,\n WAIVABLE,\n type Waiver,\n} from './archive.js';\nimport type { Brief } from './brief.js';\nimport { collisionFindings, collisions, type CollisionOptions, type CollisionReport } from './collisions.js';\nimport { type Config, ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig, typeSections } from './config.js';\nimport { buildCorpus, type Corpus, findBriefs, isReady, pendingDependencies, type SourceFile } from './corpus.js';\nimport { canonicalPath, type FileSystem, NodeFileSystem, type Reads } from './fs.js';\nimport { type CommitInfo, type FileChange, type Git, NodeGit, pullRequestUrl } from './git.js';\nimport { matchGlob, parseGlob } from './glob.js';\nimport { normalisePath } from './links.js';\nimport { lint, type Plugin } from './lint.js';\nimport { asWaivers, loadPlugins } from './plugins.js';\nimport { fileNameFor, nextId, renderNewBrief } from './scaffold.js';\nimport { planWaves, type Schedule, schedule, scheduleFindings, type ScheduleOptions } from './schedule.js';\nimport type { Finding, Phase } from './types.js';\n\nexport class EngineError extends Error {\n readonly code: 'not-found' | 'ambiguous' | 'invalid' | 'refused' | 'exists';\n\n constructor(code: EngineError['code'], message: string) {\n super(message);\n this.name = 'EngineError';\n this.code = code;\n }\n}\n\nexport interface EngineSetup {\n /** Absolute root: the directory holding the configuration. */\n readonly root: string;\n readonly config: Config;\n /** Repository-relative configuration path, or `null` when the defaults apply. */\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n readonly plugins?: readonly Plugin[];\n}\n\nexport interface OpenOptions {\n /** Where discovery starts. Defaults to the process's directory. */\n readonly cwd?: string;\n /** An explicit configuration file; discovery is skipped. */\n readonly config?: string | undefined;\n /** Use the defaults even where a configuration file exists. */\n readonly noConfig?: boolean;\n /** `null` runs without git. Defaults to git when the root is inside a working tree. */\n readonly git?: Git | null;\n readonly fs?: FileSystem;\n /**\n * `false` where nothing the caller asks of the engine reads git, as `list`\n * asks it nothing: where a configuration file names the root, git is then\n * not asked where the work tree's top is, and the engine has no git.\n * Without one, git is still asked, since its answer is the root.\n */\n readonly needsGit?: boolean;\n}\n\n/**\n * Everything an answer was read from, for a tool that would give it again\n * without starting spec-brief (ADR-0002, amended 2026-10-19). Each file is\n * named with the SHA-256 of its bytes, or `null` where it was looked for and\n * was not there; each directory with the names of the files in it, sorted,\n * or `null` where there was no directory to list. Each `null` is a path\n * that must still not be there. Paths are from the root, which wherever `keep` is true is the directory\n * the run started in. `git` is `null`, since no answer named here asks git\n * anything. `keep` is `false`, and the lists are empty, wherever that cannot\n * be named whole.\n */\nexport interface Inputs {\n readonly keep: boolean;\n readonly files: readonly { readonly path: string; readonly sha256: string | null }[];\n readonly directories: readonly { readonly path: string; readonly entries: readonly string[] | null }[];\n readonly git: null;\n}\n\nexport interface ArchiveOptions {\n readonly pr?: number | undefined;\n /** The commit the round landed as. */\n readonly commit?: string | undefined;\n /** The branch the round started from; the diff runs from its merge base. */\n readonly base?: string | undefined;\n readonly summary?: string | undefined;\n readonly date?: string | undefined;\n readonly allowDirty?: boolean | undefined;\n readonly strict?: boolean | undefined;\n /** Leave git out, even where there is a repository. */\n readonly noGit?: boolean | undefined;\n}\n\nexport interface NewOptions {\n readonly title: string;\n readonly id?: string | undefined;\n readonly type?: string | undefined;\n readonly wave?: number | undefined;\n readonly dependsOn?: readonly string[] | undefined;\n readonly date?: string | undefined;\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n // Absent, or not to be reached: the search goes on either way. An empty\n // catch would answer `undefined`, which the search reads the same.\n return false;\n }\n}\n\n/** The last second of 9999-12-31, the last date written YYYY-MM-DD. */\nconst LAST_SECOND = 253402300799;\n\n/**\n * Today, in UTC. `SOURCE_DATE_EPOCH` wins where it is set, which is what makes\n * a banner written in a reproducible pipeline reproducible.\n *\n * Set, it is what its specification says it is: whole seconds since\n * 1970-01-01 in digits alone, as `date +%s` prints them, and here no later\n * than the last date written YYYY-MM-DD. Anything else is refused by name,\n * an empty value too, which is what a command substitution that failed\n * leaves. Read as unset, it had the run write the clock's date where a\n * pipeline asked for its own, and say nothing.\n */\nexport function today(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const epoch = env['SOURCE_DATE_EPOCH'];\n let when = new Date();\n if (epoch !== undefined) {\n // Quoted as JSON, so a newline or a tab in it is seen, and the refusal stays one line.\n const held = `SOURCE_DATE_EPOCH is ${JSON.stringify(epoch)}`;\n if (!/^\\d+$/.test(epoch)) {\n throw new EngineError('invalid', `${held}, which is not a whole number of seconds since 1970-01-01; set it to one in digits alone, as \"date +%s\" prints it, or unset it`);\n }\n if (Number(epoch) > LAST_SECOND) {\n throw new EngineError('invalid', `${held}, which is after 9999-12-31, the last date written YYYY-MM-DD; set it to ${LAST_SECOND} or less, or unset it`);\n }\n when = new Date(Number(epoch) * 1000);\n }\n return when.toISOString().slice(0, 10);\n}\n\n/**\n * A deep copy of a value in which every object and array is frozen. Plain\n * data only, as `structuredClone` takes it; the recursion ends at the data's\n * own depth.\n */\nfunction frozenCopy<T>(value: T): T {\n const freeze = (item: unknown): void => {\n if (typeof item !== 'object' || item === null) return;\n for (const child of Object.values(item)) freeze(child);\n Object.freeze(item);\n };\n const copy = structuredClone(value);\n freeze(copy);\n return copy;\n}\n\nexport function isDate(text: string): boolean {\n // The shape keeps out the one other text that survives the round trip\n // below: a year past 9999 with a month, \"+010000-01\". Its anchors are for\n // the reader, since nothing longer than a date comes back as it went in.\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(text)) return false;\n const parsed = new Date(`${text}T00:00:00Z`);\n return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === text;\n}\n\nexport class BriefEngine {\n readonly root: string;\n readonly config: Config;\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n private plugins: readonly Plugin[] | undefined;\n private loaded: Corpus | undefined;\n /**\n * What the engine has read, where `open` made it, and whether that is\n * every input of an answer made of the briefs and the configuration alone.\n * Set false by a read that is not on it: the files git or a walk lists,\n * and a plugin's module.\n */\n private reading: { readonly reads: Reads; whole: boolean } | undefined;\n /** Known once the briefs are read, which every command does before it asks. */\n private missingBriefs: boolean | undefined;\n\n constructor(setup: EngineSetup) {\n this.root = setup.root;\n this.config = setup.config;\n this.configFile = setup.configFile;\n this.fs = setup.fs;\n this.git = setup.git;\n this.plugins = setup.plugins;\n }\n\n /**\n * Finds the configuration, the root it defines and the repository around it,\n * and reads every brief. The root is the directory holding the\n * configuration; without one it is the working tree's top, or `cwd`.\n */\n static async open(options: OpenOptions = {}): Promise<BriefEngine> {\n // Named as git names them, so the root and the work tree compare.\n const cwd = await canonicalPath(resolve(options.cwd ?? process.cwd()));\n const explicit = options.config === undefined ? undefined : await canonicalPath(resolve(cwd, options.config));\n // Each name looked for, so that an answer can say which were not there.\n const looked: string[] = [];\n const configPath =\n options.noConfig === true\n ? undefined\n : (explicit ??\n (await locateConfig(cwd, (path) => {\n looked.push(path);\n return exists(path);\n })));\n // Asking git costs a process; without git there is nothing to ask, and\n // with a root a configuration file names nothing to ask it where the\n // caller reads no git.\n const asksGit = options.git !== null && (configPath === undefined || options.needsGit !== false);\n const toplevel = asksGit ? await NodeGit.toplevel(cwd) : null;\n const root = configPath === undefined ? (toplevel ?? cwd) : dirname(configPath);\n const reads: Reads = { files: new Map(), directories: new Map() };\n // Each is named as not there, and the one found again by its read below.\n // Wherever an answer is kept, each was looked for in the root itself, so\n // its path from there is its name.\n for (const path of looked) reads.files.set(relative(root, path), null);\n let config = DEFAULT_CONFIG;\n if (configPath !== undefined) {\n // Only a file is read. A named pipe under the configuration's name was\n // read without end, by every command; and a directory, found or named\n // with --config, ended the run on Node's EISDIR. Each is a\n // configuration that does not load, and no defect to report a stack for.\n const found = await stat(configPath).catch(() => null);\n if (found !== null && !found.isFile()) {\n throw new ConfigError(relative(cwd, configPath), [`is ${found.isDirectory() ? 'a directory' : 'neither a file nor a directory'}, not a configuration file`]);\n }\n const text = await new NodeFileSystem(root, reads).read(basename(configPath));\n if (text === null) throw new EngineError('not-found', `${configPath} does not exist`);\n config = parseConfig(text, relative(cwd, configPath));\n }\n // Outside the work tree is its parent or a path through it, \"..\", \"../x\":\n // a directory inside it may itself be named \"..drafts\".\n const fromTop = toplevel === null ? null : relative(toplevel, root);\n const inTree = fromTop !== null && fromTop !== '..' && !fromTop.startsWith(`..${sep}`);\n const engine = new BriefEngine({\n root,\n config,\n // The root is the configuration's directory, so its path from there is its name.\n configFile: configPath === undefined ? null : basename(configPath),\n fs: options.fs ?? new NodeFileSystem(root, reads),\n git: options.git === undefined ? (inTree ? new NodeGit(root) : null) : options.git,\n });\n // Named whole only where each input is read from the directory the run\n // started in, so that a reader finds it by the path named: the engine\n // reads through its own disk, the root is that directory, and a\n // configuration file there names it, or neither a configuration nor git\n // was asked. A configuration not found was looked for above the root as\n // well, and a root git names is decided by what git reads to find the\n // top of a work tree, which no list here names.\n const whole = options.fs === undefined && !asksGit && root === cwd && (configPath !== undefined || options.noConfig === true);\n engine.reading = { reads, whole };\n await engine.load();\n return engine;\n }\n\n get corpus(): Corpus {\n if (this.loaded === undefined) throw new Error('the engine has not loaded its briefs; call load() first');\n return this.loaded;\n }\n\n /** Reads every brief again, as after an archival. */\n async load(): Promise<Corpus> {\n const files: SourceFile[] = [];\n const accept = parseGlob(this.config.files);\n const reject = this.config.exclude.map((pattern) => parseGlob(pattern));\n const isBrief = (name: string): boolean =>\n accept.ok && matchGlob(accept.glob, name) && !reject.some((r) => r.ok && matchGlob(r.glob, name));\n const read = async (directory: string, phase: Phase): Promise<boolean> => {\n const dir = normalisePath(directory);\n const names = await this.fs.list(dir);\n if (names === null) return false;\n for (const name of names) {\n if (!isBrief(name)) continue;\n const path = dir === '' ? name : `${dir}/${name}`;\n const text = await this.fs.read(path);\n // A file listed a moment ago and gone now was removed under the run; there is nothing to read.\n if (text !== null) files.push({ path, text, phase });\n }\n return true;\n };\n this.missingBriefs = !(await read(this.config.briefs, 'live'));\n await read(this.config.archive, 'archived');\n this.loaded = buildCorpus(files, this.config);\n return this.loaded;\n }\n\n /**\n * Refuses to report on a briefs directory that does not exist. A check over\n * nothing looks exactly like a clean one, and the likelier cause is a\n * configuration pointing at the wrong place.\n */\n requireBriefs(): void {\n if (this.missingBriefs) {\n throw new EngineError('not-found', `${normalisePath(this.config.briefs)}/ does not exist; run \"spec-brief init\", or set \"briefs\" in the configuration`);\n }\n }\n\n /** The plugins the configuration names, loaded once. */\n async loadedPlugins(): Promise<readonly Plugin[]> {\n if (this.reading !== undefined) this.reading.whole = false;\n this.plugins ??= await loadPlugins(this.config.plugins, this.root);\n return this.plugins;\n }\n\n /** The one brief a reference names: an id, a file name or a path. */\n find(reference: string): Brief {\n const found = findBriefs(this.corpus, reference);\n if (found.length === 0) throw new EngineError('not-found', `no brief is named \"${reference}\"`);\n if (found.length > 1) {\n throw new EngineError('ambiguous', `\"${reference}\" names ${found.length} briefs: ${found.map((b) => b.file).join(', ')}`);\n }\n return found[0] as Brief;\n }\n\n /** The files git sees, or every file when there is no git; `null` when neither can be read. */\n async repoFiles(): Promise<string[] | null> {\n if (this.reading !== undefined) this.reading.whole = false;\n try {\n return this.git === null ? await this.fs.walk() : await this.git.files();\n } catch {\n return null;\n }\n }\n\n async lint(references: readonly string[] = []): Promise<Finding[]> {\n const only = references.length === 0 ? undefined : references.map((r) => this.find(r));\n return lint(this.corpus, { plugins: await this.loadedPlugins(), repoFiles: await this.repoFiles(), ...(only ? { only } : {}) });\n }\n\n async collisions(options: CollisionOptions = {}): Promise<{ report: CollisionReport; findings: Finding[] }> {\n const report = collisions(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { report, findings: collisionFindings(this.corpus, report) };\n }\n\n async schedule(options: ScheduleOptions = {}): Promise<{ schedule: Schedule; findings: Finding[] }> {\n const computed = schedule(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { schedule: computed, findings: scheduleFindings(this.corpus, computed) };\n }\n\n /**\n * Writes each brief's proposed wave into its front matter, every other line\n * as it was, as one transaction. Refused, with nothing written, when the\n * dependencies form a cycle or a front matter cannot be edited.\n */\n async writeWaves(computed: Schedule): Promise<{ written: string[]; refused: Finding[] }> {\n const plan = planWaves(computed);\n if (computed.cycles.length > 0 || plan.refused.length > 0) return { written: [], refused: [...plan.refused] };\n await applyPlan(this.fs, plan.ops);\n await this.load();\n return { written: plan.ops.map((op) => op.path), refused: [] };\n }\n\n /**\n * Every input of an answer made of the briefs and the configuration alone,\n * as `list` gives one, read since the engine was opened (ADR-0002, amended\n * 2026-10-19).\n */\n inputs(): Inputs {\n const reading = this.reading;\n if (reading?.whole !== true) return { keep: false, files: [], directories: [], git: null };\n // Paths are unique, so no two compare equal.\n const byPath = (a: { readonly path: string }, b: { readonly path: string }): number => (a.path < b.path ? -1 : 1);\n return {\n keep: true,\n files: [...reading.reads.files].map(([path, sha256]) => ({ path, sha256 })).sort(byPath),\n // The root is \".\" where a briefs directory is the root itself. One that\n // was not there, an archive not yet made, is named with no entries: one\n // made with a brief in it changes what `list --archived` answers.\n directories: [...reading.reads.directories]\n .map(([path, entries]) => ({ path: path === '' ? '.' : path, entries: entries === null ? null : [...entries].sort() }))\n .sort(byPath),\n git: null,\n };\n }\n\n /** Live briefs that can run now: every dependency archived, and neither a draft nor deferred. */\n ready(): Brief[] {\n return this.corpus.live.filter((b) => isReady(this.corpus, b));\n }\n\n /** The live dependencies holding a brief back. */\n waitingOn(brief: Brief): string[] {\n return pendingDependencies(this.corpus, brief);\n }\n\n async planArchive(reference: string, options: ArchiveOptions = {}): Promise<Plan> {\n const brief = this.find(reference);\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n if (options.noGit === true && (options.commit !== undefined || options.base !== undefined)) {\n throw new EngineError('invalid', 'a commit or a base needs git; drop --no-git, or drop --commit and --base');\n }\n const git = options.noGit === true ? null : this.git;\n const base = options.base ?? this.config.archiving.base ?? undefined;\n const findings = await this.lint();\n\n let commit: CommitInfo | undefined;\n let changes: FileChange[] | undefined;\n let unmeasured: Unmeasured | undefined;\n let dirty: string[] | undefined;\n let pr: PullRequest | undefined;\n if (git !== null) {\n const revision = options.commit ?? (base === undefined ? undefined : 'HEAD');\n if (revision !== undefined) {\n commit = (await git.commit(revision)) ?? undefined;\n if (commit === undefined) throw new EngineError('not-found', `\"${revision}\" names no commit`);\n let from: string | null = null;\n if (base !== undefined) {\n from = await git.mergeBase(base, commit.sha);\n // No merge base: a base with a history of its own is diffed from\n // directly, and one that names nothing is a typo to report, not a\n // diff for git to fail.\n if (from === null) {\n if ((await git.commit(base)) === null) {\n throw new EngineError('not-found', `the base \"${base}\" names no commit; check --base and \"archiving.base\"`);\n }\n from = base;\n }\n }\n // A commit that is its own merge base with the base branch is already\n // in it: the diff is empty whatever the round changed, and an empty\n // diff would pass every scope check without measuring one.\n if (from === commit.sha) unmeasured = { reason: 'merged', base: base as string, commit: commit.sha };\n else changes = await git.changes(from, commit.sha);\n }\n // With neither a commit nor a base, nothing was read, and the planner\n // says so itself of a request that brings no changes and no reason.\n dirty = await git.dirty();\n } else {\n unmeasured = { reason: 'git' };\n }\n if (options.pr !== undefined) {\n pr = { number: options.pr, url: git === null ? null : pullRequestUrl(await git.remoteUrl('origin'), options.pr) };\n }\n const plan = planArchive(this.corpus, brief, {\n date,\n summary: options.summary,\n pr,\n commit,\n changes,\n unmeasured,\n dirty,\n allowDirty: options.allowDirty,\n strict: options.strict,\n findings,\n });\n return this.waive(plan, base ?? null, commit?.sha ?? null, options.strict === true);\n }\n\n /**\n * Asks each plugin with a `waive` hook which of a plan's refusals its own\n * check lifts, and turns those into notes. Asked only when the plan has a\n * refusal a plugin may lift: a hook may read git and verify signatures, and a\n * plan with nothing to waive has no question for it. A hook that throws, or\n * answers in the wrong shape, stops the run as a plugin that fails to load\n * does: an archival decided without the check it asked for is not one to\n * trust.\n *\n * A hook is handed a frozen copy of the refusals, and its waivers are\n * matched against the plan's own. Handed the plan's list, a hook could drop\n * a refusal from it, or relabel an open task as a protected file and waive\n * that, and so lift what only spec-brief may; writing to the copy throws,\n * which stops the run like any failing hook.\n */\n private async waive(plan: Plan, base: string | null, commit: string | null, strict: boolean): Promise<Plan> {\n if (!plan.blocking.some((f) => WAIVABLE.includes(f.rule))) return plan;\n const context = frozenCopy({\n root: this.root,\n brief: { id: plan.brief.id, file: plan.brief.file, text: plan.brief.text },\n findings: plan.blocking,\n base,\n commit,\n });\n const waivers: { plugin: string; waiver: Waiver }[] = [];\n for (const plugin of await this.loadedPlugins()) {\n if (plugin.waive === undefined) continue;\n let answer: unknown;\n try {\n answer = await plugin.waive(context);\n } catch (error) {\n throw new ConfigError(`plugin \"${plugin.name}\"`, [`\"waive\" failed: ${error instanceof Error ? error.message : String(error)}`]);\n }\n for (const waiver of asWaivers(answer, plugin.name)) waivers.push({ plugin: plugin.name, waiver });\n }\n return applyWaivers(plan, waivers, strict);\n }\n\n planUnarchive(reference: string, options: UnarchiveRequest = {}): Plan {\n return planUnarchive(this.corpus, this.find(reference), options);\n }\n\n /** Executes a plan and reads the briefs again. A refused plan is never applied. */\n async apply(plan: Plan): Promise<void> {\n if (plan.blocking.length > 0) {\n throw new EngineError('refused', `${plan.action} of ${plan.brief.file} is refused: ${(plan.blocking[0] as Finding).message}`);\n }\n if (plan.done) return;\n await applyPlan(this.fs, plan.ops);\n await this.load();\n }\n\n /** Scaffolds a brief and writes it; the file is new, or nothing is written. */\n async create(options: NewOptions): Promise<{ file: string; content: string }> {\n const title = options.title.trim();\n if (title === '') throw new EngineError('invalid', 'a brief needs a title');\n const id = options.id ?? nextId(this.corpus);\n if (id === null) throw new EngineError('invalid', 'the ids here are not numbers, so there is no next one; pass --id');\n if (/[\\s/\\\\]/.test(id) || id.includes(this.config.id.separator)) {\n throw new EngineError('invalid', `\"${id}\" cannot be an id: no whitespace, slashes or \"${this.config.id.separator}\"`);\n }\n if (findBriefs(this.corpus, id).some((b) => b.id !== null)) throw new EngineError('exists', `the id \"${id}\" is taken`);\n if (options.type !== undefined && typeSections(this.config, options.type) === undefined) {\n const names = Object.keys(this.config.types);\n throw new EngineError('invalid', `\"${options.type}\" is not a brief type here; use one of ${names.join(', ') || '(none)'}`);\n }\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n const template = this.config.template === null ? null : await this.fs.read(this.config.template);\n if (this.config.template !== null && template === null) {\n throw new EngineError('not-found', `the template ${this.config.template} does not exist`);\n }\n const content = renderNewBrief(\n this.config,\n { id, title, date, type: options.type, wave: options.wave, dependsOn: options.dependsOn },\n template,\n );\n const dir = normalisePath(this.config.briefs);\n const file = `${dir}/${fileNameFor(this.config, id, title)}`;\n if (await this.fs.exists(file)) throw new EngineError('exists', `${file} already exists`);\n await this.fs.write(file, content);\n await this.load();\n return { file, content };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,YAAY,EACZ,WAAW,EAEX,aAAa,EAIb,QAAQ,GAET,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAA+C,MAAM,iBAAiB,CAAC;AAC7G,OAAO,EAAe,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChH,OAAO,EAAE,WAAW,EAAe,UAAU,EAAE,OAAO,EAAE,mBAAmB,EAAmB,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,aAAa,EAAmB,cAAc,EAAE,aAAa,EAAc,MAAM,SAAS,CAAC;AACpG,OAAO,EAA8C,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/F,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAe,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,SAAS,EAAiB,QAAQ,EAAE,gBAAgB,EAAwB,MAAM,eAAe,CAAC;AAG3G,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,CAA+D;IAE5E,YAAY,IAAyB,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAyED,KAAK,UAAU,MAAM,CAAC,IAAY;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,mEAAmE;QACnE,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,WAAW,GAAG,YAAY,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,KAAK,CAAC,GAAG,GAAiD,OAAO,CAAC,GAAG;IACnF,MAAM,KAAK,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACvC,IAAI,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACtB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,uFAAuF;QACvF,MAAM,IAAI,GAAG,wBAAwB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,GAAG,IAAI,gIAAgI,CAAC,CAAC;QAC5K,CAAC;QACD,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,EAAE,CAAC;YAChC,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,GAAG,IAAI,4EAA4E,WAAW,uBAAuB,CAAC,CAAC;QAC1J,CAAC;QACD,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAI,KAAQ;IAC7B,MAAM,MAAM,GAAG,CAAC,IAAa,EAAQ,EAAE;QACrC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO;QACtD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC,CAAC;IACF,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,CAAC,IAAI,CAAC,CAAC;IACb,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,sEAAsE;IACtE,0EAA0E;IAC1E,yEAAyE;IACzE,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC;IAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC;AACvF,CAAC;AAED,MAAM,OAAO,WAAW;IACb,IAAI,CAAS;IACb,MAAM,CAAS;IACf,UAAU,CAAgB;IAC1B,EAAE,CAAa;IACf,GAAG,CAAa;IACjB,OAAO,CAAgC;IACvC,MAAM,CAAqB;IACnC;;;;;OAKG;IACK,OAAO,CAAwD;IACvE,+EAA+E;IACvE,aAAa,CAAsB;IAE3C,YAAY,KAAkB;QAC5B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,GAAgB,EAAE;QACzC,kEAAkE;QAClE,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC9G,wEAAwE;QACxE,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,UAAU,GACd,OAAO,CAAC,QAAQ,KAAK,IAAI;YACvB,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,CAAC,QAAQ;gBACT,CAAC,MAAM,YAAY,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE;oBAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAClB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;gBACtB,CAAC,CAAC,CAAC,CAAC,CAAC;QACX,uEAAuE;QACvE,qEAAqE;QACrE,uBAAuB;QACvB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC;QACjG,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9D,MAAM,IAAI,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAChF,MAAM,KAAK,GAAU,EAAE,KAAK,EAAE,IAAI,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;QAClE,yEAAyE;QACzE,yEAAyE;QACzE,mCAAmC;QACnC,KAAK,MAAM,IAAI,IAAI,MAAM;YAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;QACvE,IAAI,MAAM,GAAG,cAAc,CAAC;QAC5B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,uEAAuE;YACvE,sEAAsE;YACtE,2DAA2D;YAC3D,yEAAyE;YACzE,MAAM,IAAI,GAAG,MAAM,IAAI,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACrG,MAAM,KAAK,YAAY,aAAa,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,IAAI,4BAA4B,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAC5I,CAAC,CAAC,CAAC;YACH,IAAI,IAAI,KAAK,IAAI;gBAAE,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,GAAG,UAAU,iBAAiB,CAAC,CAAC;YACtF,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,0EAA0E;QAC1E,wDAAwD;QACxD,MAAM,OAAO,GAAG,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC;YAC7B,IAAI;YACJ,MAAM;YACN,iFAAiF;YACjF,UAAU,EAAE,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC;YAClE,EAAE,EAAE,OAAO,CAAC,EAAE,IAAI,IAAI,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC;YACjD,GAAG,EAAE,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG;SACnF,CAAC,CAAC;QACH,uEAAuE;QACvE,sEAAsE;QACtE,gEAAgE;QAChE,wEAAwE;QACxE,wEAAwE;QACxE,sEAAsE;QACtE,gDAAgD;QAChD,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;QAC9H,MAAM,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAClC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,MAAM;QACR,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC1G,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,qDAAqD;IACrD,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,CAAC,IAAY,EAAW,EAAE,CACxC,MAAM,CAAC,EAAE,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QACpG,MAAM,IAAI,GAAG,KAAK,EAAE,SAAiB,EAAE,KAAY,EAAoB,EAAE;YACvE,MAAM,GAAG,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;YACrC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YACjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC7B,MAAM,IAAI,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;gBAClD,+DAA+D;gBAC/D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC5C,+FAA+F;gBAC/F,IAAI,IAAI,KAAK,IAAI;oBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QACF,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;OAIG;IACH,aAAa;QACX,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,+EAA+E,CAAC,CAAC;QAC1J,CAAC;IACH,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,aAAa;QACjB,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;QAC3D,IAAI,CAAC,OAAO,KAAK,MAAM,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,qEAAqE;IACrE,IAAI,CAAC,SAAiB;QACpB,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACjD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,sBAAsB,SAAS,GAAG,CAAC,CAAC;QAC/F,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,IAAI,SAAS,WAAW,KAAK,CAAC,MAAM,YAAY,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5H,CAAC;QACD,OAAO,KAAK,CAAC,CAAC,CAAU,CAAC;IAC3B,CAAC;IAED,+FAA+F;IAC/F,KAAK,CAAC,SAAS;QACb,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;QAC3D,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;QAC3E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAU,GAAsB,EAAE;QAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,aAAa,EAAE,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAClI,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,OAAO,GAAqB,EAAE;QAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QAC1F,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAO,GAAoB,EAAE;QAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QAC1F,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC;IACnF,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,UAAU,CAAC,QAAkB;QACjC,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9G,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACjE,CAAC;IAED;;;;OAIG;IACH,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,OAAO,EAAE,KAAK,KAAK,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;QAC3F,6CAA6C;QAC7C,MAAM,MAAM,GAAG,CAAC,CAA4B,EAAE,CAA4B,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClH,OAAO;YACL,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;YACxF,wEAAwE;YACxE,wEAAwE;YACxE,kEAAkE;YAClE,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;iBACxC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;iBACtH,IAAI,CAAC,MAAM,CAAC;YACf,GAAG,EAAE,IAAI;SACV,CAAC;IACJ,CAAC;IAED,iGAAiG;IACjG,KAAK;QACH,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,kDAAkD;IAClD,SAAS,CAAC,KAAY;QACpB,OAAO,mBAAmB,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,SAAiB,EAAE,OAAO,GAAmB,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,IAAI,IAAI,oCAAoC,CAAC,CAAC;QAClG,IAAI,OAAO,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YAC3F,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,0EAA0E,CAAC,CAAC;QAC/G,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACrD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,IAAI,SAAS,CAAC;QACrE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAEnC,IAAI,MAA8B,CAAC;QACnC,IAAI,OAAiC,CAAC;QACtC,IAAI,UAAkC,CAAC;QACvC,IAAI,KAA2B,CAAC;QAChC,IAAI,EAA2B,CAAC;QAChC,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC7E,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC;gBACnD,IAAI,MAAM,KAAK,SAAS;oBAAE,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,IAAI,QAAQ,mBAAmB,CAAC,CAAC;gBAC9F,IAAI,IAAI,GAAkB,IAAI,CAAC;gBAC/B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACvB,IAAI,GAAG,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;oBAC7C,iEAAiE;oBACjE,kEAAkE;oBAClE,wBAAwB;oBACxB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;wBAClB,IAAI,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;4BACtC,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,aAAa,IAAI,sDAAsD,CAAC,CAAC;wBAC9G,CAAC;wBACD,IAAI,GAAG,IAAI,CAAC;oBACd,CAAC;gBACH,CAAC;gBACD,sEAAsE;gBACtE,oEAAoE;gBACpE,2DAA2D;gBAC3D,IAAI,IAAI,KAAK,MAAM,CAAC,GAAG;oBAAE,UAAU,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAc,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;;oBAChG,OAAO,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YACrD,CAAC;YACD,sEAAsE;YACtE,oEAAoE;YACpE,KAAK,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,UAAU,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACjC,CAAC;QACD,IAAI,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC7B,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;QACpH,CAAC;QACD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE;YAC3C,IAAI;YACJ,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,EAAE;YACF,MAAM;YACN,OAAO;YACP,UAAU;YACV,KAAK;YACL,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IACtF,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,KAAK,CAAC,KAAK,CAAC,IAAU,EAAE,IAAmB,EAAE,MAAqB,EAAE,MAAe;QACzF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACvE,MAAM,OAAO,GAAG,UAAU,CAAC;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YAC1E,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI;YACJ,MAAM;SACP,CAAC,CAAC;QACH,MAAM,OAAO,GAAyC,EAAE,CAAC;QACzD,KAAK,MAAM,MAAM,IAAI,MAAM,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YAChD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;gBAAE,SAAS;YACzC,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,WAAW,CAAC,WAAW,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;YAClI,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACrG,CAAC;QACD,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED,aAAa,CAAC,SAAiB,EAAE,OAAO,GAAqB,EAAE;QAC7D,OAAO,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;IACnE,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,KAAK,CAAC,IAAU;QACpB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,gBAAiB,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAChI,CAAC;QACD,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACtB,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,MAAM,CAAC,OAAmB;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,KAAK,KAAK,EAAE;YAAE,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,uBAAuB,CAAC,CAAC;QAC5E,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,EAAE,KAAK,IAAI;YAAE,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,kEAAkE,CAAC,CAAC;QACtH,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;YAChE,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,IAAI,EAAE,iDAAiD,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;QACvH,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;QACvH,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACxF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC7C,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,IAAI,OAAO,CAAC,IAAI,0CAA0C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;QAC7H,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC;QACrC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,IAAI,IAAI,oCAAoC,CAAC,CAAC;QAClG,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QACnC,MAAM,QAAQ,GACZ,KAAK,KAAK,IAAI;YACZ,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACjD,iEAAiE;gBACjE,gEAAgE;gBAChE,0DAA0D;gBAC1D,MAAM,KAAK,YAAY,aAAa,CAAC,CAAC,CAAC,IAAI,WAAW,CAAC,SAAS,EAAE,gBAAgB,KAAK,OAAO,KAAK,CAAC,IAAI,iDAAiD,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YACrK,CAAC,CAAC,CAAC;QACT,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACvD,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,gBAAgB,IAAI,CAAC,MAAM,CAAC,QAAQ,iBAAiB,CAAC,CAAC;QAC5F,CAAC;QACD,MAAM,OAAO,GAAG,cAAc,CAC5B,IAAI,CAAC,MAAM,EACX,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,EACzF,QAAQ,CACT,CAAC;QACF,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,GAAG,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC;QAC7D,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,WAAW,CAAC,QAAQ,EAAE,GAAG,IAAI,iBAAiB,CAAC,CAAC;QAC1F,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;CACF","sourcesContent":["/**\n * The engine: one repository's briefs, with the operations the CLI offers and\n * the same ones for a harness or an orchestrator that would rather call a\n * function than parse a terminal.\n *\n * This module and the CLI are where the pieces meet their implementations -\n * the real filesystem, the real git, the plugins a configuration names.\n * Everything they compose is a pure function that takes those as arguments,\n * so an engine can equally run over a `MemoryFileSystem` and no git at all.\n */\n\nimport { access } from 'node:fs/promises';\nimport { basename, dirname, relative, resolve, sep } from 'node:path';\n\nimport { applyPlan } from './apply.js';\nimport {\n applyWaivers,\n planArchive,\n type Plan,\n planUnarchive,\n type PullRequest,\n type UnarchiveRequest,\n type Unmeasured,\n WAIVABLE,\n type Waiver,\n} from './archive.js';\nimport type { Brief } from './brief.js';\nimport { collisionFindings, collisions, type CollisionOptions, type CollisionReport } from './collisions.js';\nimport { type Config, ConfigError, DEFAULT_CONFIG, locateConfig, parseConfig, typeSections } from './config.js';\nimport { buildCorpus, type Corpus, findBriefs, isReady, pendingDependencies, type SourceFile } from './corpus.js';\nimport { canonicalPath, type FileSystem, NodeFileSystem, NotAFileError, type Reads } from './fs.js';\nimport { type CommitInfo, type FileChange, type Git, NodeGit, pullRequestUrl } from './git.js';\nimport { matchGlob, parseGlob } from './glob.js';\nimport { normalisePath } from './links.js';\nimport { lint, type Plugin } from './lint.js';\nimport { asWaivers, loadPlugins } from './plugins.js';\nimport { fileNameFor, nextId, renderNewBrief } from './scaffold.js';\nimport { planWaves, type Schedule, schedule, scheduleFindings, type ScheduleOptions } from './schedule.js';\nimport type { Finding, Phase } from './types.js';\n\nexport class EngineError extends Error {\n readonly code: 'not-found' | 'ambiguous' | 'invalid' | 'refused' | 'exists';\n\n constructor(code: EngineError['code'], message: string) {\n super(message);\n this.name = 'EngineError';\n this.code = code;\n }\n}\n\nexport interface EngineSetup {\n /** Absolute root: the directory holding the configuration. */\n readonly root: string;\n readonly config: Config;\n /** Repository-relative configuration path, or `null` when the defaults apply. */\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n readonly plugins?: readonly Plugin[];\n}\n\nexport interface OpenOptions {\n /** Where discovery starts. Defaults to the process's directory. */\n readonly cwd?: string;\n /** An explicit configuration file; discovery is skipped. */\n readonly config?: string | undefined;\n /** Use the defaults even where a configuration file exists. */\n readonly noConfig?: boolean;\n /** `null` runs without git. Defaults to git when the root is inside a working tree. */\n readonly git?: Git | null;\n readonly fs?: FileSystem;\n /**\n * `false` where nothing the caller asks of the engine reads git, as `list`\n * asks it nothing: where a configuration file names the root, git is then\n * not asked where the work tree's top is, and the engine has no git.\n * Without one, git is still asked, since its answer is the root.\n */\n readonly needsGit?: boolean;\n}\n\n/**\n * Everything an answer was read from, for a tool that would give it again\n * without starting spec-brief (ADR-0002, amended 2026-10-19). Each file is\n * named with the SHA-256 of its bytes, or `null` where it was looked for and\n * was not there; each directory with the names of the files in it, sorted,\n * or `null` where there was no directory to list. Each `null` is a path\n * that must still not be there. Paths are from the root, which wherever `keep` is true is the directory\n * the run started in. `git` is `null`, since no answer named here asks git\n * anything. `keep` is `false`, and the lists are empty, wherever that cannot\n * be named whole.\n */\nexport interface Inputs {\n readonly keep: boolean;\n readonly files: readonly { readonly path: string; readonly sha256: string | null }[];\n readonly directories: readonly { readonly path: string; readonly entries: readonly string[] | null }[];\n readonly git: null;\n}\n\nexport interface ArchiveOptions {\n readonly pr?: number | undefined;\n /** The commit the round landed as. */\n readonly commit?: string | undefined;\n /** The branch the round started from; the diff runs from its merge base. */\n readonly base?: string | undefined;\n readonly summary?: string | undefined;\n readonly date?: string | undefined;\n readonly allowDirty?: boolean | undefined;\n readonly strict?: boolean | undefined;\n /** Leave git out, even where there is a repository. */\n readonly noGit?: boolean | undefined;\n}\n\nexport interface NewOptions {\n readonly title: string;\n readonly id?: string | undefined;\n readonly type?: string | undefined;\n readonly wave?: number | undefined;\n readonly dependsOn?: readonly string[] | undefined;\n readonly date?: string | undefined;\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n // Absent, or not to be reached: the search goes on either way. An empty\n // catch would answer `undefined`, which the search reads the same.\n return false;\n }\n}\n\n/** The last second of 9999-12-31, the last date written YYYY-MM-DD. */\nconst LAST_SECOND = 253402300799;\n\n/**\n * Today, in UTC. `SOURCE_DATE_EPOCH` wins where it is set, which is what makes\n * a banner written in a reproducible pipeline reproducible.\n *\n * Set, it is what its specification says it is: whole seconds since\n * 1970-01-01 in digits alone, as `date +%s` prints them, and here no later\n * than the last date written YYYY-MM-DD. Anything else is refused by name,\n * an empty value too, which is what a command substitution that failed\n * leaves. Read as unset, it had the run write the clock's date where a\n * pipeline asked for its own, and say nothing.\n */\nexport function today(env: Readonly<Record<string, string | undefined>> = process.env): string {\n const epoch = env['SOURCE_DATE_EPOCH'];\n let when = new Date();\n if (epoch !== undefined) {\n // Quoted as JSON, so a newline or a tab in it is seen, and the refusal stays one line.\n const held = `SOURCE_DATE_EPOCH is ${JSON.stringify(epoch)}`;\n if (!/^\\d+$/.test(epoch)) {\n throw new EngineError('invalid', `${held}, which is not a whole number of seconds since 1970-01-01; set it to one in digits alone, as \"date +%s\" prints it, or unset it`);\n }\n if (Number(epoch) > LAST_SECOND) {\n throw new EngineError('invalid', `${held}, which is after 9999-12-31, the last date written YYYY-MM-DD; set it to ${LAST_SECOND} or less, or unset it`);\n }\n when = new Date(Number(epoch) * 1000);\n }\n return when.toISOString().slice(0, 10);\n}\n\n/**\n * A deep copy of a value in which every object and array is frozen. Plain\n * data only, as `structuredClone` takes it; the recursion ends at the data's\n * own depth.\n */\nfunction frozenCopy<T>(value: T): T {\n const freeze = (item: unknown): void => {\n if (typeof item !== 'object' || item === null) return;\n for (const child of Object.values(item)) freeze(child);\n Object.freeze(item);\n };\n const copy = structuredClone(value);\n freeze(copy);\n return copy;\n}\n\nexport function isDate(text: string): boolean {\n // The shape keeps out the one other text that survives the round trip\n // below: a year past 9999 with a month, \"+010000-01\". Its anchors are for\n // the reader, since nothing longer than a date comes back as it went in.\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(text)) return false;\n const parsed = new Date(`${text}T00:00:00Z`);\n return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === text;\n}\n\nexport class BriefEngine {\n readonly root: string;\n readonly config: Config;\n readonly configFile: string | null;\n readonly fs: FileSystem;\n readonly git: Git | null;\n private plugins: readonly Plugin[] | undefined;\n private loaded: Corpus | undefined;\n /**\n * What the engine has read, where `open` made it, and whether that is\n * every input of an answer made of the briefs and the configuration alone.\n * Set false by a read that is not on it: the files git or a walk lists,\n * and a plugin's module.\n */\n private reading: { readonly reads: Reads; whole: boolean } | undefined;\n /** Known once the briefs are read, which every command does before it asks. */\n private missingBriefs: boolean | undefined;\n\n constructor(setup: EngineSetup) {\n this.root = setup.root;\n this.config = setup.config;\n this.configFile = setup.configFile;\n this.fs = setup.fs;\n this.git = setup.git;\n this.plugins = setup.plugins;\n }\n\n /**\n * Finds the configuration, the root it defines and the repository around it,\n * and reads every brief. The root is the directory holding the\n * configuration; without one it is the working tree's top, or `cwd`.\n */\n static async open(options: OpenOptions = {}): Promise<BriefEngine> {\n // Named as git names them, so the root and the work tree compare.\n const cwd = await canonicalPath(resolve(options.cwd ?? process.cwd()));\n const explicit = options.config === undefined ? undefined : await canonicalPath(resolve(cwd, options.config));\n // Each name looked for, so that an answer can say which were not there.\n const looked: string[] = [];\n const configPath =\n options.noConfig === true\n ? undefined\n : (explicit ??\n (await locateConfig(cwd, (path) => {\n looked.push(path);\n return exists(path);\n })));\n // Asking git costs a process; without git there is nothing to ask, and\n // with a root a configuration file names nothing to ask it where the\n // caller reads no git.\n const asksGit = options.git !== null && (configPath === undefined || options.needsGit !== false);\n const toplevel = asksGit ? await NodeGit.toplevel(cwd) : null;\n const root = configPath === undefined ? (toplevel ?? cwd) : dirname(configPath);\n const reads: Reads = { files: new Map(), directories: new Map() };\n // Each is named as not there, and the one found again by its read below.\n // Wherever an answer is kept, each was looked for in the root itself, so\n // its path from there is its name.\n for (const path of looked) reads.files.set(relative(root, path), null);\n let config = DEFAULT_CONFIG;\n if (configPath !== undefined) {\n // Only a file is read. A named pipe under the configuration's name was\n // read without end, by every command; and a directory, found or named\n // with --config, ended the run on Node's EISDIR. Each is a\n // configuration that does not load, and no defect to report a stack for.\n const text = await new NodeFileSystem(root, reads).read(basename(configPath)).catch((error: unknown) => {\n throw error instanceof NotAFileError ? new ConfigError(relative(cwd, configPath), [`is ${error.what}, not a configuration file`]) : error;\n });\n if (text === null) throw new EngineError('not-found', `${configPath} does not exist`);\n config = parseConfig(text, relative(cwd, configPath));\n }\n // Outside the work tree is its parent or a path through it, \"..\", \"../x\":\n // a directory inside it may itself be named \"..drafts\".\n const fromTop = toplevel === null ? null : relative(toplevel, root);\n const inTree = fromTop !== null && fromTop !== '..' && !fromTop.startsWith(`..${sep}`);\n const engine = new BriefEngine({\n root,\n config,\n // The root is the configuration's directory, so its path from there is its name.\n configFile: configPath === undefined ? null : basename(configPath),\n fs: options.fs ?? new NodeFileSystem(root, reads),\n git: options.git === undefined ? (inTree ? new NodeGit(root) : null) : options.git,\n });\n // Named whole only where each input is read from the directory the run\n // started in, so that a reader finds it by the path named: the engine\n // reads through its own disk, the root is that directory, and a\n // configuration file there names it, or neither a configuration nor git\n // was asked. A configuration not found was looked for above the root as\n // well, and a root git names is decided by what git reads to find the\n // top of a work tree, which no list here names.\n const whole = options.fs === undefined && !asksGit && root === cwd && (configPath !== undefined || options.noConfig === true);\n engine.reading = { reads, whole };\n await engine.load();\n return engine;\n }\n\n get corpus(): Corpus {\n if (this.loaded === undefined) throw new Error('the engine has not loaded its briefs; call load() first');\n return this.loaded;\n }\n\n /** Reads every brief again, as after an archival. */\n async load(): Promise<Corpus> {\n const files: SourceFile[] = [];\n const accept = parseGlob(this.config.files);\n const reject = this.config.exclude.map((pattern) => parseGlob(pattern));\n const isBrief = (name: string): boolean =>\n accept.ok && matchGlob(accept.glob, name) && !reject.some((r) => r.ok && matchGlob(r.glob, name));\n const read = async (directory: string, phase: Phase): Promise<boolean> => {\n const dir = normalisePath(directory);\n const names = await this.fs.list(dir);\n if (names === null) return false;\n for (const name of names) {\n if (!isBrief(name)) continue;\n const path = dir === '' ? name : `${dir}/${name}`;\n // The listing named it a file, so the read does not ask again.\n const text = await this.fs.read(path, true);\n // A file listed a moment ago and gone now was removed under the run; there is nothing to read.\n if (text !== null) files.push({ path, text, phase });\n }\n return true;\n };\n this.missingBriefs = !(await read(this.config.briefs, 'live'));\n await read(this.config.archive, 'archived');\n this.loaded = buildCorpus(files, this.config);\n return this.loaded;\n }\n\n /**\n * Refuses to report on a briefs directory that does not exist. A check over\n * nothing looks exactly like a clean one, and the likelier cause is a\n * configuration pointing at the wrong place.\n */\n requireBriefs(): void {\n if (this.missingBriefs) {\n throw new EngineError('not-found', `${normalisePath(this.config.briefs)}/ does not exist; run \"spec-brief init\", or set \"briefs\" in the configuration`);\n }\n }\n\n /** The plugins the configuration names, loaded once. */\n async loadedPlugins(): Promise<readonly Plugin[]> {\n if (this.reading !== undefined) this.reading.whole = false;\n this.plugins ??= await loadPlugins(this.config.plugins, this.root);\n return this.plugins;\n }\n\n /** The one brief a reference names: an id, a file name or a path. */\n find(reference: string): Brief {\n const found = findBriefs(this.corpus, reference);\n if (found.length === 0) throw new EngineError('not-found', `no brief is named \"${reference}\"`);\n if (found.length > 1) {\n throw new EngineError('ambiguous', `\"${reference}\" names ${found.length} briefs: ${found.map((b) => b.file).join(', ')}`);\n }\n return found[0] as Brief;\n }\n\n /** The files git sees, or every file when there is no git; `null` when neither can be read. */\n async repoFiles(): Promise<string[] | null> {\n if (this.reading !== undefined) this.reading.whole = false;\n try {\n return this.git === null ? await this.fs.walk() : await this.git.files();\n } catch {\n return null;\n }\n }\n\n async lint(references: readonly string[] = []): Promise<Finding[]> {\n const only = references.length === 0 ? undefined : references.map((r) => this.find(r));\n return lint(this.corpus, { plugins: await this.loadedPlugins(), repoFiles: await this.repoFiles(), ...(only ? { only } : {}) });\n }\n\n async collisions(options: CollisionOptions = {}): Promise<{ report: CollisionReport; findings: Finding[] }> {\n const report = collisions(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { report, findings: collisionFindings(this.corpus, report) };\n }\n\n async schedule(options: ScheduleOptions = {}): Promise<{ schedule: Schedule; findings: Finding[] }> {\n const computed = schedule(this.corpus, { repoFiles: await this.repoFiles(), ...options });\n return { schedule: computed, findings: scheduleFindings(this.corpus, computed) };\n }\n\n /**\n * Writes each brief's proposed wave into its front matter, every other line\n * as it was, as one transaction. Refused, with nothing written, when the\n * dependencies form a cycle or a front matter cannot be edited.\n */\n async writeWaves(computed: Schedule): Promise<{ written: string[]; refused: Finding[] }> {\n const plan = planWaves(computed);\n if (computed.cycles.length > 0 || plan.refused.length > 0) return { written: [], refused: [...plan.refused] };\n await applyPlan(this.fs, plan.ops);\n await this.load();\n return { written: plan.ops.map((op) => op.path), refused: [] };\n }\n\n /**\n * Every input of an answer made of the briefs and the configuration alone,\n * as `list` gives one, read since the engine was opened (ADR-0002, amended\n * 2026-10-19).\n */\n inputs(): Inputs {\n const reading = this.reading;\n if (reading?.whole !== true) return { keep: false, files: [], directories: [], git: null };\n // Paths are unique, so no two compare equal.\n const byPath = (a: { readonly path: string }, b: { readonly path: string }): number => (a.path < b.path ? -1 : 1);\n return {\n keep: true,\n files: [...reading.reads.files].map(([path, sha256]) => ({ path, sha256 })).sort(byPath),\n // The root is \".\" where a briefs directory is the root itself. One that\n // was not there, an archive not yet made, is named with no entries: one\n // made with a brief in it changes what `list --archived` answers.\n directories: [...reading.reads.directories]\n .map(([path, entries]) => ({ path: path === '' ? '.' : path, entries: entries === null ? null : [...entries].sort() }))\n .sort(byPath),\n git: null,\n };\n }\n\n /** Live briefs that can run now: every dependency archived, and neither a draft nor deferred. */\n ready(): Brief[] {\n return this.corpus.live.filter((b) => isReady(this.corpus, b));\n }\n\n /** The live dependencies holding a brief back. */\n waitingOn(brief: Brief): string[] {\n return pendingDependencies(this.corpus, brief);\n }\n\n async planArchive(reference: string, options: ArchiveOptions = {}): Promise<Plan> {\n const brief = this.find(reference);\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n if (options.noGit === true && (options.commit !== undefined || options.base !== undefined)) {\n throw new EngineError('invalid', 'a commit or a base needs git; drop --no-git, or drop --commit and --base');\n }\n const git = options.noGit === true ? null : this.git;\n const base = options.base ?? this.config.archiving.base ?? undefined;\n const findings = await this.lint();\n\n let commit: CommitInfo | undefined;\n let changes: FileChange[] | undefined;\n let unmeasured: Unmeasured | undefined;\n let dirty: string[] | undefined;\n let pr: PullRequest | undefined;\n if (git !== null) {\n const revision = options.commit ?? (base === undefined ? undefined : 'HEAD');\n if (revision !== undefined) {\n commit = (await git.commit(revision)) ?? undefined;\n if (commit === undefined) throw new EngineError('not-found', `\"${revision}\" names no commit`);\n let from: string | null = null;\n if (base !== undefined) {\n from = await git.mergeBase(base, commit.sha);\n // No merge base: a base with a history of its own is diffed from\n // directly, and one that names nothing is a typo to report, not a\n // diff for git to fail.\n if (from === null) {\n if ((await git.commit(base)) === null) {\n throw new EngineError('not-found', `the base \"${base}\" names no commit; check --base and \"archiving.base\"`);\n }\n from = base;\n }\n }\n // A commit that is its own merge base with the base branch is already\n // in it: the diff is empty whatever the round changed, and an empty\n // diff would pass every scope check without measuring one.\n if (from === commit.sha) unmeasured = { reason: 'merged', base: base as string, commit: commit.sha };\n else changes = await git.changes(from, commit.sha);\n }\n // With neither a commit nor a base, nothing was read, and the planner\n // says so itself of a request that brings no changes and no reason.\n dirty = await git.dirty();\n } else {\n unmeasured = { reason: 'git' };\n }\n if (options.pr !== undefined) {\n pr = { number: options.pr, url: git === null ? null : pullRequestUrl(await git.remoteUrl('origin'), options.pr) };\n }\n const plan = planArchive(this.corpus, brief, {\n date,\n summary: options.summary,\n pr,\n commit,\n changes,\n unmeasured,\n dirty,\n allowDirty: options.allowDirty,\n strict: options.strict,\n findings,\n });\n return this.waive(plan, base ?? null, commit?.sha ?? null, options.strict === true);\n }\n\n /**\n * Asks each plugin with a `waive` hook which of a plan's refusals its own\n * check lifts, and turns those into notes. Asked only when the plan has a\n * refusal a plugin may lift: a hook may read git and verify signatures, and a\n * plan with nothing to waive has no question for it. A hook that throws, or\n * answers in the wrong shape, stops the run as a plugin that fails to load\n * does: an archival decided without the check it asked for is not one to\n * trust.\n *\n * A hook is handed a frozen copy of the refusals, and its waivers are\n * matched against the plan's own. Handed the plan's list, a hook could drop\n * a refusal from it, or relabel an open task as a protected file and waive\n * that, and so lift what only spec-brief may; writing to the copy throws,\n * which stops the run like any failing hook.\n */\n private async waive(plan: Plan, base: string | null, commit: string | null, strict: boolean): Promise<Plan> {\n if (!plan.blocking.some((f) => WAIVABLE.includes(f.rule))) return plan;\n const context = frozenCopy({\n root: this.root,\n brief: { id: plan.brief.id, file: plan.brief.file, text: plan.brief.text },\n findings: plan.blocking,\n base,\n commit,\n });\n const waivers: { plugin: string; waiver: Waiver }[] = [];\n for (const plugin of await this.loadedPlugins()) {\n if (plugin.waive === undefined) continue;\n let answer: unknown;\n try {\n answer = await plugin.waive(context);\n } catch (error) {\n throw new ConfigError(`plugin \"${plugin.name}\"`, [`\"waive\" failed: ${error instanceof Error ? error.message : String(error)}`]);\n }\n for (const waiver of asWaivers(answer, plugin.name)) waivers.push({ plugin: plugin.name, waiver });\n }\n return applyWaivers(plan, waivers, strict);\n }\n\n planUnarchive(reference: string, options: UnarchiveRequest = {}): Plan {\n return planUnarchive(this.corpus, this.find(reference), options);\n }\n\n /** Executes a plan and reads the briefs again. A refused plan is never applied. */\n async apply(plan: Plan): Promise<void> {\n if (plan.blocking.length > 0) {\n throw new EngineError('refused', `${plan.action} of ${plan.brief.file} is refused: ${(plan.blocking[0] as Finding).message}`);\n }\n if (plan.done) return;\n await applyPlan(this.fs, plan.ops);\n await this.load();\n }\n\n /** Scaffolds a brief and writes it; the file is new, or nothing is written. */\n async create(options: NewOptions): Promise<{ file: string; content: string }> {\n const title = options.title.trim();\n if (title === '') throw new EngineError('invalid', 'a brief needs a title');\n const id = options.id ?? nextId(this.corpus);\n if (id === null) throw new EngineError('invalid', 'the ids here are not numbers, so there is no next one; pass --id');\n if (/[\\s/\\\\]/.test(id) || id.includes(this.config.id.separator)) {\n throw new EngineError('invalid', `\"${id}\" cannot be an id: no whitespace, slashes or \"${this.config.id.separator}\"`);\n }\n if (findBriefs(this.corpus, id).some((b) => b.id !== null)) throw new EngineError('exists', `the id \"${id}\" is taken`);\n if (options.type !== undefined && typeSections(this.config, options.type) === undefined) {\n const names = Object.keys(this.config.types);\n throw new EngineError('invalid', `\"${options.type}\" is not a brief type here; use one of ${names.join(', ') || '(none)'}`);\n }\n const date = options.date ?? today();\n if (!isDate(date)) throw new EngineError('invalid', `\"${date}\" is not a date written YYYY-MM-DD`);\n const named = this.config.template;\n const template =\n named === null\n ? null\n : await this.fs.read(named).catch((error: unknown) => {\n // A named pipe was read without end, and a directory or a socket\n // ended the run on Node's error and a stack: each is a template\n // the configuration names wrongly, and said so in a line.\n throw error instanceof NotAFileError ? new EngineError('invalid', `the template ${named} is ${error.what}; point \"template\" at a file, or set it to null`) : error;\n });\n if (this.config.template !== null && template === null) {\n throw new EngineError('not-found', `the template ${this.config.template} does not exist`);\n }\n const content = renderNewBrief(\n this.config,\n { id, title, date, type: options.type, wave: options.wave, dependsOn: options.dependsOn },\n template,\n );\n const dir = normalisePath(this.config.briefs);\n const file = `${dir}/${fileNameFor(this.config, id, title)}`;\n if (await this.fs.exists(file)) throw new EngineError('exists', `${file} already exists`);\n await this.fs.write(file, content);\n await this.load();\n return { file, content };\n }\n}\n"]}
|
package/dist/fs.d.ts
CHANGED
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
* exactly the operation that matters.
|
|
10
10
|
*/
|
|
11
11
|
export interface FileSystem {
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Contents, or `null` when there is no such file. What is there and is not
|
|
14
|
+
* a file is refused, and not read, unless `listed` says that `list` has
|
|
15
|
+
* just named it a file.
|
|
16
|
+
*/
|
|
17
|
+
read(path: string, listed?: boolean): Promise<string | null>;
|
|
14
18
|
/** Writes a file, creating its directory. Replaces an existing file atomically where the platform allows. */
|
|
15
19
|
write(path: string, content: string): Promise<void>;
|
|
16
20
|
/** Removes a file. Removing a file that is absent is an error. */
|
|
@@ -33,6 +37,23 @@ export interface Reads {
|
|
|
33
37
|
readonly files: Map<string, string | null>;
|
|
34
38
|
readonly directories: Map<string, readonly string[] | null>;
|
|
35
39
|
}
|
|
40
|
+
/** What stands at a path that is not a regular file, in the words a refusal says it in. */
|
|
41
|
+
export type NotAFile = 'a directory' | 'neither a file nor a directory';
|
|
42
|
+
/**
|
|
43
|
+
* What stands at a path where it is not a regular file, a link followed:
|
|
44
|
+
* `null` for a file, and for a path where nothing is or that cannot be
|
|
45
|
+
* asked about, which the read that follows reports as it always has. A
|
|
46
|
+
* named pipe was read without end, a device as nothing or without end, and
|
|
47
|
+
* a directory or a socket ended the run on Node's error and a stack
|
|
48
|
+
* (ADR-0003, amended 2026-10-24).
|
|
49
|
+
*/
|
|
50
|
+
export declare function notAFile(path: string): Promise<NotAFile | null>;
|
|
51
|
+
/** A path that is there and is not a regular file, refused before it is opened. */
|
|
52
|
+
export declare class NotAFileError extends Error {
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly what: NotAFile;
|
|
55
|
+
constructor(path: string, what: NotAFile);
|
|
56
|
+
}
|
|
36
57
|
/**
|
|
37
58
|
* The path as the filesystem names it: links and junctions followed, and on
|
|
38
59
|
* Windows short names such as `RUNNER~1` expanded. Git reports the top of a
|
|
@@ -48,7 +69,8 @@ export declare class NodeFileSystem implements FileSystem {
|
|
|
48
69
|
/** `reads`, where it is given, is told every file read and every directory listed. */
|
|
49
70
|
constructor(root: string, reads?: Reads);
|
|
50
71
|
private host;
|
|
51
|
-
|
|
72
|
+
/** Contents, or `null` when there is no such file; a {@link NotAFileError} where what is there is not one. */
|
|
73
|
+
read(path: string, listed?: boolean): Promise<string | null>;
|
|
52
74
|
write(path: string, content: string): Promise<void>;
|
|
53
75
|
remove(path: string): Promise<void>;
|
|
54
76
|
list(directory: string): Promise<string[] | null>;
|
package/dist/fs.js
CHANGED
|
@@ -12,6 +12,31 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
12
12
|
import { mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
13
13
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
14
14
|
import { normalisePath as repoPath } from './links.js';
|
|
15
|
+
/**
|
|
16
|
+
* What stands at a path where it is not a regular file, a link followed:
|
|
17
|
+
* `null` for a file, and for a path where nothing is or that cannot be
|
|
18
|
+
* asked about, which the read that follows reports as it always has. A
|
|
19
|
+
* named pipe was read without end, a device as nothing or without end, and
|
|
20
|
+
* a directory or a socket ended the run on Node's error and a stack
|
|
21
|
+
* (ADR-0003, amended 2026-10-24).
|
|
22
|
+
*/
|
|
23
|
+
export async function notAFile(path) {
|
|
24
|
+
const found = await stat(path).catch(() => null);
|
|
25
|
+
if (found === null || found.isFile())
|
|
26
|
+
return null;
|
|
27
|
+
return found.isDirectory() ? 'a directory' : 'neither a file nor a directory';
|
|
28
|
+
}
|
|
29
|
+
/** A path that is there and is not a regular file, refused before it is opened. */
|
|
30
|
+
export class NotAFileError extends Error {
|
|
31
|
+
path;
|
|
32
|
+
what;
|
|
33
|
+
constructor(path, what) {
|
|
34
|
+
super(`${path} is ${what}; only a file is read`);
|
|
35
|
+
this.name = 'NotAFileError';
|
|
36
|
+
this.path = path;
|
|
37
|
+
this.what = what;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
15
40
|
/**
|
|
16
41
|
* The path as the filesystem names it: links and junctions followed, and on
|
|
17
42
|
* Windows short names such as `RUNNER~1` expanded. Git reports the top of a
|
|
@@ -57,10 +82,22 @@ export class NodeFileSystem {
|
|
|
57
82
|
host(path) {
|
|
58
83
|
return join(this.root, ...repoPath(path).split('/'));
|
|
59
84
|
}
|
|
60
|
-
|
|
85
|
+
/** Contents, or `null` when there is no such file; a {@link NotAFileError} where what is there is not one. */
|
|
86
|
+
async read(path, listed = false) {
|
|
87
|
+
const host = this.host(path);
|
|
88
|
+
// Asked before anything is opened: a path may come from the
|
|
89
|
+
// configuration, as a template's does, or be one a plan writes over,
|
|
90
|
+
// which no listing has said is a file. One a listing has just named a
|
|
91
|
+
// file is not asked again, which cost `list` over 200 briefs 12 to 38 ms
|
|
92
|
+
// on Windows.
|
|
93
|
+
if (!listed) {
|
|
94
|
+
const what = await notAFile(host);
|
|
95
|
+
if (what !== null)
|
|
96
|
+
throw new NotAFileError(repoPath(path), what);
|
|
97
|
+
}
|
|
61
98
|
let bytes;
|
|
62
99
|
try {
|
|
63
|
-
bytes = await readFile(
|
|
100
|
+
bytes = await readFile(host);
|
|
64
101
|
}
|
|
65
102
|
catch (error) {
|
|
66
103
|
if (error.code !== 'ENOENT')
|
package/dist/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The filesystem, behind an interface.\n *\n * Every path crossing this boundary is repository-relative and POSIX; the\n * Node implementation is the only place a host path exists. The in-memory\n * implementation is not a test double bolted on afterwards: it is how a\n * harness asks \"what would archiving this do\" against files that were never\n * written, and how the transaction's rollback is exercised with a failure on\n * exactly the operation that matters.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport { mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve, sep } from 'node:path';\n\nimport { normalisePath as repoPath } from './links.js';\n\nexport interface FileSystem {\n /** Contents, or `null` when there is no such file. */\n read(path: string): Promise<string | null>;\n /** Writes a file, creating its directory. Replaces an existing file atomically where the platform allows. */\n write(path: string, content: string): Promise<void>;\n /** Removes a file. Removing a file that is absent is an error. */\n remove(path: string): Promise<void>;\n /** Names of the files directly inside a directory, or `null` when it does not exist. */\n list(directory: string): Promise<string[] | null>;\n /** Every file beneath the root, as sorted repository-relative paths, skipping {@link IGNORED_DIRECTORIES}. */\n walk(): Promise<string[]>;\n exists(path: string): Promise<boolean>;\n}\n\n/**\n * What a run read from the disk, by repository-relative path: each file with\n * the SHA-256 of its bytes, or `null` where it was looked for and was not\n * there, and each directory with the names of the files it listed, or\n * `null` where there was none. An answer that names every input it was read\n * from is one another tool may give again without starting this one\n * (ADR-0002, amended 2026-10-19).\n */\nexport interface Reads {\n readonly files: Map<string, string | null>;\n readonly directories: Map<string, readonly string[] | null>;\n}\n\n/**\n * The path as the filesystem names it: links and junctions followed, and on\n * Windows short names such as `RUNNER~1` expanded. Git reports the top of a\n * work tree this way, so a root compared with it has to be named the same.\n * A path that does not exist is returned as given, for the caller to report.\n */\nexport async function canonicalPath(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/** Directories a walk never enters: build output and other tools' state. */\nexport const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'target',\n '.venv',\n '__pycache__',\n '.stryker-tmp',\n]);\n\n/** Errors a rename on Windows raises while another process briefly holds the file. */\nconst TRANSIENT = new Set(['EPERM', 'EACCES', 'EBUSY']);\nconst RENAME_ATTEMPTS = 6;\n\nasync function pause(ms: number): Promise<void> {\n await new Promise((done) => setTimeout(done, ms));\n}\n\nexport class NodeFileSystem implements FileSystem {\n readonly root: string;\n private readonly reads: Reads | undefined;\n\n /** `reads`, where it is given, is told every file read and every directory listed. */\n constructor(root: string, reads?: Reads) {\n this.root = resolve(root);\n this.reads = reads;\n }\n\n private host(path: string): string {\n return join(this.root, ...repoPath(path).split('/'));\n }\n\n async read(path: string): Promise<string | null> {\n let bytes: Buffer;\n try {\n bytes = await readFile(this.host(path));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n this.reads?.files.set(repoPath(path), null);\n return null;\n }\n // The digest is of the bytes the text is decoded from, so that nothing\n // written between a read and a digest of it can go unseen.\n this.reads?.files.set(repoPath(path), createHash('sha256').update(bytes).digest('hex'));\n return bytes.toString('utf8');\n }\n\n async write(path: string, content: string): Promise<void> {\n const target = this.host(path);\n await mkdir(dirname(target), { recursive: true });\n // Written beside the target and renamed over it, so a reader never sees half a file.\n const temporary = join(dirname(target), `.${randomBytes(6).toString('hex')}.spec-brief.tmp`);\n // \"wx\" refuses a name that is taken rather than write over it. Six random\n // bytes are not drawn twice in a test, so none shows the flag at work.\n await writeFile(temporary, content, { flag: 'wx' });\n try {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await rename(temporary, target);\n return;\n } catch (error) {\n // An error with no code is not in the set either.\n const code = (error as NodeJS.ErrnoException).code as string;\n if (!TRANSIENT.has(code) || attempt >= RENAME_ATTEMPTS) throw error;\n // How long it waits changes when the answer comes, not what it is.\n await pause(15 * attempt);\n }\n }\n } catch (error) {\n // Forced, so that a temporary file something else removed does not hide the rename's error.\n await rm(temporary, { force: true });\n throw error;\n }\n }\n\n async remove(path: string): Promise<void> {\n await rm(this.host(path));\n }\n\n async list(directory: string): Promise<string[] | null> {\n try {\n const entries = await readdir(this.host(directory), { withFileTypes: true });\n const names = entries.filter((e) => e.isFile()).map((e) => e.name);\n this.reads?.directories.set(repoPath(directory), names);\n return names;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR') {\n this.reads?.directories.set(repoPath(directory), null);\n return null;\n }\n // A directory that may not be read must not read as \"no briefs\".\n throw error;\n }\n }\n\n async walk(): Promise<string[]> {\n const found: string[] = [];\n const visit = async (directory: string): Promise<void> => {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const full = join(directory, entry.name);\n if (entry.isDirectory()) {\n if (!IGNORED_DIRECTORIES.has(entry.name)) await visit(full);\n } else if (entry.isFile()) {\n found.push(relative(this.root, full).split(sep).join('/'));\n }\n }\n };\n await visit(this.root);\n return found.sort();\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await stat(this.host(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n\nexport type FileOperation = 'write' | 'remove';\n\n/**\n * Files in a `Map`. `fail` lets a test make one chosen operation throw, which\n * is the only honest way to prove a rollback restores what it claims to.\n */\nexport class MemoryFileSystem implements FileSystem {\n readonly files: Map<string, string>;\n fail: ((operation: FileOperation, path: string) => boolean) | undefined;\n\n constructor(files: Readonly<Record<string, string>> = {}) {\n this.files = new Map(Object.entries(files).map(([path, content]) => [repoPath(path), content]));\n this.fail = undefined;\n }\n\n read(path: string): Promise<string | null> {\n return Promise.resolve(this.files.get(repoPath(path)) ?? null);\n }\n\n write(path: string, content: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('write', normal) === true) return Promise.reject(new Error(`injected failure writing ${normal}`));\n this.files.set(normal, content);\n return Promise.resolve();\n }\n\n remove(path: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('remove', normal) === true) return Promise.reject(new Error(`injected failure removing ${normal}`));\n if (!this.files.delete(normal)) return Promise.reject(new Error(`${normal} does not exist`));\n return Promise.resolve();\n }\n\n list(directory: string): Promise<string[] | null> {\n const prefix = repoPath(directory);\n const names: string[] = [];\n let isDirectory = prefix === '';\n for (const path of this.files.keys()) {\n if (prefix !== '' && !path.startsWith(`${prefix}/`)) continue;\n isDirectory = true;\n const rest = prefix === '' ? path : path.slice(prefix.length + 1);\n if (!rest.includes('/')) names.push(rest);\n }\n return Promise.resolve(isDirectory ? names.sort() : null);\n }\n\n walk(): Promise<string[]> {\n const paths = [...this.files.keys()].filter((path) => !path.split('/').some((part) => IGNORED_DIRECTORIES.has(part)));\n return Promise.resolve(paths.sort());\n }\n\n exists(path: string): Promise<boolean> {\n const normal = repoPath(path);\n return Promise.resolve(\n this.files.has(normal) || [...this.files.keys()].some((p) => normal === '' || p.startsWith(`${normal}/`)),\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The filesystem, behind an interface.\n *\n * Every path crossing this boundary is repository-relative and POSIX; the\n * Node implementation is the only place a host path exists. The in-memory\n * implementation is not a test double bolted on afterwards: it is how a\n * harness asks \"what would archiving this do\" against files that were never\n * written, and how the transaction's rollback is exercised with a failure on\n * exactly the operation that matters.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport { mkdir, readdir, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative, resolve, sep } from 'node:path';\n\nimport { normalisePath as repoPath } from './links.js';\n\nexport interface FileSystem {\n /**\n * Contents, or `null` when there is no such file. What is there and is not\n * a file is refused, and not read, unless `listed` says that `list` has\n * just named it a file.\n */\n read(path: string, listed?: boolean): Promise<string | null>;\n /** Writes a file, creating its directory. Replaces an existing file atomically where the platform allows. */\n write(path: string, content: string): Promise<void>;\n /** Removes a file. Removing a file that is absent is an error. */\n remove(path: string): Promise<void>;\n /** Names of the files directly inside a directory, or `null` when it does not exist. */\n list(directory: string): Promise<string[] | null>;\n /** Every file beneath the root, as sorted repository-relative paths, skipping {@link IGNORED_DIRECTORIES}. */\n walk(): Promise<string[]>;\n exists(path: string): Promise<boolean>;\n}\n\n/**\n * What a run read from the disk, by repository-relative path: each file with\n * the SHA-256 of its bytes, or `null` where it was looked for and was not\n * there, and each directory with the names of the files it listed, or\n * `null` where there was none. An answer that names every input it was read\n * from is one another tool may give again without starting this one\n * (ADR-0002, amended 2026-10-19).\n */\nexport interface Reads {\n readonly files: Map<string, string | null>;\n readonly directories: Map<string, readonly string[] | null>;\n}\n\n/** What stands at a path that is not a regular file, in the words a refusal says it in. */\nexport type NotAFile = 'a directory' | 'neither a file nor a directory';\n\n/**\n * What stands at a path where it is not a regular file, a link followed:\n * `null` for a file, and for a path where nothing is or that cannot be\n * asked about, which the read that follows reports as it always has. A\n * named pipe was read without end, a device as nothing or without end, and\n * a directory or a socket ended the run on Node's error and a stack\n * (ADR-0003, amended 2026-10-24).\n */\nexport async function notAFile(path: string): Promise<NotAFile | null> {\n const found = await stat(path).catch(() => null);\n if (found === null || found.isFile()) return null;\n return found.isDirectory() ? 'a directory' : 'neither a file nor a directory';\n}\n\n/** A path that is there and is not a regular file, refused before it is opened. */\nexport class NotAFileError extends Error {\n readonly path: string;\n readonly what: NotAFile;\n\n constructor(path: string, what: NotAFile) {\n super(`${path} is ${what}; only a file is read`);\n this.name = 'NotAFileError';\n this.path = path;\n this.what = what;\n }\n}\n\n/**\n * The path as the filesystem names it: links and junctions followed, and on\n * Windows short names such as `RUNNER~1` expanded. Git reports the top of a\n * work tree this way, so a root compared with it has to be named the same.\n * A path that does not exist is returned as given, for the caller to report.\n */\nexport async function canonicalPath(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/** Directories a walk never enters: build output and other tools' state. */\nexport const IGNORED_DIRECTORIES: ReadonlySet<string> = new Set([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n 'target',\n '.venv',\n '__pycache__',\n '.stryker-tmp',\n]);\n\n/** Errors a rename on Windows raises while another process briefly holds the file. */\nconst TRANSIENT = new Set(['EPERM', 'EACCES', 'EBUSY']);\nconst RENAME_ATTEMPTS = 6;\n\nasync function pause(ms: number): Promise<void> {\n await new Promise((done) => setTimeout(done, ms));\n}\n\nexport class NodeFileSystem implements FileSystem {\n readonly root: string;\n private readonly reads: Reads | undefined;\n\n /** `reads`, where it is given, is told every file read and every directory listed. */\n constructor(root: string, reads?: Reads) {\n this.root = resolve(root);\n this.reads = reads;\n }\n\n private host(path: string): string {\n return join(this.root, ...repoPath(path).split('/'));\n }\n\n /** Contents, or `null` when there is no such file; a {@link NotAFileError} where what is there is not one. */\n async read(path: string, listed = false): Promise<string | null> {\n const host = this.host(path);\n // Asked before anything is opened: a path may come from the\n // configuration, as a template's does, or be one a plan writes over,\n // which no listing has said is a file. One a listing has just named a\n // file is not asked again, which cost `list` over 200 briefs 12 to 38 ms\n // on Windows.\n if (!listed) {\n const what = await notAFile(host);\n if (what !== null) throw new NotAFileError(repoPath(path), what);\n }\n let bytes: Buffer;\n try {\n bytes = await readFile(host);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n this.reads?.files.set(repoPath(path), null);\n return null;\n }\n // The digest is of the bytes the text is decoded from, so that nothing\n // written between a read and a digest of it can go unseen.\n this.reads?.files.set(repoPath(path), createHash('sha256').update(bytes).digest('hex'));\n return bytes.toString('utf8');\n }\n\n async write(path: string, content: string): Promise<void> {\n const target = this.host(path);\n await mkdir(dirname(target), { recursive: true });\n // Written beside the target and renamed over it, so a reader never sees half a file.\n const temporary = join(dirname(target), `.${randomBytes(6).toString('hex')}.spec-brief.tmp`);\n // \"wx\" refuses a name that is taken rather than write over it. Six random\n // bytes are not drawn twice in a test, so none shows the flag at work.\n await writeFile(temporary, content, { flag: 'wx' });\n try {\n for (let attempt = 1; ; attempt += 1) {\n try {\n await rename(temporary, target);\n return;\n } catch (error) {\n // An error with no code is not in the set either.\n const code = (error as NodeJS.ErrnoException).code as string;\n if (!TRANSIENT.has(code) || attempt >= RENAME_ATTEMPTS) throw error;\n // How long it waits changes when the answer comes, not what it is.\n await pause(15 * attempt);\n }\n }\n } catch (error) {\n // Forced, so that a temporary file something else removed does not hide the rename's error.\n await rm(temporary, { force: true });\n throw error;\n }\n }\n\n async remove(path: string): Promise<void> {\n await rm(this.host(path));\n }\n\n async list(directory: string): Promise<string[] | null> {\n try {\n const entries = await readdir(this.host(directory), { withFileTypes: true });\n const names = entries.filter((e) => e.isFile()).map((e) => e.name);\n this.reads?.directories.set(repoPath(directory), names);\n return names;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ENOTDIR') {\n this.reads?.directories.set(repoPath(directory), null);\n return null;\n }\n // A directory that may not be read must not read as \"no briefs\".\n throw error;\n }\n }\n\n async walk(): Promise<string[]> {\n const found: string[] = [];\n const visit = async (directory: string): Promise<void> => {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n const full = join(directory, entry.name);\n if (entry.isDirectory()) {\n if (!IGNORED_DIRECTORIES.has(entry.name)) await visit(full);\n } else if (entry.isFile()) {\n found.push(relative(this.root, full).split(sep).join('/'));\n }\n }\n };\n await visit(this.root);\n return found.sort();\n }\n\n async exists(path: string): Promise<boolean> {\n try {\n await stat(this.host(path));\n return true;\n } catch {\n return false;\n }\n }\n}\n\nexport type FileOperation = 'write' | 'remove';\n\n/**\n * Files in a `Map`. `fail` lets a test make one chosen operation throw, which\n * is the only honest way to prove a rollback restores what it claims to.\n */\nexport class MemoryFileSystem implements FileSystem {\n readonly files: Map<string, string>;\n fail: ((operation: FileOperation, path: string) => boolean) | undefined;\n\n constructor(files: Readonly<Record<string, string>> = {}) {\n this.files = new Map(Object.entries(files).map(([path, content]) => [repoPath(path), content]));\n this.fail = undefined;\n }\n\n read(path: string): Promise<string | null> {\n return Promise.resolve(this.files.get(repoPath(path)) ?? null);\n }\n\n write(path: string, content: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('write', normal) === true) return Promise.reject(new Error(`injected failure writing ${normal}`));\n this.files.set(normal, content);\n return Promise.resolve();\n }\n\n remove(path: string): Promise<void> {\n const normal = repoPath(path);\n if (this.fail?.('remove', normal) === true) return Promise.reject(new Error(`injected failure removing ${normal}`));\n if (!this.files.delete(normal)) return Promise.reject(new Error(`${normal} does not exist`));\n return Promise.resolve();\n }\n\n list(directory: string): Promise<string[] | null> {\n const prefix = repoPath(directory);\n const names: string[] = [];\n let isDirectory = prefix === '';\n for (const path of this.files.keys()) {\n if (prefix !== '' && !path.startsWith(`${prefix}/`)) continue;\n isDirectory = true;\n const rest = prefix === '' ? path : path.slice(prefix.length + 1);\n if (!rest.includes('/')) names.push(rest);\n }\n return Promise.resolve(isDirectory ? names.sort() : null);\n }\n\n walk(): Promise<string[]> {\n const paths = [...this.files.keys()].filter((path) => !path.split('/').some((part) => IGNORED_DIRECTORIES.has(part)));\n return Promise.resolve(paths.sort());\n }\n\n exists(path: string): Promise<boolean> {\n const normal = repoPath(path);\n return Promise.resolve(\n this.files.has(normal) || [...this.files.keys()].some((p) => normal === '' || p.startsWith(`${normal}/`)),\n );\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { type Collision, collisionFindings, type CollisionOptions, type Collisio
|
|
|
10
10
|
export { BANNER_PLACEHOLDERS, type Config, CONFIG_FILES, CONFIG_SCHEMA, ConfigError, configJsonSchema, DEFAULT_CONFIG, parseConfig, type PluginReference, resolveConfig, type SectionRule, } from './config.js';
|
|
11
11
|
export { buildCorpus, type Corpus, dependencyCycles, findBriefs, isReady, pendingDependencies, resolveDependency, type SourceFile, type Waiting, waitingOnDeferred, } from './corpus.js';
|
|
12
12
|
export { type ArchiveOptions, BriefEngine, EngineError, type EngineSetup, type Inputs, type NewOptions, type OpenOptions, today } from './engine.js';
|
|
13
|
-
export { type FileSystem, MemoryFileSystem, NodeFileSystem, type Reads } from './fs.js';
|
|
13
|
+
export { type FileSystem, MemoryFileSystem, NodeFileSystem, type NotAFile, NotAFileError, type Reads } from './fs.js';
|
|
14
14
|
export { type CommitInfo, type FileChange, type Git, GitError, NodeGit } from './git.js';
|
|
15
15
|
export { type Glob, globBase, globBases, globCovers, globWitness, intersectGlobs, type Literal, type LiteralReading, matchGlob, parseGlob, type ParseOptions, readingIn, WITNESS_BUDGET, type Witness, } from './glob.js';
|
|
16
16
|
export { integrityOf } from './integrity.js';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { collisionFindings, collisions, } from './collisions.js';
|
|
|
10
10
|
export { BANNER_PLACEHOLDERS, CONFIG_FILES, CONFIG_SCHEMA, ConfigError, configJsonSchema, DEFAULT_CONFIG, parseConfig, resolveConfig, } from './config.js';
|
|
11
11
|
export { buildCorpus, dependencyCycles, findBriefs, isReady, pendingDependencies, resolveDependency, waitingOnDeferred, } from './corpus.js';
|
|
12
12
|
export { BriefEngine, EngineError, today } from './engine.js';
|
|
13
|
-
export { MemoryFileSystem, NodeFileSystem } from './fs.js';
|
|
13
|
+
export { MemoryFileSystem, NodeFileSystem, NotAFileError } from './fs.js';
|
|
14
14
|
export { GitError, NodeGit } from './git.js';
|
|
15
15
|
export { globBase, globBases, globCovers, globWitness, intersectGlobs, matchGlob, parseGlob, readingIn, WITNESS_BUDGET, } from './glob.js';
|
|
16
16
|
export { integrityOf } from './integrity.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EACL,YAAY,EAGZ,SAAS,EAET,WAAW,EACX,aAAa,EAEb,YAAY,EAGZ,QAAQ,GAET,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAc,eAAe,EAAqB,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/H,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAEL,iBAAiB,EAGjB,UAAU,GAKX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,mBAAmB,EAEnB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,WAAW,EAEX,aAAa,GAEd,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EAEX,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,iBAAiB,EAGjB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,WAAW,EAAE,WAAW,EAAoE,KAAK,EAAE,MAAM,aAAa,CAAC;AACrJ,OAAO,EAAmB,gBAAgB,EAAE,cAAc,EAAc,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EACL,YAAY,EAGZ,SAAS,EAET,WAAW,EACX,aAAa,EAEb,YAAY,EAGZ,QAAQ,GAET,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAc,eAAe,EAAqB,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/H,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAEL,iBAAiB,EAGjB,UAAU,GAKX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,mBAAmB,EAEnB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,WAAW,EAEX,aAAa,GAEd,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EAEX,gBAAgB,EAChB,UAAU,EACV,OAAO,EACP,mBAAmB,EACnB,iBAAiB,EAGjB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,WAAW,EAAE,WAAW,EAAoE,KAAK,EAAE,MAAM,aAAa,CAAC;AACrJ,OAAO,EAAmB,gBAAgB,EAAE,cAAc,EAAiB,aAAa,EAAc,MAAM,SAAS,CAAC;AACtH,OAAO,EAA8C,QAAQ,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACzF,OAAO,EAEL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,WAAW,EACX,cAAc,EAGd,SAAS,EACT,SAAS,EAET,SAAS,EACT,cAAc,GAEf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAiC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAqB,MAAM,WAAW,CAAC;AAC9H,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAe,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1J,OAAO,EAAE,aAAa,EAAE,eAAe,EAA+D,KAAK,EAAE,MAAM,YAAY,CAAC;AAChI,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAEL,aAAa,EACb,KAAK,EACL,KAAK,EAGL,SAAS,EACT,OAAO,EAEP,QAAQ,EACR,gBAAgB,GAIjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,IAAI,EAA4B,OAAO,EAAqB,MAAM,YAAY,CAAC;AAGxG,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAuC,MAAS;IAC1E,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * The library. Everything the CLI does is reachable from here, and the\n * CLI is built from nothing else.\n */\n\nexport { applyPlan, ConflictError, TransactionError } from './apply.js';\nexport {\n applyWaivers,\n type ArchiveRequest,\n type FileOp,\n openTasks,\n type Plan,\n planArchive,\n planUnarchive,\n type PullRequest,\n renderBanner,\n type UnarchiveRequest,\n type Unmeasured,\n WAIVABLE,\n type Waiver,\n} from './archive.js';\nexport { BANNER_CLOSE, BANNER_OPEN, type Brief, BUILT_IN_FIELDS, type FieldProblem, idFromName, parseBrief } from './brief.js';\nexport { main, type CliIO, EXIT_ERROR, EXIT_FAILED, EXIT_OK, HELP, UsageError } from './cli.js';\nexport {\n type Collision,\n collisionFindings,\n type CollisionOptions,\n type CollisionReport,\n collisions,\n type Overlap,\n type SharedDirectory,\n type UndecidedPair,\n type WaveMatrix,\n} from './collisions.js';\nexport {\n BANNER_PLACEHOLDERS,\n type Config,\n CONFIG_FILES,\n CONFIG_SCHEMA,\n ConfigError,\n configJsonSchema,\n DEFAULT_CONFIG,\n parseConfig,\n type PluginReference,\n resolveConfig,\n type SectionRule,\n} from './config.js';\nexport {\n buildCorpus,\n type Corpus,\n dependencyCycles,\n findBriefs,\n isReady,\n pendingDependencies,\n resolveDependency,\n type SourceFile,\n type Waiting,\n waitingOnDeferred,\n} from './corpus.js';\nexport { type ArchiveOptions, BriefEngine, EngineError, type EngineSetup, type Inputs, type NewOptions, type OpenOptions, today } from './engine.js';\nexport { type FileSystem, MemoryFileSystem, NodeFileSystem, type NotAFile, NotAFileError, type Reads } from './fs.js';\nexport { type CommitInfo, type FileChange, type Git, GitError, NodeGit } from './git.js';\nexport {\n type Glob,\n globBase,\n globBases,\n globCovers,\n globWitness,\n intersectGlobs,\n type Literal,\n type LiteralReading,\n matchGlob,\n parseGlob,\n type ParseOptions,\n readingIn,\n WITNESS_BUDGET,\n type Witness,\n} from './glob.js';\nexport { integrityOf } from './integrity.js';\nexport { failing, lint, type LintOptions, type Plugin, ruleIds, sortFindings, summarise, type WaiveContext } from './lint.js';\nexport { asPlugin, asWaivers, loadPlugins } from './plugins.js';\nexport { type Format, FORMATS, githubCommands, gitlabCodeQuality, JSON_SCHEMA_VERSION, matrixJson, sarif, scheduleJson, sectionsJson } from './report.js';\nexport { ARCHIVE_RULES, COLLISION_RULES, type Rule, type RuleContext, type RuleInfo, type RuleResult, RULES } from './rules.js';\nexport { nextId, renderNewBrief } from './scaffold.js';\nexport {\n type Breach,\n breachReasons,\n holds,\n moves,\n type Passed,\n type Placement,\n planWaves,\n reasons,\n type Schedule,\n schedule,\n scheduleFindings,\n type ScheduleOptions,\n type Unplaced,\n type WavePlan,\n} from './schedule.js';\nexport { contradictions, meet, type Meeting, type Scope, scopeOf, type ScopePattern } from './scope.js';\nexport type { Finding, Phase, Severity, SeveritySetting, Status } from './types.js';\n\n/** Types a plugin author writes against, with inference for the rule list. */\nexport function definePlugin<T extends import('./lint.js').Plugin>(plugin: T): T {\n return plugin;\n}\n"]}
|
package/dist/plugins.js
CHANGED
|
@@ -14,6 +14,7 @@ import { createRequire } from 'node:module';
|
|
|
14
14
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
15
15
|
import { pathToFileURL } from 'node:url';
|
|
16
16
|
import { ConfigError } from './config.js';
|
|
17
|
+
import { notAFile } from './fs.js';
|
|
17
18
|
/** Asked about whatever a module exported as a severity, so it is a set of anything. */
|
|
18
19
|
const SEVERITIES = new Set(['off', 'note', 'warning', 'error']);
|
|
19
20
|
/** The conditions Node matches in "exports" when it imports a module. */
|
|
@@ -87,8 +88,19 @@ export function exportsTarget(exports, subpath) {
|
|
|
87
88
|
}
|
|
88
89
|
return best === undefined ? null : (exportTarget(map[best.key], best.match) ?? null);
|
|
89
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Refuses a path that is not a file before it is read or imported: a named
|
|
93
|
+
* pipe as a plugin's module, or as the package.json its package is found
|
|
94
|
+
* by, had the run wait without end (ADR-0003, amended 2026-10-24).
|
|
95
|
+
*/
|
|
96
|
+
async function onlyAFile(path) {
|
|
97
|
+
const what = await notAFile(path);
|
|
98
|
+
if (what !== null)
|
|
99
|
+
throw new Error(`${path} is ${what}; only a file is read`);
|
|
100
|
+
}
|
|
90
101
|
/** A package.json, `undefined` when there is none to read. */
|
|
91
102
|
async function manifest(path) {
|
|
103
|
+
await onlyAFile(path);
|
|
92
104
|
try {
|
|
93
105
|
// The encoding decides nothing here either, for the reason `version` in cli.ts has.
|
|
94
106
|
return JSON.parse(await readFile(path, 'utf8'));
|
|
@@ -135,10 +147,11 @@ async function resolvePackage(specifier, root) {
|
|
|
135
147
|
}
|
|
136
148
|
return createRequire(join(root, 'package.json')).resolve(specifier);
|
|
137
149
|
}
|
|
138
|
-
|
|
150
|
+
/** The file a specifier names, from the root. */
|
|
151
|
+
async function modulePath(specifier, root) {
|
|
139
152
|
if (specifier.startsWith('.') || isAbsolute(specifier))
|
|
140
|
-
return
|
|
141
|
-
return
|
|
153
|
+
return resolve(root, specifier);
|
|
154
|
+
return resolvePackage(specifier, root);
|
|
142
155
|
}
|
|
143
156
|
function isRule(value) {
|
|
144
157
|
if (typeof value !== 'object' || value === null)
|
|
@@ -201,7 +214,9 @@ export async function loadPlugins(references, root) {
|
|
|
201
214
|
for (const reference of references) {
|
|
202
215
|
let loaded;
|
|
203
216
|
try {
|
|
204
|
-
|
|
217
|
+
const path = await modulePath(reference.module, root);
|
|
218
|
+
await onlyAFile(path);
|
|
219
|
+
loaded = (await import(pathToFileURL(path).href));
|
|
205
220
|
}
|
|
206
221
|
catch (error) {
|
|
207
222
|
throw new ConfigError(reference.module, [`could not be loaded: ${error.message}`]);
|
package/dist/plugins.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Loading rule plugins named by the configuration.\n *\n * spec-brief does not know how another tool signs a departure, records a\n * reproducer or measures a blast radius, and should not: the tool that\n * defines a format is the one that can check it. A plugin is a module whose\n * default export is a plugin object, or a function of the configured options\n * that returns one. Loading one runs its code, exactly as loading an ESLint\n * configuration does; the configuration belongs to the repository, and so\n * does that trust.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { Waiver } from './archive.js';\nimport type { PluginReference } from './config.js';\nimport { ConfigError } from './config.js';\nimport type { Plugin } from './lint.js';\nimport type { Rule } from './rules.js';\n\n/** Asked about whatever a module exported as a severity, so it is a set of anything. */\nconst SEVERITIES: ReadonlySet<unknown> = new Set(['off', 'note', 'warning', 'error']);\n\n/** The conditions Node matches in \"exports\" when it imports a module. */\nconst IMPORT_CONDITIONS = new Set(['node', 'import', 'module-sync', 'default']);\n\n/**\n * A target of \"exports\": a path inside the package, `null` where the package\n * refuses the subpath, `undefined` where no condition matched and a fallback\n * may still. A target that is not a \"./\" path, or that climbs out of the\n * package or into another, is refused, as Node refuses it: its segments are\n * split on a backslash too, since `join` reads one as a separator on Windows,\n * where \"pkg/..\\..\\outside\" would otherwise climb out of node_modules.\n */\nfunction exportTarget(value: unknown, match: string | null): string | null | undefined {\n if (typeof value === 'string') {\n if (!value.startsWith('./')) return null;\n const path = match === null ? value : value.replaceAll('*', match);\n const parts = path.slice(2).split(/[\\\\/]/);\n return parts.some((part) => part === '..' || part === '.' || part.toLowerCase() === 'node_modules') ? null : path;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n const path = exportTarget(item, match);\n if (typeof path === 'string') return path;\n }\n return null;\n }\n if (typeof value === 'object' && value !== null) {\n for (const [condition, item] of Object.entries(value)) {\n if (!IMPORT_CONDITIONS.has(condition)) continue;\n const path = exportTarget(item, match);\n if (path !== undefined) return path;\n }\n return undefined;\n }\n return null;\n}\n\n/**\n * The file a package's \"exports\" gives a subpath (`.` or `./x`) under the\n * conditions of an import, or `null` when it gives none: Node's\n * PACKAGE_EXPORTS_RESOLVE. An exact key wins; then the \"*\" pattern with the\n * longest prefix, and of those the longest key.\n */\nexport function exportsTarget(exports: unknown, subpath: string): string | null {\n // Only a map of subpaths has a key that starts with a dot: a path's and a list's keys are their indices.\n const keys = Object.keys(Object(exports) as object);\n const dotted = keys.filter((key) => key.startsWith('.'));\n if (dotted.length === 0) return subpath === '.' ? (exportTarget(exports, null) ?? null) : null;\n // Subpaths beside conditions is a package Node refuses to read.\n if (dotted.length !== keys.length) return null;\n const map = exports as Record<string, unknown>;\n if (!subpath.includes('*') && Object.hasOwn(map, subpath)) return exportTarget(map[subpath], null) ?? null;\n let best: { readonly key: string; readonly prefix: number; readonly match: string } | undefined;\n for (const key of dotted) {\n const star = key.indexOf('*');\n if (star === -1 || key.includes('*', star + 1)) continue;\n const suffix = key.slice(star + 1);\n if (subpath.length < key.length || !subpath.startsWith(key.slice(0, star)) || !subpath.endsWith(suffix)) continue;\n // Two keys of one prefix and one length that both match are one key, so the lengths never tie.\n if (best === undefined || star > best.prefix || (star === best.prefix && key.length > best.key.length)) {\n best = { key, prefix: star, match: subpath.slice(star, subpath.length - suffix.length) };\n }\n }\n return best === undefined ? null : (exportTarget(map[best.key], best.match) ?? null);\n}\n\n/** A package.json, `undefined` when there is none to read. */\nasync function manifest(path: string): Promise<Record<string, unknown> | undefined> {\n try {\n // The encoding decides nothing here either, for the reason `version` in cli.ts has.\n return JSON.parse(await readFile(path, 'utf8'));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT' || (error as NodeJS.ErrnoException).code === 'ENOTDIR') return undefined;\n throw error;\n }\n}\n\n/**\n * The file a package specifier names, found from the root the way an import\n * there would find it. `require.resolve` reads \"exports\" under the require\n * conditions, so a package that exports only for \"import\" - an ESM-only\n * plugin - could not be found; Node's `import.meta.resolve` resolves only\n * from the module calling it, not from the root. So the nearest\n * node_modules/<name> is found here and its \"exports\" read under the import\n * conditions; a package with no \"exports\" resolves as it always has.\n */\nasync function resolvePackage(specifier: string, root: string): Promise<string> {\n const slash = specifier.indexOf('/', specifier.startsWith('@') ? specifier.indexOf('/') + 1 : 0);\n const name = slash === -1 ? specifier : specifier.slice(0, slash);\n const subpath = slash === -1 ? '.' : `.${specifier.slice(slash)}`;\n let directory = root;\n // Bounded by the depth of the path: each pass moves one directory up, and\n // the search stops at the top, so no path reaches the bound and one pass\n // more or fewer of it is the same search.\n for (let depth = 0; depth < 256; depth += 1) {\n const packageDirectory = join(directory, 'node_modules', name);\n const found = await manifest(join(packageDirectory, 'package.json'));\n if (found !== undefined) {\n if (found['exports'] === undefined || found['exports'] === null) break;\n const target = exportsTarget(found['exports'], subpath);\n if (target === null) throw new Error(`${name} exports no \"${subpath}\" for import`);\n return join(packageDirectory, target);\n }\n const parent = dirname(directory);\n // Stopping at the top saves asking it again until the bound: the answer is the same.\n if (parent === directory) break;\n directory = parent;\n }\n return createRequire(join(root, 'package.json')).resolve(specifier);\n}\n\nasync function specifierUrl(specifier: string, root: string): Promise<string> {\n if (specifier.startsWith('.') || isAbsolute(specifier)) return pathToFileURL(resolve(root, specifier)).href;\n return pathToFileURL(await resolvePackage(specifier, root)).href;\n}\n\nfunction isRule(value: unknown): value is Rule {\n if (typeof value !== 'object' || value === null) return false;\n const rule = value as Record<string, unknown>;\n return (\n typeof rule['id'] === 'string' &&\n /^[a-z0-9][a-z0-9-]*$/.test(rule['id']) &&\n typeof rule['description'] === 'string' &&\n SEVERITIES.has(rule['severity']) &&\n typeof rule['check'] === 'function'\n );\n}\n\n/** Checks the shape a module exported, since nothing typed it on the way in. */\nexport function asPlugin(value: unknown, module: string, options: unknown): Plugin {\n const problems: string[] = [];\n if (typeof value !== 'object' || value === null) {\n throw new ConfigError(module, ['does not export a plugin: expected an object with \"name\" and \"rules\"']);\n }\n const candidate = value as Record<string, unknown>;\n const name = candidate['name'];\n const rules = candidate['rules'];\n const waive = candidate['waive'];\n if (typeof name !== 'string' || !/^[a-z0-9@][a-z0-9@/._-]*$/i.test(name)) problems.push('\"name\" must be a plain identifier');\n if (!Array.isArray(rules)) problems.push('\"rules\" must be a list');\n else {\n rules.forEach((rule, i) => {\n if (!isRule(rule)) problems.push(`rules[${i}] needs a lower-case \"id\", a \"description\", a \"severity\" and a \"check\" function`);\n });\n }\n if (waive !== undefined && typeof waive !== 'function') problems.push('\"waive\" must be a function');\n if (problems.length > 0) throw new ConfigError(module, problems);\n const plugin: Plugin = { name: name as string, rules: rules as Rule[], options };\n return waive === undefined ? plugin : { ...plugin, waive: waive as NonNullable<Plugin['waive']> };\n}\n\n/**\n * Checks what a `waive` hook returned: a list of `{ rule, path, reason }`,\n * each a string. A hook that returns nothing waives nothing: a function that\n * found no ruling and fell off its end has answered, not failed.\n */\nexport function asWaivers(value: unknown, plugin: string): Waiver[] {\n const isWaiver = (item: unknown): item is Waiver => {\n if (typeof item !== 'object' || item === null) return false;\n const w = item as Record<string, unknown>;\n return typeof w['rule'] === 'string' && typeof w['path'] === 'string' && typeof w['reason'] === 'string';\n };\n if (value === undefined) return [];\n if (!Array.isArray(value) || !value.every(isWaiver)) {\n throw new ConfigError(`plugin \"${plugin}\"`, ['\"waive\" must return a list of { rule, path, reason }, each a string']);\n }\n return value.map((w) => ({ rule: w.rule, path: w.path, reason: w.reason }));\n}\n\nexport async function loadPlugins(references: readonly PluginReference[], root: string): Promise<Plugin[]> {\n const plugins: Plugin[] = [];\n for (const reference of references) {\n let loaded: Record<string, unknown>;\n try {\n loaded = (await import(await specifierUrl(reference.module, root))) as Record<string, unknown>;\n } catch (error) {\n throw new ConfigError(reference.module, [`could not be loaded: ${(error as Error).message}`]);\n }\n const exported = loaded['default'] ?? loaded['plugin'];\n const value = typeof exported === 'function' ? await (exported as (o: unknown) => unknown)(reference.options) : exported;\n plugins.push(asPlugin(value, reference.module, reference.options));\n }\n const names = plugins.map((p) => p.name);\n const repeated = names.filter((n, i) => names.indexOf(n) !== i);\n if (repeated.length > 0) throw new ConfigError('plugins', [`two plugins are named \"${repeated[0] as string}\"`]);\n return plugins;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Loading rule plugins named by the configuration.\n *\n * spec-brief does not know how another tool signs a departure, records a\n * reproducer or measures a blast radius, and should not: the tool that\n * defines a format is the one that can check it. A plugin is a module whose\n * default export is a plugin object, or a function of the configured options\n * that returns one. Loading one runs its code, exactly as loading an ESLint\n * configuration does; the configuration belongs to the repository, and so\n * does that trust.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { Waiver } from './archive.js';\nimport type { PluginReference } from './config.js';\nimport { ConfigError } from './config.js';\nimport { notAFile } from './fs.js';\nimport type { Plugin } from './lint.js';\nimport type { Rule } from './rules.js';\n\n/** Asked about whatever a module exported as a severity, so it is a set of anything. */\nconst SEVERITIES: ReadonlySet<unknown> = new Set(['off', 'note', 'warning', 'error']);\n\n/** The conditions Node matches in \"exports\" when it imports a module. */\nconst IMPORT_CONDITIONS = new Set(['node', 'import', 'module-sync', 'default']);\n\n/**\n * A target of \"exports\": a path inside the package, `null` where the package\n * refuses the subpath, `undefined` where no condition matched and a fallback\n * may still. A target that is not a \"./\" path, or that climbs out of the\n * package or into another, is refused, as Node refuses it: its segments are\n * split on a backslash too, since `join` reads one as a separator on Windows,\n * where \"pkg/..\\..\\outside\" would otherwise climb out of node_modules.\n */\nfunction exportTarget(value: unknown, match: string | null): string | null | undefined {\n if (typeof value === 'string') {\n if (!value.startsWith('./')) return null;\n const path = match === null ? value : value.replaceAll('*', match);\n const parts = path.slice(2).split(/[\\\\/]/);\n return parts.some((part) => part === '..' || part === '.' || part.toLowerCase() === 'node_modules') ? null : path;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n const path = exportTarget(item, match);\n if (typeof path === 'string') return path;\n }\n return null;\n }\n if (typeof value === 'object' && value !== null) {\n for (const [condition, item] of Object.entries(value)) {\n if (!IMPORT_CONDITIONS.has(condition)) continue;\n const path = exportTarget(item, match);\n if (path !== undefined) return path;\n }\n return undefined;\n }\n return null;\n}\n\n/**\n * The file a package's \"exports\" gives a subpath (`.` or `./x`) under the\n * conditions of an import, or `null` when it gives none: Node's\n * PACKAGE_EXPORTS_RESOLVE. An exact key wins; then the \"*\" pattern with the\n * longest prefix, and of those the longest key.\n */\nexport function exportsTarget(exports: unknown, subpath: string): string | null {\n // Only a map of subpaths has a key that starts with a dot: a path's and a list's keys are their indices.\n const keys = Object.keys(Object(exports) as object);\n const dotted = keys.filter((key) => key.startsWith('.'));\n if (dotted.length === 0) return subpath === '.' ? (exportTarget(exports, null) ?? null) : null;\n // Subpaths beside conditions is a package Node refuses to read.\n if (dotted.length !== keys.length) return null;\n const map = exports as Record<string, unknown>;\n if (!subpath.includes('*') && Object.hasOwn(map, subpath)) return exportTarget(map[subpath], null) ?? null;\n let best: { readonly key: string; readonly prefix: number; readonly match: string } | undefined;\n for (const key of dotted) {\n const star = key.indexOf('*');\n if (star === -1 || key.includes('*', star + 1)) continue;\n const suffix = key.slice(star + 1);\n if (subpath.length < key.length || !subpath.startsWith(key.slice(0, star)) || !subpath.endsWith(suffix)) continue;\n // Two keys of one prefix and one length that both match are one key, so the lengths never tie.\n if (best === undefined || star > best.prefix || (star === best.prefix && key.length > best.key.length)) {\n best = { key, prefix: star, match: subpath.slice(star, subpath.length - suffix.length) };\n }\n }\n return best === undefined ? null : (exportTarget(map[best.key], best.match) ?? null);\n}\n\n/**\n * Refuses a path that is not a file before it is read or imported: a named\n * pipe as a plugin's module, or as the package.json its package is found\n * by, had the run wait without end (ADR-0003, amended 2026-10-24).\n */\nasync function onlyAFile(path: string): Promise<void> {\n const what = await notAFile(path);\n if (what !== null) throw new Error(`${path} is ${what}; only a file is read`);\n}\n\n/** A package.json, `undefined` when there is none to read. */\nasync function manifest(path: string): Promise<Record<string, unknown> | undefined> {\n await onlyAFile(path);\n try {\n // The encoding decides nothing here either, for the reason `version` in cli.ts has.\n return JSON.parse(await readFile(path, 'utf8'));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT' || (error as NodeJS.ErrnoException).code === 'ENOTDIR') return undefined;\n throw error;\n }\n}\n\n/**\n * The file a package specifier names, found from the root the way an import\n * there would find it. `require.resolve` reads \"exports\" under the require\n * conditions, so a package that exports only for \"import\" - an ESM-only\n * plugin - could not be found; Node's `import.meta.resolve` resolves only\n * from the module calling it, not from the root. So the nearest\n * node_modules/<name> is found here and its \"exports\" read under the import\n * conditions; a package with no \"exports\" resolves as it always has.\n */\nasync function resolvePackage(specifier: string, root: string): Promise<string> {\n const slash = specifier.indexOf('/', specifier.startsWith('@') ? specifier.indexOf('/') + 1 : 0);\n const name = slash === -1 ? specifier : specifier.slice(0, slash);\n const subpath = slash === -1 ? '.' : `.${specifier.slice(slash)}`;\n let directory = root;\n // Bounded by the depth of the path: each pass moves one directory up, and\n // the search stops at the top, so no path reaches the bound and one pass\n // more or fewer of it is the same search.\n for (let depth = 0; depth < 256; depth += 1) {\n const packageDirectory = join(directory, 'node_modules', name);\n const found = await manifest(join(packageDirectory, 'package.json'));\n if (found !== undefined) {\n if (found['exports'] === undefined || found['exports'] === null) break;\n const target = exportsTarget(found['exports'], subpath);\n if (target === null) throw new Error(`${name} exports no \"${subpath}\" for import`);\n return join(packageDirectory, target);\n }\n const parent = dirname(directory);\n // Stopping at the top saves asking it again until the bound: the answer is the same.\n if (parent === directory) break;\n directory = parent;\n }\n return createRequire(join(root, 'package.json')).resolve(specifier);\n}\n\n/** The file a specifier names, from the root. */\nasync function modulePath(specifier: string, root: string): Promise<string> {\n if (specifier.startsWith('.') || isAbsolute(specifier)) return resolve(root, specifier);\n return resolvePackage(specifier, root);\n}\n\nfunction isRule(value: unknown): value is Rule {\n if (typeof value !== 'object' || value === null) return false;\n const rule = value as Record<string, unknown>;\n return (\n typeof rule['id'] === 'string' &&\n /^[a-z0-9][a-z0-9-]*$/.test(rule['id']) &&\n typeof rule['description'] === 'string' &&\n SEVERITIES.has(rule['severity']) &&\n typeof rule['check'] === 'function'\n );\n}\n\n/** Checks the shape a module exported, since nothing typed it on the way in. */\nexport function asPlugin(value: unknown, module: string, options: unknown): Plugin {\n const problems: string[] = [];\n if (typeof value !== 'object' || value === null) {\n throw new ConfigError(module, ['does not export a plugin: expected an object with \"name\" and \"rules\"']);\n }\n const candidate = value as Record<string, unknown>;\n const name = candidate['name'];\n const rules = candidate['rules'];\n const waive = candidate['waive'];\n if (typeof name !== 'string' || !/^[a-z0-9@][a-z0-9@/._-]*$/i.test(name)) problems.push('\"name\" must be a plain identifier');\n if (!Array.isArray(rules)) problems.push('\"rules\" must be a list');\n else {\n rules.forEach((rule, i) => {\n if (!isRule(rule)) problems.push(`rules[${i}] needs a lower-case \"id\", a \"description\", a \"severity\" and a \"check\" function`);\n });\n }\n if (waive !== undefined && typeof waive !== 'function') problems.push('\"waive\" must be a function');\n if (problems.length > 0) throw new ConfigError(module, problems);\n const plugin: Plugin = { name: name as string, rules: rules as Rule[], options };\n return waive === undefined ? plugin : { ...plugin, waive: waive as NonNullable<Plugin['waive']> };\n}\n\n/**\n * Checks what a `waive` hook returned: a list of `{ rule, path, reason }`,\n * each a string. A hook that returns nothing waives nothing: a function that\n * found no ruling and fell off its end has answered, not failed.\n */\nexport function asWaivers(value: unknown, plugin: string): Waiver[] {\n const isWaiver = (item: unknown): item is Waiver => {\n if (typeof item !== 'object' || item === null) return false;\n const w = item as Record<string, unknown>;\n return typeof w['rule'] === 'string' && typeof w['path'] === 'string' && typeof w['reason'] === 'string';\n };\n if (value === undefined) return [];\n if (!Array.isArray(value) || !value.every(isWaiver)) {\n throw new ConfigError(`plugin \"${plugin}\"`, ['\"waive\" must return a list of { rule, path, reason }, each a string']);\n }\n return value.map((w) => ({ rule: w.rule, path: w.path, reason: w.reason }));\n}\n\nexport async function loadPlugins(references: readonly PluginReference[], root: string): Promise<Plugin[]> {\n const plugins: Plugin[] = [];\n for (const reference of references) {\n let loaded: Record<string, unknown>;\n try {\n const path = await modulePath(reference.module, root);\n await onlyAFile(path);\n loaded = (await import(pathToFileURL(path).href)) as Record<string, unknown>;\n } catch (error) {\n throw new ConfigError(reference.module, [`could not be loaded: ${(error as Error).message}`]);\n }\n const exported = loaded['default'] ?? loaded['plugin'];\n const value = typeof exported === 'function' ? await (exported as (o: unknown) => unknown)(reference.options) : exported;\n plugins.push(asPlugin(value, reference.module, reference.options));\n }\n const names = plugins.map((p) => p.name);\n const repeated = names.filter((n, i) => names.indexOf(n) !== i);\n if (repeated.length > 0) throw new ConfigError('plugins', [`two plugins are named \"${repeated[0] as string}\"`]);\n return plugins;\n}\n"]}
|
package/package.json
CHANGED