/**
 * Project-level progress rollup — the headline of the projects subsystem.
 *
 * At 50–100 agents the per-agent activity line is noise; what matters is the
 * PROJECT. This aggregates the signals already carried per session (status,
 * plan progress, open PRs, tickets, worktrees) into one row per project, keyed
 * by matching each session's cwd to a defined project root (`projectNameForCwd`).
 * The session set is whatever the caller passes (today `getActiveSessions()` —
 * this machine's live view, matched by local-home cwd; a fleet-wide fan-out is a
 * deferred follow-up). Pure over an `ActiveSession[]` so the aggregation is
 * unit-testable; the merged-PR signal IS repo-global (harvested via `gh`) and the
 * artifact signal is local, both added in `enrichProjectSignals`.
 */
import type { ActiveSession, ActiveStatus } from './session/active.js';
import { type ProjectDef } from './projects.js';
/** One live agent on a project — the WHO behind the byStatus count. */
export interface ProjectMember {
    /** Harness name (claude / codex / …), from the session's `kind`. */
    agent: string;
    /** Lifecycle status (running / idle / …). */
    status: string;
    /** Tracker ticket the session is tied to, when any. */
    ticket?: string;
    /** Machine the session runs on (provenance host / fleet peer), when known. */
    host?: string;
}
/** One project's live session rollup. */
export interface ProjectSessionRollup {
    name: string;
    /** Total sessions whose cwd is inside this project. */
    agents: number;
    /** Count per lifecycle status. */
    byStatus: Partial<Record<ActiveStatus, number>>;
    /** Which agents are on the project (one per matched session). */
    members: ProjectMember[];
    /** Summed checklist progress across this project's sessions. */
    plan: {
        done: number;
        total: number;
    };
    /** Distinct open PRs held by this project's sessions. */
    openPrs: {
        url: string;
        number?: number;
    }[];
    /** Distinct tickets worked or created by this project's sessions. */
    tickets: string[];
    /** Sessions running inside a worktree. */
    worktrees: number;
}
/**
 * Roll active sessions up by project. Returns a map keyed by project name,
 * containing only projects with at least one matched session — callers merge
 * with the full definition list to show zero-agent projects.
 */
/**
 * Ensure every session carries a host for the card roster. Local
 * `getActiveSessions()` omits `machine`; remotes already set it. Pure.
 */
export declare function withDefaultMachine<T extends {
    machine?: string;
}>(sessions: T[], defaultHost: string): T[];
export declare function rollupSessionsByProject(defs: ProjectDef[], sessions: ActiveSession[]): Map<string, ProjectSessionRollup>;
/**
 * True when a session's status means it is over. Exported so the card can keep
 * the `agents` roster to live sessions: the headline and the `dead` row already
 * separate the two, and a roster that reads `crashed ×25` beside `23 live`
 * makes the reader distrust both numbers.
 */
export declare function isDeadStatus(status: string): boolean;
/** Live vs finished sessions on a project. */
export interface LiveDeadSplit {
    live: number;
    dead: number;
    /** Dead broken out by status, for the card's parenthetical. */
    deadByStatus: Array<{
        status: string;
        n: number;
    }>;
}
/**
 * Split a rollup's sessions into what is working and what is wreckage.
 *
 * The headline used to be the raw session count, which on a real project read
 * `39 agents` when 19 of those had crashed. A count that is half corpses is not
 * a throughput signal — but the corpses are worth their own number, because 19
 * crashed sessions is itself a thing to go fix.
 */
export declare function liveDeadSplit(byStatus: Partial<Record<ActiveStatus, number>>): LiveDeadSplit;
/**
 * The `dead` row body: `41 crashed` when every dead session shares one status,
 * else `12 finished or lost (8 crashed, 4 closed)`. The generic "finished or
 * lost" only earns its keep when the statuses actually differ — with a single
 * status it just hides which one behind a parenthetical that repeats the count.
 * Pure — chalk styling only; the caller adds the `dead` label. Assumes
 * `split.dead > 0` (the caller gates on it).
 */
export declare function formatDeadSummary(split: LiveDeadSplit): string;
/** Sort members for the card: running first, then idle, then the rest; agent name asc within a state. */
export declare function sortProjectMembers(members: ProjectMember[]): ProjectMember[];
/** Cap for the members line before it collapses to `+N more`. */
export declare const MEMBERS_LINE_LIMIT = 6;
/** Cap for host groups on the multi-line agents roster. */
export declare const MEMBERS_HOST_LIMIT = 8;
/**
 * Collapse members into distinct state cells (`agent · status · ticket[@host]`),
 * counting duplicates as `×N`. Pure.
 */
export declare function collapseMemberCells(members: ProjectMember[], opts?: {
    includeHostOnCell?: boolean;
}): Array<{
    cell: string;
    n: number;
    members: number;
}>;
/**
 * The `agents` line under `live`: one cell per DISTINCT member state —
 * `claude · running · RUSH-2107 @zion` — with identical cells collapsed to a
 * `×N` count (35 same-harness sessions in one state are one fact, not six
 * truncated duplicates), capped at {@link MEMBERS_LINE_LIMIT} cells with a
 * `+N more` tail counting members, not cells. Pure (chalk styling only); the
 * caller adds the label.
 *
 * Prefer {@link formatProjectMembersByHost} on the card: a flat line hides which
 * machine is running the work when the same harness×status spans hosts.
 */
export declare function formatProjectMembers(members: ProjectMember[], limit?: number): string;
/**
 * Host-grouped agents roster. One content line per host:
 * `@zion  claude · running ×9  ·  claude · idle ×4`
 *
 * When no member carries a host (local-only rollup with no machine stamp), falls
 * back to a single flat line via {@link formatProjectMembers}. Pure.
 */
export declare function formatProjectMembersByHost(members: ProjectMember[], opts?: {
    cellLimit?: number;
    hostLimit?: number;
}): string[];
/** A card-level warning collected for the footer. */
export type ProjectWarningSeverity = 'critical' | 'continue';
export interface ProjectWarning {
    severity: ProjectWarningSeverity;
    /** One human line. */
    text: string;
    /** Optional fix or next step. */
    remediation?: string;
}
/**
 * Severity markers for the warnings footer. User-facing by design: critical
 * stops you (wrong repo, missing checkout, large drift); continue is a soft
 * nudge (dirty tree, schedule not measurable).
 */
export declare function warningEmoji(severity: ProjectWarningSeverity): string;
/** Stable sort: critical first, then continue; stable within a tier. */
export declare function sortProjectWarnings(warnings: ProjectWarning[]): ProjectWarning[];
/**
 * Format one or more warning lines for the card footer. Pure (chalk only).
 * Returns empty when there is nothing to say.
 */
export declare function formatProjectWarnings(warnings: ProjectWarning[]): string[];
/** Harvested signals not on the session list: repo-global merged PRs + releases, local artifacts, in a time window. */
export interface ProjectRemoteSignals {
    windowDays: number;
    /** PRs merged into the primary repo within the window (via `gh`). */
    mergedPrs: number;
    /**
     * True when the `gh` fetch cap cut the count short — `mergedPrs` is then a
     * LOWER bound (rendered `100+`), never presented as the complete count. Same
     * contract `LinearProjectCounts.truncated` keeps for the Linear line.
     */
    mergedPrsTruncated?: boolean;
    /** Artifacts agents produced within the window (activity.created milestones). */
    artifacts: number;
    /** Basename of the most recent artifact, when any. */
    lastArtifact?: string;
    /** Latest release of the PRIMARY repo (via `gh release list`), when any. */
    latestRelease?: {
        tag: string;
        publishedAt: string;
    };
}
/**
 * Harvest the signals that don't live on the active-session list: recently
 * merged PRs (from GitHub via `gh`) and artifacts agents produced (from the
 * local activity-milestone log, matched to the project by cwd). Best-effort —
 * a missing `gh`, no auth, or no repo degrades to zero rather than throwing, so
 * `projects status` still renders. `nowMs` is injected for testability.
 */
export declare function enrichProjectSignals(def: ProjectDef, windowDays: number, nowMs: number, opts?: {
    activityRoot?: string;
    skipRemote?: boolean;
}): Promise<ProjectRemoteSignals>;
