import type { TaskFlowRegistry } from '../tasks/TaskFlowRegistry.js';
import type { DriftVerdict } from './types.js';
export type InitiativePhaseStatus = 'pending' | 'in-progress' | 'done' | 'blocked';
export interface InitiativePhase {
    /** Stable identifier within this initiative (e.g. 'phase-a'). */
    id: string;
    /** Human-readable name (e.g. 'Phase A: Scaffolding'). */
    name: string;
    /** Short summary of what this phase delivers. */
    summary?: string;
    status: InitiativePhaseStatus;
    /** ISO timestamp when status first became 'in-progress'. */
    startedAt?: string;
    /** ISO timestamp when status first became 'done'. */
    completedAt?: string;
}
export type InitiativeStatus = 'active' | 'completed' | 'archived' | 'abandoned' | 'paused' | 'halted' | 'awaiting-user';
export type InitiativeKind = 'task' | 'project';
/** Post-ship rollout stages for a ships-staged feature. The stage is DERIVED
 *  from observing the feature's config flag (never written by the driver) —
 *  GRADUATED-FEATURE-ROLLOUT-SPEC §4.2-4.3. */
export type RolloutStage = 'dark' | 'dry-run' | 'live' | 'default-on';
/** Typed rollout metadata for a ships-staged feature task. Operational criteria
 *  live here (typed), NOT in free-form phase summaries. */
export interface RolloutInfo {
    /** Dotted config key whose observed value derives the stage, e.g.
     *  'monitoring.sessionReaper'. The driver NEVER writes this. */
    flagPath: string;
    /** Stage derived from the last observed flag value. */
    stage: RolloutStage;
    /** Where the twice-weekly driver gathers promotion evidence. */
    evidenceSource?: {
        type: 'log-filter' | 'endpoint';
        ref: string;
        filter?: string;
    };
    /** Human-readable promotion gate (e.g. "≥2wk + ≥3 genuinely-idle would-reaps"). */
    promotionCriteria?: string;
    /** Near-silent edge dedupe: last time a needs-user line was surfaced for this track. */
    lastDigestNotifiedAt?: string;
}
export type PipelineStage = 'outline' | 'spec-drafted' | 'spec-converged' | 'approved' | 'building' | 'merged' | 'regressed' | 'skipped';
export type RoundStatus = 'pending' | 'ready' | 'in-progress' | 'partially-complete' | 'complete' | 'complete-with-skips' | 'failed' | 'regressed';
export interface InitiativeRound {
    name: string;
    /** Child initiative IDs in this round. */
    itemIds: string[];
    status: RoundStatus;
    /** ISO; populated when prior round completes and this one becomes ready. */
    autoAdvanceAt?: string;
    completedAt?: string;
    haltedAt?: string;
    haltReason?: string;
    /** Counter; round-runner caps this at 3. */
    resumeAttempts?: number;
    /** Cached drift verdict from the most recent attempt. See DriftVerdict in types.ts. */
    lastDriftVerdict?: DriftVerdict;
    /** ISO timestamp the lastDriftVerdict was computed; round-runner uses this for the 24h freshness window. */
    lastDriftVerdictAt?: string;
    /** Hashes of referenced files at verdict time; round-runner re-runs drift if any changed. */
    lastDriftReferencedFileHashes?: Record<string, string>;
}
export interface InitiativeConflictPatch {
    patchId: string;
    recordId: string;
    path: string;
    oursValue: unknown;
    theirsValue: unknown;
    baseValue?: unknown;
    losingMachineId: string;
    capturedAt: string;
}
export interface InitiativeLink {
    type: 'spec' | 'pr' | 'commit' | 'topic' | 'doc' | 'other';
    label: string;
    url?: string;
    ref?: string;
}
export interface Initiative {
    /** URL-safe slug (stable identifier). */
    id: string;
    title: string;
    description: string;
    status: InitiativeStatus;
    phases: InitiativePhase[];
    /** Index into phases[] of the phase currently active (or last worked on). */
    currentPhaseIndex: number;
    /** ISO timestamp of the last phase/status update. */
    lastTouchedAt: string;
    /** Optional ISO timestamp; digest job flags if past and status === 'active'. */
    nextCheckAt?: string;
    /** True when waiting on the user (decision, approval, ratification). */
    needsUser: boolean;
    /** Short rationale when needsUser === true. */
    needsUserReason?: string;
    /** Free-text list of current blockers (not necessarily user-blocked). */
    blockers: string[];
    /** External references: spec docs, PRs, commits, Telegram topics, etc. */
    links: InitiativeLink[];
    createdAt: string;
    updatedAt: string;
    /** Distinguishes leaf work items ('task') from rollups ('project').
     *  Immutable after creation. Defaults to 'task' when omitted. */
    kind?: InitiativeKind;
    /** Bumped on backfill / migration. */
    schemaVersion?: number;
    /** Optimistic concurrency counter. Increments on every successful write.
     *  Starts at 1 on create. */
    version?: number;
    /** Back-pointer to a project that lists this child in `rounds[].itemIds`. */
    parentProjectId?: string;
    pipelineStage?: PipelineStage;
    /** Relative to repo root; required for stages ≥ 'spec-drafted'. */
    specPath?: string;
    /** Required for stages = 'building' or 'merged'. */
    prNumber?: number;
    /** GitHub-reported merge commit; recorded at building → merged. */
    mergeCommitOid?: string;
    /** ISO; last revalidation against origin/main. */
    ciCheckedAt?: string;
    skippedAt?: string;
    skippedBy?: string;
    skippedReason?: string;
    /** Recorded on skipped → outline reverse transition. */
    unskippedAt?: string;
    /** Default true at runtime; false marks infrastructure-of-tracker specs. */
    driftCheck?: boolean;
    /** Post-ship rollout track (ships-staged features). Present only when the
     *  feature matures behind a flag. Stage is derived from observing flagPath. */
    rollout?: RolloutInfo;
    rounds?: InitiativeRound[];
    /** Paths jailed to project-root allowlist. */
    sourceDocs?: string[];
    /** Default true at runtime. */
    autoAdvance?: boolean;
    /** For round-complete and halt notifications. */
    telegramTopicId?: string;
    /** Current round owner (Phase 1.5). */
    ownerMachineId?: string;
    /** Absolute path to the target source repo; required for projects. */
    targetRepoPath?: string;
    /** Increments on each auto-advance without ack; pauses project at ≥ 2. */
    unacknowledgedAdvanceCount?: number;
    /** Populated when user acks the first-launch digest. */
    firstLaunchAckAt?: string;
    /** Highest round index acked. */
    lastAckedRoundIndex?: number;
    /** Populated by git-sync conflict handler (Phase 1.12). */
    awaitingReconciliation?: InitiativeConflictPatch[];
    /** For cache invalidation on drift-prompt edits. */
    driftPromptTemplateVersion?: number;
}
export interface InitiativeCreateInput {
    id: string;
    title: string;
    description: string;
    phases: Array<{
        id: string;
        name: string;
        summary?: string;
        status?: InitiativePhaseStatus;
    }>;
    links?: InitiativeLink[];
    nextCheckAt?: string;
    needsUser?: boolean;
    needsUserReason?: string;
    blockers?: string[];
    /** Defaults to 'task' if omitted. Immutable after creation. */
    kind?: InitiativeKind;
    parentProjectId?: string;
    pipelineStage?: PipelineStage;
    specPath?: string;
    prNumber?: number;
    mergeCommitOid?: string;
    ciCheckedAt?: string;
    driftCheck?: boolean;
    rollout?: RolloutInfo;
    rounds?: InitiativeRound[];
    sourceDocs?: string[];
    autoAdvance?: boolean;
    telegramTopicId?: string;
    ownerMachineId?: string;
    targetRepoPath?: string;
    driftPromptTemplateVersion?: number;
}
export interface InitiativeUpdateInput {
    title?: string;
    description?: string;
    status?: InitiativeStatus;
    nextCheckAt?: string | null;
    needsUser?: boolean;
    needsUserReason?: string | null;
    blockers?: string[];
    links?: InitiativeLink[];
    /** Optimistic concurrency guard. When provided, must equal the current
     *  `version` or update() throws `OccVersionMismatchError`. Backward
     *  compatible: callers that omit ifMatch get unconditional writes. */
    ifMatch?: number;
    /** Presence triggers immutability check: any value that differs from
     *  the current `kind` throws `KindImmutableError`. */
    kind?: InitiativeKind;
    /** Set to a project id to attach; set to null to clear. Bidirectional
     *  validation against the named project's `rounds[].itemIds` runs on set. */
    parentProjectId?: string | null;
    pipelineStage?: PipelineStage;
    specPath?: string | null;
    prNumber?: number | null;
    mergeCommitOid?: string | null;
    ciCheckedAt?: string | null;
    skippedAt?: string | null;
    skippedBy?: string | null;
    skippedReason?: string | null;
    unskippedAt?: string | null;
    driftCheck?: boolean;
    rollout?: RolloutInfo | null;
    rounds?: InitiativeRound[];
    sourceDocs?: string[];
    autoAdvance?: boolean;
    telegramTopicId?: string | null;
    ownerMachineId?: string | null;
    targetRepoPath?: string;
    unacknowledgedAdvanceCount?: number;
    firstLaunchAckAt?: string | null;
    lastAckedRoundIndex?: number;
    awaitingReconciliation?: InitiativeConflictPatch[];
    driftPromptTemplateVersion?: number;
}
/**
 * Thrown by `update()` when the caller supplied `ifMatch` and it didn't
 * equal the current `version`. The HTTP layer (Phase 1.3) translates this
 * to a 409 response with body `{ currentVersion }`.
 */
export declare class OccVersionMismatchError extends Error {
    readonly currentVersion: number;
    constructor(message: string, currentVersion: number);
}
/**
 * Thrown by `update()` when the caller attempted to change `kind`. The
 * `kind` field is set at creation time and never changes thereafter.
 */
export declare class KindImmutableError extends Error {
    constructor(message?: string);
}
/**
 * Thrown by `update()` when setting `parentProjectId` and the named parent
 * either doesn't exist, isn't a `kind: 'project'` initiative, or doesn't
 * list this child in any of its `rounds[].itemIds`. Bidirectional check
 * keeps the parent/child relationship internally consistent.
 */
export declare class InvalidParentProjectError extends Error {
    constructor(message: string);
}
export interface DigestItem {
    initiativeId: string;
    title: string;
    reason: 'stale' | 'needs-user' | 'next-check-due' | 'ready-to-advance';
    detail: string;
}
export interface Digest {
    generatedAt: string;
    items: DigestItem[];
}
/**
 * Staleness threshold for the digest scan (7 days without an update on an
 * active initiative triggers a 'stale' flag).
 */
export declare const STALE_THRESHOLD_MS: number;
/**
 * TaskFlow controller identity for InitiativeTracker (Phase 4 migration).
 * Every initiative's flow is owned by this controllerId; the registry uses
 * it for OCC scope checks. Single instance per server process.
 */
export declare const INITIATIVE_TASKFLOW_CONTROLLER_ID = "InitiativeTracker";
export declare class InitiativeTracker {
    private readonly filePath;
    /** In-process cache. Authoritative when TaskFlow is not wired; otherwise
     * a read-side projection of TaskFlow's stateJson. */
    private readonly initiatives;
    private taskFlowRegistry;
    private taskFlowControllerInstanceId;
    private digestCacheInvalidator;
    constructor(stateDir: string);
    /**
     * Register a callback invoked once after every successful mutating call
     * (`create`, `update`, `setPhaseStatus`, `remove`). The default is a
     * no-op so legacy callers see no change. PR 3 wires the real cache
     * invalidator.
     */
    setDigestCacheInvalidator(fn: () => void): void;
    /**
     * Wire a TaskFlow registry to make TaskFlow the source of truth.
     * Idempotent — safe to call multiple times. After wiring, all reads come
     * from TaskFlow and all writes go through TaskFlow APIs. The legacy JSON
     * file is no longer written.
     *
     * Callers are expected to call `migrateExistingToTaskFlow()` after this
     * to backfill any initiatives that were loaded from the legacy file.
     */
    setTaskFlowRegistry(registry: TaskFlowRegistry, controllerInstanceId: string): void;
    /** True when TaskFlow is wired and authoritative. */
    isTaskFlowEnabled(): boolean;
    private taskFlowPrincipal;
    private loadFromDisk;
    private saveToDisk;
    private initiativeFromFlow;
    private refreshCacheFromTaskFlow;
    /**
     * Layer TaskFlow data on top of the existing cache without clearing it.
     * Used during `setTaskFlowRegistry` so legacy-loaded initiatives are
     * preserved as backfill candidates while any pre-existing TaskFlow
     * records take precedence.
     */
    private layerCacheFromTaskFlow;
    /**
     * A flow is "tombstoned" when remove() marked it as removed before
     * cancelling. The marker lives in stateJson._removed and is the
     * single signal that a flow's owning Initiative should be hidden from
     * list() / get(). We can't delete TaskFlow records from this layer, so
     * the marker is the durable equivalent of deletion.
     */
    private isTombstoned;
    private waitJsonForInitiative;
    /** Determine the desired TaskFlow status for an Initiative. */
    private desiredFlowStatus;
    /**
     * Persist an Initiative through TaskFlow. Walks the flow state machine
     * to the desired target (running / waiting / succeeded / failed /
     * cancelled), updating `stateJson` along the way so reads see the latest
     * shape. Returns the Initiative as projected from TaskFlow after
     * transitions settle.
     */
    private persistThroughTaskFlow;
    /**
     * Patch a running flow's stateJson without changing its observable
     * status. Uses a brief setFlowWaiting → resumeFlow round-trip (the only
     * atomic state-bearing transitions for `running` flows that accept
     * `statePatch`). The wait kind `'human-review'` with a sentinel question
     * is used; the wait is consumed in the same call, so no external
     * observer sees `waiting`.
     */
    private patchStateJson;
    private findFlowIdForInitiative;
    private logTaskFlowError;
    /**
     * Backfill all initiatives currently in the in-memory cache (loaded from
     * legacy JSON in the constructor) into TaskFlow. Idempotent via
     * `findIdempotent`. Safe to call multiple times.
     */
    migrateExistingToTaskFlow(): Promise<{
        created: number;
        alreadyExisted: number;
        advanced: number;
        skipped: number;
    }>;
    list(filter?: {
        status?: InitiativeStatus;
        kind?: InitiativeKind;
    }): Initiative[];
    /**
     * Reverse-lookup by exact merge-commit OID (Ingestion-sources spec §3.1).
     * Scans via `list()` (which refreshes the TaskFlow cache) — NOT the raw
     * `this.initiatives` Map — so it stays correct under TaskFlow. Exact-OID
     * match only; never a branch/substring match. No index needed at current
     * initiative scale.
     */
    findByMergeCommit(mergeCommitOid: string): Initiative | undefined;
    /** Reverse-lookup by exact PR number (Ingestion-sources spec §3.1). Same TaskFlow-safe scan. */
    findByPrNumber(prNumber: number): Initiative | undefined;
    get(id: string): Initiative | undefined;
    create(input: InitiativeCreateInput): Promise<Initiative>;
    update(id: string, input: InitiativeUpdateInput): Promise<Initiative>;
    /**
     * Verify that `parentId` names an existing `kind: 'project'` initiative
     * AND that one of its rounds lists `childId` in itemIds. Throws
     * `InvalidParentProjectError` on any failure. Used by `update()` when
     * a child's `parentProjectId` is set to a non-null value.
     */
    private assertValidParentProject;
    setPhaseStatus(id: string, phaseId: string, status: InitiativePhaseStatus): Promise<Initiative>;
    remove(id: string): Promise<boolean>;
    /**
     * One-time backfill helper for TaskFlow-enabled installs: scans all
     * records, identifies those missing `kind`, and updates them through
     * TaskFlow. Idempotent — running twice produces no change after the
     * first pass. The legacy-JSON path runs an equivalent backfill inside
     * `loadFromDisk()` on first load.
     *
     * Returns counts so callers can log/observe what happened.
     */
    backfillKindAndSchema(): Promise<{
        backfilled: number;
        scanned: number;
    }>;
    /**
     * Scan active initiatives for anything actionable. Empty items[] means
     * "quiet day, don't spam the user."
     */
    digest(now?: Date): Digest;
}
//# sourceMappingURL=InitiativeTracker.d.ts.map