/** * Project-agnostic tool resolver for evidence-based verification (ADR-051). * * `cleo verify --evidence "tool:"` historically hardcoded a pnpm/biome/tsc * table inside `evidence.ts`, violating the package-boundary contract that * `@cleocode/core` MUST be agnostic to any specific project type. This module * replaces the hardcoded `TOOL_COMMANDS` table with a resolver that: * * 1. Maps a logical (canonical) tool name to a runnable command. * 2. Sources the command from `.cleo/project-context.json` when the user * has captured a project-specific override (`testing.command`, * `build.command`, …). * 3. For JavaScript projects, runs the project's own `package.json` script * of the canonical name through its package manager (T12633), so the * project's definition — and its `pre`/`post` hooks — is what gets run. * 4. Falls back to per-`primaryType` defaults (node, python, rust, go, …) * when neither of the above specifies the tool. * 5. Honours legacy aliases (`pnpm-test`, `tsc`, `biome`, …) for backwards * compatibility with already-stored evidence atoms. * * The resolved command always includes its `source` so audit and cache layers * can disambiguate "user-supplied" from "language-default" invocations. * * @task T1534 * @adr ADR-051 §3 * @adr ADR-061 */ import type { ProjectType } from '../store/project-detect.js'; /** * Canonical (project-agnostic) tool names accepted by `cleo verify * --evidence "tool:"`. * * Each canonical name maps to a project-specific command via * {@link resolveToolCommand}. Adding a new canonical tool requires: * * 1. Adding the name here. * 2. Adding a default for each {@link ProjectType} in `LANGUAGE_DEFAULTS`. * 3. Updating {@link checkGateEvidenceMinimum} if the new tool can satisfy * a verification gate. */ export declare const CANONICAL_TOOLS: readonly ["test", "build", "lint", "typecheck", "audit", "security-scan", "nexus-impact-full"]; /** * Type of a canonical (project-agnostic) tool name. * * @task T1534 */ export type CanonicalTool = (typeof CANONICAL_TOOLS)[number]; /** * Where a resolved command originated. Surfaced in cache keys and audit * trails so reviewers can distinguish project-supplied commands from CLEO * defaults. */ export type ResolutionSource = 'project-context' | 'package-script' | 'language-default' | 'legacy-alias'; /** * A resolved tool command ready for spawning. `cmd` and `args` are * shell-escaping-free — callers MUST pass them to `child_process.spawn` * (NOT a shell) to avoid injection. * * @task T1534 */ export interface ResolvedToolCommand { /** Canonical tool name (post-alias resolution). */ canonical: CanonicalTool; /** Human-friendly tool name for stdout / audit (often equal to `canonical`). */ displayName: string; /** Executable to spawn. */ cmd: string; /** Arguments. */ args: string[]; /** Origin of this command — used by cache keys. */ source: ResolutionSource; /** When `source === 'language-default'`, the `primaryType` that was matched. */ primaryType?: ProjectType; } /** * Result envelope returned by {@link resolveToolCommand}. * * @task T1534 */ export type ResolveToolResult = { ok: true; command: ResolvedToolCommand; } | { ok: false; reason: string; /** * `E_TOOL_COMMAND_INVALID`: the declared project-context command uses * shell syntax (or an open quote) it cannot run without a shell (T12718). */ codeName: 'E_TOOL_UNKNOWN' | 'E_TOOL_UNAVAILABLE' | 'E_TOOL_NOT_APPLICABLE' | 'E_TOOL_COMMAND_INVALID'; /** With `E_TOOL_COMMAND_INVALID`: the declared command, verbatim. */ rawCommand?: string; }; /** * Set of all valid `tool:` payloads — canonical names, legacy aliases, * and the diff-planned `test-affected`. Returned by {@link listValidToolNames} * for help / validation surfaces. * * @task T1534 * @task T12964 */ export declare function listValidToolNames(): string[]; interface ResolveOptions { /** * Override for `primaryType` lookup — set in tests where no real * `project-context.json` is present. */ primaryTypeOverride?: ProjectType; /** * The tree the resolved command will RUN in, when it differs from the * store root that holds `.cleo/project-context.json` (a worktree). The * project's `package.json` scripts and `tsconfig.json` are read from here, * because they describe the code under test. Defaults to `projectRoot`. * * @task T12633 */ executionRoot?: string; /** * The project context to read declared commands from, in place of the * working copy: evidence passes the default-branch copy when a change edits * it (`evidenceProjectContext`, T13779), so a change cannot relax its own * checks. `null` means none. */ projectContext?: Record | null; } /** * The canonical tool a `tool:` name means: itself, or the canonical tool a * legacy alias maps to (`tsc` → `typecheck`). * * @param toolName - Canonical name or legacy alias. * @returns The canonical tool, or null for an unknown name. * @task T13427 */ export declare function canonicalToolName(toolName: string): CanonicalTool | null; /** * Resolve a `tool:` evidence atom to a runnable command. * * Resolution order: * * 1. Map alias → canonical (e.g. `pnpm-test` → `test`). * 2. Check `.cleo/project-context.json` via {@link PROJECT_CONTEXT_KEY_MAP}: * - `test` → `testing.command` * - `build` → `build.command` * - `lint` → `lint.command` * - `typecheck` → `typecheck.command` * - `audit` → `audit.command` * - `security-scan` → `security-scan.command` * 3. Read `primaryType` from `project-context.json` (or detect from cwd). * 4. For node/bun projects, a `package.json` script of the canonical name * (`test`, `build`, `lint`, `typecheck`) runs as ` run ` * (T12633). * 5. Look up the canonical name in `LANGUAGE_DEFAULTS[primaryType]`. The * node `typecheck` default switches to build mode (`tsc -b`) for a * references-only root tsconfig (T12633). * 6. Verify the resolved binary exists on `PATH` (best-effort, non-fatal — * missing binaries are reported but do not block resolution; the * validator will surface the spawn error if the binary is truly absent). * * @param toolName - The user-supplied tool name (canonical or alias). * @param projectRoot - Absolute path to project root. * @param opts - Options for testing. * @returns Resolved command or a structured error. * * @example * ```ts * const r = resolveToolCommand('pnpm-test', '/repo'); * if (r.ok) { * // r.command.canonical === 'test' * // r.command.cmd === 'pnpm', r.command.args === ['run', 'test'] * // r.command.source === 'project-context' * } * ``` * * @task T1534 */ export declare function resolveToolCommand(toolName: string, projectRoot: string, opts?: ResolveOptions): ResolveToolResult; /** * Why each command a project-context declares cannot run as written (T14039): * every tool command of {@link PROJECT_CONTEXT_KEY_MAP} plus * `testing.affectedCommand`, split as {@link resolveToolCommand} splits them. * A command using shell syntax (`a && b`) is named with the operator and the * package-script remedy, so `tool:config-schema` refuses the config before a * tool run trips on it. * * @param ctx - A parsed project-context.json (anything else declares no command). * @returns One message per unrunnable command; empty when all can run. * @task T14039 */ export declare function projectContextCommandProblems(ctx: unknown): string[]; /** * Load project-context.json *without* relying on `loadProjectContext`'s * imports (escape-hatch for tests that need to inspect raw context). * * @internal */ export declare function readRawProjectContext(projectRoot: string): Record | null; /** * Parse the text of a project-context.json — from disk, or from a git * revision (`git show :…`, T13135) — into its raw object. * * @param raw - The file's text. * @returns The object, or `null` when it is not a JSON object. */ export declare function parseRawProjectContext(raw: string): Record | null; export {}; //# sourceMappingURL=tool-resolver.d.ts.map