/**
 * Per-project Linear issue counts for the `agents projects status` card.
 *
 * When a project definition carries `linear.projectId` (set via
 * `agents projects link <name> --linear`), the card shows one outcome line —
 * `12/30 done · 5 in progress` — counted from the Linear GraphQL API by state
 * TYPE (triage / backlog / unstarted / started / completed / canceled), never
 * hardcoded state names.
 *
 * The same fetch also yields the **next milestone** — the earliest-dated
 * milestone with unfinished issues — because each issue node carries its
 * `projectMilestone`. A percentage tells you how far along a project is; the
 * milestone tells you what it is due to hit next, which is the thing a person
 * actually plans around. Deriving it here costs no extra request.
 *
 * This is a best-effort card enrichment, not an explicit command: every failure
 * (no credential, offline, API error, timeout) degrades to `undefined` and the
 * card simply omits the line — never a hang, never a throw. `--no-remote`
 * skips it (it's network). The API key resolves through the same chain the rest
 * of the stack uses: $LINEAR_API_KEY → macOS Keychain (`resolveLinearApiKey`)
 * → the linear-cli config (`~/.linear-cli/config.json` `apiKey`).
 *
 * Paging is capped (10 × 250 issues) so a pathological project can't burn the
 * budget; a capped fetch reports `truncated: true` and the card renders the
 * total as a lower bound (`2500+ done`), never as the complete count.
 */
/**
 * The next checkpoint the project is working toward: the earliest-dated
 * milestone that still has unfinished issues. A percentage says how far along
 * the project is; this says what it is due to hit next.
 */
export interface LinearMilestone {
    name: string;
    /** `YYYY-MM-DD` as Linear stores it. Absent when the milestone has no date. */
    targetDate?: string;
    /** Issues in this milestone in a `completed`-type state. */
    done: number;
    /**
     * Issues assigned to this milestone. Legitimately `0` — a milestone can be
     * declared with a date long before any issue is filed under it (that is the
     * state of every milestone in this repo's own Linear project), and such a
     * milestone is still the next checkpoint. The card omits the fraction rather
     * than printing a meaningless `0/0`.
     */
    total: number;
    /** True when Linear itself flags this as the project's next milestone. */
    isNext?: boolean;
}
/** A milestone as the project declares it, independent of any issue. */
export interface LinearMilestoneNode {
    id?: string;
    name?: string;
    targetDate?: string | null;
    /**
     * Linear's own marker. Observed values: `"next"` (it flags exactly one) and
     * `"unstarted"`. Treated as an opaque string and only compared to `"next"` —
     * the enum is not documented as closed, so switching exhaustively on it would
     * break the day Linear adds a value.
     */
    status?: string | null;
}
/** The counts the card renders. `total` counts every issue in the project. */
export interface LinearProjectCounts {
    /** Issues in a `completed`-type state. */
    done: number;
    /** All issues in the project (any state type, including canceled). */
    total: number;
    /** Issues in a `started`-type state. */
    inProgress: number;
    /**
     * True when the page cap cut the fetch short — `total` is then a LOWER
     * bound (rendered `2500+`), never presented as the complete count.
     */
    truncated?: boolean;
    /**
     * True when this answer came from the cache after a failed or skipped fetch.
     * The card labels it rather than dropping the line — a populated Linear row
     * that silently vanishes on one timeout is the defect this replaces.
     */
    stale?: boolean;
    /**
     * Every milestone the project declares, in the order the card shows them:
     * unfinished first by target date, then the finished ones. A project with
     * three checkpoints has three; showing only the next one hides the shape of
     * the plan, which is what `projects view` exists to show.
     */
    milestones?: LinearMilestone[];
    /**
     * The one the project is working toward — `milestones[0]` when there is an
     * unfinished one. Kept as its own field because the compact card shows only
     * this, while `view` shows the whole list.
     */
    nextMilestone?: LinearMilestone;
}
/** One issue node as the query selects it. */
export interface LinearIssueNode {
    state?: {
        type?: string;
    } | null;
    projectMilestone?: {
        id?: string;
        name?: string;
        targetDate?: string | null;
    } | null;
}
/** The GraphQL response shape this module consumes (recorded for the tests). */
export interface LinearIssuesResponse {
    issues?: {
        nodes?: LinearIssueNode[];
        pageInfo?: {
            hasNextPage?: boolean;
            endCursor?: string | null;
        };
    };
    /** Only the FIRST page asks for this — the milestone list does not paginate. */
    project?: {
        projectMilestones?: {
            nodes?: LinearMilestoneNode[];
        };
    } | null;
}
/**
 * Pure mapping: a Linear issues response → card counts, grouping by state
 * type. Defensive at the boundary — a missing `issues`/`nodes` yields zeros,
 * an issue with no state still counts toward `total`.
 */
export declare function countsFromIssuesResponse(data: LinearIssuesResponse): LinearProjectCounts;
/**
 * Pick the milestone the project is working toward next.
 *
 * The **declared** list is authoritative for which milestones exist, their
 * names, and their dates; issues only supply progress. Deriving the list from
 * issues instead looks tempting (it costs no extra request) and is wrong: a
 * milestone with nothing filed under it yet would be invisible, and that is the
 * common case — every milestone in this repo's own Linear project has zero
 * issues assigned, so an issue-derived list showed nothing at all.
 *
 * Next = the earliest-dated milestone that is not finished. A milestone with no
 * issues counts as unfinished (it is upcoming work, not completed work). Undated
 * milestones sort last, ties break by declaration order, so the answer is stable.
 */
export declare function orderedMilestones(declared: LinearMilestoneNode[], nodes: LinearIssueNode[]): LinearMilestone[];
/**
 * The milestone the project is working toward next.
 *
 * Linear flags one itself (`status: "next"`), and that is the answer the user
 * sees in Linear's own UI, so it wins when present. Only when nothing is
 * flagged does this fall back to "earliest-dated unfinished", which is a
 * reasonable guess but still a guess.
 */
export declare function nextMilestone(declared: LinearMilestoneNode[], nodes: LinearIssueNode[]): LinearMilestone | undefined;
/**
 * Fetch issue counts for one Linear project, paging `issues` filtered by
 * project id. One shared AbortController bounds the WHOLE paged fetch at ~8s;
 * any failure (no key, network, API error, abort) returns undefined so the
 * card just omits the line. `fetchPage` is injectable for tests — the
 * accumulator (cursor hand-off, cap) is the risky logic, not the HTTP.
 */
export declare function fetchLinearProjectCounts(projectId: string, fetchPage?: (projectId: string, after: string | undefined, signal: AbortSignal) => Promise<LinearIssuesResponse | undefined>, nowMs?: number): Promise<LinearProjectCounts | undefined>;
