@descent-vtt/spec-harness 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +325 -0
  4. package/README.md +39 -18
  5. package/dist/briefs.d.ts +45 -0
  6. package/dist/briefs.js +73 -0
  7. package/dist/briefs.js.map +1 -1
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.js +308 -142
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands.js +7 -2
  12. package/dist/commands.js.map +1 -1
  13. package/dist/config.d.ts +13 -1
  14. package/dist/config.js +14 -3
  15. package/dist/config.js.map +1 -1
  16. package/dist/configure.d.ts +94 -3
  17. package/dist/configure.js +204 -30
  18. package/dist/configure.js.map +1 -1
  19. package/dist/fs.d.ts +104 -1
  20. package/dist/fs.js +383 -11
  21. package/dist/fs.js.map +1 -1
  22. package/dist/git.d.ts +88 -2
  23. package/dist/git.js +155 -8
  24. package/dist/git.js.map +1 -1
  25. package/dist/guard.d.ts +20 -1
  26. package/dist/guard.js +31 -0
  27. package/dist/guard.js.map +1 -1
  28. package/dist/hooks.d.ts +59 -0
  29. package/dist/hooks.js +76 -3
  30. package/dist/hooks.js.map +1 -1
  31. package/dist/round.d.ts +27 -1
  32. package/dist/round.js +75 -17
  33. package/dist/round.js.map +1 -1
  34. package/dist/sandbox.js +4 -3
  35. package/dist/sandbox.js.map +1 -1
  36. package/dist/server.d.ts +25 -1
  37. package/dist/server.js +230 -28
  38. package/dist/server.js.map +1 -1
  39. package/dist/setup.js +40 -10
  40. package/dist/setup.js.map +1 -1
  41. package/dist/siblings.d.ts +29 -6
  42. package/dist/siblings.js +152 -39
  43. package/dist/siblings.js.map +1 -1
  44. package/dist/usage.d.ts +42 -7
  45. package/dist/usage.js +65 -6
  46. package/dist/usage.js.map +1 -1
  47. package/dist/workspace.d.ts +45 -3
  48. package/dist/workspace.js +143 -13
  49. package/dist/workspace.js.map +1 -1
  50. package/package.json +3 -3
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 edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, readFileSync, realpathSync, statSync, type Stats } from 'node:fs';\nimport { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing. */\nfunction standing(path: string): Stats | null {\n try {\n return statSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Reads `.spec-harness.json` at the root, or the defaults when there is none.\n *\n * Only a file is read. A named pipe under the file's name was read without\n * end, by every command and by the hook before every write; and Node's words\n * for a directory, EISDIR, are not the harness's.\n */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n const there = standing(file);\n if (there === null) return { config: DEFAULT_CONFIG, file: null };\n if (!there.isFile()) throw new ConfigError(`${CONFIG_FILE} is ${there.isDirectory() ? 'a directory' : 'neither a file nor a directory'}, not a configuration file`);\n let raw: unknown;\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n raw = JSON.parse(await readFile(file, 'utf8'));\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`);\n }\n return { config: parseConfig(raw), file };\n}\n\n/**\n * A path where the harness keeps its state that holds something else: said\n * in a line, exit 2, where Node's ENOTDIR and a stack sent a person looking\n * for a defect (spec-core ADR-0005).\n */\nexport class StateError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StateError';\n }\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n // join leaves a real path as it is, and reads a segment's leading\n // separator as the one between it and the last.\n return join(realpathSync.native(current), ...missing.reverse());\n } catch {\n const parent = dirname(current);\n // Only a root that does not exist fails at the top, a drive Windows\n // does not have; everywhere else the loop ends in the try.\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length));\n current = parent;\n }\n }\n}\n\n/**\n * Whether `directory` is the one git names as the top of a work tree: the\n * two spelled alike once the filesystem has spelled the directory, links\n * and short names resolved, as git spells a top. A spelling that differs is\n * read as another directory: what git said from the one is then not taken\n * for what it would say from the other.\n */\nexport function isTop(directory: string, top: string): boolean {\n return realSpelling(directory).split(sep).join('/') === top;\n}\n\n/**\n * The repository's main work tree, where git keeps the repository as that\n * work tree's `.git`, as `git init` and `git clone` do and every linked\n * worktree's common directory then is: `null` for a common directory of any\n * other name, a bare repository's, a submodule's under its superproject's\n * `modules`, or one kept apart with `--separate-git-dir`. Read from the\n * name, with no process: it says only where an install is looked for.\n */\nexport function mainWorkTree(commonDir: string): string | null {\n return basename(commonDir) === '.git' ? dirname(commonDir) : null;\n}\n\n/** Whether a `.git` stands in `directory`, a repository's or the file that names one, as at the top of a work tree. */\nexport function holdsGit(directory: string): boolean {\n return existsSync(join(directory, '.git'));\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/**\n * The harness's own directory under the git common directory, created on\n * demand (ADR-0003). Where something that is no directory stands at its path,\n * or at one between it and git's, Node refuses to make it, with ENOTDIR or\n * EEXIST, and the harness says which path holds what.\n */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n try {\n await mkdir(directory, { recursive: true });\n } catch (error) {\n const { code } = error as NodeJS.ErrnoException;\n if (code !== 'ENOTDIR' && code !== 'EEXIST') throw error;\n // mkdir refuses for this alone, so one of the steps is there and is no\n // directory, and every step before it is one.\n const steps = ['spec-harness', ...parts].map((_, at, all) => join(commonDir, ...all.slice(0, at + 1)));\n const there = steps.find((step) => !standing(step)!.isDirectory())!;\n throw new StateError(`${there} is not a directory, where spec-harness keeps its state in one: move it away, and run the command again`);\n }\n return directory;\n}\n\n/**\n * A work tree and its git directories as the files git keeps name them, read\n * with no process: the nearest directory at or above `root` that holds a\n * `.git`; the git directory, that `.git` or the one a `.git` file names\n * (`gitdir: <path>`, relative to the file's directory); and the common\n * directory, the one that git directory's `commondir` file names, relative\n * to it, or the git directory itself (gitrepository-layout(5)). `exact` is\n * whether git, asked, would name the same work tree: not where\n * `GIT_DIR` or `GIT_WORK_TREE` sends it elsewhere, and not where the\n * repository's configuration names a `worktree`, which `core.worktree`\n * moves it to (git-config(1)), or keeps one of its own for a worktree.\n * `null` where no directory up to the top of the filesystem holds one, or\n * a `.git` file names none.\n */\nexport function gitDirectories(root: string, env: Readonly<Record<string, string | undefined>>): { readonly top: string; readonly common: string; readonly exact: boolean } | null {\n let top = realSpelling(root);\n let found: Stats | null = null;\n for (;;) {\n found = standing(join(top, '.git'));\n if (found !== null) break;\n const parent = dirname(top);\n if (parent === top) return null;\n top = parent;\n }\n const dotGit = join(top, '.git');\n let gitDir = dotGit;\n if (found.isFile()) {\n // The path is the rest of the line: `.` stops at a line's end, a\n // carriage return among them, so the mutant that drops `$` is\n // equivalent; and a Buffer is read as its text, so is the encoding's.\n const named = /^gitdir: (.+)$/m.exec(readFileSync(dotGit, 'utf8'))?.[1];\n if (named === undefined) return null;\n gitDir = resolve(top, named);\n }\n const commondir = textOf(join(gitDir, 'commondir'));\n const common = commondir === null ? gitDir : resolve(gitDir, commondir.trim());\n const moved = env['GIT_DIR'] !== undefined || env['GIT_WORK_TREE'] !== undefined;\n // No configuration holds no key, as a text without one does: the mutant\n // of the empty text is equivalent.\n const elsewhere = /^\\s*worktree\\s*=/im.test(textOf(join(common, 'config')) ?? '') || existsSync(join(gitDir, 'config.worktree'));\n return { top, common, exact: !moved && !elsewhere };\n}\n\n/** A file's text, or `null` where it cannot be read. */\nfunction textOf(file: string): string | null {\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return null;\n }\n}\n\n/**\n * How long a call's decisions are kept for a run after the write that does\n * not come: a call refused by the person, one that failed, one stopped\n * (ADR-0002). A day, since a call waits on a person's prompt for as long as\n * the person is away, and an entry removed too soon costs only that call's\n * answer being decided again.\n */\nexport const CALL_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps what a run before a write decided, under the common directory, for\n * the run after it, and removes what no run after a write read in a day.\n * Nothing here may change the answer before the write: whatever fails is\n * passed over, and the run after the write then decides as it did.\n */\nexport async function keepCall(commonDir: string, name: string, text: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'calls');\n await writeAtomic(join(directory, name), text);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > CALL_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the call is decided again after the write.\n }\n}\n\n/** What was kept for a call, taken away as it is read; `null` where nothing was. */\nexport async function takeCall(commonDir: string, name: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'calls', name);\n try {\n const text = await readFile(file, 'utf8');\n // Taken away by another run since it was read, the mutants of `force`\n // decide the call again, which answers as the text read does: they are\n // equivalent.\n await rm(file, { force: true });\n return text;\n } catch {\n return null;\n }\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n // utf8 is writeFile's own default, so naming it is equivalent to its mutant.\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/**\n * A JSON object from a file: `null` when there is no file, `'unreadable'`\n * when it holds anything else. Only a file is read: a named pipe where a\n * settings file is expected was read without end, and neither doctor nor\n * init came back. What is neither a file nor a directory holds no JSON\n * object, as a directory holds none.\n */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n const there = standing(file);\n if (there === null) return null;\n if (!there.isFile()) return 'unreadable';\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n const value: unknown = JSON.parse(await readFile(file, 'utf8'));\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\n/** Whether a path names a directory that is there; one that cannot be read names none. */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n // Each caller reads the answer with `!`, so the mutant that answers\n // nothing here is equivalent.\n return false;\n }\n}\n\nexport async function readText(file: string): Promise<string | null> {\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets the disk: the configuration, the state\n * directory outside the work tree, and paths as the filesystem spells them.\n */\n\nimport { existsSync, lstatSync, readdirSync, readFileSync, readlinkSync, realpathSync, statSync, type Stats } from 'node:fs';\nimport { lstat, mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';\n\nimport type { BriefInputs } from './briefs.js';\nimport { ConfigError, CONFIG_FILE, DEFAULT_CONFIG, parseConfig, type HarnessConfig } from './config.js';\n\n/** What stands at a path, links followed, or `null` where nothing does: a link to nothing is nothing. */\nfunction standing(path: string): Stats | null {\n try {\n return statSync(path);\n } catch {\n return null;\n }\n}\n\n/**\n * Whether a regular file stands at a path, links followed. Only one is read\n * where a file is expected: opened, a named pipe nothing writes to is read\n * without end, and one read as the process waits on it, as the harness reads\n * a few small files, stops every timer with it (ADR-0002).\n */\nexport function isFile(path: string): boolean {\n return standing(path)?.isFile() === true;\n}\n\n/**\n * Reads `.spec-harness.json` at the root, or the defaults when there is none.\n *\n * Only a file is read. A named pipe under the file's name was read without\n * end, by every command and by the hook before every write; and Node's words\n * for a directory, EISDIR, are not the harness's. A file that cannot be read\n * is said as that, with the reason: one the account may not read, or one\n * another process holds open on Windows, was said to be JSON that is not\n * valid, which sent a person to its text. What does not load is thrown with\n * the path it stands at, which the hook lets a write reach (ADR-0005).\n */\nexport async function loadConfig(root: string): Promise<{ config: HarnessConfig; file: string | null }> {\n const file = join(root, CONFIG_FILE);\n const there = standing(file);\n if (there === null) return { config: DEFAULT_CONFIG, file: null };\n if (!there.isFile()) throw new ConfigError(`${CONFIG_FILE} is ${there.isDirectory() ? 'a directory' : 'neither a file nor a directory'}, not a configuration file`, file);\n let text: string;\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n text = await readFile(file, 'utf8');\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} cannot be read: ${(error as Error).message}`, file);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new ConfigError(`${CONFIG_FILE} is not valid JSON: ${(error as Error).message}`, file);\n }\n try {\n return { config: parseConfig(raw), file };\n } catch (error) {\n // parseConfig throws nothing but its own refusal, whose words are kept.\n throw new ConfigError((error as Error).message, file);\n }\n}\n\n/**\n * The real path of a file that stands at `path` under that name alone: a\n * file, not a link to one, and with no other name, since a write to a file\n * with two names writes both. `null` for anything else, and for nothing.\n */\nfunction soleFile(path: string): string | null {\n try {\n const entry = lstatSync(path);\n return entry.isFile() && entry.nlink === 1 ? realpathSync.native(path) : null;\n } catch {\n // Nothing at the path, or nothing the account may look at. Its callers\n // compare the answer with a real path, which neither `null` nor what an\n // empty block answers is, so that mutant is equivalent.\n return null;\n }\n}\n\n/**\n * Whether every path of a write names `file` and nothing else: a file that\n * stands under that name alone, which the path names as the hook resolves a\n * path to decide it ({@link realSpelling}), and as the filesystem resolves\n * it to write, which on Linux and macOS follows a link before a `..` where\n * the hook reads the `..` first. A path that only reads like it, a link to\n * it, a second name of it, and a file of another work tree are none of\n * these. `cwd` is what a relative path is read from, its words kept as\n * written.\n */\nexport function writesOnly(paths: readonly string[], file: string, cwd: string): boolean {\n // Where the file is none, no path's real spelling is `null`, so no path\n // names it.\n const own = soleFile(file);\n return paths.every((given) => {\n const path = isAbsolute(given) ? given : `${cwd}${sep}${given}`;\n return soleFile(path) === own && realSpelling(path) === own;\n });\n}\n\n/**\n * A path where the harness keeps its state that holds something else: said\n * in a line, exit 2, where Node's ENOTDIR and a stack sent a person looking\n * for a defect (spec-core ADR-0005).\n */\nexport class StateError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StateError';\n }\n}\n\n/**\n * The real spelling of a path: links resolved and, on a filesystem that\n * ignores case, the case the directory entries have. A path that does not\n * exist yet is resolved through its nearest existing ancestor.\n *\n * This is what stops `SRC/db/schema.ts` from walking past a protection on\n * `src/db/schema.ts` on Windows or macOS, where both name one file.\n */\nexport function realSpelling(path: string): string {\n const missing: string[] = [];\n let current = resolve(path);\n for (;;) {\n try {\n // join leaves a real path as it is, and reads a segment's leading\n // separator as the one between it and the last.\n return join(realpathSync.native(current), ...missing.reverse());\n } catch {\n const parent = dirname(current);\n // Only a root that does not exist fails at the top, a drive Windows\n // does not have; everywhere else the loop ends in the try.\n if (parent === current) return resolve(path);\n missing.push(current.slice(parent.length));\n current = parent;\n }\n }\n}\n\n/**\n * The most links the written reading of a path follows. Linux follows at\n * most 40 in resolving a pathname, and fails the call with ELOOP past them\n * (path_resolution(7)), so no write reaches a file through more.\n */\nconst MOST_LINKS = 40;\n\n/**\n * A path's names, separated as the system separates them: Windows takes `/`\n * as well as `\\`, where Linux and macOS take a `\\` for a character of a name.\n * The sweep runs on Linux, where the mutants that read the pattern there, or\n * change it, are equivalent; the suite's Windows leg holds them.\n */\nfunction namesOf(path: string): string[] {\n return path.split(sep === '/' ? '/' : /[\\\\/]/);\n}\n\n/**\n * The real spelling of the file a write of `given` reaches, read from `cwd`\n * where it is relative; `null` where it reaches none, through more links\n * than the system follows or a loop of them. Linux reads a `..` after the\n * link before it, and macOS is read so, so `deep/../schema.ts`, `deep` a\n * link to `lib/x`, is written as `lib/schema.ts` (measured on Linux), where\n * {@link realSpelling} reads the `..` first and names `schema.ts`; Windows\n * reads the `..` first, as resolve does (measured, through a junction). And\n * a link to nothing is followed, since a write opens the file it names and\n * makes it there (measured on Linux and on GitHub's Windows runner), where\n * `realSpelling` names the link.\n * The names are walked one by one from the root, each link replaced by what\n * it names, so the directory reached holds no link, and `join` reads a `.`,\n * a `..` and an empty name from it as the system does; what is not there\n * yet is made as written.\n */\nexport function writtenSpelling(given: string, cwd: string): string | null {\n // On Linux, where the sweep runs, the mutant that reads every path as\n // Windows does is caught, and the one that reads every path as Linux does\n // is equivalent; the suite's Windows leg holds it. The root is taken off\n // the names as it is off a link's, below.\n const absolute = sep === '/' ? (isAbsolute(given) ? given : `${cwd}/${given}`) : resolve(cwd, given);\n const root = parse(absolute).root;\n const names = namesOf(absolute.slice(root.length));\n let at = root;\n let links = 0;\n while (names.length > 0) {\n const next = join(at, names.shift()!);\n let target: string;\n try {\n if (!lstatSync(next).isSymbolicLink()) {\n at = next;\n continue;\n }\n target = readlinkSync(next);\n } catch {\n // Nothing there, or nothing the account may look into: a write makes\n // what is not there as written, and reaches nothing past a directory\n // it may not look into.\n at = join(next, ...names);\n break;\n }\n links += 1;\n if (links > MOST_LINKS) return null;\n // A link names a path from the directory it stands in, or from a root,\n // which is taken off its names: on Linux and macOS the root is `/`,\n // whose name is empty and read as none, so keeping it there is\n // equivalent; on Windows it is the drive (a test holds it there).\n const top = parse(target).root;\n if (top !== '') at = top;\n names.unshift(...namesOf(target.slice(top.length)));\n }\n // The walk spells what exists as given, links followed; where case is\n // ignored, the entries' case is the real one. On Linux, where the sweep\n // runs, the two are one, so the mutant that leaves this out is equivalent\n // there (a test holds it on Windows and macOS).\n return realSpelling(at);\n}\n\n/**\n * Every file a write of `given` may reach, each by its real spelling: the\n * one the path reads as ({@link realSpelling}), and the one the system\n * writes ({@link writtenSpelling}) where that is another. The hook does not\n * know which of the two a tool writes, so it decides both (ADR-0005). A\n * path that reaches no file is read as it reads.\n */\nexport function reachedBy(given: string, cwd: string): string[] {\n const read = realSpelling(resolve(cwd, given));\n const written = writtenSpelling(given, cwd);\n return written === null || written === read ? [read] : [read, written];\n}\n\n/**\n * Whether `directory` is the one git names as the top of a work tree: the\n * two spelled alike once the filesystem has spelled the directory, links\n * and short names resolved, as git spells a top. A spelling that differs is\n * read as another directory: what git said from the one is then not taken\n * for what it would say from the other.\n */\nexport function isTop(directory: string, top: string): boolean {\n return realSpelling(directory).split(sep).join('/') === top;\n}\n\n/**\n * The repository's main work tree, where git keeps the repository as that\n * work tree's `.git`, as `git init` and `git clone` do and every linked\n * worktree's common directory then is: `null` for a common directory of any\n * other name, a bare repository's, a submodule's under its superproject's\n * `modules`, or one kept apart with `--separate-git-dir`. Read from the\n * name, with no process: it says only where an install is looked for.\n */\nexport function mainWorkTree(commonDir: string): string | null {\n return basename(commonDir) === '.git' ? dirname(commonDir) : null;\n}\n\n/** Whether a `.git` stands in `directory`, a repository's or the file that names one, as at the top of a work tree. */\nexport function holdsGit(directory: string): boolean {\n return existsSync(join(directory, '.git'));\n}\n\n/**\n * A path as the repository names it - relative to the root, POSIX-separated,\n * real spelling - or `null` when it lies outside the root.\n */\nexport function repositoryPath(given: string, root: string, cwd: string): string | null {\n const absolute = isAbsolute(given) ? given : resolve(cwd, given);\n const real = realSpelling(absolute);\n const realRoot = realSpelling(root);\n const rel = relative(realRoot, real);\n // `..env` at the root is inside it; only a `..` segment climbs out.\n if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return null;\n return rel.split(sep).join('/');\n}\n\n/**\n * The top of every work tree of the repository whose common directory is\n * `common`, `top` among them, read with no process: the main work tree,\n * whose `.git` the common directory is, and each linked worktree, by the\n * `gitdir` file git keeps for it under the common directory's `worktrees`,\n * which names the worktree's `.git` (gitrepository-layout(5)), absolute or\n * from its own directory. `null` where they cannot all be named: a common\n * directory of any other name, whose main work tree only its configuration\n * names, and an entry under `worktrees` that cannot be read.\n */\nexport function workTrees(top: string, common: string): string[] | null {\n const main = mainWorkTree(common);\n if (main === null) return null;\n const kept = join(common, 'worktrees');\n let names: string[] = [];\n try {\n names = readdirSync(kept);\n } catch (error) {\n // No such directory is no linked worktree, and anything else there is\n // one that cannot be read.\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return null;\n }\n const linked = names.map((name) => textOf(join(kept, name, 'gitdir')));\n if (linked.includes(null)) return null;\n // The line's end stands in the last name, the worktree's `.git`, which\n // dirname takes away with it.\n return [top, main, ...names.map((name, at) => dirname(resolve(kept, name, linked[at]!)))];\n}\n\n/**\n * Whether every path lies outside every work tree of the repository whose\n * top and common directory are given, as it reads and as the system writes\n * it ({@link reachedBy}): such a path is no file of the repository's, so no\n * brief of its decides it, whatever is wrong with the brief or the\n * configuration (ADR-0005). Where the work trees cannot all be named, a\n * path is not known to be outside them, and this is `false`.\n */\nexport function outsideEvery(paths: readonly string[], top: string, common: string, cwd: string): boolean {\n // A write that names no path is no write outside: no caller asks of\n // one, and a test holds the answer.\n if (paths.length === 0) return false;\n // A path in the work tree that was opened, as nearly every write's is,\n // is told with nothing of git's directory read; read anyway, `top` is\n // among the work trees, so the mutant without this test is equivalent.\n if (paths.some((path) => repositoryPath(path, top, cwd) !== null)) return false;\n // A path with a `..` in it was held here when the hook read a path one\n // way, its `..` before its links: `/elsewhere/link/../a.ts`, the link to\n // a directory of the work tree, read as outside and was written inside.\n // Both readings are read below, and such a path is held as it was all\n // the same (ADR-0005).\n if (paths.some((path) => path.split(/[\\\\/]/).includes('..'))) return false;\n // As a path reads and as the system writes it, a link to nothing among\n // its links: the file it reaches is outside too, or the path is not.\n const reached = paths.flatMap((path) => reachedBy(path, cwd));\n const trees = workTrees(top, common);\n return trees !== null && reached.every((path) => trees.every((tree) => repositoryPath(path, tree, cwd) === null));\n}\n\n/**\n * The harness's own directory under the git common directory, created on\n * demand (ADR-0003). Where something that is no directory stands at its path,\n * or at one between it and git's, Node refuses to make it, with ENOTDIR or\n * EEXIST, and the harness says which path holds what.\n */\nexport async function stateDirectory(commonDir: string, ...parts: string[]): Promise<string> {\n const directory = join(commonDir, 'spec-harness', ...parts);\n try {\n await mkdir(directory, { recursive: true });\n } catch (error) {\n const { code } = error as NodeJS.ErrnoException;\n if (code !== 'ENOTDIR' && code !== 'EEXIST') throw error;\n // mkdir refuses for this alone, so one of the steps is there and is no\n // directory, and every step before it is one.\n const steps = ['spec-harness', ...parts].map((_, at, all) => join(commonDir, ...all.slice(0, at + 1)));\n const there = steps.find((step) => !standing(step)!.isDirectory())!;\n throw new StateError(`${there} is not a directory, where spec-harness keeps its state in one: move it away, and run the command again`);\n }\n return directory;\n}\n\n/**\n * A work tree and its git directories as the files git keeps name them, read\n * with no process: the nearest directory at or above `root` that holds a\n * `.git`; the git directory, that `.git` or the one a `.git` file names\n * (`gitdir: <path>`, relative to the file's directory); and the common\n * directory, the one that git directory's `commondir` file names, relative\n * to it, or the git directory itself (gitrepository-layout(5)). `exact` is\n * whether git, asked, would name the same work tree: not where\n * `GIT_DIR` or `GIT_WORK_TREE` sends it elsewhere, and not where the\n * repository's configuration names a `worktree`, which `core.worktree`\n * moves it to (git-config(1)), or keeps one of its own for a worktree.\n * `null` where no directory up to the top of the filesystem holds one, or\n * a `.git` file names none.\n */\nexport function gitDirectories(root: string, env: Readonly<Record<string, string | undefined>>): { readonly top: string; readonly git: string; readonly common: string; readonly exact: boolean } | null {\n let top = realSpelling(root);\n let found: Stats | null = null;\n for (;;) {\n found = standing(join(top, '.git'));\n if (found !== null) break;\n const parent = dirname(top);\n if (parent === top) return null;\n top = parent;\n }\n const dotGit = join(top, '.git');\n let gitDir = dotGit;\n if (found.isFile()) {\n // The path is the rest of the line: `.` stops at a line's end, a\n // carriage return among them, so the mutant that drops `$` is\n // equivalent; and a Buffer is read as its text, so is the encoding's.\n const named = /^gitdir: (.+)$/m.exec(readFileSync(dotGit, 'utf8'))?.[1];\n if (named === undefined) return null;\n gitDir = resolve(top, named);\n }\n const commondir = textOf(join(gitDir, 'commondir'));\n const common = commondir === null ? gitDir : resolve(gitDir, commondir.trim());\n const moved = env['GIT_DIR'] !== undefined || env['GIT_WORK_TREE'] !== undefined;\n // No configuration holds no key, as a text without one does: the mutant\n // of the empty text is equivalent.\n const elsewhere = /^\\s*worktree\\s*=/im.test(textOf(join(common, 'config')) ?? '') || existsSync(join(gitDir, 'config.worktree'));\n return { top, git: gitDir, common, exact: !moved && !elsewhere };\n}\n\n/** A file's text, or `null` where it cannot be read or is no file. */\nfunction textOf(file: string): string | null {\n if (!isFile(file)) return null;\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return null;\n }\n}\n\n/**\n * How long a call's decisions are kept for a run after the write that does\n * not come: a call refused by the person, one that failed, one stopped\n * (ADR-0002). A day, since a call waits on a person's prompt for as long as\n * the person is away, and an entry removed too soon costs only that call's\n * answer being decided again.\n */\nexport const CALL_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps what a run before a write decided, under the common directory, for\n * the run after it, and removes what no run after a write read in a day.\n * Nothing here may change the answer before the write: whatever fails is\n * passed over, and the run after the write then decides as it did.\n */\nexport async function keepCall(commonDir: string, name: string, text: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'calls');\n await writeAtomic(join(directory, name), text);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > CALL_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the call is decided again after the write.\n }\n}\n\n/** What was kept for a call, taken away as it is read; `null` where nothing was. */\nexport async function takeCall(commonDir: string, name: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'calls', name);\n // What is no file holds nothing kept, and is written over at the next write.\n if (!isFile(file)) return null;\n try {\n const text = await readFile(file, 'utf8');\n // Taken away by another run since it was read, the mutants of `force`\n // decide the call again, which answers as the text read does: they are\n // equivalent.\n await rm(file, { force: true });\n return text;\n } catch {\n return null;\n }\n}\n\n/** SHA-256 of a text or of bytes, in hex. */\nexport type Hash = (data: string | Uint8Array) => string;\n\n/** SHA-256, loaded where something is hashed: a run that keeps no answer loads no hash (ADR-0002). */\nexport async function hasher(): Promise<Hash> {\n const { createHash } = await import('node:crypto');\n return (data) => createHash('sha256').update(data).digest('hex');\n}\n\n/**\n * How long a session's answer of spec-brief's is kept once it was last\n * kept (ADR-0003). A day, as a call's: an answer removed costs one start of\n * spec-brief, and one kept is a few kilobytes.\n */\nexport const ANSWER_LIFE_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Keeps a session's answer of spec-brief's under the common directory, with\n * the key it answers, and removes what was kept over a day ago. Whatever\n * fails is passed over: the answer is the one given whether or not it is\n * kept.\n */\nexport async function keepAnswer(commonDir: string, name: string, key: string, answer: string, now: number = Date.now()): Promise<void> {\n try {\n const directory = await stateDirectory(commonDir, 'answers');\n await writeAtomic(join(directory, name), `${JSON.stringify({ key, answer })}\\n`);\n // A file another run takes away before it is removed here ends the\n // pruning in the catch below, where `force` would pass over it, and what\n // is left is pruned by a later run: the mutants of `force` are\n // equivalent, and so is the one that also removes a file kept a day ago\n // to the millisecond.\n for (const entry of await readdir(directory)) {\n const file = join(directory, entry);\n if (now - (await stat(file)).mtimeMs > ANSWER_LIFE_MS) await rm(file, { force: true });\n }\n } catch {\n // A full disk, a directory another run removed: the next run starts spec-brief.\n }\n}\n\n/**\n * The answer kept under `name` for `key`, or `null` where there is none:\n * nothing there, something that is not a file, a text that is not what\n * `keepAnswer` writes, or another key's.\n */\nexport async function keptAnswer(commonDir: string, name: string, key: string): Promise<string | null> {\n const file = join(commonDir, 'spec-harness', 'answers', name);\n try {\n // Only a file is read: a named pipe would be read without end.\n if (!(await stat(file)).isFile()) return null;\n // A text that holds null throws on the read, and any other that is no\n // object has no key: the catch answers for both. A Buffer parses as its\n // text, so the encoding is equivalent to its mutant.\n const kept: { readonly key?: unknown; readonly answer?: unknown } = JSON.parse(await readFile(file, 'utf8'));\n return kept.key === key && typeof kept.answer === 'string' ? kept.answer : null;\n } catch {\n // Falling out of the catch answers undefined, which no answer is read\n // from either: the mutant without the return is equivalent.\n return null;\n }\n}\n\n/** Whether a path that could not be read names nothing: it, or a directory on the way to it, is not there. */\nfunction nothingThere(error: unknown): boolean {\n const { code } = error as NodeJS.ErrnoException;\n return code === 'ENOENT' || code === 'ENOTDIR';\n}\n\n/**\n * Whether a file reads as listed: a file, never a pipe or a directory, whose\n * bytes have the digest; or, listed as not there, nothing at its path.\n */\nasync function fileReads(file: string, sha256: string | null, hash: Hash): Promise<boolean> {\n let there: Stats;\n try {\n there = await stat(file);\n } catch (error) {\n return sha256 === null && nothingThere(error);\n }\n // Only a file is read: a named pipe would be read without end. A file\n // listed as not there has no digest a file's bytes have.\n if (!there.isFile()) return false;\n return hash(await readFile(file)) === sha256;\n}\n\n/**\n * Whether a directory is there and holds the files listed, and no other: a\n * file as spec-brief lists one, an entry that is a file itself, where a\n * directory or a link is not one. What is no directory, a named pipe among\n * it, is refused by the listing, which throws. Listed as not there, nothing\n * may stand at its path in any form, a link that leads nowhere among them.\n */\nasync function entriesRead(directory: string, entries: readonly string[] | null): Promise<boolean> {\n if (entries === null) {\n try {\n await lstat(directory);\n return false;\n } catch (error) {\n return nothingThere(error);\n }\n }\n // The entries are names no two of which are one (inputsOf), and so are a\n // directory's: as many of each, every one of the one among the other, is\n // the same names, in whatever order the disk lists them.\n const names = (await readdir(directory, { withFileTypes: true })).flatMap((entry) => (entry.isFile() ? [entry.name] : []));\n const listed = new Set(entries);\n return names.length === listed.size && names.every((name) => listed.has(name));\n}\n\n/**\n * Whether every input of an answer reads as listed, with no process\n * started: each file by the digest of its bytes, or as not there; each\n * directory by its entries; and, where git was asked, the index of the work\n * tree's git directory and each ignore file. A path is read from the work\n * tree's top, or as it stands where it is absolute. Anything that cannot be\n * read, and an index that is not the one git reads for the work tree, is a\n * doubt, and answers `false` (ADR-0002).\n */\nexport async function readsAsListed(root: string, inputs: BriefInputs, hash: Hash, env: Readonly<Record<string, string | undefined>> = process.env): Promise<boolean> {\n const at = (path: string): string => (isAbsolute(path) ? path : join(root, ...path.split('/')));\n const git = inputs.git;\n let index: string | null = null;\n if (git !== null) {\n // The index of the work tree's own git directory, as the files git keeps\n // name it: not where git is sent elsewhere, GIT_DIR, GIT_WORK_TREE or a\n // worktree key (`exact`), GIT_INDEX_FILE or GIT_COMMON_DIR. A top git\n // named always holds a `.git`, so the test of none is the compiler's, and\n // a null read past it throws, which the caller reads as a doubt: that\n // mutant is equivalent. The top found is the work tree's own but where\n // git names a top that holds no `.git`, a work tree `core.worktree`\n // names away from its repository and inside another, whose index the\n // one found would be.\n const found = gitDirectories(root, env);\n if (found === null || !found.exact || env['GIT_INDEX_FILE'] !== undefined || env['GIT_COMMON_DIR'] !== undefined || found.top !== realSpelling(root)) return false;\n index = join(found.git, 'index');\n }\n // Made once every check is known to be made, so that none is left to fail\n // with no one reading it. A text put among them where git was asked\n // nothing is one `every` reads as true, so that mutant is equivalent.\n const checks = [\n ...inputs.files.map((file) => fileReads(at(file.path), file.sha256, hash)),\n ...inputs.directories.map((directory) => entriesRead(at(directory.path), directory.entries)),\n ...(git === null ? [] : [fileReads(index!, git.index, hash), ...git.ignores.map((file) => fileReads(at(file.path), file.sha256, hash))]),\n ];\n try {\n return (await Promise.all(checks)).every((reads) => reads);\n } catch {\n // Falling out of the catch answers undefined, which the caller reads as\n // false: the mutant without the return is equivalent.\n return false;\n }\n}\n\n/** Writes a file whole or not at all: a temporary file renamed over the target. */\nexport async function writeAtomic(file: string, content: string): Promise<void> {\n await mkdir(dirname(file), { recursive: true });\n const temporary = `${file}.${process.pid}.tmp`;\n // utf8 is writeFile's own default, so naming it is equivalent to its mutant.\n await writeFile(temporary, content, 'utf8');\n await rename(temporary, file);\n}\n\n/**\n * A JSON object from a file: `null` when there is no file, `'unreadable'`\n * when it holds anything else. Only a file is read: a named pipe where a\n * settings file is expected was read without end, and neither doctor nor\n * init came back. What is neither a file nor a directory holds no JSON\n * object, as a directory holds none.\n */\nexport async function readJsonObject(file: string): Promise<Record<string, unknown> | null | 'unreadable'> {\n const there = standing(file);\n if (there === null) return null;\n if (!there.isFile()) return 'unreadable';\n try {\n // A Buffer parses as its text, so the encoding is equivalent to its mutant.\n const value: unknown = JSON.parse(await readFile(file, 'utf8'));\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : 'unreadable';\n } catch {\n return 'unreadable';\n }\n}\n\n/** Whether a path names a directory that is there; one that cannot be read names none. */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n // Each caller reads the answer with `!`, so the mutant that answers\n // nothing here is equivalent.\n return false;\n }\n}\n\n/** A file's text, or `null` where it cannot be read or is no file: a brief, a document one cites, a shim. */\nexport async function readText(file: string): Promise<string | null> {\n if (!isFile(file)) return null;\n try {\n return await readFile(file, 'utf8');\n } catch {\n return null;\n }\n}\n"]}
package/dist/git.d.ts CHANGED
@@ -12,8 +12,51 @@ export interface Run {
12
12
  readonly stdout: string;
13
13
  readonly stderr: string;
14
14
  }
15
- /** Runs git and answers whatever it answered; a missing git is a code of -1. */
16
- export declare function git(args: readonly string[], cwd: string): Promise<Run>;
15
+ /**
16
+ * How long a process the harness asks a question of is given to answer:
17
+ * git's reads, spec-brief's list of the briefs. Claude Code gives the hook's
18
+ * entry 60 seconds, as init and the plugin write it, and cancels a hook that
19
+ * reaches them, which before a write lets the write go ahead (ADR-0002). A
20
+ * process that does not answer within this is stopped and its question
21
+ * answered as one that failed, which before a write holds it, well inside
22
+ * that minute; and the slowest such process measured, on a Windows
23
+ * workstation under load and on GitHub's runner, answered in a fraction of
24
+ * it.
25
+ */
26
+ export declare const ANSWER_MS = 20000;
27
+ /**
28
+ * How long a process that does a round's work is given: a diff of the
29
+ * round, a worktree added for a probe or taken away, a sibling's check of
30
+ * the repository. Its length is the repository's, and none runs before a
31
+ * write.
32
+ */
33
+ export declare const WORK_MS = 600000;
34
+ /** Runs `work` with every process it starts given no more than what is left of `ms` from now. */
35
+ export declare function withBudget<T>(ms: number, work: () => Promise<T>): Promise<T>;
36
+ /**
37
+ * The time a process is given, `limit`, or what is left of the budget of
38
+ * the run it is started in where that is less, with the words that say
39
+ * which: the run's budget is said as such, so that a write held at it says
40
+ * why. A run with nothing left is given 0, and starts nothing.
41
+ */
42
+ export declare function timeFor(limit: number): {
43
+ readonly ms: number;
44
+ readonly said: string;
45
+ };
46
+ /**
47
+ * Whether a process ended because the harness stopped it at its time
48
+ * limit: Node kills it then and says so with `killed`. It kills one for
49
+ * output past `maxBuffer` as well, and names that by its code.
50
+ */
51
+ export declare function stoppedAtLimit(error: unknown): boolean;
52
+ /**
53
+ * Runs git and answers whatever it answered; a missing git is a code of -1.
54
+ * A git that does not answer within `limit` is stopped, and rejected as a
55
+ * `GitError` that says so: read as a failure, a question about the work
56
+ * tree or the branch would answer as a directory in none, or a head on no
57
+ * branch, under which the hook checks no write.
58
+ */
59
+ export declare function git(args: readonly string[], cwd: string, limit?: number): Promise<Run>;
17
60
  export declare class GitError extends Error {
18
61
  constructor(message: string);
19
62
  }
@@ -98,10 +141,53 @@ export declare function mergeBase(a: string, b: string, cwd: string): Promise<st
98
141
  export declare function parseNameStatus(output: string): FileChange[];
99
142
  /** The files changed from one revision to another, renames found. */
100
143
  export declare function changes(from: string, to: string, cwd: string): Promise<FileChange[]>;
144
+ /**
145
+ * The paths `git status --porcelain=v2 -z` names, each once, in the order
146
+ * git gives them: a rename or a copy by both its paths. A path with a space
147
+ * in it is the rest of its record, which `-z` prints as is.
148
+ */
149
+ export declare function parseStatus(output: string): string[];
150
+ /**
151
+ * Every path the work tree holds changed from HEAD: tracked files that
152
+ * differ in the index or in the work tree, unmerged ones, and each
153
+ * untracked file, those in an untracked directory one by one. One start of
154
+ * git, with no optional lock taken: a status that writes the index as it
155
+ * refreshes it holds a lock that can make another git running beside it
156
+ * fail, and the manual has a status run in the background take none
157
+ * (git-status(1), *Background Refresh*, and git(1), `--no-optional-locks`,
158
+ * as Git for Windows 2.55.0 has them, read 2026-10-19).
159
+ */
160
+ export declare function changedPaths(cwd: string): Promise<string[]>;
101
161
  /** The files a commit would change: what is staged. */
102
162
  export declare function stagedChanges(cwd: string): Promise<FileChange[]>;
103
163
  /** A file's content at a revision, or `null` when it does not exist there. */
104
164
  export declare function show(ref: string, path: string, cwd: string): Promise<string | null>;
165
+ /**
166
+ * Whether a commit's tree holds `path`, a path from the top of the work tree
167
+ * read as written: `true` where git lists it, `false` where git lists
168
+ * nothing for it, and `null` where git could not say, for a commit it does
169
+ * not have among them. {@link show} answers `null` for a file that is not
170
+ * there and for a git that failed alike, which can only make an answer
171
+ * stricter; this tells the two apart, so that an answer may be loosened by a
172
+ * file that git says is not there.
173
+ *
174
+ * ls-tree reads its paths as patterns, from the directory it is started in
175
+ * (git-ls-tree(1), Description, as Git for Windows 2.55.0 has it, read
176
+ * 2026-10-24): `--full-tree` has it read them from the top, and
177
+ * `--literal-pathspecs` with no globbing and no magic (git(1), Options), so
178
+ * a name such as `:(top)x.md` names that file, where ls-tree would read it
179
+ * as `x.md`.
180
+ */
181
+ export declare function holdsPath(commit: string, path: string, cwd: string): Promise<boolean | null>;
182
+ /**
183
+ * Whether the history is cut short, as a clone or a fetch with `--depth`
184
+ * leaves it, where a merge base git finds can be an older commit than the
185
+ * one the whole history has; `null` where git could not say. rev-parse
186
+ * learned the question in 2.15.0 (its release notes), and prints `true` or
187
+ * `false` (git-rev-parse(1), Options for Files, read 2026-10-24); anything
188
+ * else is no answer.
189
+ */
190
+ export declare function isShallow(cwd: string): Promise<boolean | null>;
105
191
  /** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
106
192
  export declare function isUncommitted(sha: string): boolean;
107
193
  /**
package/dist/git.js CHANGED
@@ -6,13 +6,78 @@
6
6
  * call is an argument vector, never a shell string, so a path or a ref cannot
7
7
  * become a command.
8
8
  */
9
+ import { AsyncLocalStorage } from 'node:async_hooks';
9
10
  import { execFile } from 'node:child_process';
10
- /** Runs git and answers whatever it answered; a missing git is a code of -1. */
11
- export function git(args, cwd) {
12
- return new Promise((resolve) => {
11
+ /**
12
+ * How long a process the harness asks a question of is given to answer:
13
+ * git's reads, spec-brief's list of the briefs. Claude Code gives the hook's
14
+ * entry 60 seconds, as init and the plugin write it, and cancels a hook that
15
+ * reaches them, which before a write lets the write go ahead (ADR-0002). A
16
+ * process that does not answer within this is stopped and its question
17
+ * answered as one that failed, which before a write holds it, well inside
18
+ * that minute; and the slowest such process measured, on a Windows
19
+ * workstation under load and on GitHub's runner, answered in a fraction of
20
+ * it.
21
+ */
22
+ export const ANSWER_MS = 20_000;
23
+ /**
24
+ * How long a process that does a round's work is given: a diff of the
25
+ * round, a worktree added for a probe or taken away, a sibling's check of
26
+ * the repository. Its length is the repository's, and none runs before a
27
+ * write.
28
+ */
29
+ export const WORK_MS = 600_000;
30
+ /** The run a process is started in, where it has a budget: its length, and when it ends, as `performance.now()` reads it. */
31
+ const budget = new AsyncLocalStorage();
32
+ /** Runs `work` with every process it starts given no more than what is left of `ms` from now. */
33
+ export function withBudget(ms, work) {
34
+ return budget.run({ ms, end: performance.now() + ms }, work);
35
+ }
36
+ /**
37
+ * The time a process is given, `limit`, or what is left of the budget of
38
+ * the run it is started in where that is less, with the words that say
39
+ * which: the run's budget is said as such, so that a write held at it says
40
+ * why. A run with nothing left is given 0, and starts nothing.
41
+ */
42
+ export function timeFor(limit) {
43
+ const run = budget.getStore();
44
+ const left = run === undefined ? limit : run.end - performance.now();
45
+ if (left >= limit)
46
+ return { ms: limit, said: `${limit / 1000} seconds` };
47
+ return { ms: Math.max(0, Math.ceil(left)), said: `the ${run.ms / 1000} seconds the hook's run is given` };
48
+ }
49
+ /**
50
+ * Whether a process ended because the harness stopped it at its time
51
+ * limit: Node kills it then and says so with `killed`. It kills one for
52
+ * output past `maxBuffer` as well, and names that by its code.
53
+ */
54
+ export function stoppedAtLimit(error) {
55
+ const failure = error;
56
+ return failure !== null && failure.killed === true && failure.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';
57
+ }
58
+ /** The git command a line of arguments runs, past the `-c` settings before it: every line the harness hands git names one. */
59
+ function subcommand(args) {
60
+ return args.find((arg, index) => !arg.startsWith('-') && args[index - 1] !== '-c');
61
+ }
62
+ /**
63
+ * Runs git and answers whatever it answered; a missing git is a code of -1.
64
+ * A git that does not answer within `limit` is stopped, and rejected as a
65
+ * `GitError` that says so: read as a failure, a question about the work
66
+ * tree or the branch would answer as a directory in none, or a head on no
67
+ * branch, under which the hook checks no write.
68
+ */
69
+ export function git(args, cwd, limit = ANSWER_MS) {
70
+ const { ms, said } = timeFor(limit);
71
+ if (ms === 0)
72
+ return Promise.reject(new GitError(`git ${subcommand(args)} was not started: ${said} had passed`));
73
+ return new Promise((resolve, reject) => {
13
74
  // windowsHide only keeps a console window from flashing up on Windows,
14
75
  // and the sweep runs on Linux, where its mutant is equivalent.
15
- execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {
76
+ execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true, timeout: ms }, (error, stdout, stderr) => {
77
+ if (stoppedAtLimit(error)) {
78
+ reject(new GitError(`git ${subcommand(args)} did not answer within ${said}, and was stopped`));
79
+ return;
80
+ }
16
81
  // Every caller tells 0 from any other code, never one failure from
17
82
  // another, so which code a failure gets is equivalent to its mutants.
18
83
  const code = error === null ? 0 : typeof error.code === 'number' ? error.code : -1;
@@ -164,14 +229,62 @@ export function parseNameStatus(output) {
164
229
  }
165
230
  /** The files changed from one revision to another, renames found. */
166
231
  export async function changes(from, to, cwd) {
167
- const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd);
232
+ const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd, WORK_MS);
168
233
  if (run.code !== 0)
169
234
  throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);
170
235
  return parseNameStatus(run.stdout);
171
236
  }
237
+ /**
238
+ * How many words stand before the path in each kind of record of
239
+ * `git status --porcelain=v2`, as git-status(1) has them (Git for Windows
240
+ * 2.55.0, *Porcelain Format Version 2*, read 2026-10-19): an ordinary
241
+ * change, `1`, and seven; a rename or a copy, `2`, and eight, its path
242
+ * followed by the path it came from as a record of its own under `-z`; an
243
+ * unmerged path, `u`, and nine; an untracked or an ignored path, `?` or
244
+ * `!`, and none. A header, `#`, names no path.
245
+ */
246
+ const STATUS_FIELDS = { '1': 8, '2': 9, u: 10, '?': 1, '!': 1 };
247
+ /**
248
+ * The paths `git status --porcelain=v2 -z` names, each once, in the order
249
+ * git gives them: a rename or a copy by both its paths. A path with a space
250
+ * in it is the rest of its record, which `-z` prints as is.
251
+ */
252
+ export function parseStatus(output) {
253
+ const records = output.split('\0');
254
+ const paths = new Set();
255
+ for (let at = 0; at < records.length; at += 1) {
256
+ const record = records[at];
257
+ const fields = STATUS_FIELDS[record.charAt(0)];
258
+ if (fields === undefined)
259
+ continue;
260
+ paths.add(record.split(' ').slice(fields).join(' '));
261
+ if (record.startsWith('2')) {
262
+ at += 1;
263
+ paths.add(records[at] ?? '');
264
+ }
265
+ }
266
+ paths.delete('');
267
+ return [...paths];
268
+ }
269
+ /**
270
+ * Every path the work tree holds changed from HEAD: tracked files that
271
+ * differ in the index or in the work tree, unmerged ones, and each
272
+ * untracked file, those in an untracked directory one by one. One start of
273
+ * git, with no optional lock taken: a status that writes the index as it
274
+ * refreshes it holds a lock that can make another git running beside it
275
+ * fail, and the manual has a status run in the background take none
276
+ * (git-status(1), *Background Refresh*, and git(1), `--no-optional-locks`,
277
+ * as Git for Windows 2.55.0 has them, read 2026-10-19).
278
+ */
279
+ export async function changedPaths(cwd) {
280
+ const run = await git(['--no-optional-locks', 'status', '--porcelain=v2', '-z', '--untracked-files=all'], cwd);
281
+ if (run.code !== 0)
282
+ throw new GitError(`git status failed: ${run.stderr.trim()}`);
283
+ return parseStatus(run.stdout);
284
+ }
172
285
  /** The files a commit would change: what is staged. */
173
286
  export async function stagedChanges(cwd) {
174
- const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd);
287
+ const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd, WORK_MS);
175
288
  if (run.code !== 0)
176
289
  throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);
177
290
  return parseNameStatus(run.stdout);
@@ -181,6 +294,40 @@ export async function show(ref, path, cwd) {
181
294
  const run = await git(['show', `${ref}:${path}`], cwd);
182
295
  return run.code === 0 ? run.stdout : null;
183
296
  }
297
+ /**
298
+ * Whether a commit's tree holds `path`, a path from the top of the work tree
299
+ * read as written: `true` where git lists it, `false` where git lists
300
+ * nothing for it, and `null` where git could not say, for a commit it does
301
+ * not have among them. {@link show} answers `null` for a file that is not
302
+ * there and for a git that failed alike, which can only make an answer
303
+ * stricter; this tells the two apart, so that an answer may be loosened by a
304
+ * file that git says is not there.
305
+ *
306
+ * ls-tree reads its paths as patterns, from the directory it is started in
307
+ * (git-ls-tree(1), Description, as Git for Windows 2.55.0 has it, read
308
+ * 2026-10-24): `--full-tree` has it read them from the top, and
309
+ * `--literal-pathspecs` with no globbing and no magic (git(1), Options), so
310
+ * a name such as `:(top)x.md` names that file, where ls-tree would read it
311
+ * as `x.md`.
312
+ */
313
+ export async function holdsPath(commit, path, cwd) {
314
+ const run = await git(['--literal-pathspecs', 'ls-tree', '-z', '--full-tree', '--name-only', commit, '--', path], cwd);
315
+ return run.code === 0 ? run.stdout !== '' : null;
316
+ }
317
+ /**
318
+ * Whether the history is cut short, as a clone or a fetch with `--depth`
319
+ * leaves it, where a merge base git finds can be an older commit than the
320
+ * one the whole history has; `null` where git could not say. rev-parse
321
+ * learned the question in 2.15.0 (its release notes), and prints `true` or
322
+ * `false` (git-rev-parse(1), Options for Files, read 2026-10-24); anything
323
+ * else is no answer.
324
+ */
325
+ export async function isShallow(cwd) {
326
+ const answer = await value(['rev-parse', '--is-shallow-repository'], cwd);
327
+ if (answer === 'true')
328
+ return true;
329
+ return answer === 'false' ? false : null;
330
+ }
184
331
  /** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */
185
332
  export function isUncommitted(sha) {
186
333
  return /^0+$/.test(sha);
@@ -215,7 +362,7 @@ export async function verifyCommit(sha, allowedSignersFile, cwd) {
215
362
  return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };
216
363
  }
217
364
  export async function addWorktree(path, rev, cwd) {
218
- const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);
365
+ const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd, WORK_MS);
219
366
  if (run.code !== 0)
220
367
  throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);
221
368
  }
@@ -232,6 +379,6 @@ export async function removeWorktree(path, cwd) {
232
379
  // The sandbox asks again when this answers false, and git, asked again
233
380
  // about a worktree it has forgotten, changes nothing: the mutant that
234
381
  // always answers false is equivalent.
235
- return (await git(['worktree', 'remove', '--force', path], cwd)).code === 0;
382
+ return (await git(['worktree', 'remove', '--force', path], cwd, WORK_MS)).code === 0;
236
383
  }
237
384
  //# sourceMappingURL=git.js.map
package/dist/git.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets git.\n *\n * Reads, except for two things ADR-0003 allows and bounds: a temporary\n * worktree, added and always removed by the sandbox, and nothing else. Every\n * call is an argument vector, never a shell string, so a path or a ref cannot\n * become a command.\n */\n\nimport { execFile } from 'node:child_process';\n\nimport type { FileChange, LocalBranch } from './types.js';\n\nexport interface Run {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/** Runs git and answers whatever it answered; a missing git is a code of -1. */\nexport function git(args: readonly string[], cwd: string): Promise<Run> {\n return new Promise((resolve) => {\n // windowsHide only keeps a console window from flashing up on Windows,\n // and the sweep runs on Linux, where its mutant is equivalent.\n execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true }, (error, stdout, stderr) => {\n // Every caller tells 0 from any other code, never one failure from\n // another, so which code a failure gets is equivalent to its mutants.\n const code = error === null ? 0 : typeof (error as { code?: unknown }).code === 'number' ? (error as { code: number }).code : -1;\n resolve({ code, stdout, stderr });\n });\n });\n}\n\nasync function value(args: readonly string[], cwd: string): Promise<string | null> {\n const run = await git(args, cwd);\n return run.code === 0 ? run.stdout.trim() : null;\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n/** The top of the work tree holding `cwd`, POSIX-separated, or `null` outside one. */\nexport async function workTreeRoot(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--show-toplevel'], cwd);\n}\n\n/** The git directory every worktree of this repository shares. */\nexport async function commonDirectory(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);\n}\n\n/** A work tree as one start of git names it: its top, and the git directory its worktrees share. */\nexport interface WorkTree {\n readonly root: string;\n readonly commonDir: string;\n}\n\n/**\n * The top of the work tree holding `cwd` and the common directory, asked of\n * git in one start where {@link workTreeRoot} and {@link commonDirectory}\n * are two: `null` outside a work tree, and `'unread'` where the one answer\n * cannot be taken for the two, which are then to be asked for as they were.\n *\n * Measured with git 2.43 and 2.55, rev-parse answers the two in the order\n * they stand on its line, a line each, and prints nothing where the first\n * fails. Its manual says of that only that `--show-toplevel` reports an\n * error where there is no working tree (git-rev-parse(1), Options for\n * Files, as git 2.55.0 has it, read 2026-10-14), so nothing is read on\n * trust. Two lines that are each what `trim` would leave, from a git that\n * did not fail, are the two answers. Nothing printed, by a git that\n * failed, is the first question failing, the one {@link workTreeRoot}\n * asks: `cwd` is in no work tree. Anything else is not read: a path that\n * holds a line break is more lines than two, and so is what a git before\n * 2.31 would print, to which `--path-format=absolute` is a flag to hand\n * back (the same manual, Options for Filtering; the option is new in the\n * release notes of 2.31.0). No such git was measured.\n *\n * `--path-format` reaches only what follows it on the line (Options for\n * Files), so the top is printed as it is where it is asked for alone. The\n * common directory is asked for from `cwd` here, and from the top by\n * {@link commonDirectory}: the same directory only where `cwd` is the top.\n */\nexport async function workTree(cwd: string): Promise<WorkTree | 'unread' | null> {\n const run = await git(['rev-parse', '--show-toplevel', '--path-format=absolute', '--git-common-dir'], cwd);\n if (run.stdout === '') return run.code === 0 ? 'unread' : null;\n const lines = run.stdout.split('\\n');\n if (run.code !== 0 || lines.length !== 3 || lines[2] !== '') return 'unread';\n const root = lines[0]!;\n const commonDir = lines[1]!;\n if (root === '' || commonDir === '' || root.trim() !== root || commonDir.trim() !== commonDir) return 'unread';\n return { root, commonDir };\n}\n\n/**\n * Where git runs the hook `name` from, absolute: `core.hooksPath` read as git\n * reads it - relative to the work tree, absolute, or under `~` - and a linked\n * worktree's shared hooks. Where git cannot say, the first line of what it\n * said instead: under a `core.hooksPath` that names a file it refuses the\n * path as one that is not a directory (measured with git 2.47 and 2.55).\n */\nexport async function hookPath(name: string, cwd: string): Promise<{ readonly path: string } | { readonly refused: string }> {\n const run = await git(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);\n if (run.code === 0) return { path: run.stdout.trim() };\n return { refused: run.stderr.split(/\\r?\\n/)[0]! };\n}\n\nconst HEADS = 'refs/heads/';\n\n/**\n * The checked-out branch's name, or `null` on a detached head: what HEAD\n * points at, with `refs/heads/` taken off and nothing else. `--short` gives\n * the shortest name git reads as that ref and no other, which is another\n * name where a tag, or a ref under `refs/`, has the branch's: measured with\n * git 2.55, on `claude/brief-042-x` beside the tag `claude/brief-042-x` it\n * answers `heads/claude/brief-042-x`, which no template reads, and the\n * brief went unnamed (ADR-0005). A HEAD that points outside `refs/heads/`,\n * as `git symbolic-ref HEAD refs/tags/x` leaves it, is named as git status\n * names it, the ref whole.\n */\nexport async function currentBranch(cwd: string): Promise<string | null> {\n const ref = await value(['symbolic-ref', '--quiet', 'HEAD'], cwd);\n return ref !== null && ref.startsWith(HEADS) ? ref.slice(HEADS.length) : ref;\n}\n\n/**\n * The remote's default branch as a local ref, such as `origin/main`, or\n * `null`. This is a ref the harness hands git and spec-brief as the base, so\n * it is the short name git reads as that ref and no other: beside a local\n * branch or a tag named `origin/main`, measured with git 2.55, it is\n * `remotes/origin/main`, where `origin/main` would be read as the branch or\n * the tag (gitrevisions(7), the order a ref name is read in).\n */\nexport async function remoteDefault(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);\n}\n\n/**\n * Every local branch. The two are one name but where a tag, or a ref under\n * `refs/`, has the branch's: `main` beside the tag `main` is the ref\n * `heads/main`. A ref name holds no control character\n * (git-check-ref-format(1)), so a tab parts the two.\n */\nexport async function localBranches(cwd: string): Promise<LocalBranch[]> {\n const listed = await value(['for-each-ref', '--format=%(refname:lstrip=2)%09%(refname:short)', HEADS], cwd);\n // for-each-ref fails only where git itself does, outside a repository,\n // where init never runs: the null check is there for the type.\n if (listed === null || listed === '') return [];\n return listed.split('\\n').map((line) => {\n const parts = line.split('\\t');\n return { name: parts[0]!, ref: parts[1]! };\n });\n}\n\nexport async function revision(ref: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);\n}\n\nexport async function mergeBase(a: string, b: string, cwd: string): Promise<string | null> {\n return value(['merge-base', a, b], cwd);\n}\n\n/** Parses `git diff --name-status -z` output. */\nexport function parseNameStatus(output: string): FileChange[] {\n const fields = output.split('\\0');\n const out: FileChange[] = [];\n for (let i = 0; i < fields.length; ) {\n const code = fields[i] as string;\n if (code === '') {\n i += 1;\n continue;\n }\n const letter = code.charAt(0);\n const status = letter as FileChange['status'];\n if (status === 'R' || status === 'C') {\n out.push({ status, from: fields[i + 1] ?? '', path: fields[i + 2] ?? '' });\n i += 3;\n } else {\n out.push({ status, path: fields[i + 1] ?? '' });\n i += 2;\n }\n }\n return out;\n}\n\n/** The files changed from one revision to another, renames found. */\nexport async function changes(from: string, to: string, cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** The files a commit would change: what is staged. */\nexport async function stagedChanges(cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd);\n if (run.code !== 0) throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** A file's content at a revision, or `null` when it does not exist there. */\nexport async function show(ref: string, path: string, cwd: string): Promise<string | null> {\n const run = await git(['show', `${ref}:${path}`], cwd);\n return run.code === 0 ? run.stdout : null;\n}\n\n/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */\nexport function isUncommitted(sha: string): boolean {\n return /^0+$/.test(sha);\n}\n\n/**\n * The commit that last changed one line of a file, as of a revision, or of\n * the working tree when `ref` is `null` - where a line nobody committed yet\n * belongs to the all-zero commit.\n */\nexport async function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null> {\n const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);\n // A blame that fails prints nothing on stdout, which names no commit. Its\n // output opens with the commit, so the anchor is equivalent to its mutant.\n const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];\n return sha ?? null;\n}\n\n/** A good signature names its allowed signer's principal; any other names none. */\nexport type Signature =\n | { readonly good: true; readonly principal: string; readonly detail: string }\n | { readonly good: false; readonly principal: null; readonly detail: string };\n\n/**\n * Whether a commit carries a good SSH signature by a principal in the given\n * allowed-signers file. The file is passed in, never read from the commit's\n * own tree: a round cannot vouch for itself (ADR-0006).\n */\nexport async function verifyCommit(sha: string, allowedSignersFile: string, cwd: string): Promise<Signature> {\n const run = await git(\n ['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha],\n cwd,\n );\n const text = `${run.stderr}\\n${run.stdout}`;\n const principal = /Good \"git\" signature for (\\S+)/.exec(text)?.[1];\n // git writes the check to stderr, so the trim changes the first line only\n // for output on stdout alone, which verify-commit does not print; and split\n // always answers a first part.\n const detail = text.trim().split('\\n')[0]!;\n // git exits 0 only for a signature by a listed principal, which it names:\n // either condition alone is equivalent to both, and both trust neither.\n return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };\n}\n\nexport async function addWorktree(path: string, rev: string, cwd: string): Promise<void> {\n const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd);\n if (run.code !== 0) throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);\n}\n\n/**\n * Has git remove the worktree at `path`, and no other, with its record of it,\n * and answers whether git did. Git deletes what it can of the worktree's\n * directory and forgets the worktree even when it cannot delete it all. It\n * refuses a worktree whose directory has lost its `.git` file, and removes\n * its record of one whose directory is gone, found by the path it was added\n * at: `git worktree prune` would also forget every other worktree git has\n * lost track of, the person's among them.\n */\nexport async function removeWorktree(path: string, cwd: string): Promise<boolean> {\n // The sandbox asks again when this answers false, and git, asked again\n // about a worktree it has forgotten, changes nothing: the mutant that\n // always answers false is equivalent.\n return (await git(['worktree', 'remove', '--force', path], cwd)).code === 0;\n}\n"]}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The edge where the harness meets git.\n *\n * Reads, except for two things ADR-0003 allows and bounds: a temporary\n * worktree, added and always removed by the sandbox, and nothing else. Every\n * call is an argument vector, never a shell string, so a path or a ref cannot\n * become a command.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { execFile } from 'node:child_process';\n\nimport type { FileChange, LocalBranch } from './types.js';\n\nexport interface Run {\n readonly code: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/**\n * How long a process the harness asks a question of is given to answer:\n * git's reads, spec-brief's list of the briefs. Claude Code gives the hook's\n * entry 60 seconds, as init and the plugin write it, and cancels a hook that\n * reaches them, which before a write lets the write go ahead (ADR-0002). A\n * process that does not answer within this is stopped and its question\n * answered as one that failed, which before a write holds it, well inside\n * that minute; and the slowest such process measured, on a Windows\n * workstation under load and on GitHub's runner, answered in a fraction of\n * it.\n */\nexport const ANSWER_MS = 20_000;\n\n/**\n * How long a process that does a round's work is given: a diff of the\n * round, a worktree added for a probe or taken away, a sibling's check of\n * the repository. Its length is the repository's, and none runs before a\n * write.\n */\nexport const WORK_MS = 600_000;\n\n/** The run a process is started in, where it has a budget: its length, and when it ends, as `performance.now()` reads it. */\nconst budget = new AsyncLocalStorage<{ readonly ms: number; readonly end: number }>();\n\n/** Runs `work` with every process it starts given no more than what is left of `ms` from now. */\nexport function withBudget<T>(ms: number, work: () => Promise<T>): Promise<T> {\n return budget.run({ ms, end: performance.now() + ms }, work);\n}\n\n/**\n * The time a process is given, `limit`, or what is left of the budget of\n * the run it is started in where that is less, with the words that say\n * which: the run's budget is said as such, so that a write held at it says\n * why. A run with nothing left is given 0, and starts nothing.\n */\nexport function timeFor(limit: number): { readonly ms: number; readonly said: string } {\n const run = budget.getStore();\n const left = run === undefined ? limit : run.end - performance.now();\n if (left >= limit) return { ms: limit, said: `${limit / 1000} seconds` };\n return { ms: Math.max(0, Math.ceil(left)), said: `the ${run!.ms / 1000} seconds the hook's run is given` };\n}\n\n/**\n * Whether a process ended because the harness stopped it at its time\n * limit: Node kills it then and says so with `killed`. It kills one for\n * output past `maxBuffer` as well, and names that by its code.\n */\nexport function stoppedAtLimit(error: unknown): boolean {\n const failure = error as { killed?: unknown; code?: unknown } | null;\n return failure !== null && failure.killed === true && failure.code !== 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER';\n}\n\n/** The git command a line of arguments runs, past the `-c` settings before it: every line the harness hands git names one. */\nfunction subcommand(args: readonly string[]): string {\n return args.find((arg, index) => !arg.startsWith('-') && args[index - 1] !== '-c')!;\n}\n\n/**\n * Runs git and answers whatever it answered; a missing git is a code of -1.\n * A git that does not answer within `limit` is stopped, and rejected as a\n * `GitError` that says so: read as a failure, a question about the work\n * tree or the branch would answer as a directory in none, or a head on no\n * branch, under which the hook checks no write.\n */\nexport function git(args: readonly string[], cwd: string, limit = ANSWER_MS): Promise<Run> {\n const { ms, said } = timeFor(limit);\n if (ms === 0) return Promise.reject(new GitError(`git ${subcommand(args)} was not started: ${said} had passed`));\n return new Promise((resolve, reject) => {\n // windowsHide only keeps a console window from flashing up on Windows,\n // and the sweep runs on Linux, where its mutant is equivalent.\n execFile('git', [...args], { cwd, maxBuffer: 256 * 1024 * 1024, encoding: 'utf8', windowsHide: true, timeout: ms }, (error, stdout, stderr) => {\n if (stoppedAtLimit(error)) {\n reject(new GitError(`git ${subcommand(args)} did not answer within ${said}, and was stopped`));\n return;\n }\n // Every caller tells 0 from any other code, never one failure from\n // another, so which code a failure gets is equivalent to its mutants.\n const code = error === null ? 0 : typeof (error as { code?: unknown }).code === 'number' ? (error as { code: number }).code : -1;\n resolve({ code, stdout, stderr });\n });\n });\n}\n\nasync function value(args: readonly string[], cwd: string): Promise<string | null> {\n const run = await git(args, cwd);\n return run.code === 0 ? run.stdout.trim() : null;\n}\n\nexport class GitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitError';\n }\n}\n\n/** The top of the work tree holding `cwd`, POSIX-separated, or `null` outside one. */\nexport async function workTreeRoot(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--show-toplevel'], cwd);\n}\n\n/** The git directory every worktree of this repository shares. */\nexport async function commonDirectory(cwd: string): Promise<string | null> {\n return value(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);\n}\n\n/** A work tree as one start of git names it: its top, and the git directory its worktrees share. */\nexport interface WorkTree {\n readonly root: string;\n readonly commonDir: string;\n}\n\n/**\n * The top of the work tree holding `cwd` and the common directory, asked of\n * git in one start where {@link workTreeRoot} and {@link commonDirectory}\n * are two: `null` outside a work tree, and `'unread'` where the one answer\n * cannot be taken for the two, which are then to be asked for as they were.\n *\n * Measured with git 2.43 and 2.55, rev-parse answers the two in the order\n * they stand on its line, a line each, and prints nothing where the first\n * fails. Its manual says of that only that `--show-toplevel` reports an\n * error where there is no working tree (git-rev-parse(1), Options for\n * Files, as git 2.55.0 has it, read 2026-10-14), so nothing is read on\n * trust. Two lines that are each what `trim` would leave, from a git that\n * did not fail, are the two answers. Nothing printed, by a git that\n * failed, is the first question failing, the one {@link workTreeRoot}\n * asks: `cwd` is in no work tree. Anything else is not read: a path that\n * holds a line break is more lines than two, and so is what a git before\n * 2.31 would print, to which `--path-format=absolute` is a flag to hand\n * back (the same manual, Options for Filtering; the option is new in the\n * release notes of 2.31.0). No such git was measured.\n *\n * `--path-format` reaches only what follows it on the line (Options for\n * Files), so the top is printed as it is where it is asked for alone. The\n * common directory is asked for from `cwd` here, and from the top by\n * {@link commonDirectory}: the same directory only where `cwd` is the top.\n */\nexport async function workTree(cwd: string): Promise<WorkTree | 'unread' | null> {\n const run = await git(['rev-parse', '--show-toplevel', '--path-format=absolute', '--git-common-dir'], cwd);\n if (run.stdout === '') return run.code === 0 ? 'unread' : null;\n const lines = run.stdout.split('\\n');\n if (run.code !== 0 || lines.length !== 3 || lines[2] !== '') return 'unread';\n const root = lines[0]!;\n const commonDir = lines[1]!;\n if (root === '' || commonDir === '' || root.trim() !== root || commonDir.trim() !== commonDir) return 'unread';\n return { root, commonDir };\n}\n\n/**\n * Where git runs the hook `name` from, absolute: `core.hooksPath` read as git\n * reads it - relative to the work tree, absolute, or under `~` - and a linked\n * worktree's shared hooks. Where git cannot say, the first line of what it\n * said instead: under a `core.hooksPath` that names a file it refuses the\n * path as one that is not a directory (measured with git 2.47 and 2.55).\n */\nexport async function hookPath(name: string, cwd: string): Promise<{ readonly path: string } | { readonly refused: string }> {\n const run = await git(['rev-parse', '--path-format=absolute', '--git-path', `hooks/${name}`], cwd);\n if (run.code === 0) return { path: run.stdout.trim() };\n return { refused: run.stderr.split(/\\r?\\n/)[0]! };\n}\n\nconst HEADS = 'refs/heads/';\n\n/**\n * The checked-out branch's name, or `null` on a detached head: what HEAD\n * points at, with `refs/heads/` taken off and nothing else. `--short` gives\n * the shortest name git reads as that ref and no other, which is another\n * name where a tag, or a ref under `refs/`, has the branch's: measured with\n * git 2.55, on `claude/brief-042-x` beside the tag `claude/brief-042-x` it\n * answers `heads/claude/brief-042-x`, which no template reads, and the\n * brief went unnamed (ADR-0005). A HEAD that points outside `refs/heads/`,\n * as `git symbolic-ref HEAD refs/tags/x` leaves it, is named as git status\n * names it, the ref whole.\n */\nexport async function currentBranch(cwd: string): Promise<string | null> {\n const ref = await value(['symbolic-ref', '--quiet', 'HEAD'], cwd);\n return ref !== null && ref.startsWith(HEADS) ? ref.slice(HEADS.length) : ref;\n}\n\n/**\n * The remote's default branch as a local ref, such as `origin/main`, or\n * `null`. This is a ref the harness hands git and spec-brief as the base, so\n * it is the short name git reads as that ref and no other: beside a local\n * branch or a tag named `origin/main`, measured with git 2.55, it is\n * `remotes/origin/main`, where `origin/main` would be read as the branch or\n * the tag (gitrevisions(7), the order a ref name is read in).\n */\nexport async function remoteDefault(cwd: string): Promise<string | null> {\n return value(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], cwd);\n}\n\n/**\n * Every local branch. The two are one name but where a tag, or a ref under\n * `refs/`, has the branch's: `main` beside the tag `main` is the ref\n * `heads/main`. A ref name holds no control character\n * (git-check-ref-format(1)), so a tab parts the two.\n */\nexport async function localBranches(cwd: string): Promise<LocalBranch[]> {\n const listed = await value(['for-each-ref', '--format=%(refname:lstrip=2)%09%(refname:short)', HEADS], cwd);\n // for-each-ref fails only where git itself does, outside a repository,\n // where init never runs: the null check is there for the type.\n if (listed === null || listed === '') return [];\n return listed.split('\\n').map((line) => {\n const parts = line.split('\\t');\n return { name: parts[0]!, ref: parts[1]! };\n });\n}\n\nexport async function revision(ref: string, cwd: string): Promise<string | null> {\n return value(['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], cwd);\n}\n\nexport async function mergeBase(a: string, b: string, cwd: string): Promise<string | null> {\n return value(['merge-base', a, b], cwd);\n}\n\n/** Parses `git diff --name-status -z` output. */\nexport function parseNameStatus(output: string): FileChange[] {\n const fields = output.split('\\0');\n const out: FileChange[] = [];\n for (let i = 0; i < fields.length; ) {\n const code = fields[i] as string;\n if (code === '') {\n i += 1;\n continue;\n }\n const letter = code.charAt(0);\n const status = letter as FileChange['status'];\n if (status === 'R' || status === 'C') {\n out.push({ status, from: fields[i + 1] ?? '', path: fields[i + 2] ?? '' });\n i += 3;\n } else {\n out.push({ status, path: fields[i + 1] ?? '' });\n i += 2;\n }\n }\n return out;\n}\n\n/** The files changed from one revision to another, renames found. */\nexport async function changes(from: string, to: string, cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--name-status', '-z', '--find-renames', '--no-color', from, to, '--'], cwd, WORK_MS);\n if (run.code !== 0) throw new GitError(`git diff ${from} ${to} failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/**\n * How many words stand before the path in each kind of record of\n * `git status --porcelain=v2`, as git-status(1) has them (Git for Windows\n * 2.55.0, *Porcelain Format Version 2*, read 2026-10-19): an ordinary\n * change, `1`, and seven; a rename or a copy, `2`, and eight, its path\n * followed by the path it came from as a record of its own under `-z`; an\n * unmerged path, `u`, and nine; an untracked or an ignored path, `?` or\n * `!`, and none. A header, `#`, names no path.\n */\nconst STATUS_FIELDS: Readonly<Record<string, number>> = { '1': 8, '2': 9, u: 10, '?': 1, '!': 1 };\n\n/**\n * The paths `git status --porcelain=v2 -z` names, each once, in the order\n * git gives them: a rename or a copy by both its paths. A path with a space\n * in it is the rest of its record, which `-z` prints as is.\n */\nexport function parseStatus(output: string): string[] {\n const records = output.split('\\0');\n const paths = new Set<string>();\n for (let at = 0; at < records.length; at += 1) {\n const record = records[at]!;\n const fields = STATUS_FIELDS[record.charAt(0)];\n if (fields === undefined) continue;\n paths.add(record.split(' ').slice(fields).join(' '));\n if (record.startsWith('2')) {\n at += 1;\n paths.add(records[at] ?? '');\n }\n }\n paths.delete('');\n return [...paths];\n}\n\n/**\n * Every path the work tree holds changed from HEAD: tracked files that\n * differ in the index or in the work tree, unmerged ones, and each\n * untracked file, those in an untracked directory one by one. One start of\n * git, with no optional lock taken: a status that writes the index as it\n * refreshes it holds a lock that can make another git running beside it\n * fail, and the manual has a status run in the background take none\n * (git-status(1), *Background Refresh*, and git(1), `--no-optional-locks`,\n * as Git for Windows 2.55.0 has them, read 2026-10-19).\n */\nexport async function changedPaths(cwd: string): Promise<string[]> {\n const run = await git(['--no-optional-locks', 'status', '--porcelain=v2', '-z', '--untracked-files=all'], cwd);\n if (run.code !== 0) throw new GitError(`git status failed: ${run.stderr.trim()}`);\n return parseStatus(run.stdout);\n}\n\n/** The files a commit would change: what is staged. */\nexport async function stagedChanges(cwd: string): Promise<FileChange[]> {\n const run = await git(['diff', '--cached', '--name-status', '-z', '--find-renames', '--no-color', '--'], cwd, WORK_MS);\n if (run.code !== 0) throw new GitError(`git diff --cached failed: ${run.stderr.trim()}`);\n return parseNameStatus(run.stdout);\n}\n\n/** A file's content at a revision, or `null` when it does not exist there. */\nexport async function show(ref: string, path: string, cwd: string): Promise<string | null> {\n const run = await git(['show', `${ref}:${path}`], cwd);\n return run.code === 0 ? run.stdout : null;\n}\n\n/**\n * Whether a commit's tree holds `path`, a path from the top of the work tree\n * read as written: `true` where git lists it, `false` where git lists\n * nothing for it, and `null` where git could not say, for a commit it does\n * not have among them. {@link show} answers `null` for a file that is not\n * there and for a git that failed alike, which can only make an answer\n * stricter; this tells the two apart, so that an answer may be loosened by a\n * file that git says is not there.\n *\n * ls-tree reads its paths as patterns, from the directory it is started in\n * (git-ls-tree(1), Description, as Git for Windows 2.55.0 has it, read\n * 2026-10-24): `--full-tree` has it read them from the top, and\n * `--literal-pathspecs` with no globbing and no magic (git(1), Options), so\n * a name such as `:(top)x.md` names that file, where ls-tree would read it\n * as `x.md`.\n */\nexport async function holdsPath(commit: string, path: string, cwd: string): Promise<boolean | null> {\n const run = await git(['--literal-pathspecs', 'ls-tree', '-z', '--full-tree', '--name-only', commit, '--', path], cwd);\n return run.code === 0 ? run.stdout !== '' : null;\n}\n\n/**\n * Whether the history is cut short, as a clone or a fetch with `--depth`\n * leaves it, where a merge base git finds can be an older commit than the\n * one the whole history has; `null` where git could not say. rev-parse\n * learned the question in 2.15.0 (its release notes), and prints `true` or\n * `false` (git-rev-parse(1), Options for Files, read 2026-10-24); anything\n * else is no answer.\n */\nexport async function isShallow(cwd: string): Promise<boolean | null> {\n const answer = await value(['rev-parse', '--is-shallow-repository'], cwd);\n if (answer === 'true') return true;\n return answer === 'false' ? false : null;\n}\n\n/** Whether a commit id from {@link blameLine} is the all-zero one of a line nobody committed. */\nexport function isUncommitted(sha: string): boolean {\n return /^0+$/.test(sha);\n}\n\n/**\n * The commit that last changed one line of a file, as of a revision, or of\n * the working tree when `ref` is `null` - where a line nobody committed yet\n * belongs to the all-zero commit.\n */\nexport async function blameLine(ref: string | null, path: string, line: number, cwd: string): Promise<string | null> {\n const run = await git(['blame', '--porcelain', '-L', `${line},${line}`, ...(ref === null ? [] : [ref]), '--', path], cwd);\n // A blame that fails prints nothing on stdout, which names no commit. Its\n // output opens with the commit, so the anchor is equivalent to its mutant.\n const sha = /^([0-9a-f]{40,64}) /.exec(run.stdout)?.[1];\n return sha ?? null;\n}\n\n/** A good signature names its allowed signer's principal; any other names none. */\nexport type Signature =\n | { readonly good: true; readonly principal: string; readonly detail: string }\n | { readonly good: false; readonly principal: null; readonly detail: string };\n\n/**\n * Whether a commit carries a good SSH signature by a principal in the given\n * allowed-signers file. The file is passed in, never read from the commit's\n * own tree: a round cannot vouch for itself (ADR-0006).\n */\nexport async function verifyCommit(sha: string, allowedSignersFile: string, cwd: string): Promise<Signature> {\n const run = await git(\n ['-c', `gpg.ssh.allowedSignersFile=${allowedSignersFile}`, '-c', 'gpg.format=ssh', 'verify-commit', '--raw', sha],\n cwd,\n );\n const text = `${run.stderr}\\n${run.stdout}`;\n const principal = /Good \"git\" signature for (\\S+)/.exec(text)?.[1];\n // git writes the check to stderr, so the trim changes the first line only\n // for output on stdout alone, which verify-commit does not print; and split\n // always answers a first part.\n const detail = text.trim().split('\\n')[0]!;\n // git exits 0 only for a signature by a listed principal, which it names:\n // either condition alone is equivalent to both, and both trust neither.\n return run.code === 0 && principal !== undefined ? { good: true, principal, detail } : { good: false, principal: null, detail };\n}\n\nexport async function addWorktree(path: string, rev: string, cwd: string): Promise<void> {\n const run = await git(['worktree', 'add', '--detach', '--force', path, rev], cwd, WORK_MS);\n if (run.code !== 0) throw new GitError(`git worktree add failed: ${run.stderr.trim()}`);\n}\n\n/**\n * Has git remove the worktree at `path`, and no other, with its record of it,\n * and answers whether git did. Git deletes what it can of the worktree's\n * directory and forgets the worktree even when it cannot delete it all. It\n * refuses a worktree whose directory has lost its `.git` file, and removes\n * its record of one whose directory is gone, found by the path it was added\n * at: `git worktree prune` would also forget every other worktree git has\n * lost track of, the person's among them.\n */\nexport async function removeWorktree(path: string, cwd: string): Promise<boolean> {\n // The sandbox asks again when this answers false, and git, asked again\n // about a worktree it has forgotten, changes nothing: the mutant that\n // always answers false is equivalent.\n return (await git(['worktree', 'remove', '--force', path], cwd, WORK_MS)).code === 0;\n}\n"]}