import { basename } from "node:path"; import type { AppendFilesMode } from "./gate.types.ts"; const RECIPE_RUNNERS = new Set(["just", "make", "task", "mise", "rake"]); /** * hazard: `npm` was absent from the no-append set while `missing script:` sat in the resolution-failure patterns — * so the codebase already knew npm invokes a script and still appended file paths to it. Measured on a real install: * `npm test` with `appendFiles: "auto"` had changed files appended, and whether they reach the runner depends on the * package manager and its version. The harness cannot know, so it must not guess * ([/decisions/ad-033.md](/decisions/ad-033.md)). * * why: the reason is the recipe-runner reason. The argument is forwarded to somebody else's script, and what that * script does with a path is not something this process can reason about. `never` and `always` stay available for an * operator who knows their own command. */ const SCRIPT_RUNNERS = new Set(["npm", "yarn", "pnpm"]); /** * why: `npx`, `bunx` and `pnpm dlx` are transparent — they run the tool named next, and that tool is what decides * whether a file path narrows the run. Treating them as script runners would refuse to narrow `npx jest `, * which is the shape narrowing exists for. */ const TRANSPARENT_PREFIXES = new Set(["npx", "bunx", "dlx", "exec"]); const GLOB_CHARS = /[*?[\]]/; export type AppendVerdict = { appends: boolean; reason?: string }; const RESOLUTION_FAILURE_PATTERNS = [ /does not contain recipe/i, /no rule to make target/i, /unknown recipe/i, /missing script:/i, /task ".*" does not exist/i, /don't know how to build task/i, ]; /** why: resolves through a transparent prefix to the tool that actually receives the arguments. */ function effectiveCommand(command: string[]): string[] { let rest = command; while (rest.length > 1 && TRANSPARENT_PREFIXES.has(bareName(rest[0] as string))) { rest = rest.slice(1); } // why: `bun run