/** * Single source of truth for the step-contract object + writer shared by both * orchestrators (drift-plan R3). Before this module existed, audit-code * (`src/cli/steps.ts`) and remediate-code (`src/steps/stepWriter.ts`) each had * their own `writeCurrentStep` with REAL behavioural drift: remediate * normalized every host-facing path to forward slashes via `toPromptPathToken`, * but audit wrote raw Windows paths (backslashes), which break in the bash-like * shells a host may use to run the step's commands. This module owns: * * - the `steps/` filenames (`current-step.json`, `current-prompt.md`), via the * shared `stepsDir` helper; * - `mkdir` of the steps dir, the `current-prompt.md` write, and the atomic * `writeJsonFile` of `current-step.json`; * - the `toPromptPathToken` normalization of ALL host-facing path fields * (`prompt_path`, `repo_root`, `artifacts_dir`, and every value in * `artifact_paths`); * - the "computed canonical paths win" merge guard: caller-supplied * `artifact_paths` are merged FIRST so the canonical `current_step` / * `current_prompt` entries always overwrite them — a caller (or step config) * must never be able to repoint a host at a different current-step.json or * current-prompt.md. * * Each orchestrator extends `BaseStepContract` with its own `step_kind` enum * and optional fields (progress, allowed_mcp_tools, access, ...) and calls * `writeStepContract` with its concrete types; neither writes raw paths. */ /** * Path of `current-step.json`. With no `agentId` this is the SHARED * `steps/current-step.json` "latest" slot (single-agent default + back-compat); * with an `agentId` it is the per-agent `steps//current-step.json` slot * (cooperative multi-agent, spec/multi-ide-concurrent-runs-design.md). Each * `writeStepContract` returns the per-agent path so a concurrent peer never reads * another peer's prompt from a clobbered shared file. */ export declare function currentStepPath(artifactsDir: string, agentId?: string): string; /** Path of `current-prompt.md` (shared with no `agentId`, per-agent with one). */ export declare function currentPromptPath(artifactsDir: string, agentId?: string): string; export declare function processAgentId(): string; /** * Fields every step contract shares. Orchestrators extend this with a narrowed * `step_kind` (their own enum), a narrowed `contract_version` literal, and any * orchestrator-specific optional fields. * * `TStepKind` is the orchestrator's step-kind enum; `TArtifactValue` is whether * artifact path values may be `null` (audit allows null entries for not-yet * materialized artifacts; remediate does not). */ export interface BaseStepContract { contract_version: string; step_kind: TStepKind; status: string; prompt_path: string; run_id: string | null; allowed_commands: string[]; stop_condition: string; repo_root: string; artifacts_dir: string; artifact_paths: Record; } /** * Input to {@link writeStepContract}. `contractVersion`, `stepKind`, `status`, * `runId`, `allowedCommands`, `stopCondition`, `repoRoot`, `artifactsDir`, and * `prompt` map onto the matching base contract fields. `artifactPaths` is the * caller's extra artifact map (merged before the canonical step/prompt keys). * `extraFields` is a shallow object spread onto the contract AFTER the base * fields but BEFORE the canonical `prompt_path`/`repo_root`/`artifacts_dir`/ * `artifact_paths` normalization — so an orchestrator's optional fields * (progress, allowed_mcp_tools, access, ...) ride along without this module * knowing about them, and can never clobber the normalized path fields. */ export interface WriteStepContractInput { contractVersion: string; stepKind: TStepKind; status: string; runId: string | null; allowedCommands: string[]; stopCondition: string; repoRoot: string; artifactsDir: string; prompt: string; /** * Caller-supplied artifact map. Merged FIRST; the canonical `current_step` * and `current_prompt` entries always win. All values are normalized to * forward-slash prompt path tokens. */ artifactPaths?: Record; /** * Orchestrator-specific optional fields (progress, allowed_mcp_tools, * access, ...). Spread onto the contract before the canonical path fields, * so they can never overwrite the normalized paths or `artifact_paths`. */ extraFields?: Record; /** * Whether to trim leading whitespace from the prompt before writing it. * Remediate trims (its prompts are built with a leading newline); audit * writes the prompt verbatim. Defaults to `false` (verbatim). */ trimPromptStart?: boolean; } /** * Write `current-prompt.md` and an atomically-replaced `current-step.json` * under `/steps/`, returning the contract object that was * persisted (path fields normalized to forward slashes). The generic * parameters let each orchestrator recover its concrete contract type. * * Path normalization is applied to EVERY host-facing path field so a step * never carries Windows backslashes into the JSON a host reads and runs * commands from. */ export declare function writeStepContract, TStepKind extends string = string, TArtifactValue extends string | null = string | null>(input: WriteStepContractInput): Promise; /** * Run `body` under the terminal-exit backstop both next-step CLIs share * (backlog: abnormal-exit no-step-contract). If `body` throws — a * mis-shaped-submission parse crash, a host-handoff abort, an IO failure — * write a blocked step contract naming the cause via * `writeBlockedStep`, then rethrow the ORIGINAL error so the caller's exit * semantics (stderr report + nonzero exit) are unchanged. This guarantees the * step-contract property mechanically: after ANY terminal exit of a next-step * invocation, `steps/current-step.json` reflects that invocation's outcome — a * consumer can never read the previous step as a live instruction. * * `writeBlockedStep` failures are swallowed deliberately: the backstop must * never mask the original failure with a secondary write error. */ export declare function runWithBlockedStepBackstop(body: () => Promise, writeBlockedStep: (reason: string) => Promise): Promise; /** * Canonical blocked-step prompt. Single-sourced here (one core, two draws) so * the two orchestrators' blocked prompts cannot drift; `tool` is the CLI name * for the heading ("audit-code" / "remediate-code"). */ export declare function renderBlockedStepPrompt(tool: string, reason: string): string; /** * Single-sourced blocked-step ASSEMBLY (one core, two draws — the semantics * live in `runWithBlockedStepBackstop`, the contract shape lives here). Each * orchestrator's draw supplies only its genuine per-mode inputs: the contract * version and its run-id convention (audit: `null`; remediate: a minted id). * The step JSON always carries `progress.summary` naming the cause — a * contract-only consumer may never read the prompt file — and the * prompt is the shared `renderBlockedStepPrompt`. */ export declare function writeBlockedStepContract(params: { /** CLI name for the prompt heading, e.g. "audit-code". */ tool: string; contractVersion: string; artifactsDir: string; repoRoot: string; runId: string | null; reason: string; /** Optional pointers a specific blocked path adds (e.g. operator_handoff). */ artifactPaths?: Record; }): Promise; //# sourceMappingURL=stepContractWriter.d.ts.map