/** * Draft a `.commandmate/verify.yaml` from what a repository already declares * as "passing" (Issue #2061). * * The single implementation of that drafting step. `commandmate verify init` * calls it directly, `POST /api/worktrees/:id/verify/config` calls it for the * Verification pane's "draft from CI" button, and the `cmate-verify` Skill's * step 1 describes this same priority order in prose for environments that have * no CommandMate installed. Two scanners would drift, and a repository whose * gates differ depending on *which* surface drafted them is worse than one with * no drafter at all. * * The priority order is `.claude/skills/cmate-verify/SKILL.md` step 1's: * the CI workflow definitions first — they are the repository's existing answer * to "what has to be green" — then `package.json` scripts as a supplement for * the canonical names CI did not happen to run. * * **What is deliberately NOT drafted.** A gate is a command that may be re-run * any number of times with no outward effect, so publish / deploy / audit / * install steps are refused rather than emitted commented-out-and-forgotten, * and every refusal is reported with its reason: a draft that silently drops * half of CI reads as "CI only checks these four things". * * Output constraints — the rendered YAML has to be readable by BOTH runners: * the product loader (`verify-config.ts`, a real YAML parser) and the Skill's * standalone `verify-run.sh` (awk over a closed YAML subset: two-space indent, * one-line scalars, comments only on their own line). The subset is the * narrower of the two, so this renderer targets it. * * Server-only: reads from disk. Imported by the CLI through a relative path * (`tsconfig.cli.json` resets `paths` to `{}`), so it must not reach for * anything the CLI bundle cannot carry — `fs` / `path` / `yaml` only. * * @module lib/verification/verify-draft */ /** Where a drafted command was read from. */ export type DraftSourceKind = 'workflow' | 'npm-script'; /** Provenance of one drafted (or refused) command. */ export interface DraftSource { kind: DraftSourceKind; /** Repository-relative file the command was read from. */ file: string; /** Workflow job key (`kind === 'workflow'`). */ job?: string; /** Workflow step `name:`, or `step ` when the step declared none. */ step?: string; /** `scripts` key (`kind === 'npm-script'`). */ script?: string; } /** * Why a command found in CI is not a gate. * * Reported rather than dropped. The reader of a draft has to be able to tell * "CI runs nothing else" from "CI runs eight more things this tool refuses to * turn into gates", and only the second is true of a real repository. */ export type DraftExclusionReason = /** Dependency / toolchain installation; not a check. */ 'setup' /** Talks to a registry or the network, so its verdict is not about the tree. */ | 'network' /** Publishes, deploys or pushes — an outward effect a gate must never have. */ | 'release' /** Needs a container runtime the worktree is not guaranteed to have. */ | 'container' /** Rewrites the tree it is supposed to be judging (`lint --fix`, formatters). */ | 'mutating' /** Minutes-long browser suites; declared gates run on every verification. */ | 'long-running' /** A multi-line `run:` block, which the YAML subset cannot carry. */ | 'multi-line' /** Shell composition (`&&`, `|`, `;`, redirection) outside quotes. */ | 'multi-command' /** Uses `${{ }}` or a runner-only variable, so it cannot run in a worktree. */ | 'runner-specific' /** Contains both quote characters, so no one-line scalar can carry it. */ | 'unquotable' /** Its natural id collides with a built-in gate id. */ | 'reserved-id' /** Prints a message; nothing is judged. */ | 'not-a-check' /** A watcher (`vitest`, `--watch`): it never exits, so it can only time out. */ | 'interactive' /** An umbrella script whose narrower suites are already gates. */ | 'redundant'; /** Coarse kind of check, which decides ordering and the default timeout. */ export type DraftGateCategory = 'guard' | 'lint' | 'typecheck' | 'build' | 'test' | 'other'; /** One gate the draft proposes. */ export interface DraftGate { id: string; command: string; timeoutSec: number; category: DraftGateCategory; source: DraftSource; } /** One command the scan found and refused to turn into a gate. */ export interface DraftExclusion { command: string; reason: DraftExclusionReason; source: DraftSource; } /** Everything one scan of a repository produced. */ export interface VerifyDraft { gates: DraftGate[]; excluded: DraftExclusion[]; /** Repository-relative files that existed and were read. */ scanned: string[]; } /** * Scan a repository and propose gates. * * Never throws for a malformed workflow: a repository with one unparseable YAML * file still has a usable answer in the others, and a drafter that refuses * everything because of one file sends the reader back to writing the config by * hand — which is the state this exists to end. */ export declare function draftVerifyGates(repoPath: string): VerifyDraft; /** Refusal reason for a command, or null when it may become a gate. */ export declare function refuse(command: string): DraftExclusionReason | null; /** * Gate id for a command. * * Reads the *command*, not the CI step's `name:`. Step names are prose * ("Run ESLint", "Build Next.js") in whatever language the workflow is written * in, while `npm run lint` names the same gate in every repository — and the id * is what a task contract's `verify.gates` and `commandmate verify --gates` * have to spell. */ export declare function deriveGateId(command: string): string; /** * Quote character a one-line YAML scalar can carry this command in, or null. * * `verify-run.sh` strips one pair of outer quotes and performs no unescaping, * so the only spellings both parsers agree on are "wrapped in a quote character * the command does not itself contain". A backslash rules out double quotes for * the same reason: the YAML parser would unescape it and awk would not. */ export declare function quoteFor(command: string): '"' | "'" | null; /** * Render a draft as `.commandmate/verify.yaml` v1. * * Inside the Skill runner's YAML subset: two-space indent, one-line scalars, * comments only on lines of their own. Provenance is a comment above each gate * because the first question asked of a generated config is "where did this * come from, and may I delete it". */ export declare function renderVerifyYaml(draft: VerifyDraft): string; /** One line of provenance: which file, which job, which step. */ export declare function describeSource(source: DraftSource): string; /** Why {@link writeVerifyConfigDraft} declined to write. */ export type DraftRefusal = 'exists' | 'no-gates'; export interface DraftWriteResult { created: boolean; /** Absolute path of the config, whether or not it was written. */ configPath: string; /** The same path, repository-relative, for messages. */ relativePath: string; draft: VerifyDraft; /** Rendered YAML — the file's content when created, the proposal otherwise. */ yaml: string; refusedBecause?: DraftRefusal; } /** * What {@link writeVerifyConfigDraft} would do, without touching the disk. * * `created` is always false here — nothing was. `refusedBecause` carries the * same vocabulary the writer uses, so `commandmate verify init --dry-run` and a * real run report the same thing about the same repository instead of the * preview growing its own idea of when a draft is refused. */ export declare function planVerifyConfigDraft(repoPath: string): DraftWriteResult; /** * Draft and write `/.commandmate/verify.yaml`. * * **Never overwrites.** An existing config is the repository's own judgement of * what passing means, usually with the reasoning for each gate in comments * beside it; regenerating over it would replace a considered file with a guess * and destroy the reasoning, and no `--force` is offered because "I meant to * throw it away" is spelled by deleting the file. */ export declare function writeVerifyConfigDraft(repoPath: string): DraftWriteResult; //# sourceMappingURL=verify-draft.d.ts.map