/** * DERIVING which surface a run went by — the attribution half of the * `CHECK_COUNT` channel (see `check-count.ts`). * * ## What was broken * * "Is this skill tested?" was answered by a file name. Colocation says a file * named `foo.eval.mjs` sits in `foo/`; it says nothing about whether that file * ran, and an EMPTY one counts. Reproduce it in ten seconds: `touch * .claude/skills/foo/foo.eval.mjs` and watch the untested count drop by one. * * Every mature coverage tool answers from EXECUTION — `go test -cover`, * coverage.py, nyc, tarpaulin — and uses the name only to find the file to run. * We cannot run a skill without a model, so the name stays as a fallback; but * when a run DID happen, its own record must outrank the fallback. * * ## Why the machinery derives it instead of the author declaring it * * A `surface:` field on a spec would be the retired `vigiles:covers` marker with * extra steps: a claim about a test, written by whoever wrote the test, checked * by nobody. Every tier already knows what it was pointed at, so the attribution * is read back out of the run itself: * * | tier | what it goes by | * |---|---| * | `runScript` / `runHook` | the COMMAND LINE — it names a program file | * | `runHarnessTest` / eval / trigger-rate | the TRANSCRIPT — what actually fired | * * 🔴 THE TRANSCRIPT, NOT THE INSTALL SET, and this is the load-bearing choice. * A trigger-rate run installs many skills so the one under test competes for * selection; crediting all of them would credit a skill for LOSING. What fired * is one skill, and that is the one the run measured. * * Both halves are pure and exported so they are testable without a process: the * recording wrappers are one line each on top. * * ## Why this file PARSES at all — asked properly 2026-08-12, after five rounds * * This module has produced findings in five separate review rounds, every one the * same shape: it infers what executed by parsing an arbitrary command string, and * a string's grammar is unbounded. The obvious question is whether the EXECUTOR * could just report the path instead. Measured, not assumed: * * - **The subprocess tier structurally cannot.** `runHook`/`runScript` take a * COMMAND STRING and hand it to `spawnSync(cmd, { shell: true })`. vigiles * never resolves a path — `sh -c` does — so there is nothing to report back. * And the corpus confirms there is often no path to report at all: this repo's * own examples pass inline program TEXT * (`CMD=$(cat | jq -r …); case "$CMD" in …`, `node -e ''`), where no * file is named by anyone. That API contract is the feature — it is how you * test a hook you did not write, verbatim as its plugin ships it. * * - **The in-process tier CAN — and since 2026-08-13 it DOES.** ⚠️ This bullet * used to read *"`harness-assert.ts` / `load-hook.ts` contain ZERO * `recordSurfaceProbe` calls — the tier where attribution needs no parsing at * all is the tier that attributes nothing"*, and that is no longer true: * `runCountedHookProgram` in harness-assert.ts records a `command` probe for a * hook it is about to evaluate, when `loadHook` knew the file. Verified by * count, not by memory — `harness-assert.ts` has the call, `load-hook.ts` * still does not, and deliberately so (loading a hook is not running it; see * that function's note). Left corrected rather than deleted because a header * that advertises a gap somebody already closed sends the next reader to build * a second recorder. * * It did not retire this parser, as predicted: it ADDS a source, and * `runHook(command)` is still public. * * So parsing stays, and the lesson taken instead is about DIRECTION. The scan was * a deny-list — skip what we recognise, attribute what is left — so every gap in * every table produced a FALSE GRANT: a claim that a surface was tested when * nothing ran it. Where the grammar cannot be closed, the rule is now inverted: * attribute only what is positively recognised, and abstain otherwise * ({@link classifyCluster}, {@link runProgramRef}). A missed probe costs one * coverage line; a false grant costs the one thing this whole tier exists to * protect. */ import { type SurfaceProbe } from "./check-count.js"; /** * The program files a command line EXECUTES. * * 🔴 THE WORD IS NOT THE POSITION, and this used to be a scan for script-looking * TOKENS anywhere in the command (and anywhere in the env). Which means a * passing harness that runs `cat hooks/pre-edit.sh`, `cp hooks/pre-edit.sh …` or * `grep -n foo hooks/pre-edit.sh` minted an execution-tier coverage record for a * hook that never ran — the same substitution this whole tier exists to remove, * activity taken for the property, only now manufactured by the attribution half * instead of the recording half. So the command is PARSED (`leafArgvSource`, * mvdan-sh) and only these positions count: * * - the leaf HEAD, when it is itself a script (`./hooks/x.sh`, `/abs/x.mjs`); * - the first non-flag operand of an {@link INTERPRETERS} head (`bash x.sh`); * - the word after our own {@link RUN_PROGRAM_VERB}. * * AST-backed, so a leaf nested behind `&&`, a pipeline or a subshell is still * found and `cd /repo && bash hooks/x.sh` attributes the hook — which the old * token scan also did, but for the wrong reason. * * The env is still consulted, because the documented `runHook` idiom passes the * path through one: `runHook('"$GUARD"', event, { env: { GUARD: guardPath } })`. * It is now an EXPANSION, not a scan: `$GUARD` is substituted into the word that * references it and then classified by position, so an env entry the command * never mentions (a fixture path, a temp dir) attributes nothing. * * Deliberately not resolved to absolute paths here: the process's cwd is the * test's, not the repo's, and inventing a root would manufacture a match. The * runner resolves refs against real discovered surfaces or drops them. */ export declare function commandRefs(command: string, env?: Record): string[]; /** * The files that a `hook-runtime run-program` command needs to exist: the hook * file it names, and the runtime script when the command starts it by path * (`node "$DIR/node_modules/vigiles/dist/cli.js" hook-runtime run-program …`). * * Narrower than {@link commandRefs}, which lists every program a command runs. * This lists only what a compiled hook depends on, because the start-of-session * check (`hook-check.ts`) reports these files as missing. `bash scripts/build.sh` * is not one of them; reporting it missing in a fresh clone would be a false * alarm that `npm install` does not fix. * * Like `commandRefs`, it only counts a command that really starts vigiles, so * `echo vigiles hook-runtime run-program x.mjs` names nothing. */ export declare function runProgramFiles(command: string): string[]; /** The transcript shape this module reads — structural, so it imports no tier. */ export interface ProbeableTrace { readonly toolCalls: readonly { readonly name: string; readonly input?: unknown; readonly isError?: boolean; }[]; readonly hooks?: readonly { readonly name: string; }[]; } /** * The surfaces a run's transcript shows ACTIVATING — resolved `Skill` calls and * reporting hooks. * * An errored `Skill` call is excluded, matching `skillResolved`/`whichSkillsFired`: * the tool was reached and the skill was not. Counting it would make "the skill * is broken" indistinguishable from "the skill ran". */ export declare function traceRefs(trace: ProbeableTrace): SurfaceProbe[]; /** Record what a command line named. One line, so every tier can afford it. */ export declare function probeCommand(command: string, env?: Record): void; /** Record what a run's transcript shows firing. */ export declare function probeTrace(trace: ProbeableTrace): void; //# sourceMappingURL=coverage-probe.d.ts.map