@descent-vtt/spec-harness 0.13.0 → 0.15.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 (52) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +75 -0
  4. package/README.md +18 -3
  5. package/dist/answers.d.ts +6 -0
  6. package/dist/answers.js +12 -1
  7. package/dist/answers.js.map +1 -1
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.js +81 -43
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands.js +1 -0
  12. package/dist/commands.js.map +1 -1
  13. package/dist/configure.d.ts +58 -2
  14. package/dist/configure.js +116 -3
  15. package/dist/configure.js.map +1 -1
  16. package/dist/context.d.ts +16 -7
  17. package/dist/context.js +36 -11
  18. package/dist/context.js.map +1 -1
  19. package/dist/fs.js +1 -0
  20. package/dist/fs.js.map +1 -1
  21. package/dist/git.js +2 -1
  22. package/dist/git.js.map +1 -1
  23. package/dist/hooks.js.map +1 -1
  24. package/dist/host.js.map +1 -1
  25. package/dist/literal.d.ts +18 -0
  26. package/dist/literal.js +21 -0
  27. package/dist/literal.js.map +1 -0
  28. package/dist/probe.js +1 -1
  29. package/dist/probe.js.map +1 -1
  30. package/dist/reader.js +2 -0
  31. package/dist/reader.js.map +1 -1
  32. package/dist/round.d.ts +12 -1
  33. package/dist/round.js +23 -7
  34. package/dist/round.js.map +1 -1
  35. package/dist/sandbox.js.map +1 -1
  36. package/dist/server.js +2 -1
  37. package/dist/server.js.map +1 -1
  38. package/dist/setup.js +2 -1
  39. package/dist/setup.js.map +1 -1
  40. package/dist/siblings.js +2 -1
  41. package/dist/siblings.js.map +1 -1
  42. package/dist/signers.js +5 -0
  43. package/dist/signers.js.map +1 -1
  44. package/dist/usage.d.ts +83 -27
  45. package/dist/usage.js +61 -16
  46. package/dist/usage.js.map +1 -1
  47. package/dist/versions.js +3 -0
  48. package/dist/versions.js.map +1 -1
  49. package/dist/workspace.d.ts +9 -0
  50. package/dist/workspace.js +48 -32
  51. package/dist/workspace.js.map +1 -1
  52. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `init` writes into each tool's configuration, and what `doctor` says\n * of the result.\n *\n * Pure: `setup.ts` and the command line read the files and ask git, and\n * write what these return, so the core sweep holds these to the unit suite.\n * Each merge keeps what a person wrote and returns `null` when there is\n * nothing to add.\n */\n\nimport { isAbsolute } from 'node:path';\n\nimport type { AllowedSigner } from './signers.js';\nimport { parseGlobList } from './vendor/spec-core/pattern/index.js';\nimport { CLAUDE_CODE_MINIMUM, type ClaudeCodeCheck } from './versions.js';\n\ntype Json = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is Json {\n return typeof value === 'object' && value !== null;\n}\n\nfunction sameStrings(value: unknown, expected: readonly string[]): boolean {\n return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);\n}\n\n/**\n * The command line as the project installed it. Claude Code starts a plugin's\n * server in the plugin's own directory and a hook wherever the session\n * stands, so neither can rely on `npx` finding the project's install; and on\n * Windows a server's command is started without a shell, where `npx` is a\n * shim that cannot start at all. `node` and the script's path work\n * everywhere. git's hook runs the same script, from the top of the work\n * tree, where git starts it.\n */\nconst BIN = 'node_modules/@descent-vtt/spec-harness/bin/spec-harness.js';\n\n/** The project root, as Claude Code substitutes it in a hook's arguments and in a plugin server's. */\nexport const PROJECT_DIR = '${CLAUDE_PROJECT_DIR}';\n\n/**\n * The project root in a project's own `.mcp.json`. Claude Code expands it\n * there from its own environment, which does not hold it, so it needs a\n * default; `.` is right, since a project's server starts in the project.\n */\nexport const PROJECT_DIR_OR_HERE = '${CLAUDE_PROJECT_DIR:-.}';\n\nconst HOOK_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';\nconst GUARDED_EVENTS = ['PreToolUse', 'PostToolUse'];\nconst GUARD_ARGS: readonly string[] = Object.freeze([`${PROJECT_DIR}/${BIN}`, 'hook', 'claude']);\n\n/**\n * The guard as a Claude Code hook: exec form, `args` given, so the project's\n * path is one argument whatever it holds, and no shell is involved.\n */\nexport const GUARD_HOOK: Readonly<Json> = Object.freeze({ type: 'command', command: 'node', args: GUARD_ARGS, timeout: 60 });\n\n// bare-name: what 0.1 installed, an npx command found from wherever the\n// session stood. Read here so that init replaces it, and never run.\nconst LEGACY_HOOK = 'npx --no-install spec-harness hook claude';\nconst LEGACY_SERVER_ARGS = ['--no-install', 'spec-harness', 'mcp'];\n\nfunction isGuard(hook: unknown): boolean {\n return isObject(hook) && hook['command'] === 'node' && sameStrings(hook['args'], GUARD_ARGS);\n}\n\nfunction isLegacyGuard(hook: unknown): boolean {\n return isObject(hook) && hook['command'] === LEGACY_HOOK;\n}\n\nfunction hooksOf(group: unknown): unknown[] {\n return isObject(group) && Array.isArray(group['hooks']) ? group['hooks'] : [];\n}\n\n/** A group with 0.1's guard replaced where it stands, keeping its other fields; the group itself when it has none. */\nfunction upgraded(group: unknown): unknown {\n const hooks = hooksOf(group);\n if (!hooks.some(isLegacyGuard)) return group;\n return { ...(group as Json), hooks: hooks.map((hook) => (isLegacyGuard(hook) ? { ...(hook as Json), command: 'node', args: [...GUARD_ARGS] } : hook)) };\n}\n\n/** `.claude/settings.json`, or the plugin's hooks, with the guard hooks merged in; `null` when they are already there. */\nexport function mergeClaudeSettings(current: Json): Json | null {\n const hooks = isObject(current['hooks']) ? current['hooks'] : {};\n let changed = false;\n const next: Json = { ...hooks };\n for (const event of GUARDED_EVENTS) {\n const listed = hooks[event];\n const before: unknown[] = Array.isArray(listed) ? listed : [];\n const groups = before.map(upgraded);\n const replaced = groups.some((group, index) => group !== before[index]);\n const present = groups.some((group) => hooksOf(group).some(isGuard));\n if (!present) groups.push({ matcher: HOOK_MATCHER, hooks: [{ ...GUARD_HOOK, args: [...GUARD_ARGS] }] });\n if (replaced || !present) {\n next[event] = groups;\n changed = true;\n }\n }\n return changed ? { ...current, hooks: next } : null;\n}\n\n/** The server's entry, reading the project at `projectDir`: {@link PROJECT_DIR} for the plugin, {@link PROJECT_DIR_OR_HERE} for a project. */\nexport function mcpServer(projectDir: string): Json {\n return { command: 'node', args: [`${projectDir}/${BIN}`, 'mcp', '--root', projectDir] };\n}\n\nfunction isLegacyServer(server: unknown): boolean {\n return isObject(server) && server['command'] === 'npx' && sameStrings(server['args'], LEGACY_SERVER_ARGS);\n}\n\n/**\n * A project's `.mcp.json` with the harness's server merged in, or `null` when\n * one is registered. A server registered under the name some other way is\n * the person's, and stays; 0.1's npx entry is replaced, keeping its other\n * fields.\n */\nexport function mergeMcp(current: Json): Json | null {\n const servers = isObject(current['mcpServers']) ? current['mcpServers'] : {};\n const existing = servers['spec-harness'];\n if ('spec-harness' in servers && !isLegacyServer(existing)) return null;\n return { ...current, mcpServers: { ...servers, 'spec-harness': { ...(existing as Json | undefined), ...mcpServer(PROJECT_DIR_OR_HERE) } } };\n}\n\n/** A key of a file read as JSON; a file that is missing, or is not a JSON object, holds nothing. */\nfunction field(file: unknown, key: string): unknown {\n return isObject(file) ? file[key] : undefined;\n}\n\n/** Whether Claude Code settings hold the guard hooks, as this release or 0.1 wrote them. */\nexport function holdsGuard(settings: unknown): boolean {\n const hooks = field(settings, 'hooks');\n if (!isObject(hooks)) return false;\n return GUARDED_EVENTS.some((event) => {\n const groups = hooks[event];\n return Array.isArray(groups) && groups.some((group) => hooksOf(group).some((hook) => isGuard(hook) || isLegacyGuard(hook)));\n });\n}\n\n/** Whether a project's `.mcp.json` registers a server under the harness's name, however it runs it. */\nexport function registersServer(mcp: unknown): boolean {\n const servers = field(mcp, 'mcpServers');\n return isObject(servers) && 'spec-harness' in servers;\n}\n\n/** A Claude Code settings file, named as a person finds it, and what reading it gave: an object, or nothing usable. */\nexport interface ClaudeSettings {\n readonly file: string;\n readonly settings: unknown;\n}\n\n/** The plugin's id in `enabledPlugins`, and the settings file that turns it on. */\nexport interface EnabledPlugin {\n readonly id: string;\n readonly file: string;\n}\n\n/** `enabledPlugins` keys a plugin `<name>@<marketplace>`; this repository's marketplace lists the plugin as `spec-harness`. */\nconst PLUGIN_ID = /^spec-harness@[^@]+$/;\n\n/**\n * Whether Claude Code runs this package's plugin, from its settings files\n * given lowest precedence first: user, project, local. Claude Code merges\n * `enabledPlugins` key by key, so for each id the last file that sets it to\n * `true` or `false` decides. A plugin named `spec-harness` counts from any\n * marketplace, since another can list this repository. `null` when no file\n * turns it on.\n */\nexport function enabledPlugin(sources: readonly ClaudeSettings[]): EnabledPlugin | null {\n const decided = new Map<string, EnabledPlugin | null>();\n for (const { file, settings } of sources) {\n const plugins = field(settings, 'enabledPlugins');\n if (!isObject(plugins)) continue;\n for (const [id, on] of Object.entries(plugins)) {\n if (PLUGIN_ID.test(id) && typeof on === 'boolean') decided.set(id, on ? { id, file } : null);\n }\n }\n return [...decided.values()].find((entry) => entry !== null) ?? null;\n}\n\n/** Where the plugin is on, as a person would look it up. */\nfunction isOn(plugin: EnabledPlugin): string {\n return `the spec-harness plugin is on (${plugin.id} in ${plugin.file})`;\n}\n\n/** How to have init's entries in place of the plugin's, for this project alone. */\nfunction turnOff(plugin: EnabledPlugin): string {\n return `turn the plugin off for this project with claude plugin disable ${plugin.id} --scope local`;\n}\n\nconst PARTS = {\n hooks: { brings: 'the guard hooks', twice: 'every write is guarded twice', skipped: 'init writes none', entry: \"spec-harness's hooks\", them: 'them' },\n server: { brings: 'the server', twice: 'the server is registered twice', skipped: 'init registers none', entry: 'the spec-harness server', them: 'it' },\n} as const;\n\n/**\n * What init says of a Claude Code file it leaves alone because the plugin is\n * on and brings the same `part`. A file that holds init's entry already is a\n * double install for the person to settle: init removes nothing, since the\n * file is the repository's and a clone without the plugin relies on it.\n */\nexport function describeSkipped(plugin: EnabledPlugin, part: 'hooks' | 'server', present: boolean): string {\n const words = PARTS[part];\n if (present) return `${isOn(plugin)} and brings ${words.brings} this file holds as well, so ${words.twice}: take ${words.entry} out of this file, or ${turnOff(plugin)}`;\n return `${isOn(plugin)} and brings ${words.brings}, so ${words.skipped}: with both, ${words.twice}. To have ${words.them} here instead, for every clone of the repository, ${turnOff(plugin)} and run init again`;\n}\n\n/** How Claude Code is wired to the harness: the plugin, init's entries, both or neither. */\nexport interface ClaudeCodeWiring {\n readonly plugin: EnabledPlugin | null;\n /** The settings files that hold the guard hooks. */\n readonly hooks: readonly string[];\n /** Whether `.mcp.json` registers the server. */\n readonly server: boolean;\n}\n\nexport type WiringState = 'plugin' | 'init' | 'both' | 'none';\n\n/** Which of the four the wiring is. `both` is a double install, and doctor fails it; either of init's entries counts. */\nexport function wiringState(wiring: ClaudeCodeWiring): WiringState {\n const init = wiring.hooks.length > 0 || wiring.server;\n if (wiring.plugin === null) return init ? 'init' : 'none';\n return init ? 'both' : 'plugin';\n}\n\n/** What `doctor` says of the wiring, and what to do when it is doubled or missing. */\nexport function describeClaudeCode(wiring: ClaudeCodeWiring): string {\n const { plugin } = wiring;\n const entries = [...wiring.hooks.map((file) => `the guard hooks in ${file}`), ...(wiring.server ? ['the server in .mcp.json'] : [])].join(', ');\n if (plugin === null) {\n if (entries === '') return 'neither the spec-harness plugin nor the hooks init writes, so Claude Code guards no write: run spec-harness init --write, or install the plugin';\n return `init's entries: ${entries}`;\n }\n if (entries === '') return `${isOn(plugin)} and brings the guard hooks and the server`;\n const twice = [...(wiring.hooks.length > 0 ? [PARTS.hooks.twice] : []), ...(wiring.server ? [PARTS.server.twice] : [])].join(' and ');\n return `${isOn(plugin)}, beside ${entries}: ${twice}. Keep one: take spec-harness's entries out of those files, or ${turnOff(plugin)}`;\n}\n\n/** The spec-brief plugin this package ships, as spec-brief's `plugins` names it. */\nexport const PLUGIN = '@descent-vtt/spec-harness/spec-brief-plugin';\n\n/** spec-brief's configuration files, in the order spec-brief looks for them in a directory. */\nexport const SPEC_BRIEF_CONFIGS: readonly string[] = ['.spec-brief.json', 'spec-brief.json'];\n\n/** The conditions an import meets in Node, its `defaultConditions`, and `default`, which every one meets. */\nconst IMPORT_CONDITIONS: readonly string[] = ['node', 'import', 'default'];\n\n/** A target Node refuses as invalid, which an array passes over and anything else stops at. */\nconst INVALID = Symbol('invalid target');\n\nfunction resolveTarget(target: unknown): string | null | undefined | typeof INVALID {\n if (typeof target === 'string') return target.startsWith('./') ? target : INVALID;\n if (Array.isArray(target)) {\n for (const item of target) {\n const resolved = resolveTarget(item);\n if (resolved !== undefined && resolved !== INVALID) return resolved;\n }\n return null;\n }\n if (typeof target !== 'object' || target === null) return target === null ? null : INVALID;\n for (const [condition, value] of Object.entries(target)) {\n if (!IMPORT_CONDITIONS.includes(condition)) continue;\n const resolved = resolveTarget(value);\n if (resolved !== undefined) return resolved;\n }\n return undefined;\n}\n\n/**\n * The file a package's `exports` target names for an import, as Node's\n * PACKAGE_TARGET_RESOLVE finds it, or `null` when it names none: a string\n * that starts with `./`; in an object, the target of the first key, in the\n * object's own order, that an import meets - `node`, `import` or\n * `default` - and that resolves to anything, a `null` included; in an\n * array, the first item that resolves, one Node refuses passed over. A\n * `null` target is not exported, and ends the search there, where a\n * condition an import does not meet lets it go on.\n */\nexport function exportedFile(target: unknown): string | null {\n const resolved = resolveTarget(target);\n return typeof resolved === 'string' ? resolved : null;\n}\n\n/**\n * Whether spec-brief's configuration loads the plugin, by name or as\n * `{ module, options }`. spec-brief also loads a plugin by a path, one that\n * starts with `.` or is absolute, read from the root; such a path is the\n * plugin when `isPluginFile` says it names the plugin's file, which only the\n * disk can tell. Without one, no path is: the default's `false` and an\n * `undefined` read the same in `some`, so that mutant is equivalent.\n */\nexport function loadsPlugin(config: Json, isPluginFile: (path: string) => boolean = () => false): boolean {\n const plugins = config['plugins'];\n const names = (specifier: unknown): boolean =>\n specifier === PLUGIN || (typeof specifier === 'string' && (specifier.startsWith('.') || isAbsolute(specifier)) && isPluginFile(specifier));\n return Array.isArray(plugins) && plugins.some((entry) => names(entry) || (isObject(entry) && names(entry['module'])));\n}\n\n/** Whether spec-brief's configuration names the base its archive measures a round from. */\nexport function measuresArchive(config: Json): boolean {\n const archiving = config['archiving'];\n return isObject(archiving) && typeof archiving['base'] === 'string';\n}\n\n/**\n * spec-brief's configuration with the plugin loaded and the archive measured\n * from `base`, or `null` when both already hold. spec-brief's archive\n * refuses a round that changed a protected file, and learns that a signed\n * ruling allows it only by asking the plugin (ADR-0006). It checks the file at\n * all only when it knows the base: without one it warns that the scope went\n * unmeasured and passes, so the gate is open until a base is named. A base a\n * person wrote is kept.\n */\nexport function mergeSpecBrief(current: Json, base: string | null = null, isPluginFile?: (path: string) => boolean): Json | null {\n const plugin = loadsPlugin(current, isPluginFile);\n const measure = base !== null && !measuresArchive(current);\n if (plugin && !measure) return null;\n const plugins: unknown[] = Array.isArray(current['plugins']) ? current['plugins'] : [];\n const archiving: Json = isObject(current['archiving']) ? current['archiving'] : {};\n return {\n ...current,\n ...(plugin ? {} : { plugins: [...plugins, PLUGIN] }),\n ...(measure ? { archiving: { ...archiving, base } } : {}),\n };\n}\n\n/** Whether spec-brief loads the plugin, as its configuration at the root says. */\nexport type PluginState =\n | { readonly kind: 'loaded' | 'not-loaded' | 'unreadable'; readonly file: string }\n | { readonly kind: 'unconfigured' };\n\n/** What a person reads about the plugin, and what to do when it is not loaded. */\nexport function describePlugin(state: PluginState): string {\n switch (state.kind) {\n case 'loaded':\n return `spec-brief loads spec-harness's plugin (${state.file}): its archive accepts a protected file a signed ruling allows`;\n case 'not-loaded':\n return `spec-brief does not load spec-harness's plugin, so its archive refuses a protected file whatever ruling is signed: add \"${PLUGIN}\" to \"plugins\" in ${state.file}, or run spec-harness init --write`;\n case 'unreadable':\n return `${state.file} cannot be read as JSON, so whether spec-brief loads spec-harness's plugin is unknown`;\n case 'unconfigured':\n return \"spec-brief has no configuration at the root, so it loads no plugin: spec-harness init --write writes one that loads spec-harness's\";\n }\n}\n\n/** What git says that tells a base apart. */\nexport interface BaseFacts {\n /** The remote's default branch as a local ref, such as `origin/main`, or `null`. */\n readonly remoteDefault: string | null;\n /** The branch checked out, or `null` on a detached head. */\n readonly branch: string | null;\n /** Every local branch. */\n readonly branches: readonly string[];\n}\n\n/** The base `init` names, and why; `base` is `null` when none can be told. */\nexport interface BaseChoice {\n readonly base: string | null;\n readonly detail: string;\n}\n\n/**\n * The base `init` writes into `.spec-harness.json`. Every ruling is verified\n * against the allowed signers on it, so it is named rather than left to be\n * found. The remote's default branch comes first, but git records it when\n * it clones, and before 2.48 not on a fetch: a repository made with `git\n * init` and pushed to a remote often has none. There the branch init runs on\n * is the base when it is `main` or `master`, or the only branch; otherwise\n * the person names it.\n */\nexport function chooseBase(facts: BaseFacts): BaseChoice {\n const { remoteDefault, branch } = facts;\n if (remoteDefault !== null) return { base: remoteDefault, detail: `rounds are measured from ${remoteDefault}, the remote's default branch` };\n const unrecorded = 'no remote records a default branch (refs/remotes/origin/HEAD)';\n if (branch !== null) {\n const conventional = branch === 'main' || branch === 'master';\n if (conventional || facts.branches.every((name) => name === branch)) {\n const which = conventional ? 'the branch init runs on' : 'the only branch';\n return { base: branch, detail: `rounds are measured from ${branch}, ${which}: ${unrecorded}; change \"base\" if rounds merge into another` };\n }\n }\n const where = branch === null ? 'HEAD is detached' : `${branch} is not main, master or the only branch`;\n return {\n base: null,\n detail: `no base can be told: ${unrecorded}, and ${where}; set \"base\" to the branch rounds merge into, or run git remote set-head origin --auto and init again`,\n };\n}\n\n/** Where a base came from: `--base`, the configuration, or the remote. */\nexport type BaseSource = 'flag' | 'config' | 'remote';\n\nconst SOURCES: Readonly<Record<BaseSource, string>> = { flag: '--base', config: '.spec-harness.json', remote: \"the remote's default branch\" };\n\n/** The base a command resolved, and how; or why there is none. */\nexport function describeBase(base: { readonly ref: string; readonly source: BaseSource; readonly mergeBase: string } | { readonly reason: string }): string {\n if ('reason' in base) return `none: ${base.reason}`;\n return `${base.ref} (${SOURCES[base.source]}), merge base ${base.mergeBase.slice(0, 12)}`;\n}\n\n/** Whether the allowed-signers file is on the base, where every ruling's signature is checked against it. */\nexport function describeSigners(file: string, base: string | null, onBase: boolean): string {\n if (base === null) return `${file}, read from the base, which could not be resolved: no ruling can count`;\n if (onBase) return `${file} is on ${base}`;\n return `${file} is not on ${base}, so no ruling can count: commit it there, one line per person: <email> namespaces=\"git\" <public key>`;\n}\n\n/**\n * The note doctor gives on the signers whose key is not FIDO2, or `null`\n * when every one's is. A note, never a failure: ADR-0006 accepts a key the\n * agent's account cannot read too, and a PIV or PKCS#11 hardware key is\n * written as a plain key.\n */\nexport function describeSignerKeys(signers: readonly Pick<AllowedSigner, 'line' | 'principals' | 'keyType'>[]): string | null {\n if (signers.length === 0) return null;\n const named = signers.map((signer) => `${signer.principals.join(',')} (${signer.keyType}, line ${signer.line})`).join(', ');\n return (\n `note: signers whose key is not a FIDO2 key: ${named}. ` +\n \"Where an agent runs as the person, ADR-0006 recommends a FIDO2 key, ssh-keygen -t ed25519-sk, whose signature needs a touch no process can supply, or a key the agent's account cannot read; \" +\n 'a PIV or PKCS#11 hardware key reads as a plain ssh-rsa or ecdsa line, so this is a note, not a failure'\n );\n}\n\n/** The note doctor gives on a line of the allowed-signers file that is not a signer. */\nexport function describeSignerProblem(problem: { readonly line: number; readonly message: string }): string {\n return `note: line ${problem.line} is not a signer: ${problem.message}`;\n}\n\n/** What doctor says of the Claude Code on `PATH`, which runs the guard's hooks only from the minimum on. */\nexport function describeClaudeRelease(check: ClaudeCodeCheck): string {\n // A version read from a package.json says so: nothing ran to tell it.\n const release = (found: { readonly version: string; readonly file?: string }): string =>\n `Claude Code ${found.version}${found.file === undefined ? '' : ` (as ${found.file} declares)`}`;\n switch (check.state) {\n case 'ok':\n return `${release(check)} runs the hooks, which need ${CLAUDE_CODE_MINIMUM} or later`;\n case 'outdated':\n return `${release(check)} is older than ${CLAUDE_CODE_MINIMUM}, which the hooks need: it ignores a hook's args and runs a bare node, which fails, and a PreToolUse hook that fails blocks nothing, so every write passes unguarded: update Claude Code, with claude update`;\n case 'unknown':\n return `whether Claude Code is ${CLAUDE_CODE_MINIMUM} or later, which the hooks need, cannot be told: ${check.reason}; an older release lets every write pass unguarded, so check claude --version where Claude Code runs`;\n }\n}\n\n/** Where git's pre-commit hook stands: git's path for it, shown from the root, and what is there. */\nexport type GitHookState =\n | { readonly state: 'runs' | 'absent' | 'other' | 'inert'; readonly file: string }\n | { readonly state: 'unknown'; readonly reason: string };\n\n/**\n * The advice for a missing hook, which init gives beside the file and doctor\n * with it. The hook is the guard for a write the agent's hooks never see,\n * one made through a shell (ADR-0005).\n */\nexport function missingGitHook(file: string | null): string {\n const where = file === null ? '' : ` (${file})`;\n return `no pre-commit hook runs spec-harness${where}: one refuses a commit that changes what the active brief protects, for any agent or none, a shell's writes included; run spec-harness init --git-hook --write to add it`;\n}\n\n/**\n * How to keep the files the rules live in from changing unread, on each\n * forge, as spec-core's ADR-0005 asks: the forge protects them, and CI checks\n * a change against the base branch's rules. GitLab Free has no Code Owners\n * approval, so there the person who merges is the reviewer.\n */\nexport const PROTECT_RULE_FILES =\n \"Have the forge protect this file, the ADRs, the tool configurations and the CI configuration, so no change to them reaches the base branch without a person, and check each change in CI against the base branch's rules: \" +\n 'on GitHub, CODEOWNERS and a protected branch; on GitLab Premium, Code Owners; on GitLab Free, a protected branch no one pushes to, merged by Maintainers, agents as Developers, and pipelines that must succeed. ' +\n \"spec-core's docs/adopting.md has the settings\";\n\n/**\n * The line to add to a hook the repository has of its own: node and the\n * script in the project's install, as Claude Code's hooks are run. git starts\n * a pre-commit hook at the top of the work tree it commits in, a linked\n * worktree's own, wherever the hook is kept, so the path is read from there.\n */\nexport const GIT_HOOK_LINE = `node ${BIN} hook git`;\n\n/**\n * The hook init writes. It is a POSIX sh script on every platform, since Git\n * for Windows runs a hook with the sh it ships. Where the project's install\n * is not in the work tree - a linked worktree nobody installed into, a\n * repository that shares its hooks with one that uses the harness - node\n * would end on a stack trace, so the hook says what is missing and where, and\n * the commit waits, as it did when npx found nothing to run.\n */\nexport const PRE_COMMIT = `#!/bin/sh\n# spec-harness: refuse a commit that changes what the active brief protects.\n# git runs this at the top of the work tree it commits in, a linked worktree's\n# own, wherever the hook is kept: the project's install is read from there.\nbin=${BIN}\nif [ ! -f \"$bin\" ]; then\n printf '%s\\\\n' \"spec-harness: $bin is not in $PWD, the work tree git commits in: install the project's dependencies there, as with npm ci, and commit again\" >&2\n exit 2\nfi\nexec node \"$bin\" hook git\n`;\n\n/**\n * What doctor and init add of a hook whose text runs the harness through\n * npx: the hook init wrote through 0.9.1, or the line it advised for a hook\n * of the repository's own. A note, never a failure: the hook guards as it\n * did. npx starts npm to start node, and npm 12 says on stderr what it runs,\n * twice a commit. A comment that names the command runs nothing, and neither\n * does another runner's line, which is the repository's to choose.\n */\nexport function gitHookNote(text: string): string | null {\n const npx = text.split('\\n').some((line) => /^[^#]*\\bnpx\\b.*\\bspec-harness\\s+hook\\s+git\\b/.test(line));\n if (!npx) return null;\n return `it runs spec-harness through npx, which starts npm to start node on every commit, and under npm 12 prints two \"npm notice run\" lines each time: to run it with node from the project's install, as Claude Code's hooks are, replace the npx command there with \"${GIT_HOOK_LINE}\"`;\n}\n\n/** What doctor says of git's pre-commit hook. */\nexport function describeGitHook(hook: GitHookState): string {\n switch (hook.state) {\n case 'runs':\n return `${hook.file} runs spec-harness`;\n case 'inert':\n return `${hook.file} runs spec-harness, but is not executable, so git skips it: chmod +x ${hook.file}`;\n case 'other':\n return `${hook.file} does not run spec-harness: add the line \"${GIT_HOOK_LINE}\" to it`;\n case 'absent':\n return missingGitHook(hook.file);\n case 'unknown':\n return `where git runs its hooks cannot be told: ${hook.reason}`;\n }\n}\n\n/** spec-graph's configuration files, in the order spec-graph reads them; then a \"spec-graph\" key in package.json. */\nexport const SPEC_GRAPH_CONFIGS: readonly string[] = ['.spec-graph.json', 'spec-graph.config.json'];\n\n/**\n * spec-graph's own patterns when its configuration names none, its\n * DEFAULT_PATTERNS as of 0.9.0. They decide only what init says, never what\n * it writes: `patterns` in the configuration replaces them, so writing the\n * briefs into it would freeze spec-graph's defaults into the file.\n */\nconst SPEC_GRAPH_DEFAULTS: readonly string[] = ['docs/**/*.md', 'doc/**/*.md', 'adr/**/*.md', 'rfcs/**/*.md', 'specs/**/*.md', '*.md'];\n\n/** Whether spec-graph, configured as `config` says, reads the briefs in the directory `briefs`. */\nexport function graphReadsBriefs(config: Json, briefs: string): boolean {\n const configured = Array.isArray(config['patterns']) ? config['patterns'].filter((pattern): pattern is string => typeof pattern === 'string') : [];\n const list = parseGlobList(configured.length > 0 ? configured : SPEC_GRAPH_DEFAULTS, { dialect: 'path', caseSensitive: true, backslash: 'separator' });\n return list.ok && list.list.match(`${briefs}/001_brief.md`);\n}\n\n/**\n * What the history entry does. spec-graph reads a brief whose status says\n * `archived` as a record already; the entry makes one a record whatever word\n * its status uses, or none - and it does anything only where spec-graph\n * reads the briefs, which its defaults do not in `briefs/`.\n */\nexport function describeGraph(archiveGlob: string, briefs: string, reads: boolean, added: boolean): string {\n const history = added ? `read ${archiveGlob} as history` : `${archiveGlob} is history`;\n if (reads) return `${history}: an archived brief is a record whatever its status says, so a live brief that depends on one is not a stale premise`;\n return `${history}, for when spec-graph reads the briefs; its patterns do not reach ${briefs}/, so it checks none of them now: add \"${briefs}/**/*.md\" to its \"patterns\" if it should`;\n}\n\n/** spec-graph's configuration with the archive read as history, or `null` when it already is. */\nexport function mergeSpecGraph(current: Json, archiveGlob: string): Json | null {\n const history = Array.isArray(current['historyPatterns']) ? (current['historyPatterns'] as unknown[]).filter((p): p is string => typeof p === 'string') : [];\n if (history.includes(archiveGlob)) return null;\n return { ...current, historyPatterns: [...history, archiveGlob] };\n}\n"]}
1
+ {"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What `init` writes into each tool's configuration, and what `doctor` says\n * of the result.\n *\n * Pure: `setup.ts` and the command line read the files and ask git, and\n * write what these return, so the core sweep holds these to the unit suite.\n * Each merge keeps what a person wrote and returns `null` when there is\n * nothing to add.\n */\n\nimport { isAbsolute } from 'node:path';\n\nimport { literal } from './literal.js';\nimport type { AllowedSigner } from './signers.js';\nimport type { HookLine } from './usage.js';\nimport { parseGlobList } from './vendor/spec-core/pattern/index.js';\nimport { CLAUDE_CODE_MINIMUM, type ClaudeCodeCheck } from './versions.js';\n\ntype Json = Record<string, unknown>;\n\nfunction isObject(value: unknown): value is Json {\n return typeof value === 'object' && value !== null;\n}\n\nfunction sameStrings(value: unknown, expected: readonly string[]): boolean {\n return Array.isArray(value) && value.length === expected.length && value.every((item, index) => item === expected[index]);\n}\n\n/**\n * The command line as the project installed it. Claude Code starts a plugin's\n * server in the plugin's own directory and a hook wherever the session\n * stands, so neither can rely on `npx` finding the project's install; and on\n * Windows a server's command is started without a shell, where `npx` is a\n * shim that cannot start at all. `node` and the script's path work\n * everywhere. git's hook runs the same script, from the top of the work\n * tree, where git starts it.\n */\nconst BIN = 'node_modules/@descent-vtt/spec-harness/bin/spec-harness.js';\n\n/** The project root, as Claude Code substitutes it in a hook's arguments and in a plugin server's. */\nexport const PROJECT_DIR = '${CLAUDE_PROJECT_DIR}';\n\n/**\n * The project root in a project's own `.mcp.json`. Claude Code expands it\n * there from its own environment, which does not hold it, so it needs a\n * default; `.` is right, since a project's server starts in the project.\n */\nexport const PROJECT_DIR_OR_HERE = '${CLAUDE_PROJECT_DIR:-.}';\n\nconst HOOK_MATCHER = 'Edit|Write|MultiEdit|NotebookEdit';\nconst GUARDED_EVENTS = ['PreToolUse', 'PostToolUse'];\nconst GUARD_ARGS: readonly string[] = Object.freeze([`${PROJECT_DIR}/${BIN}`, 'hook', 'claude']);\n\n/**\n * The guard as a Claude Code hook: exec form, `args` given, so the project's\n * path is one argument whatever it holds, and no shell is involved.\n */\nexport const GUARD_HOOK: Readonly<Json> = Object.freeze({ type: 'command', command: 'node', args: GUARD_ARGS, timeout: 60 });\n\n// bare-name: what 0.1 installed, an npx command found from wherever the\n// session stood. Read here so that init replaces it, and never run.\nconst LEGACY_HOOK = 'npx --no-install spec-harness hook claude';\nconst LEGACY_SERVER_ARGS = ['--no-install', 'spec-harness', 'mcp'];\n\nfunction isGuard(hook: unknown): boolean {\n return isObject(hook) && hook['command'] === 'node' && sameStrings(hook['args'], GUARD_ARGS);\n}\n\nfunction isLegacyGuard(hook: unknown): boolean {\n return isObject(hook) && hook['command'] === LEGACY_HOOK;\n}\n\nfunction hooksOf(group: unknown): unknown[] {\n return isObject(group) && Array.isArray(group['hooks']) ? group['hooks'] : [];\n}\n\n/** A group with 0.1's guard replaced where it stands, keeping its other fields; the group itself when it has none. */\nfunction upgraded(group: unknown): unknown {\n const hooks = hooksOf(group);\n if (!hooks.some(isLegacyGuard)) return group;\n return { ...(group as Json), hooks: hooks.map((hook) => (isLegacyGuard(hook) ? { ...(hook as Json), command: 'node', args: [...GUARD_ARGS] } : hook)) };\n}\n\n/** `.claude/settings.json`, or the plugin's hooks, with the guard hooks merged in; `null` when they are already there. */\nexport function mergeClaudeSettings(current: Json): Json | null {\n const hooks = isObject(current['hooks']) ? current['hooks'] : {};\n let changed = false;\n const next: Json = { ...hooks };\n for (const event of GUARDED_EVENTS) {\n const listed = hooks[event];\n const before: unknown[] = Array.isArray(listed) ? listed : [];\n const groups = before.map(upgraded);\n const replaced = groups.some((group, index) => group !== before[index]);\n const present = groups.some((group) => hooksOf(group).some(isGuard));\n if (!present) groups.push({ matcher: HOOK_MATCHER, hooks: [{ ...GUARD_HOOK, args: [...GUARD_ARGS] }] });\n if (replaced || !present) {\n next[event] = groups;\n changed = true;\n }\n }\n return changed ? { ...current, hooks: next } : null;\n}\n\n/** The server's entry, reading the project at `projectDir`: {@link PROJECT_DIR} for the plugin, {@link PROJECT_DIR_OR_HERE} for a project. */\nexport function mcpServer(projectDir: string): Json {\n return { command: 'node', args: [`${projectDir}/${BIN}`, 'mcp', '--root', projectDir] };\n}\n\nfunction isLegacyServer(server: unknown): boolean {\n return isObject(server) && server['command'] === 'npx' && sameStrings(server['args'], LEGACY_SERVER_ARGS);\n}\n\n/**\n * A project's `.mcp.json` with the harness's server merged in, or `null` when\n * one is registered. A server registered under the name some other way is\n * the person's, and stays; 0.1's npx entry is replaced, keeping its other\n * fields.\n */\nexport function mergeMcp(current: Json): Json | null {\n const servers = isObject(current['mcpServers']) ? current['mcpServers'] : {};\n const existing = servers['spec-harness'];\n if ('spec-harness' in servers && !isLegacyServer(existing)) return null;\n return { ...current, mcpServers: { ...servers, 'spec-harness': { ...(existing as Json | undefined), ...mcpServer(PROJECT_DIR_OR_HERE) } } };\n}\n\n/** A key of a file read as JSON; a file that is missing, or is not a JSON object, holds nothing. */\nfunction field(file: unknown, key: string): unknown {\n return isObject(file) ? file[key] : undefined;\n}\n\n/** Whether Claude Code settings hold the guard hooks, as this release or 0.1 wrote them. */\nexport function holdsGuard(settings: unknown): boolean {\n const hooks = field(settings, 'hooks');\n if (!isObject(hooks)) return false;\n return GUARDED_EVENTS.some((event) => {\n const groups = hooks[event];\n return Array.isArray(groups) && groups.some((group) => hooksOf(group).some((hook) => isGuard(hook) || isLegacyGuard(hook)));\n });\n}\n\n/** The harness's command on a line: its script, or its name, alone or at the end of a path. */\nfunction isLauncher(word: string): boolean {\n const name = word.split(/[\\\\/]/).at(-1);\n return name === 'spec-harness' || name === 'spec-harness.js';\n}\n\n/** Anything in a word that a shell reads before the program is handed it: a quote, a variable, an operator, a pattern. */\nconst SHELL_READS = /[^A-Za-z0-9_./:=@%+,-]/;\n\n/** A line of the harness's in Claude Code's settings: what follows its command there. */\nexport interface WrittenHook {\n /** The events whose hooks hold the line, of the two the guard answers. */\n readonly events: readonly string[];\n /** What follows the harness's command, word for word as written. */\n readonly words: readonly string[];\n /**\n * Whether a shell reads those words first, and can change them: a line\n * with no `args`, which Claude Code hands to a shell, holding a quote, a\n * variable or anything else a shell rewrites. What the hook is handed is\n * then the shell's to say. With `args`, Claude Code hands each word over\n * as it stands.\n */\n readonly shell: boolean;\n /** Whether the entry sets `onFailure` to `\"block\"`. */\n readonly blocks: boolean;\n}\n\nfunction writtenHook(hook: unknown): Omit<WrittenHook, 'events'> | null {\n if (!isObject(hook)) return null;\n const { command, args } = hook;\n if (typeof command !== 'string') return null;\n let line: string[];\n // Without arguments the line is a shell's, which splits it at its spaces:\n // at each one here, and what stands between two of them is no word.\n if (args === undefined) line = command.split(/\\s/).filter((word) => word !== '');\n else if (Array.isArray(args) && args.every((arg): arg is string => typeof arg === 'string')) line = [command, ...args];\n // Arguments that are not all text are no line Claude Code can start.\n else return null;\n const shell = args === undefined;\n // A quote around the path of the command is the shell's, dropped to know the command by.\n const at = line.findIndex((word) => isLauncher(shell ? word.replace(/[\"']/g, '') : word));\n if (at === -1) return null;\n const words = line.slice(at + 1);\n return { words, shell: shell && words.some((word) => SHELL_READS.test(word)), blocks: hook['onFailure'] === 'block' };\n}\n\n/**\n * Every line in Claude Code's settings that runs the harness, under the two\n * events the guard answers: a line that stands under both, as init writes\n * it, is one line. Whose line each is, the guard's or another command's, is\n * the reader's to say (usage.ts): this knows the harness by its command\n * alone.\n */\nexport function writtenHooks(settings: unknown): WrittenHook[] {\n const hooks = field(settings, 'hooks');\n if (!isObject(hooks)) return [];\n const found: { events: string[]; words: readonly string[]; shell: boolean; blocks: boolean }[] = [];\n for (const event of GUARDED_EVENTS) {\n const groups = hooks[event];\n if (!Array.isArray(groups)) continue;\n for (const hook of groups.flatMap(hooksOf)) {\n const written = writtenHook(hook);\n if (written === null) continue;\n const same = found.find((other) => other.shell === written.shell && other.blocks === written.blocks && sameStrings(other.words, written.words));\n if (same === undefined) found.push({ events: [event], ...written });\n else if (!same.events.includes(event)) same.events.push(event);\n }\n }\n return found;\n}\n\n/** What doctor reports of one line of Claude Code's hook. */\nexport interface HookReport {\n /** The settings file that holds it, as a person finds it. */\n readonly file: string;\n readonly events: readonly string[];\n /** What follows the harness's command, as written. */\n readonly line: readonly string[];\n /** `unknown`: a shell reads the line before the hook does, so what the hook is handed cannot be told. */\n readonly state: 'guards' | 'off' | 'unknown';\n readonly onFailure: 'block' | null;\n /** What there is to say of the line, or `null` for the line a release wrote, which guards. */\n readonly detail: string | null;\n /** What `onFailure` changes, where the entry sets it to `\"block\"`. */\n readonly note: string | null;\n}\n\n/**\n * The release of Claude Code that reads `onFailure` on a hook's entry, by its\n * hooks reference (https://code.claude.com/docs/en/hooks, read 2026-10-12).\n */\nconst ON_FAILURE_SINCE = '2.1.295';\n\n/** What `onFailure: \"block\"` changes for a line, named by `where` it stands, or `null` where the entry does not set it. */\nfunction describeOnFailure(where: string, blocks: boolean, off: boolean): string | null {\n if (!blocks) return null;\n const holds = `${where} sets onFailure to \"block\", so Claude Code ${ON_FAILURE_SINCE} or later holds a write when the hook fails`;\n if (off) return `${holds}, which with this line is every write, the one that mends the line among them: mend it by hand`;\n return `${holds}, where it lets the write go ahead without it: by its exit 1 for a question it cannot read, by not starting, or by running past its timeout`;\n}\n\n/**\n * The lines of Claude Code's hook in the settings files, each with what the\n * hook does with it. `read` is the hook's own reading of the words it is\n * handed (usage.ts), `null` for a line that is not its; the words are handed\n * to it as Claude Code hands them to the hook, each `${CLAUDE_PROJECT_DIR}`\n * filled with `project`.\n *\n * A line that guards and holds no more than a release wrote is reported\n * with nothing to say, as it always was. One with more on it is said as it\n * is, and fails nothing. One the hook cannot read, or whose root is no\n * directory, is a guard that is off: a problem, and doctor exits 1 for it.\n */\nexport function reportHooks(sources: readonly ClaudeSettings[], project: string, read: (words: readonly string[]) => HookLine | null): HookReport[] {\n return sources.flatMap(({ file, settings }) =>\n writtenHooks(settings).flatMap((hook): HookReport[] => {\n const handed = read(hook.words.map((word) => word.split(PROJECT_DIR).join(project)));\n if (handed === null) return [];\n const both = GUARDED_EVENTS.every((event) => hook.events.includes(event));\n // An empty word is shown as a shell spells one, where joined it would not show at all.\n // A line that is not under both events is under one.\n const where = `\"${hook.words.map((word) => (word === '' ? \"''\" : word)).join(' ')}\" in ${file}${both ? '' : ` (under ${hook.events[0]} alone)`}`;\n const written = { file, events: hook.events, line: hook.words, onFailure: hook.blocks ? 'block' : null } satisfies Partial<HookReport>;\n if (hook.shell) {\n const detail = `${where} is a shell's to read before the hook is handed it, so what the hook does with it cannot be told here: written with \"args\", each word reaches the hook as it stands`;\n return [{ ...written, state: 'unknown', detail, note: describeOnFailure(where, hook.blocks, false) }];\n }\n if (handed.state === 'off') {\n const detail = `${where}: ${handed.fault.wrong}, so the guard is off and no write is checked; ${handed.fault.mend}`;\n return [{ ...written, state: 'off', detail, note: describeOnFailure(where, hook.blocks, true) }];\n }\n // What a release wrote: its hook, and nothing after it.\n if (sameStrings(hook.words, ['hook', 'claude'])) return [{ ...written, state: 'guards', detail: null, note: describeOnFailure(where, hook.blocks, false) }];\n const root = handed.root === undefined ? '' : `, and its root, ${handed.root}, is a directory`;\n return [{ ...written, state: 'guards', detail: `${where} is more than init writes, and guards: the hook reads the line${root}`, note: describeOnFailure(where, hook.blocks, false) }];\n }),\n );\n}\n\n/** Whether a project's `.mcp.json` registers a server under the harness's name, however it runs it. */\nexport function registersServer(mcp: unknown): boolean {\n const servers = field(mcp, 'mcpServers');\n return isObject(servers) && 'spec-harness' in servers;\n}\n\n/** A Claude Code settings file, named as a person finds it, and what reading it gave: an object, or nothing usable. */\nexport interface ClaudeSettings {\n readonly file: string;\n readonly settings: unknown;\n}\n\n/** The plugin's id in `enabledPlugins`, and the settings file that turns it on. */\nexport interface EnabledPlugin {\n readonly id: string;\n readonly file: string;\n}\n\n/** `enabledPlugins` keys a plugin `<name>@<marketplace>`; this repository's marketplace lists the plugin as `spec-harness`. */\nconst PLUGIN_ID = /^spec-harness@[^@]+$/;\n\n/**\n * Whether Claude Code runs this package's plugin, from its settings files\n * given lowest precedence first: user, project, local. Claude Code merges\n * `enabledPlugins` key by key, so for each id the last file that sets it to\n * `true` or `false` decides. A plugin named `spec-harness` counts from any\n * marketplace, since another can list this repository. `null` when no file\n * turns it on.\n */\nexport function enabledPlugin(sources: readonly ClaudeSettings[]): EnabledPlugin | null {\n const decided = new Map<string, EnabledPlugin | null>();\n for (const { file, settings } of sources) {\n const plugins = field(settings, 'enabledPlugins');\n if (!isObject(plugins)) continue;\n for (const [id, on] of Object.entries(plugins)) {\n if (PLUGIN_ID.test(id) && typeof on === 'boolean') decided.set(id, on ? { id, file } : null);\n }\n }\n return [...decided.values()].find((entry) => entry !== null) ?? null;\n}\n\n/** Where the plugin is on, as a person would look it up. */\nfunction isOn(plugin: EnabledPlugin): string {\n return `the spec-harness plugin is on (${plugin.id} in ${plugin.file})`;\n}\n\n/** How to have init's entries in place of the plugin's, for this project alone. */\nfunction turnOff(plugin: EnabledPlugin): string {\n return `turn the plugin off for this project with claude plugin disable ${plugin.id} --scope local`;\n}\n\nconst PARTS = literal({\n hooks: { brings: 'the guard hooks', twice: 'every write is guarded twice', skipped: 'init writes none', entry: \"spec-harness's hooks\", them: 'them' },\n server: { brings: 'the server', twice: 'the server is registered twice', skipped: 'init registers none', entry: 'the spec-harness server', them: 'it' },\n});\n\n/**\n * What init says of a Claude Code file it leaves alone because the plugin is\n * on and brings the same `part`. A file that holds init's entry already is a\n * double install for the person to settle: init removes nothing, since the\n * file is the repository's and a clone without the plugin relies on it.\n */\nexport function describeSkipped(plugin: EnabledPlugin, part: 'hooks' | 'server', present: boolean): string {\n const words = PARTS[part];\n if (present) return `${isOn(plugin)} and brings ${words.brings} this file holds as well, so ${words.twice}: take ${words.entry} out of this file, or ${turnOff(plugin)}`;\n return `${isOn(plugin)} and brings ${words.brings}, so ${words.skipped}: with both, ${words.twice}. To have ${words.them} here instead, for every clone of the repository, ${turnOff(plugin)} and run init again`;\n}\n\n/** How Claude Code is wired to the harness: the plugin, init's entries, both or neither. */\nexport interface ClaudeCodeWiring {\n readonly plugin: EnabledPlugin | null;\n /** The settings files that hold the guard hooks. */\n readonly hooks: readonly string[];\n /** Each line of the hook in those files, with what the hook does with it. */\n readonly lines: readonly HookReport[];\n /** Whether `.mcp.json` registers the server. */\n readonly server: boolean;\n}\n\nexport type WiringState = 'plugin' | 'init' | 'both' | 'none';\n\n/** Which of the four the wiring is. `both` is a double install, and doctor fails it; either of init's entries counts. */\nexport function wiringState(wiring: Pick<ClaudeCodeWiring, 'plugin' | 'hooks' | 'server'>): WiringState {\n const init = wiring.hooks.length > 0 || wiring.server;\n if (wiring.plugin === null) return init ? 'init' : 'none';\n return init ? 'both' : 'plugin';\n}\n\n/** What `doctor` says of the wiring, and what to do when it is doubled or missing. */\nexport function describeClaudeCode(wiring: Pick<ClaudeCodeWiring, 'plugin' | 'hooks' | 'server'>): string {\n const { plugin } = wiring;\n const entries = [...wiring.hooks.map((file) => `the guard hooks in ${file}`), ...(wiring.server ? ['the server in .mcp.json'] : [])].join(', ');\n if (plugin === null) {\n if (entries === '') return 'neither the spec-harness plugin nor the hooks init writes, so Claude Code guards no write: run spec-harness init --write, or install the plugin';\n return `init's entries: ${entries}`;\n }\n if (entries === '') return `${isOn(plugin)} and brings the guard hooks and the server`;\n const twice = [...(wiring.hooks.length > 0 ? [PARTS.hooks.twice] : []), ...(wiring.server ? [PARTS.server.twice] : [])].join(' and ');\n return `${isOn(plugin)}, beside ${entries}: ${twice}. Keep one: take spec-harness's entries out of those files, or ${turnOff(plugin)}`;\n}\n\n/** The spec-brief plugin this package ships, as spec-brief's `plugins` names it. */\nexport const PLUGIN = '@descent-vtt/spec-harness/spec-brief-plugin';\n\n/** spec-brief's configuration files, in the order spec-brief looks for them in a directory. */\nexport const SPEC_BRIEF_CONFIGS: readonly string[] = ['.spec-brief.json', 'spec-brief.json'];\n\n/** The conditions an import meets in Node, its `defaultConditions`, and `default`, which every one meets. */\nconst IMPORT_CONDITIONS: readonly string[] = ['node', 'import', 'default'];\n\n/** A target Node refuses as invalid, which an array passes over and anything else stops at. */\nconst INVALID = Symbol('invalid target');\n\nfunction resolveTarget(target: unknown): string | null | undefined | typeof INVALID {\n if (typeof target === 'string') return target.startsWith('./') ? target : INVALID;\n if (Array.isArray(target)) {\n for (const item of target) {\n const resolved = resolveTarget(item);\n if (resolved !== undefined && resolved !== INVALID) return resolved;\n }\n return null;\n }\n if (typeof target !== 'object' || target === null) return target === null ? null : INVALID;\n for (const [condition, value] of Object.entries(target)) {\n if (!IMPORT_CONDITIONS.includes(condition)) continue;\n const resolved = resolveTarget(value);\n if (resolved !== undefined) return resolved;\n }\n return undefined;\n}\n\n/**\n * The file a package's `exports` target names for an import, as Node's\n * PACKAGE_TARGET_RESOLVE finds it, or `null` when it names none: a string\n * that starts with `./`; in an object, the target of the first key, in the\n * object's own order, that an import meets - `node`, `import` or\n * `default` - and that resolves to anything, a `null` included; in an\n * array, the first item that resolves, one Node refuses passed over. A\n * `null` target is not exported, and ends the search there, where a\n * condition an import does not meet lets it go on.\n */\nexport function exportedFile(target: unknown): string | null {\n const resolved = resolveTarget(target);\n return typeof resolved === 'string' ? resolved : null;\n}\n\n/**\n * Whether spec-brief's configuration loads the plugin, by name or as\n * `{ module, options }`. spec-brief also loads a plugin by a path, one that\n * starts with `.` or is absolute, read from the root; such a path is the\n * plugin when `isPluginFile` says it names the plugin's file, which only the\n * disk can tell. Without one, no path is: the default's `false` and an\n * `undefined` read the same in `some`, so that mutant is equivalent.\n */\nexport function loadsPlugin(config: Json, isPluginFile: (path: string) => boolean = () => false): boolean {\n const plugins = config['plugins'];\n const names = (specifier: unknown): boolean =>\n specifier === PLUGIN || (typeof specifier === 'string' && (specifier.startsWith('.') || isAbsolute(specifier)) && isPluginFile(specifier));\n return Array.isArray(plugins) && plugins.some((entry) => names(entry) || (isObject(entry) && names(entry['module'])));\n}\n\n/** Whether spec-brief's configuration names the base its archive measures a round from. */\nexport function measuresArchive(config: Json): boolean {\n const archiving = config['archiving'];\n return isObject(archiving) && typeof archiving['base'] === 'string';\n}\n\n/**\n * spec-brief's configuration with the plugin loaded and the archive measured\n * from `base`, or `null` when both already hold. spec-brief's archive\n * refuses a round that changed a protected file, and learns that a signed\n * ruling allows it only by asking the plugin (ADR-0006). It checks the file at\n * all only when it knows the base: without one it warns that the scope went\n * unmeasured and passes, so the gate is open until a base is named. A base a\n * person wrote is kept.\n */\nexport function mergeSpecBrief(current: Json, base: string | null = null, isPluginFile?: (path: string) => boolean): Json | null {\n const plugin = loadsPlugin(current, isPluginFile);\n const measure = base !== null && !measuresArchive(current);\n if (plugin && !measure) return null;\n const plugins: unknown[] = Array.isArray(current['plugins']) ? current['plugins'] : [];\n const archiving: Json = isObject(current['archiving']) ? current['archiving'] : {};\n return {\n ...current,\n ...(plugin ? {} : { plugins: [...plugins, PLUGIN] }),\n ...(measure ? { archiving: { ...archiving, base } } : {}),\n };\n}\n\n/** Whether spec-brief loads the plugin, as its configuration at the root says. */\nexport type PluginState =\n | { readonly kind: 'loaded' | 'not-loaded' | 'unreadable'; readonly file: string }\n | { readonly kind: 'unconfigured' };\n\n/** What a person reads about the plugin, and what to do when it is not loaded. */\nexport function describePlugin(state: PluginState): string {\n switch (state.kind) {\n case 'loaded':\n return `spec-brief loads spec-harness's plugin (${state.file}): its archive accepts a protected file a signed ruling allows`;\n case 'not-loaded':\n return `spec-brief does not load spec-harness's plugin, so its archive refuses a protected file whatever ruling is signed: add \"${PLUGIN}\" to \"plugins\" in ${state.file}, or run spec-harness init --write`;\n case 'unreadable':\n return `${state.file} cannot be read as JSON, so whether spec-brief loads spec-harness's plugin is unknown`;\n case 'unconfigured':\n return \"spec-brief has no configuration at the root, so it loads no plugin: spec-harness init --write writes one that loads spec-harness's\";\n }\n}\n\n/** What git says that tells a base apart. */\nexport interface BaseFacts {\n /** The remote's default branch as a local ref, such as `origin/main`, or `null`. */\n readonly remoteDefault: string | null;\n /** The branch checked out, or `null` on a detached head. */\n readonly branch: string | null;\n /** Every local branch. */\n readonly branches: readonly string[];\n}\n\n/** The base `init` names, and why; `base` is `null` when none can be told. */\nexport interface BaseChoice {\n readonly base: string | null;\n readonly detail: string;\n}\n\n/**\n * The base `init` writes into `.spec-harness.json`. Every ruling is verified\n * against the allowed signers on it, so it is named rather than left to be\n * found. The remote's default branch comes first, but git records it when\n * it clones, and before 2.48 not on a fetch: a repository made with `git\n * init` and pushed to a remote often has none. There the branch init runs on\n * is the base when it is `main` or `master`, or the only branch; otherwise\n * the person names it.\n */\nexport function chooseBase(facts: BaseFacts): BaseChoice {\n const { remoteDefault, branch } = facts;\n if (remoteDefault !== null) return { base: remoteDefault, detail: `rounds are measured from ${remoteDefault}, the remote's default branch` };\n const unrecorded = 'no remote records a default branch (refs/remotes/origin/HEAD)';\n if (branch !== null) {\n const conventional = branch === 'main' || branch === 'master';\n if (conventional || facts.branches.every((name) => name === branch)) {\n const which = conventional ? 'the branch init runs on' : 'the only branch';\n return { base: branch, detail: `rounds are measured from ${branch}, ${which}: ${unrecorded}; change \"base\" if rounds merge into another` };\n }\n }\n const where = branch === null ? 'HEAD is detached' : `${branch} is not main, master or the only branch`;\n return {\n base: null,\n detail: `no base can be told: ${unrecorded}, and ${where}; set \"base\" to the branch rounds merge into, or run git remote set-head origin --auto and init again`,\n };\n}\n\n/** Where a base came from: `--base`, the configuration, or the remote. */\nexport type BaseSource = 'flag' | 'config' | 'remote';\n\nconst SOURCES: Readonly<Record<BaseSource, string>> = { flag: '--base', config: '.spec-harness.json', remote: \"the remote's default branch\" };\n\n/** The base a command resolved, and how; or why there is none. */\nexport function describeBase(base: { readonly ref: string; readonly source: BaseSource; readonly mergeBase: string } | { readonly reason: string }): string {\n if ('reason' in base) return `none: ${base.reason}`;\n return `${base.ref} (${SOURCES[base.source]}), merge base ${base.mergeBase.slice(0, 12)}`;\n}\n\n/** Whether the allowed-signers file is on the base, where every ruling's signature is checked against it. */\nexport function describeSigners(file: string, base: string | null, onBase: boolean): string {\n if (base === null) return `${file}, read from the base, which could not be resolved: no ruling can count`;\n if (onBase) return `${file} is on ${base}`;\n return `${file} is not on ${base}, so no ruling can count: commit it there, one line per person: <email> namespaces=\"git\" <public key>`;\n}\n\n/**\n * The note doctor gives on the signers whose key is not FIDO2, or `null`\n * when every one's is. A note, never a failure: ADR-0006 accepts a key the\n * agent's account cannot read too, and a PIV or PKCS#11 hardware key is\n * written as a plain key.\n */\nexport function describeSignerKeys(signers: readonly Pick<AllowedSigner, 'line' | 'principals' | 'keyType'>[]): string | null {\n if (signers.length === 0) return null;\n const named = signers.map((signer) => `${signer.principals.join(',')} (${signer.keyType}, line ${signer.line})`).join(', ');\n return (\n `note: signers whose key is not a FIDO2 key: ${named}. ` +\n \"Where an agent runs as the person, ADR-0006 recommends a FIDO2 key, ssh-keygen -t ed25519-sk, whose signature needs a touch no process can supply, or a key the agent's account cannot read; \" +\n 'a PIV or PKCS#11 hardware key reads as a plain ssh-rsa or ecdsa line, so this is a note, not a failure'\n );\n}\n\n/** The note doctor gives on a line of the allowed-signers file that is not a signer. */\nexport function describeSignerProblem(problem: { readonly line: number; readonly message: string }): string {\n return `note: line ${problem.line} is not a signer: ${problem.message}`;\n}\n\n/** What doctor says of the Claude Code on `PATH`, which runs the guard's hooks only from the minimum on. */\nexport function describeClaudeRelease(check: ClaudeCodeCheck): string {\n // A version read from a package.json says so: nothing ran to tell it.\n const release = (found: { readonly version: string; readonly file?: string }): string =>\n `Claude Code ${found.version}${found.file === undefined ? '' : ` (as ${found.file} declares)`}`;\n switch (check.state) {\n case 'ok':\n return `${release(check)} runs the hooks, which need ${CLAUDE_CODE_MINIMUM} or later`;\n case 'outdated':\n return `${release(check)} is older than ${CLAUDE_CODE_MINIMUM}, which the hooks need: it ignores a hook's args and runs a bare node, which fails, and a PreToolUse hook that fails blocks nothing, so every write passes unguarded: update Claude Code, with claude update`;\n case 'unknown':\n return `whether Claude Code is ${CLAUDE_CODE_MINIMUM} or later, which the hooks need, cannot be told: ${check.reason}; an older release lets every write pass unguarded, so check claude --version where Claude Code runs`;\n }\n}\n\n/** Where git's pre-commit hook stands: git's path for it, shown from the root, and what is there. */\nexport type GitHookState =\n | { readonly state: 'runs' | 'absent' | 'other' | 'inert'; readonly file: string }\n | { readonly state: 'unknown'; readonly reason: string };\n\n/**\n * The advice for a missing hook, which init gives beside the file and doctor\n * with it. The hook is the guard for a write the agent's hooks never see,\n * one made through a shell (ADR-0005).\n */\nexport function missingGitHook(file: string | null): string {\n const where = file === null ? '' : ` (${file})`;\n return `no pre-commit hook runs spec-harness${where}: one refuses a commit that changes what the active brief protects, for any agent or none, a shell's writes included; run spec-harness init --git-hook --write to add it`;\n}\n\n/**\n * How to keep the files the rules live in from changing unread, on each\n * forge, as spec-core's ADR-0005 asks: the forge protects them, and CI checks\n * a change against the base branch's rules. GitLab Free has no Code Owners\n * approval, so there the person who merges is the reviewer.\n */\nexport const PROTECT_RULE_FILES =\n \"Have the forge protect this file, the ADRs, the tool configurations and the CI configuration, so no change to them reaches the base branch without a person, and check each change in CI against the base branch's rules: \" +\n 'on GitHub, CODEOWNERS and a protected branch; on GitLab Premium, Code Owners; on GitLab Free, a protected branch no one pushes to, merged by Maintainers, agents as Developers, and pipelines that must succeed. ' +\n \"spec-core's docs/adopting.md has the settings\";\n\n/**\n * The line to add to a hook the repository has of its own: node and the\n * script in the project's install, as Claude Code's hooks are run. git starts\n * a pre-commit hook at the top of the work tree it commits in, a linked\n * worktree's own, wherever the hook is kept, so the path is read from there.\n */\nexport const GIT_HOOK_LINE = `node ${BIN} hook git`;\n\n/**\n * The hook init writes. It is a POSIX sh script on every platform, since Git\n * for Windows runs a hook with the sh it ships. Where the project's install\n * is not in the work tree - a linked worktree nobody installed into, a\n * repository that shares its hooks with one that uses the harness - node\n * would end on a stack trace, so the hook says what is missing and where, and\n * the commit waits, as it did when npx found nothing to run.\n */\nexport const PRE_COMMIT = `#!/bin/sh\n# spec-harness: refuse a commit that changes what the active brief protects.\n# git runs this at the top of the work tree it commits in, a linked worktree's\n# own, wherever the hook is kept: the project's install is read from there.\nbin=${BIN}\nif [ ! -f \"$bin\" ]; then\n printf '%s\\\\n' \"spec-harness: $bin is not in $PWD, the work tree git commits in: install the project's dependencies there, as with npm ci, and commit again\" >&2\n exit 2\nfi\nexec node \"$bin\" hook git\n`;\n\n/**\n * What doctor and init add of a hook whose text runs the harness through\n * npx: the hook init wrote through 0.9.1, or the line it advised for a hook\n * of the repository's own. A note, never a failure: the hook guards as it\n * did. npx starts npm to start node, and npm 12 says on stderr what it runs,\n * twice a commit. A comment that names the command runs nothing, and neither\n * does another runner's line, which is the repository's to choose.\n */\nexport function gitHookNote(text: string): string | null {\n const npx = text.split('\\n').some((line) => /^[^#]*\\bnpx\\b.*\\bspec-harness\\s+hook\\s+git\\b/.test(line));\n if (!npx) return null;\n return `it runs spec-harness through npx, which starts npm to start node on every commit, and under npm 12 prints two \"npm notice run\" lines each time: to run it with node from the project's install, as Claude Code's hooks are, replace the npx command there with \"${GIT_HOOK_LINE}\"`;\n}\n\n/** What doctor says of git's pre-commit hook. */\nexport function describeGitHook(hook: GitHookState): string {\n switch (hook.state) {\n case 'runs':\n return `${hook.file} runs spec-harness`;\n case 'inert':\n return `${hook.file} runs spec-harness, but is not executable, so git skips it: chmod +x ${hook.file}`;\n case 'other':\n return `${hook.file} does not run spec-harness: add the line \"${GIT_HOOK_LINE}\" to it`;\n case 'absent':\n return missingGitHook(hook.file);\n case 'unknown':\n return `where git runs its hooks cannot be told: ${hook.reason}`;\n }\n}\n\n/** spec-graph's configuration files, in the order spec-graph reads them; then a \"spec-graph\" key in package.json. */\nexport const SPEC_GRAPH_CONFIGS: readonly string[] = ['.spec-graph.json', 'spec-graph.config.json'];\n\n/**\n * spec-graph's own patterns when its configuration names none, its\n * DEFAULT_PATTERNS as of 0.9.0. They decide only what init says, never what\n * it writes: `patterns` in the configuration replaces them, so writing the\n * briefs into it would freeze spec-graph's defaults into the file.\n */\nconst SPEC_GRAPH_DEFAULTS: readonly string[] = ['docs/**/*.md', 'doc/**/*.md', 'adr/**/*.md', 'rfcs/**/*.md', 'specs/**/*.md', '*.md'];\n\n/** Whether spec-graph, configured as `config` says, reads the briefs in the directory `briefs`. */\nexport function graphReadsBriefs(config: Json, briefs: string): boolean {\n const configured = Array.isArray(config['patterns']) ? config['patterns'].filter((pattern): pattern is string => typeof pattern === 'string') : [];\n const list = parseGlobList(configured.length > 0 ? configured : SPEC_GRAPH_DEFAULTS, { dialect: 'path', caseSensitive: true, backslash: 'separator' });\n return list.ok && list.list.match(`${briefs}/001_brief.md`);\n}\n\n/**\n * What the history entry does. spec-graph reads a brief whose status says\n * `archived` as a record already; the entry makes one a record whatever word\n * its status uses, or none - and it does anything only where spec-graph\n * reads the briefs, which its defaults do not in `briefs/`.\n */\nexport function describeGraph(archiveGlob: string, briefs: string, reads: boolean, added: boolean): string {\n const history = added ? `read ${archiveGlob} as history` : `${archiveGlob} is history`;\n if (reads) return `${history}: an archived brief is a record whatever its status says, so a live brief that depends on one is not a stale premise`;\n return `${history}, for when spec-graph reads the briefs; its patterns do not reach ${briefs}/, so it checks none of them now: add \"${briefs}/**/*.md\" to its \"patterns\" if it should`;\n}\n\n/** spec-graph's configuration with the archive read as history, or `null` when it already is. */\nexport function mergeSpecGraph(current: Json, archiveGlob: string): Json | null {\n const patterns = current['historyPatterns'];\n const history = Array.isArray(patterns) ? patterns.filter((p): p is string => typeof p === 'string') : [];\n if (history.includes(archiveGlob)) return null;\n return { ...current, historyPatterns: [...history, archiveGlob] };\n}\n"]}
package/dist/context.d.ts CHANGED
@@ -39,20 +39,27 @@ export interface RuleInForce {
39
39
  /**
40
40
  * The rules in force for the scope; `none` when spec-guard reads no
41
41
  * specification here, so none can be in force; `unavailable` when the rules
42
- * could not be read.
42
+ * could not be read; and the rules of the `rest` when spec patterns matched
43
+ * no file beside others that did, with those patterns, as written, as
44
+ * `unmatched`: a rule kept under one of them is in no list.
43
45
  */
44
46
  export type Rules = readonly RuleInForce[] | {
45
47
  readonly none: string;
46
48
  } | {
47
49
  readonly unavailable: string;
50
+ } | {
51
+ readonly rest: readonly RuleInForce[];
52
+ readonly unmatched: readonly string[];
48
53
  };
49
54
  /**
50
- * spec-guard's answer to `query --json`, read. It exits 2 both when no spec
51
- * file matched its patterns - a repository that keeps none, or keeps them
52
- * where it does not look - and when it could not read the specs it has; only
53
- * the first prints a document, one with no spec file in it, so that is how
54
- * the two are told apart. What it said on stderr is the reason for the second.
55
- * A document of another shape is refused (answers.ts).
55
+ * spec-guard's answer to `query --json`, read. It exits 2 when no spec file
56
+ * matched its patterns - a repository that keeps none, or keeps them where it
57
+ * does not look - when a pattern matched none beside others that did, and
58
+ * when it could not read the specs it has. The first two print a document
59
+ * and are told by it: one with no spec file in it, and, since spec-guard
60
+ * 0.22.0, one that names the patterns as `unmatchedSpecs` and still holds the
61
+ * rules of every file that did match. What it said on stderr is the reason
62
+ * for the third. A document of another shape is refused (answers.ts).
56
63
  */
57
64
  export declare function readRules(answer: {
58
65
  readonly code: number;
@@ -107,6 +114,8 @@ export interface ContextPacket {
107
114
  readonly unreadableProtections: readonly UnreadablePattern[];
108
115
  /** Paths of the rulings in force the guard cannot read, in the order the rulings are listed: each allows nothing. */
109
116
  readonly unreadableRulingPaths: readonly UnreadableRulingPath[];
117
+ /** spec-guard's spec patterns that matched no file beside others that did, as written: a rule kept under one is not among the rules listed. */
118
+ readonly unmatchedSpecs: readonly string[];
110
119
  }
111
120
  /**
112
121
  * A brief's title without the id it repeats, by the rules spec-brief reads
package/dist/context.js CHANGED
@@ -16,26 +16,37 @@
16
16
  * as one it can; and one a leading `/` roots, which names no path the guard
17
17
  * decides.
18
18
  */
19
- import { readQueryRules } from './answers.js';
19
+ import { readQueryRules, readQueryUnmatched } from './answers.js';
20
20
  import { sameId } from './branch.js';
21
21
  import { rooted, whyUnreadable } from './guard.js';
22
22
  /**
23
- * spec-guard's answer to `query --json`, read. It exits 2 both when no spec
24
- * file matched its patterns - a repository that keeps none, or keeps them
25
- * where it does not look - and when it could not read the specs it has; only
26
- * the first prints a document, one with no spec file in it, so that is how
27
- * the two are told apart. What it said on stderr is the reason for the second.
28
- * A document of another shape is refused (answers.ts).
23
+ * spec-guard's answer to `query --json`, read. It exits 2 when no spec file
24
+ * matched its patterns - a repository that keeps none, or keeps them where it
25
+ * does not look - when a pattern matched none beside others that did, and
26
+ * when it could not read the specs it has. The first two print a document
27
+ * and are told by it: one with no spec file in it, and, since spec-guard
28
+ * 0.22.0, one that names the patterns as `unmatchedSpecs` and still holds the
29
+ * rules of every file that did match. What it said on stderr is the reason
30
+ * for the third. A document of another shape is refused (answers.ts).
29
31
  */
30
32
  export function readRules(answer) {
31
- const document = (answer.document ?? {});
33
+ const printed = answer.document ?? {};
34
+ const document = printed;
32
35
  if (answer.code !== 0) {
33
36
  if (Array.isArray(document.specFiles) && document.specFiles.length === 0) {
34
37
  return { none: 'no spec file matched its patterns ("specs" in its configuration, docs/**/*.md by default)' };
35
38
  }
36
- const said = (answer.stderr.trim().split('\n')[0] ?? '').replace(/^spec-guard:\s*/, '');
37
- return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };
39
+ // Only a document that names such a pattern is an answer beside a
40
+ // refusal. Read as any other refusal, it took with it every rule the
41
+ // packet could have listed.
42
+ if (!Array.isArray(document.unmatchedSpecs) || document.unmatchedSpecs.length === 0) {
43
+ const said = (answer.stderr.trim().split('\n')[0] ?? '').replace(/^spec-guard:\s*/, '');
44
+ return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };
45
+ }
38
46
  }
47
+ // Read whatever the exit code: the patterns are what the document says of
48
+ // its own rules, and a list without them would pass for a whole one.
49
+ const unmatched = readQueryUnmatched(answer.document);
39
50
  const seen = new Set();
40
51
  const out = [];
41
52
  for (const rules of readQueryRules(answer.document)) {
@@ -47,7 +58,7 @@ export function readRules(answer) {
47
58
  out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason });
48
59
  }
49
60
  }
50
- return out;
61
+ return unmatched.length === 0 ? out : { rest: out, unmatched };
51
62
  }
52
63
  function list(items) {
53
64
  return items.map((item) => `- \`${item}\``).join('\n');
@@ -90,6 +101,15 @@ function rulesSection(rules, scopeUnread, scopeRooted) {
90
101
  const reason = rules.unavailable.endsWith('.') ? rules.unavailable.slice(0, -1) : rules.unavailable;
91
102
  return `The rules spec-guard holds this code to could not be read: ${reason}. Treat every ADR as binding until they can.`;
92
103
  }
104
+ if ('unmatched' in rules) {
105
+ // spec-guard refuses the question for such a pattern, and its answer
106
+ // still holds the rules of every spec file that matched: they are
107
+ // listed, under what the list lacks and why.
108
+ const one = rules.unmatched.length === 1;
109
+ const named = rules.unmatched.map((pattern) => `\`${pattern}\``).join(', ');
110
+ const lacks = `spec-guard's ${one ? 'pattern' : 'patterns'} ${named} matched no spec file, so a rule kept under ${one ? 'it' : 'them'} is not listed here; remove ${one ? 'the pattern' : 'them'} from spec-guard's \`specs\`.`;
111
+ return `${lacks}\n\n${rules.rest.length === 0 ? 'The spec files that did match hold no rule over this scope.' : grouped(rules.rest)}`;
112
+ }
93
113
  if (rules.length === 0) {
94
114
  // With no pattern of the scope readable, spec-guard was asked about no
95
115
  // path, so an empty answer is no answer: "no rule" would tell the agent
@@ -101,6 +121,10 @@ function rulesSection(rules, scopeUnread, scopeRooted) {
101
121
  ? "No pattern in `affectedFiles` puts a path in the scope: each is rooted at the filesystem's root or cannot be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed."
102
122
  : 'spec-guard holds no rule over this scope.';
103
123
  }
124
+ return grouped(rules);
125
+ }
126
+ /** The rules under the documents that state them; never given an empty list. */
127
+ function grouped(rules) {
104
128
  const byDocument = new Map();
105
129
  for (const rule of rules)
106
130
  byDocument.set(rule.document, [...(byDocument.get(rule.document) ?? []), rule]);
@@ -273,6 +297,7 @@ export function renderContext(input) {
273
297
  unreadableScope,
274
298
  unreadableProtections,
275
299
  unreadableRulingPaths,
300
+ unmatchedSpecs: 'unmatched' in input.rules ? input.rules.unmatched : [],
276
301
  };
277
302
  }
278
303
  //# sourceMappingURL=context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What an agent needs to start a round, and nothing it does not.\n *\n * The brief in full - it is the contract, and a summary of a contract is a\n * different contract. Then what the brief implies but does not say: the scope\n * as the guard will read it, the rulings already signed, the briefs it waits\n * on, the rules spec-guard holds the scope's code to, and the documents the\n * brief cites. The cited documents are the one part that can be large, so\n * they fill what the budget leaves, in the order the brief cites them, and\n * the rest are named rather than dropped silently. So is a document whose\n * front matter is never closed, whose status cannot be read, rather than\n * shown as one without a status, and a line of front matter that is not\n * `key: value`, passed over with any status written on it; and a pattern in\n * the scope, a protection or a ruling's path the guard cannot read, with\n * spec-core's reason and what the guard does without it, rather than listed\n * as one it can; and one a leading `/` roots, which names no path the guard\n * decides.\n */\n\nimport { readQueryRules } from './answers.js';\nimport { sameId } from './branch.js';\nimport type { UnreadableFrontMatterLine } from './document.js';\nimport { rooted, whyUnreadable } from './guard.js';\nimport type { BriefRow } from './types.js';\n\nexport interface CitedDocument {\n readonly path: string;\n readonly title: string | null;\n readonly status: string | null;\n /** `null` when the link does not resolve to a readable file. */\n readonly text: string | null;\n /** YAML front matter opened on line 1 and never closed, so no status was read from it. */\n readonly unclosedFrontMatter?: boolean | undefined;\n /** Lines of its front matter that are not `key: value`, passed over with any status written on them. */\n readonly unreadableFrontMatter?: readonly UnreadableFrontMatterLine[] | undefined;\n}\n\nexport interface RuleInForce {\n readonly document: string;\n readonly line: number;\n readonly kind: string;\n readonly description: string;\n readonly reason: string | null;\n}\n\n/**\n * The rules in force for the scope; `none` when spec-guard reads no\n * specification here, so none can be in force; `unavailable` when the rules\n * could not be read.\n */\nexport type Rules = readonly RuleInForce[] | { readonly none: string } | { readonly unavailable: string };\n\ninterface QueryDocument {\n readonly specFiles?: unknown;\n}\n\n/**\n * spec-guard's answer to `query --json`, read. It exits 2 both when no spec\n * file matched its patterns - a repository that keeps none, or keeps them\n * where it does not look - and when it could not read the specs it has; only\n * the first prints a document, one with no spec file in it, so that is how\n * the two are told apart. What it said on stderr is the reason for the second.\n * A document of another shape is refused (answers.ts).\n */\nexport function readRules(answer: { readonly code: number; readonly document: unknown; readonly stderr: string }): Rules {\n const document = (answer.document ?? {}) as QueryDocument;\n if (answer.code !== 0) {\n if (Array.isArray(document.specFiles) && document.specFiles.length === 0) {\n return { none: 'no spec file matched its patterns (\"specs\" in its configuration, docs/**/*.md by default)' };\n }\n const said = (answer.stderr.trim().split('\\n')[0] ?? '').replace(/^spec-guard:\\s*/, '');\n return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };\n }\n const seen = new Set<string>();\n const out: RuleInForce[] = [];\n for (const rules of readQueryRules(answer.document)) {\n for (const rule of rules) {\n const key = `${rule.document}:${rule.line}`;\n if (!rule.inForce || seen.has(key)) continue;\n seen.add(key);\n out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason });\n }\n }\n return out;\n}\n\nexport interface RulingInForce {\n readonly id: string;\n readonly paths: readonly string[];\n readonly signer: string;\n}\n\nexport interface ContextInput {\n readonly brief: BriefRow;\n readonly briefText: string;\n readonly dependencies: readonly BriefRow[];\n readonly cited: readonly CitedDocument[];\n /** The rules in force for the scope, none because spec-guard reads no specification, or why they could not be read. */\n readonly rules: Rules;\n readonly rulings: readonly RulingInForce[];\n readonly branch: string | null;\n readonly base: string | null;\n readonly budget: number;\n}\n\n/** A pattern in the brief's scope or protections that the guard cannot read, with spec-core's reason. */\nexport interface UnreadablePattern {\n readonly pattern: string;\n readonly reason: string;\n}\n\n/** A line of a cited document's front matter that is not `key: value`, with spec-core's reason. */\nexport interface UnreadableFrontMatter extends UnreadableFrontMatterLine {\n readonly path: string;\n}\n\n/** A path of a ruling in force that the guard cannot read, with the ruling's id and spec-core's reason. */\nexport interface UnreadableRulingPath extends UnreadablePattern {\n readonly ruling: string;\n}\n\nexport interface ContextPacket {\n readonly markdown: string;\n /** Cited documents included whole. */\n readonly included: readonly string[];\n /** Cited documents named but left out for the budget. */\n readonly omitted: readonly string[];\n /** Links in the brief that resolve to nothing. */\n readonly unresolved: readonly string[];\n /** Cited documents whose front matter opens on line 1 and is never closed, so no status was read from them. */\n readonly unclosedFrontMatter: readonly string[];\n /** Lines of cited documents' front matter that are not `key: value`, in the order the brief cites them: a status written on one was not read. */\n readonly unreadableFrontMatter: readonly UnreadableFrontMatter[];\n /** Patterns in `affectedFiles` the guard cannot read, in the brief's order: each puts no path in the scope. */\n readonly unreadableScope: readonly UnreadablePattern[];\n /** Patterns in `protectedFiles` the guard cannot read, in the brief's order: while one stands, the guard refuses every write but to the brief. */\n readonly unreadableProtections: readonly UnreadablePattern[];\n /** Paths of the rulings in force the guard cannot read, in the order the rulings are listed: each allows nothing. */\n readonly unreadableRulingPaths: readonly UnreadableRulingPath[];\n}\n\nfunction list(items: readonly string[]): string {\n return items.map((item) => `- \\`${item}\\``).join('\\n');\n}\n\n/** An unread line of front matter: where it is, as written, and spec-core's reason. */\nfunction unreadLine(found: UnreadableFrontMatter): string {\n return `- \\`${found.path}\\`, line ${found.line}, \\`${found.text}\\`: ${found.reason}`;\n}\n\n/**\n * Each pattern a line, as the guard reads it: as written, or with spec-core's\n * reason the guard cannot read it and what the guard does without it. The\n * guard's own compile decides, so the packet and the guard cannot disagree.\n * A pattern a leading `/` roots, or one of whose alternatives it roots, is\n * marked with `rootedNote`, and counted when every alternative is rooted.\n */\nfunction patternLines(\n patterns: readonly string[],\n consequence: string,\n rootedNote: { readonly whole: string; readonly part: string },\n): { lines: string[]; unreadable: UnreadablePattern[]; rootedWhole: number } {\n const unreadable: UnreadablePattern[] = [];\n let rootedWhole = 0;\n const lines = patterns.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n const root = rooted(pattern);\n if (root === null) return `- \\`${pattern}\\``;\n if (root === 'whole') rootedWhole += 1;\n return `- \\`${pattern}\\`, ${rootedNote[root]}`;\n }\n unreadable.push({ pattern, reason });\n return `- \\`${pattern}\\`, which the guard cannot read: ${reason}; ${consequence}`;\n });\n return { lines, unreadable, rootedWhole };\n}\n\nfunction rulesSection(rules: Rules, scopeUnread: boolean, scopeRooted: boolean): string {\n if ('none' in rules) return `spec-guard holds no rule over this scope: ${rules.none}.`;\n if ('unavailable' in rules) {\n // The reason is often a line a sibling said, and spec-guard ends its\n // refusals with a full stop of their own: the sentence is ended once.\n const reason = rules.unavailable.endsWith('.') ? rules.unavailable.slice(0, -1) : rules.unavailable;\n return `The rules spec-guard holds this code to could not be read: ${reason}. Treat every ADR as binding until they can.`;\n }\n if (rules.length === 0) {\n // With no pattern of the scope readable, spec-guard was asked about no\n // path, so an empty answer is no answer: \"no rule\" would tell the agent\n // the code it writes is unconstrained.\n if (scopeUnread) {\n return 'The scope could not be read: no pattern in `affectedFiles` can be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.';\n }\n return scopeRooted\n ? \"No pattern in `affectedFiles` puts a path in the scope: each is rooted at the filesystem's root or cannot be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.\"\n : 'spec-guard holds no rule over this scope.';\n }\n const byDocument = new Map<string, RuleInForce[]>();\n for (const rule of rules) byDocument.set(rule.document, [...(byDocument.get(rule.document) ?? []), rule]);\n const parts: string[] = [];\n for (const [document, found] of byDocument) {\n parts.push(`### ${document}`, '');\n for (const rule of found) parts.push(`- line ${rule.line}: ${rule.description}${rule.reason === null ? '' : ` - ${rule.reason}`}`);\n parts.push('');\n }\n return parts.join('\\n').trimEnd();\n}\n\n/**\n * A brief's title without the id it repeats, by the rules spec-brief reads\n * it with: the brief's own id, then an em dash, which `spec-brief new`\n * writes, an en dash, a full-width colon or a colon, spaced or not, or a\n * hyphen with a space on each side; `012 - Rotate tokens`, `012\\u2014Rotate\n * tokens` and `012\\uFF1ARotate tokens` read as `Rotate tokens`. A bare hyphen\n * is part of a word or a number, as in `001-2 migration`, and only the\n * brief's own id is taken off: `Fix - the login bug` is a title, and so is\n * `0010 \\u2014 x` on brief 001.\n */\nexport function titleOf(brief: Pick<BriefRow, 'id' | 'title'>): string | null {\n if (brief.title === null) return null;\n return brief.title.replace(/^\\s*([^\\s:\\uFF1A\\u2013\\u2014]+)(?:\\s*[:\\uFF1A\\u2013\\u2014]\\s*|\\s+-\\s+)(?=\\S)/, (whole, first: string) =>\n sameId(first, brief.id) ? '' : whole,\n );\n}\n\n/** Renders the packet, filling the budget with cited documents in the order the brief cites them. */\nexport function renderContext(input: ContextInput): ContextPacket {\n const { brief } = input;\n const title = titleOf(brief);\n const header = [\n `# Round ${brief.id}${title === null ? '' : `: ${title}`}`,\n '',\n [\n `Status ${brief.status ?? 'unknown'}`,\n brief.wave === null ? null : `wave ${brief.wave}`,\n input.branch === null ? null : `branch \\`${input.branch}\\``,\n input.base === null ? null : `measured from \\`${input.base}\\``,\n ]\n .filter((part) => part !== null)\n .join(' · '),\n '',\n ].join('\\n');\n\n const contract = ['## The contract', '', `\\`${brief.file}\\`, in full:`, '', '````markdown', input.briefText.trimEnd(), '````', ''].join('\\n');\n\n // The guard passes over a ruling's path it cannot read, so the path allows\n // nothing. The paths share a line, so the note is in parentheses, closed\n // before the next path.\n const unreadableRulingPaths: UnreadableRulingPath[] = [];\n const rulings =\n input.rulings.length === 0\n ? '- none'\n : input.rulings\n .map((ruling) => {\n const paths = ruling.paths.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n // A rooted path can be read, and names no path the guard decides.\n const root = rooted(pattern);\n if (root === 'whole') return `\\`${pattern}\\` (which a leading \\`/\\` roots at the filesystem's root; it allows nothing)`;\n if (root === 'part') return `\\`${pattern}\\` (an alternative of which a leading \\`/\\` roots at the filesystem's root; that alternative allows nothing)`;\n return `\\`${pattern}\\``;\n }\n unreadableRulingPaths.push({ ruling: ruling.id, pattern, reason });\n return `\\`${pattern}\\` (which the guard cannot read: ${reason}; it allows nothing)`;\n });\n return `- ${ruling.id}, signed by ${ruling.signer}: ${paths.join(', ')}`;\n })\n .join('\\n');\n // A rooted pattern can be read and puts no path in the scope: every path\n // the guard decides is repository-relative. spec-guard is not asked about it.\n const mayWrite = patternLines(brief.affectedFiles, 'it puts no path in the scope', {\n whole: \"which a leading `/` roots at the filesystem's root: it puts no path in the scope, and spec-guard is not asked about it\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative puts no path in the scope, and spec-guard is not asked about it\",\n });\n const unreadableScope = mayWrite.unreadable;\n // While a protection the guard cannot read stands, the guard refuses every\n // write, and no ruling waives it; a write to the brief is let through,\n // since that is where it is fixed. A rooted protection can be read, and\n // protects nothing: the file it was meant to protect is the round's to write.\n const mustNot = patternLines(brief.protectedFiles, 'until it is fixed, the guard refuses every write but to the brief', {\n whole: \"which a leading `/` roots at the filesystem's root: it protects no path\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative protects no path\",\n });\n const unreadableProtections = mustNot.unreadable;\n const scope = [\n '## Scope, as the guard reads it',\n '',\n 'May write:',\n mayWrite.lines.length === 0 ? '- nothing declared: every write is outside the scope' : mayWrite.lines.join('\\n'),\n '',\n 'Must not change without a ruling:',\n mustNot.lines.length === 0 ? '- nothing declared' : mustNot.lines.join('\\n'),\n '',\n 'Rulings in force:',\n rulings,\n '',\n ].join('\\n');\n\n const waiting = input.dependencies.map((dependency) => {\n const state = dependency.phase === 'archived' ? 'archived, done' : `still ${dependency.status ?? 'live'}`;\n const named = titleOf(dependency);\n return `- ${dependency.id}${named === null ? '' : ` ${named}`}: ${state}`;\n });\n const dependencies = ['## Depends on', '', waiting.length === 0 ? '- nothing' : waiting.join('\\n'), ''].join('\\n');\n\n const scopeUnread = mayWrite.lines.length > 0 && unreadableScope.length === mayWrite.lines.length;\n const scopeRooted = mayWrite.rootedWhole > 0 && unreadableScope.length + mayWrite.rootedWhole === mayWrite.lines.length;\n const rules = ['## Rules in force for this scope', '', rulesSection(input.rules, scopeUnread, scopeRooted), ''].join('\\n');\n\n const howTo = [\n '## How this round works',\n '',\n '- Before writing a file, ask whether the round may: `spec-harness guard <path>`, or the `check_path` tool.',\n '- A protected file is changed only under a ruling a person signs. If the round cannot be done without one, stop and ask: `spec-harness escalate --path <file> --reason <why>`, or the `request_escalation` tool. Do not work around it.',\n '- Every box in the brief ends ticked, or with a note under it that starts with a disposition (`**Delegated`, `**Accepted debt`, `**Rejected`, or what the repository configures).',\n '- When the work is done, run `spec-harness audit` and fix what it reports. The archive is a person\\'s decision, not the round\\'s.',\n '',\n ].join('\\n');\n\n const fixed = [header, contract, scope, dependencies, rules, howTo].join('\\n');\n const included: string[] = [];\n const omitted: string[] = [];\n const unresolved: string[] = [];\n const unclosed: string[] = [];\n const unreadableFrontMatter: UnreadableFrontMatter[] = [];\n const documents: string[] = [];\n let used = fixed.length;\n for (const cited of input.cited) {\n if (cited.text === null) {\n unresolved.push(cited.path);\n continue;\n }\n // Named whether or not the budget leaves room for it: the status is\n // unread either way, and the fix is the same.\n if (cited.unclosedFrontMatter === true) unclosed.push(cited.path);\n for (const line of cited.unreadableFrontMatter ?? []) unreadableFrontMatter.push({ path: cited.path, ...line });\n const heading = `### \\`${cited.path}\\`${cited.title === null ? '' : ` - ${cited.title}`}${cited.status === null ? '' : ` (${cited.status})`}`;\n const block = [heading, '', '````markdown', cited.text.trimEnd(), '````', ''].join('\\n');\n if (used + block.length > input.budget) {\n omitted.push(cited.path);\n continue;\n }\n used += block.length;\n included.push(cited.path);\n documents.push(block);\n }\n const citedSection = ['## Documents the brief cites', ''];\n if (documents.length === 0 && omitted.length === 0 && unresolved.length === 0) citedSection.push('None.', '');\n citedSection.push(...documents);\n if (omitted.length > 0) {\n citedSection.push(`Left out to stay within ${input.budget} characters; read them when the work reaches them:`, list(omitted), '');\n }\n if (unresolved.length > 0) citedSection.push('Cited but not found in the repository:', list(unresolved), '');\n if (unclosed.length > 0) {\n citedSection.push('Front matter opened on line 1 and never closed, so the status was not read; close the block with `---` on a line of its own:', list(unclosed), '');\n }\n // The last note: the section is trimmed at its end, so no blank line follows it.\n if (unreadableFrontMatter.length > 0) {\n citedSection.push(\n 'Front matter lines that are not `key: value` were not read, so a status written on one, if any, was not read either:',\n unreadableFrontMatter.map(unreadLine).join('\\n'),\n );\n }\n\n return {\n markdown: `${fixed}\\n${citedSection.join('\\n').trimEnd()}\\n`,\n included,\n omitted,\n unresolved,\n unclosedFrontMatter: unclosed,\n unreadableFrontMatter,\n unreadableScope,\n unreadableProtections,\n unreadableRulingPaths,\n };\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAyCnD;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,MAAsF;IAC9G,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,OAAwB,CAAC;IAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACtB,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzE,OAAO,EAAE,IAAI,EAAE,2FAA2F,EAAE,CAAC;QAC/G,CAAC;QACD,kEAAkE;QAClE,qEAAqE;QACrE,4BAA4B;QAC5B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,QAAQ,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpF,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;YACxF,OAAO,EAAE,WAAW,EAAE,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,qBAAqB,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,qBAAqB,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QACzH,CAAC;IACH,CAAC;IACD,0EAA0E;IAC1E,qEAAqE;IACrE,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YAC7C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9H,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC;AA2DD,SAAS,IAAI,CAAC,KAAwB;IACpC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC;AAED,uFAAuF;AACvF,SAAS,UAAU,CAAC,KAA4B;IAC9C,OAAO,OAAO,KAAK,CAAC,IAAI,YAAY,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;AACvF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CACnB,QAA2B,EAC3B,WAAmB,EACnB,UAA6D;IAE7D,MAAM,UAAU,GAAwB,EAAE,CAAC;IAC3C,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;YAC7B,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,OAAO,OAAO,IAAI,CAAC;YAC7C,IAAI,IAAI,KAAK,OAAO;gBAAE,WAAW,IAAI,CAAC,CAAC;YACvC,OAAO,OAAO,OAAO,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACrC,OAAO,OAAO,OAAO,oCAAoC,MAAM,KAAK,WAAW,EAAE,CAAC;IACpF,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAC5C,CAAC;AAED,SAAS,YAAY,CAAC,KAAY,EAAE,WAAoB,EAAE,WAAoB;IAC5E,IAAI,MAAM,IAAI,KAAK;QAAE,OAAO,6CAA6C,KAAK,CAAC,IAAI,GAAG,CAAC;IACvF,IAAI,aAAa,IAAI,KAAK,EAAE,CAAC;QAC3B,qEAAqE;QACrE,sEAAsE;QACtE,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC;QACpG,OAAO,8DAA8D,MAAM,8CAA8C,CAAC;IAC5H,CAAC;IACD,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;QACzB,qEAAqE;QACrE,kEAAkE;QAClE,6CAA6C;QAC7C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAG,gBAAgB,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,IAAI,KAAK,+CAA+C,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,+BAA+B,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,+BAA+B,CAAC;QAChO,OAAO,GAAG,KAAK,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,6DAA6D,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IACxI,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,uEAAuE;QACvE,wEAAwE;QACxE,uCAAuC;QACvC,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,iLAAiL,CAAC;QAC3L,CAAC;QACD,OAAO,WAAW;YAChB,CAAC,CAAC,4NAA4N;YAC9N,CAAC,CAAC,2CAA2C,CAAC;IAClD,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,gFAAgF;AAChF,SAAS,OAAO,CAAC,KAA6B;IAC5C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAyB,CAAC;IACpD,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;IAC1G,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,OAAO,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,OAAO,CAAC,KAAqC;IAC3D,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,8EAA8E,EAAE,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE,CAClI,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CACrC,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG;QACb,WAAW,KAAK,CAAC,EAAE,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,EAAE;QAC1D,EAAE;QACF;YACE,UAAU,KAAK,CAAC,MAAM,IAAI,SAAS,EAAE;YACrC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,IAAI,EAAE;YACjD,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,MAAM,IAAI;YAC3D,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,KAAK,CAAC,IAAI,IAAI;SAC/D;aACE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;aAC/B,IAAI,CAAC,KAAK,CAAC;QACd,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,QAAQ,GAAG,CAAC,iBAAiB,EAAE,EAAE,EAAE,KAAK,KAAK,CAAC,IAAI,cAAc,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE9I,2EAA2E;IAC3E,yEAAyE;IACzE,wBAAwB;IACxB,MAAM,qBAAqB,GAA2B,EAAE,CAAC;IACzD,MAAM,OAAO,GACX,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QACxB,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,KAAK,CAAC,OAAO;aACV,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE;YACd,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBACzC,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;gBACtC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;oBACpB,kEAAkE;oBAClE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;oBAC7B,IAAI,IAAI,KAAK,OAAO;wBAAE,OAAO,KAAK,OAAO,8EAA8E,CAAC;oBACxH,IAAI,IAAI,KAAK,MAAM;wBAAE,OAAO,KAAK,OAAO,8GAA8G,CAAC;oBACvJ,OAAO,KAAK,OAAO,IAAI,CAAC;gBAC1B,CAAC;gBACD,qBAAqB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;gBACnE,OAAO,KAAK,OAAO,oCAAoC,MAAM,sBAAsB,CAAC;YACtF,CAAC,CAAC,CAAC;YACH,OAAO,KAAK,MAAM,CAAC,EAAE,eAAe,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3E,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,yEAAyE;IACzE,8EAA8E;IAC9E,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,aAAa,EAAE,8BAA8B,EAAE;QACjF,KAAK,EAAE,wHAAwH;QAC/H,IAAI,EAAE,wJAAwJ;KAC/J,CAAC,CAAC;IACH,MAAM,eAAe,GAAG,QAAQ,CAAC,UAAU,CAAC;IAC5C,2EAA2E;IAC3E,uEAAuE;IACvE,wEAAwE;IACxE,8EAA8E;IAC9E,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,cAAc,EAAE,mEAAmE,EAAE;QACtH,KAAK,EAAE,yEAAyE;QAChF,IAAI,EAAE,yGAAyG;KAChH,CAAC,CAAC;IACH,MAAM,qBAAqB,GAAG,OAAO,CAAC,UAAU,CAAC;IACjD,MAAM,KAAK,GAAG;QACZ,iCAAiC;QACjC,EAAE;QACF,YAAY;QACZ,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,sDAAsD,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAChH,EAAE;QACF,mCAAmC;QACnC,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5E,EAAE;QACF,mBAAmB;QACnB,OAAO;QACP,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE;QACpD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,UAAU,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;QAC1G,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,KAAK,UAAU,CAAC,EAAE,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,KAAK,KAAK,EAAE,CAAC;IAC5E,CAAC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG,CAAC,eAAe,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEnH,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,eAAe,CAAC,MAAM,KAAK,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;IAClG,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,GAAG,CAAC,IAAI,eAAe,CAAC,MAAM,GAAG,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;IACxH,MAAM,KAAK,GAAG,CAAC,kCAAkC,EAAE,EAAE,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE3H,MAAM,KAAK,GAAG;QACZ,yBAAyB;QACzB,EAAE;QACF,4GAA4G;QAC5G,yOAAyO;QACzO,mLAAmL;QACnL,mIAAmI;QACnI,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,qBAAqB,GAA4B,EAAE,CAAC;IAC1D,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;IACxB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChC,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACxB,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5B,SAAS;QACX,CAAC;QACD,oEAAoE;QACpE,8CAA8C;QAC9C,IAAI,KAAK,CAAC,mBAAmB,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,qBAAqB,IAAI,EAAE;YAAE,qBAAqB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;QAChH,MAAM,OAAO,GAAG,SAAS,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;QAC9I,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzF,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;QACrB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,YAAY,GAAG,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC;IAC1D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC9G,YAAY,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAC;IAChC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,YAAY,CAAC,IAAI,CAAC,2BAA2B,KAAK,CAAC,MAAM,oDAAoD,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACpI,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,YAAY,CAAC,IAAI,CAAC,wCAAwC,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7G,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,YAAY,CAAC,IAAI,CAAC,8HAA8H,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IACxK,CAAC;IACD,iFAAiF;IACjF,IAAI,qBAAqB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrC,YAAY,CAAC,IAAI,CACf,sHAAsH,EACtH,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACjD,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,GAAG,KAAK,KAAK,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,IAAI;QAC5D,QAAQ;QACR,OAAO;QACP,UAAU;QACV,mBAAmB,EAAE,QAAQ;QAC7B,qBAAqB;QACrB,eAAe;QACf,qBAAqB;QACrB,qBAAqB;QACrB,cAAc,EAAE,WAAW,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE;KACxE,CAAC;AACJ,CAAC","sourcesContent":["/**\n * What an agent needs to start a round, and nothing it does not.\n *\n * The brief in full - it is the contract, and a summary of a contract is a\n * different contract. Then what the brief implies but does not say: the scope\n * as the guard will read it, the rulings already signed, the briefs it waits\n * on, the rules spec-guard holds the scope's code to, and the documents the\n * brief cites. The cited documents are the one part that can be large, so\n * they fill what the budget leaves, in the order the brief cites them, and\n * the rest are named rather than dropped silently. So is a document whose\n * front matter is never closed, whose status cannot be read, rather than\n * shown as one without a status, and a line of front matter that is not\n * `key: value`, passed over with any status written on it; and a pattern in\n * the scope, a protection or a ruling's path the guard cannot read, with\n * spec-core's reason and what the guard does without it, rather than listed\n * as one it can; and one a leading `/` roots, which names no path the guard\n * decides.\n */\n\nimport { readQueryRules, readQueryUnmatched } from './answers.js';\nimport { sameId } from './branch.js';\nimport type { UnreadableFrontMatterLine } from './document.js';\nimport { rooted, whyUnreadable } from './guard.js';\nimport type { BriefRow } from './types.js';\n\nexport interface CitedDocument {\n readonly path: string;\n readonly title: string | null;\n readonly status: string | null;\n /** `null` when the link does not resolve to a readable file. */\n readonly text: string | null;\n /** YAML front matter opened on line 1 and never closed, so no status was read from it. */\n readonly unclosedFrontMatter?: boolean | undefined;\n /** Lines of its front matter that are not `key: value`, passed over with any status written on them. */\n readonly unreadableFrontMatter?: readonly UnreadableFrontMatterLine[] | undefined;\n}\n\nexport interface RuleInForce {\n readonly document: string;\n readonly line: number;\n readonly kind: string;\n readonly description: string;\n readonly reason: string | null;\n}\n\n/**\n * The rules in force for the scope; `none` when spec-guard reads no\n * specification here, so none can be in force; `unavailable` when the rules\n * could not be read; and the rules of the `rest` when spec patterns matched\n * no file beside others that did, with those patterns, as written, as\n * `unmatched`: a rule kept under one of them is in no list.\n */\nexport type Rules =\n | readonly RuleInForce[]\n | { readonly none: string }\n | { readonly unavailable: string }\n | { readonly rest: readonly RuleInForce[]; readonly unmatched: readonly string[] };\n\ninterface QueryDocument {\n readonly specFiles?: unknown;\n readonly unmatchedSpecs?: unknown;\n}\n\n/**\n * spec-guard's answer to `query --json`, read. It exits 2 when no spec file\n * matched its patterns - a repository that keeps none, or keeps them where it\n * does not look - when a pattern matched none beside others that did, and\n * when it could not read the specs it has. The first two print a document\n * and are told by it: one with no spec file in it, and, since spec-guard\n * 0.22.0, one that names the patterns as `unmatchedSpecs` and still holds the\n * rules of every file that did match. What it said on stderr is the reason\n * for the third. A document of another shape is refused (answers.ts).\n */\nexport function readRules(answer: { readonly code: number; readonly document: unknown; readonly stderr: string }): Rules {\n const printed = answer.document ?? {};\n const document = printed as QueryDocument;\n if (answer.code !== 0) {\n if (Array.isArray(document.specFiles) && document.specFiles.length === 0) {\n return { none: 'no spec file matched its patterns (\"specs\" in its configuration, docs/**/*.md by default)' };\n }\n // Only a document that names such a pattern is an answer beside a\n // refusal. Read as any other refusal, it took with it every rule the\n // packet could have listed.\n if (!Array.isArray(document.unmatchedSpecs) || document.unmatchedSpecs.length === 0) {\n const said = (answer.stderr.trim().split('\\n')[0] ?? '').replace(/^spec-guard:\\s*/, '');\n return { unavailable: said === '' ? `spec-guard exited ${answer.code}` : `spec-guard exited ${answer.code}: ${said}` };\n }\n }\n // Read whatever the exit code: the patterns are what the document says of\n // its own rules, and a list without them would pass for a whole one.\n const unmatched = readQueryUnmatched(answer.document);\n const seen = new Set<string>();\n const out: RuleInForce[] = [];\n for (const rules of readQueryRules(answer.document)) {\n for (const rule of rules) {\n const key = `${rule.document}:${rule.line}`;\n if (!rule.inForce || seen.has(key)) continue;\n seen.add(key);\n out.push({ document: rule.document, line: rule.line, kind: rule.kind, description: rule.description, reason: rule.reason });\n }\n }\n return unmatched.length === 0 ? out : { rest: out, unmatched };\n}\n\nexport interface RulingInForce {\n readonly id: string;\n readonly paths: readonly string[];\n readonly signer: string;\n}\n\nexport interface ContextInput {\n readonly brief: BriefRow;\n readonly briefText: string;\n readonly dependencies: readonly BriefRow[];\n readonly cited: readonly CitedDocument[];\n /** The rules in force for the scope, none because spec-guard reads no specification, or why they could not be read. */\n readonly rules: Rules;\n readonly rulings: readonly RulingInForce[];\n readonly branch: string | null;\n readonly base: string | null;\n readonly budget: number;\n}\n\n/** A pattern in the brief's scope or protections that the guard cannot read, with spec-core's reason. */\nexport interface UnreadablePattern {\n readonly pattern: string;\n readonly reason: string;\n}\n\n/** A line of a cited document's front matter that is not `key: value`, with spec-core's reason. */\nexport interface UnreadableFrontMatter extends UnreadableFrontMatterLine {\n readonly path: string;\n}\n\n/** A path of a ruling in force that the guard cannot read, with the ruling's id and spec-core's reason. */\nexport interface UnreadableRulingPath extends UnreadablePattern {\n readonly ruling: string;\n}\n\nexport interface ContextPacket {\n readonly markdown: string;\n /** Cited documents included whole. */\n readonly included: readonly string[];\n /** Cited documents named but left out for the budget. */\n readonly omitted: readonly string[];\n /** Links in the brief that resolve to nothing. */\n readonly unresolved: readonly string[];\n /** Cited documents whose front matter opens on line 1 and is never closed, so no status was read from them. */\n readonly unclosedFrontMatter: readonly string[];\n /** Lines of cited documents' front matter that are not `key: value`, in the order the brief cites them: a status written on one was not read. */\n readonly unreadableFrontMatter: readonly UnreadableFrontMatter[];\n /** Patterns in `affectedFiles` the guard cannot read, in the brief's order: each puts no path in the scope. */\n readonly unreadableScope: readonly UnreadablePattern[];\n /** Patterns in `protectedFiles` the guard cannot read, in the brief's order: while one stands, the guard refuses every write but to the brief. */\n readonly unreadableProtections: readonly UnreadablePattern[];\n /** Paths of the rulings in force the guard cannot read, in the order the rulings are listed: each allows nothing. */\n readonly unreadableRulingPaths: readonly UnreadableRulingPath[];\n /** spec-guard's spec patterns that matched no file beside others that did, as written: a rule kept under one is not among the rules listed. */\n readonly unmatchedSpecs: readonly string[];\n}\n\nfunction list(items: readonly string[]): string {\n return items.map((item) => `- \\`${item}\\``).join('\\n');\n}\n\n/** An unread line of front matter: where it is, as written, and spec-core's reason. */\nfunction unreadLine(found: UnreadableFrontMatter): string {\n return `- \\`${found.path}\\`, line ${found.line}, \\`${found.text}\\`: ${found.reason}`;\n}\n\n/**\n * Each pattern a line, as the guard reads it: as written, or with spec-core's\n * reason the guard cannot read it and what the guard does without it. The\n * guard's own compile decides, so the packet and the guard cannot disagree.\n * A pattern a leading `/` roots, or one of whose alternatives it roots, is\n * marked with `rootedNote`, and counted when every alternative is rooted.\n */\nfunction patternLines(\n patterns: readonly string[],\n consequence: string,\n rootedNote: { readonly whole: string; readonly part: string },\n): { lines: string[]; unreadable: UnreadablePattern[]; rootedWhole: number } {\n const unreadable: UnreadablePattern[] = [];\n let rootedWhole = 0;\n const lines = patterns.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n const root = rooted(pattern);\n if (root === null) return `- \\`${pattern}\\``;\n if (root === 'whole') rootedWhole += 1;\n return `- \\`${pattern}\\`, ${rootedNote[root]}`;\n }\n unreadable.push({ pattern, reason });\n return `- \\`${pattern}\\`, which the guard cannot read: ${reason}; ${consequence}`;\n });\n return { lines, unreadable, rootedWhole };\n}\n\nfunction rulesSection(rules: Rules, scopeUnread: boolean, scopeRooted: boolean): string {\n if ('none' in rules) return `spec-guard holds no rule over this scope: ${rules.none}.`;\n if ('unavailable' in rules) {\n // The reason is often a line a sibling said, and spec-guard ends its\n // refusals with a full stop of their own: the sentence is ended once.\n const reason = rules.unavailable.endsWith('.') ? rules.unavailable.slice(0, -1) : rules.unavailable;\n return `The rules spec-guard holds this code to could not be read: ${reason}. Treat every ADR as binding until they can.`;\n }\n if ('unmatched' in rules) {\n // spec-guard refuses the question for such a pattern, and its answer\n // still holds the rules of every spec file that matched: they are\n // listed, under what the list lacks and why.\n const one = rules.unmatched.length === 1;\n const named = rules.unmatched.map((pattern) => `\\`${pattern}\\``).join(', ');\n const lacks = `spec-guard's ${one ? 'pattern' : 'patterns'} ${named} matched no spec file, so a rule kept under ${one ? 'it' : 'them'} is not listed here; remove ${one ? 'the pattern' : 'them'} from spec-guard's \\`specs\\`.`;\n return `${lacks}\\n\\n${rules.rest.length === 0 ? 'The spec files that did match hold no rule over this scope.' : grouped(rules.rest)}`;\n }\n if (rules.length === 0) {\n // With no pattern of the scope readable, spec-guard was asked about no\n // path, so an empty answer is no answer: \"no rule\" would tell the agent\n // the code it writes is unconstrained.\n if (scopeUnread) {\n return 'The scope could not be read: no pattern in `affectedFiles` can be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.';\n }\n return scopeRooted\n ? \"No pattern in `affectedFiles` puts a path in the scope: each is rooted at the filesystem's root or cannot be read, so spec-guard was not asked for the rules over it. Treat every ADR as binding until the scope is fixed.\"\n : 'spec-guard holds no rule over this scope.';\n }\n return grouped(rules);\n}\n\n/** The rules under the documents that state them; never given an empty list. */\nfunction grouped(rules: readonly RuleInForce[]): string {\n const byDocument = new Map<string, RuleInForce[]>();\n for (const rule of rules) byDocument.set(rule.document, [...(byDocument.get(rule.document) ?? []), rule]);\n const parts: string[] = [];\n for (const [document, found] of byDocument) {\n parts.push(`### ${document}`, '');\n for (const rule of found) parts.push(`- line ${rule.line}: ${rule.description}${rule.reason === null ? '' : ` - ${rule.reason}`}`);\n parts.push('');\n }\n return parts.join('\\n').trimEnd();\n}\n\n/**\n * A brief's title without the id it repeats, by the rules spec-brief reads\n * it with: the brief's own id, then an em dash, which `spec-brief new`\n * writes, an en dash, a full-width colon or a colon, spaced or not, or a\n * hyphen with a space on each side; `012 - Rotate tokens`, `012\\u2014Rotate\n * tokens` and `012\\uFF1ARotate tokens` read as `Rotate tokens`. A bare hyphen\n * is part of a word or a number, as in `001-2 migration`, and only the\n * brief's own id is taken off: `Fix - the login bug` is a title, and so is\n * `0010 \\u2014 x` on brief 001.\n */\nexport function titleOf(brief: Pick<BriefRow, 'id' | 'title'>): string | null {\n if (brief.title === null) return null;\n return brief.title.replace(/^\\s*([^\\s:\\uFF1A\\u2013\\u2014]+)(?:\\s*[:\\uFF1A\\u2013\\u2014]\\s*|\\s+-\\s+)(?=\\S)/, (whole, first: string) =>\n sameId(first, brief.id) ? '' : whole,\n );\n}\n\n/** Renders the packet, filling the budget with cited documents in the order the brief cites them. */\nexport function renderContext(input: ContextInput): ContextPacket {\n const { brief } = input;\n const title = titleOf(brief);\n const header = [\n `# Round ${brief.id}${title === null ? '' : `: ${title}`}`,\n '',\n [\n `Status ${brief.status ?? 'unknown'}`,\n brief.wave === null ? null : `wave ${brief.wave}`,\n input.branch === null ? null : `branch \\`${input.branch}\\``,\n input.base === null ? null : `measured from \\`${input.base}\\``,\n ]\n .filter((part) => part !== null)\n .join(' · '),\n '',\n ].join('\\n');\n\n const contract = ['## The contract', '', `\\`${brief.file}\\`, in full:`, '', '````markdown', input.briefText.trimEnd(), '````', ''].join('\\n');\n\n // The guard passes over a ruling's path it cannot read, so the path allows\n // nothing. The paths share a line, so the note is in parentheses, closed\n // before the next path.\n const unreadableRulingPaths: UnreadableRulingPath[] = [];\n const rulings =\n input.rulings.length === 0\n ? '- none'\n : input.rulings\n .map((ruling) => {\n const paths = ruling.paths.map((pattern) => {\n const reason = whyUnreadable(pattern);\n if (reason === null) {\n // A rooted path can be read, and names no path the guard decides.\n const root = rooted(pattern);\n if (root === 'whole') return `\\`${pattern}\\` (which a leading \\`/\\` roots at the filesystem's root; it allows nothing)`;\n if (root === 'part') return `\\`${pattern}\\` (an alternative of which a leading \\`/\\` roots at the filesystem's root; that alternative allows nothing)`;\n return `\\`${pattern}\\``;\n }\n unreadableRulingPaths.push({ ruling: ruling.id, pattern, reason });\n return `\\`${pattern}\\` (which the guard cannot read: ${reason}; it allows nothing)`;\n });\n return `- ${ruling.id}, signed by ${ruling.signer}: ${paths.join(', ')}`;\n })\n .join('\\n');\n // A rooted pattern can be read and puts no path in the scope: every path\n // the guard decides is repository-relative. spec-guard is not asked about it.\n const mayWrite = patternLines(brief.affectedFiles, 'it puts no path in the scope', {\n whole: \"which a leading `/` roots at the filesystem's root: it puts no path in the scope, and spec-guard is not asked about it\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative puts no path in the scope, and spec-guard is not asked about it\",\n });\n const unreadableScope = mayWrite.unreadable;\n // While a protection the guard cannot read stands, the guard refuses every\n // write, and no ruling waives it; a write to the brief is let through,\n // since that is where it is fixed. A rooted protection can be read, and\n // protects nothing: the file it was meant to protect is the round's to write.\n const mustNot = patternLines(brief.protectedFiles, 'until it is fixed, the guard refuses every write but to the brief', {\n whole: \"which a leading `/` roots at the filesystem's root: it protects no path\",\n part: \"an alternative of which a leading `/` roots at the filesystem's root: that alternative protects no path\",\n });\n const unreadableProtections = mustNot.unreadable;\n const scope = [\n '## Scope, as the guard reads it',\n '',\n 'May write:',\n mayWrite.lines.length === 0 ? '- nothing declared: every write is outside the scope' : mayWrite.lines.join('\\n'),\n '',\n 'Must not change without a ruling:',\n mustNot.lines.length === 0 ? '- nothing declared' : mustNot.lines.join('\\n'),\n '',\n 'Rulings in force:',\n rulings,\n '',\n ].join('\\n');\n\n const waiting = input.dependencies.map((dependency) => {\n const state = dependency.phase === 'archived' ? 'archived, done' : `still ${dependency.status ?? 'live'}`;\n const named = titleOf(dependency);\n return `- ${dependency.id}${named === null ? '' : ` ${named}`}: ${state}`;\n });\n const dependencies = ['## Depends on', '', waiting.length === 0 ? '- nothing' : waiting.join('\\n'), ''].join('\\n');\n\n const scopeUnread = mayWrite.lines.length > 0 && unreadableScope.length === mayWrite.lines.length;\n const scopeRooted = mayWrite.rootedWhole > 0 && unreadableScope.length + mayWrite.rootedWhole === mayWrite.lines.length;\n const rules = ['## Rules in force for this scope', '', rulesSection(input.rules, scopeUnread, scopeRooted), ''].join('\\n');\n\n const howTo = [\n '## How this round works',\n '',\n '- Before writing a file, ask whether the round may: `spec-harness guard <path>`, or the `check_path` tool.',\n '- A protected file is changed only under a ruling a person signs. If the round cannot be done without one, stop and ask: `spec-harness escalate --path <file> --reason <why>`, or the `request_escalation` tool. Do not work around it.',\n '- Every box in the brief ends ticked, or with a note under it that starts with a disposition (`**Delegated`, `**Accepted debt`, `**Rejected`, or what the repository configures).',\n '- When the work is done, run `spec-harness audit` and fix what it reports. The archive is a person\\'s decision, not the round\\'s.',\n '',\n ].join('\\n');\n\n const fixed = [header, contract, scope, dependencies, rules, howTo].join('\\n');\n const included: string[] = [];\n const omitted: string[] = [];\n const unresolved: string[] = [];\n const unclosed: string[] = [];\n const unreadableFrontMatter: UnreadableFrontMatter[] = [];\n const documents: string[] = [];\n let used = fixed.length;\n for (const cited of input.cited) {\n if (cited.text === null) {\n unresolved.push(cited.path);\n continue;\n }\n // Named whether or not the budget leaves room for it: the status is\n // unread either way, and the fix is the same.\n if (cited.unclosedFrontMatter === true) unclosed.push(cited.path);\n for (const line of cited.unreadableFrontMatter ?? []) unreadableFrontMatter.push({ path: cited.path, ...line });\n const heading = `### \\`${cited.path}\\`${cited.title === null ? '' : ` - ${cited.title}`}${cited.status === null ? '' : ` (${cited.status})`}`;\n const block = [heading, '', '````markdown', cited.text.trimEnd(), '````', ''].join('\\n');\n if (used + block.length > input.budget) {\n omitted.push(cited.path);\n continue;\n }\n used += block.length;\n included.push(cited.path);\n documents.push(block);\n }\n const citedSection = ['## Documents the brief cites', ''];\n if (documents.length === 0 && omitted.length === 0 && unresolved.length === 0) citedSection.push('None.', '');\n citedSection.push(...documents);\n if (omitted.length > 0) {\n citedSection.push(`Left out to stay within ${input.budget} characters; read them when the work reaches them:`, list(omitted), '');\n }\n if (unresolved.length > 0) citedSection.push('Cited but not found in the repository:', list(unresolved), '');\n if (unclosed.length > 0) {\n citedSection.push('Front matter opened on line 1 and never closed, so the status was not read; close the block with `---` on a line of its own:', list(unclosed), '');\n }\n // The last note: the section is trimmed at its end, so no blank line follows it.\n if (unreadableFrontMatter.length > 0) {\n citedSection.push(\n 'Front matter lines that are not `key: value` were not read, so a status written on one, if any, was not read either:',\n unreadableFrontMatter.map(unreadLine).join('\\n'),\n );\n }\n\n return {\n markdown: `${fixed}\\n${citedSection.join('\\n').trimEnd()}\\n`,\n included,\n omitted,\n unresolved,\n unclosedFrontMatter: unclosed,\n unreadableFrontMatter,\n unreadableScope,\n unreadableProtections,\n unreadableRulingPaths,\n unmatchedSpecs: 'unmatched' in input.rules ? input.rules.unmatched : [],\n };\n}\n"]}
package/dist/fs.js CHANGED
@@ -86,6 +86,7 @@ export async function readJsonObject(file) {
86
86
  if (!existsSync(file))
87
87
  return null;
88
88
  try {
89
+ // A Buffer parses as its text, so the encoding is equivalent to its mutant.
89
90
  const value = JSON.parse(await readFile(file, 'utf8'));
90
91
  return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : 'unreadable';
91
92
  }