/**
 * Project pull — fast-forward every fleet checkout of a named project.
 *
 * Safety contract (binding for all paths in this module):
 *   - Dirty trees are blocked immediately (no fetch attempted).
 *   - Only checkouts on the remote's default branch are fast-forwarded.
 *   - Local commits ahead of upstream block the pull.
 *   - Fast-forward ONLY — no rebase, no reset, no history rewrite.
 *   - Missing checkouts are reported, never cloned.
 *   - Malformed or partial peer envelopes fail CLOSED and LOUD (the parse
 *     returns `valid: false`, so the peer lands in `parseFailed` and drives a
 *     non-zero exit — never an empty result set that reads as "nothing to do").
 *   - Git hook symlinks are NEVER installed during a pull.
 *
 * The fan-out extends the `projects status` seam: the same
 * `projectRepoTargetsForDef` expansion drives both commands, so they
 * always operate on exactly the same set of directories.
 */
import { type ProjectRepoTarget } from './projects.js';
import { type RemoteAgentsJsonParseResult } from './remote-agents-json.js';
export type ProjectPullStatus = 'updated' | 'current' | 'missing' | 'blocked' | 'failed';
export interface ProjectPullResult {
    /** Machine that ran the pull (from {@link machineId}). */
    host: string;
    /** Home-relative path — re-roots on each fleet device. */
    path: string;
    /** Expected GitHub slug, echoed from the target for reference. */
    expectedSlug?: string;
    status: ProjectPullStatus;
    branch?: string;
    upstream?: string;
    /** Short commit hash HEAD was at before the pull. */
    before?: string;
    /** Short commit hash HEAD is at after the pull. */
    after?: string;
    /** Human-readable status or error; present when status is not `current`. */
    message?: string;
}
/** The wire format this machine emits for the fleet fan-out. Fail-closed on parse. */
export interface ProjectPullEnvelope {
    schemaVersion: 1;
    kind: 'project-pull';
    /** The emitting machine's {@link machineId} — verified by the caller. */
    machine: string;
    /** Hash of the target list the caller sent — verified before trusting results. */
    targetFingerprint: string;
    results: ProjectPullResult[];
}
/**
 * Deterministic hash of a target list so the orchestrator can verify the peer
 * pulled the same set of directories it intended to request. Computed from the
 * canonical `path\0expectedSlug` representation, sorted so insertion order does
 * not matter.
 */
export declare function fingerprintTargets(targets: ProjectRepoTarget[]): string;
/**
 * Encode a target list as the single `--targets` argument the orchestrating
 * `pull` hands to a peer's hidden `pull-local`.
 *
 * A target is a `{ path, expectedSlug }` PAIR, and both halves must survive the
 * hop: `expectedSlug` is what makes the peer refuse to fast-forward a directory
 * whose `origin` is a different repo, and it is also hashed into
 * {@link fingerprintTargets}. Sending bare paths therefore broke the fan-out
 * twice over — slug verification silently became a no-op on every peer, and the
 * peer's slug-less fingerprint could never match the caller's, so
 * {@link parseProjectPullEnvelope} discarded the peer's whole result set. JSON
 * keeps the pair intact; both transports quote it as one argument
 * (`shellQuote` on bash, `powershellQuote` inside a base64 `-EncodedCommand`
 * on Windows), so no escaping is owed here.
 */
export declare function encodePullTargets(targets: ProjectRepoTarget[]): string;
/**
 * The exact `agents …` argv the fleet fan-out runs on each peer. Owned here,
 * beside the decoder, so the two halves of the hop cannot drift apart and a
 * test can exercise the real caller-side arguments rather than a retyped copy.
 */
export declare function pullLocalArgs(targets: ProjectRepoTarget[]): string[];
/**
 * Decode the `--targets` argument back into targets on the peer. THROWS on any
 * malformed input rather than returning a partial list: a peer that cannot tell
 * exactly which directories it was asked to pull must fail loudly, not
 * fast-forward a guessed subset.
 */
export declare function decodePullTargets(raw: string): ProjectRepoTarget[];
/**
 * Pull each target on THIS machine, sequentially (concurrent worktree writes
 * can corrupt the git index). Returns one result per target.
 *
 * Missing checkouts (no directory or no `.git`) are reported as `missing` and
 * skipped — never cloned. A slug mismatch (the directory's `origin` remote does
 * not match the expected slug) is reported as `blocked`.
 */
export declare function pullProjectTargets(targets: ProjectRepoTarget[], host?: string): Promise<ProjectPullResult[]>;
export declare function buildPullEnvelope(results: ProjectPullResult[], targets: ProjectRepoTarget[]): ProjectPullEnvelope;
/**
 * Parse a peer's `projects pull-local` stdout. Fails CLOSED **and
 * LOUD**: any structural anomaly (wrong schema version, wrong kind, machine
 * mismatch, fingerprint mismatch, non-array results, malformed rows) returns
 * `{ items: [], valid: false }` rather than silently accepting a partial or
 * spoofed payload.
 *
 * `valid: false` is what makes the failure visible. A bare `[]` normalizes to
 * `{ items: [], valid: true }` in `normalizeRemoteAgentsJsonParse`, so the peer
 * would be recorded as having answered with nothing to report — indistinguishable
 * from a device that genuinely had no work, even though it had already run
 * `git fetch` + `merge --ff-only` or hit a real `blocked`/`failed`. Returning
 * the result shape instead lands the peer in `parseFailed`, which the caller
 * both prints and treats as a non-zero exit.
 */
export declare function parseProjectPullEnvelope(stdout: string, machine: string, opts?: {
    expectedFingerprint?: string;
}): RemoteAgentsJsonParseResult<ProjectPullResult>;
/**
 * Returns `true` when every pull completed successfully (`updated` or
 * `current`). `missing` paths are skipped by design — their absence does not
 * count as a failure. `blocked` and `failed` paths drive a non-zero exit.
 */
export declare function projectPullComplete(results: ProjectPullResult[]): boolean;
/**
 * Print a human-readable per-host / per-path pull summary to stdout, followed
 * by a one-line counts footer. Does not exit — the caller controls the exit code.
 *
 * `unavailableDevices` never answered (offline, no CLI, timed out).
 * `unverifiedDevices` DID answer but their envelope failed verification, so
 * their real outcome is unknown — a strictly worse state than silence, and the
 * one the caller turns into a non-zero exit.
 */
export declare function printProjectPullSummary(projectName: string, results: ProjectPullResult[], unavailableDevices: string[], unverifiedDevices?: string[]): void;
