/** * * The Test Tool Detection block is prepended to every sub-agent prompt * dispatched by peaks-loop (both the single-dispatch chokepoint in * `src/cli/commands/dispatch-commands.ts` and the DAG-dispatch chokepoint * in `src/services/code/dag-orchestrator.ts`). It tells the sub-agent * to read `package.json#scripts.test` first and use the project-local * runner — never `npx ` — so it cannot bypass the lockfile or * spawn a network-bound tool call. * * Why a static constant (not a per-call detector): * 1. Predictable byte budget — the block is part of PROMPT_LIMIT_BYTES * accounting (see dispatch-commands.ts line 113), and the size must * be stable across calls. * 2. I/O-free — dispatch is a hot path; reading package.json here would * add a filesystem hit per dispatch and run before the LLM has any * context to act on the result. * 3. LLM does runtime lookup — the block itself instructs the sub-agent * to introspect `package.json#scripts.test` (or `peaks test --json`) * at run time, so the LLM gets the resolved framework + argv with * its full context. The dispatch CLI just primes the instruction. * * Why every sub-agent gets the block (Karpathy #2 Simplicity First): * the rule is machine-injected, not a prompt ritual. The LLM cannot * "remove the redundancy" of a test-tool-detection instruction because * the dispatch CLI always prepends it. This is a guarantee, not a * suggestion. * * runner-table EXAMPLES were dropped — they were never rules, and * `package.json#scripts.test` is the source of truth at run time — while * the one missing real rule is stated inline: PB-5, i.e. repo-defined * `test` / `test:*` scripts are the human/LLM direct path and are NOT * gated by the scope rule. The soft fallback ("only as a last resort, ask * the user before assuming a runner") and the Windows-aware note on * `peaks test ` are RETAINED — they are quality guidance, not * examples. No role split remains, so every role receives a byte-identical * block. */ export declare const TEST_TOOL_DETECTION_BLOCK = "## Test Tool Detection (mandatory)\n\nRead `package.json#scripts.test` for the project's framework and use the project-local runner \u2014 do NOT invoke `npx `. If unsure, run `peaks test --json` first. Only as a last resort, ask the user before assuming a runner. `peaks test ` already resolves the local binary for you (Windows-aware).\n\n## Test Scope (mandatory)\n\nAny test command MUST be **scoped** to a single file or pattern \u2014 a bare `./node_modules/.bin/vitest run` is **not permitted** without the explicit opt-in token `PEAKS_FULL_TEST=1` (CI / release verification only, never routine slice verification). Nothing inspects the command you run: this is a rule you follow, not a gate that fires.\n\nA suite whose subject is the tree, not one file, is in scope for every slice, whatever the slice changed \u2014 it can go red for a change outside its own subtree. This repo's tier is `tests/unit/standards`, `tests/unit/lint` and the root of `tests/unit/` (`tests/unit/*.test.ts`): add all three to a slice's run, once per slice rather than per reviewer pass.\n\nRepo-defined `test` / `test:*` scripts are the human/LLM direct path and are NOT gated by this scope rule (PB-5)."; /** * Pure helper that returns the block. Exists as a function (not just an * exported constant) so future variants can take a runtime parameter * (e.g. projectRoot for context) without churning every call site. * * The whole-tree tier (2026-10-16) names `tests/unit/standards`, `tests/unit/lint` * and the root of `tests/unit/` (`tests/unit/*.test.ts`). A file-scoped run cannot * cover a suite whose subject is the tree: the push gate's area rule turns * `src/` into `tests/unit/`, and no guard directory has an area of its * own to be reached through. That gap is a HAND-run's alone — the classifier's * `subset()` unions the whole tier into every subset verdict, so the gate reaches * the tier on every diff it runs at all. The one diff it does NOT run is gate data * alone whose content did not move: that verdict selects 0 tests, root tier included. */ export declare function formatTestToolDetection(): string; /** * pure scope classifier. Returns a typed result so callers (the * dispatch prompt template, future test-runner bridges) can decide * whether the command is allowed, refused, or requires the explicit * opt-in. * * NO CALLER YET, stated so the next reader does not look for one: nothing * under `src/`, `tests/` or `scripts/` imports this. The dispatch block * states the scope as a rule the agent follows, and does not claim a run is * refused — wiring this into the `peaks test` CLI or a hook is a separate * change with its own blast radius, and it is the owner's call, not a * consequence of the rule text. * * - 'scoped' — the command names a file/pattern after `run` * - 'opt-in' — the user/LLM set PEAKS_FULL_TEST=1 to override * - 'refused' — bare `vitest run` with no path and no opt-in * - 'unsupported' — the command is not a vitest/jest/mocha run at * all (the static block does not gate it; the caller decides) * * Pure: no I/O, no process spawn, no env reads. The dispatcher * passes `env` explicitly so the test seam can assert behavior * without mutating process.env. */ export type TestScopeClassification = 'scoped' | 'opt-in' | 'refused' | 'unsupported'; export interface TestScopeDecision { readonly classification: TestScopeClassification; readonly reason: string; readonly extractedPath: string | null; } export declare function classifyTestCommand(command: string, env?: Readonly>): TestScopeDecision;