import { type Stage } from "../core/paths.js"; /** One recorded /senai-brainstorm event. `afterStage` is the stage that * was active when the discussion ran; undefined for discussions recorded * before any run started (no active run — brainstorm has its own run id * in .IDE_Plans/pi-senai/Brainstorm//). */ export interface DiscussionEvent { ts: string; transcriptPath: string; briefPath: string; /** Brainstorm run id when the brief lives under .IDE_Plans/pi-senai/Brainstorm/; * undefined for legacy pre-run briefs. */ brainstormRunId?: string; afterStage?: Stage; } /** Mission type picked in the UNDERSTAND step of the new brainstorm flow. * explore = research only, ends at a go/clarify/kill decision (no plan). * docs = documentation-only work; test = writing tests. Both run the * normal plan → implement road, implement just targets different files. */ export declare const MISSION_TYPES: readonly ["bugfix", "feature", "upgrade", "explore", "docs", "test"]; export type MissionType = (typeof MISSION_TYPES)[number]; /** Scan kinds offered at the scan-plan gate. */ export type ScanType = "code" | "doc" | "community"; /** Per-question lifecycle in the DISCUSS loop. "draft" and "discussing" are * open (block approve); the other three are terminal. */ export type BrainstormQuestionState = "draft" | "discussing" | "agreed" | "not-wanted" | "replaced"; export interface BrainstormQuestion { id: string; text: string; suggestedAnswer?: string; state: BrainstormQuestionState; /** Required for "not-wanted" (why the user rejected it) and used by * "replaced" (what superseded it). */ reason?: string; } export interface SenaiState { version: number; mission: string; runId: string; currentStage: Stage; startedAt: string; updatedAt: string; stageResults: Record; /** Total discussions finalized for the active run (resets when a new run * starts). Pre-run / brainstorm discussions do not bump this counter. */ discussions?: number; /** Append-only log of finalized discussions, oldest first. */ discussionEvents?: DiscussionEvent[]; /** Path to the mission-brief.md consumed by /senai-plan. Relative to cwd, * slash-style. Set when /senai-plan adopts a brainstorm run id (points * at .IDE_Plans/pi-senai/runs//mission-brief.md) or when it * consumes a legacy pre-run brief. */ missionBriefPath?: string; /** Path to the discussion document (decisions ledger) copied into the run * folder when /senai-plan adopts a brainstorm run id. The Plan stage reads * the Agreed / Not wanted / Open lists from this file directly. */ discussionDocPath?: string; /** Brainstorm run id generated by /senai-brainstorm on entry. Persisted in * state.json so /senai-plan can adopt it instead of minting a new one. * Lives under .IDE_Plans/pi-senai/Brainstorm// while * brainstorm is active. Multiple brainstorms for the same project each * get their own id, so there is no conflict. */ brainstormRunId?: string; /** Number of subagent dispatches the parent LLM has made during the * active brainstorm. Tracked so the parent stays under * BRAINSTORM_DISPATCH_CAP (3). Reset whenever a new brainstorm run id * is minted. Doctor + audit log both surface this counter. */ brainstormDispatchCount?: number; /** Hard lock for the new brainstorm flow: true only after the user has * confirmed the parent's one-paragraph understanding. No scan gate, * dispatch, or approve may proceed while this is false. Presence of * `missionType` marks a new-flow session; legacy brainstorms (no * missionType) are exempt from this lock. */ understandingConfirmed?: boolean; /** Mission type picked in the UNDERSTAND step; drives the dynamic * question pack and the default scan selection. */ missionType?: MissionType; /** Scan kinds the user selected at the scan-plan gate. */ scansSelected?: ScanType[]; /** Per-question states for the DISCUSS loop of the active brainstorm. * Open states ("draft", "discussing") hard-block approve. */ brainstormQuestions?: BrainstormQuestion[]; /** Terminal door for explore missions: go (seed a new brainstorm), * clarify (keep researching), kill (drop it). Required before the * explore decide door can finalize; meaningless for other types. */ exploreDecision?: ExploreDecision; } /** The three doors an explore mission can end at. */ export declare const EXPLORE_DECISIONS: readonly ["go", "clarify", "kill"]; export type ExploreDecision = (typeof EXPLORE_DECISIONS)[number]; export declare function defaultState(): SenaiState; export declare function loadState(cwd: string): SenaiState; /** * Persist the run state to `.IDE_Plans/pi-senai/state.json`. * * Lock contract: every caller that mutates `state.json` (advanceStage, * recordDiscussion, setMissionBriefPath, plus any future helper) must run * inside `withRunLock()` at the call site. The lock prevents two concurrent * Pi sessions from racing on this file. `saveState` itself only guarantees * atomicity against process crashes (temp file + rename). */ export declare function saveState(cwd: string, state: SenaiState): void; /** Brainstorm fields carried into a fresh run when /senai-plan starts * without adopting a brainstorm run id (e.g. the brief file is missing). * Without this, plan/implement gates keyed on missionType would silently * no-op for those runs. */ export interface StartRunCarry { missionType?: MissionType; brainstormRunId?: string; } export declare function startRun(cwd: string, mission: string, carry?: StartRunCarry): SenaiState; /** Generate a brainstorm run id from a seed. Format matches makeRunId but * carries a `brainstorm-` prefix so brainstorm and plan run ids never * collide on the same minute stamp. */ export declare function makeBrainstormRunId(seed: string): string; /** Start (or resume) a brainstorm session. Generates a brainstorm run id, * creates the Brainstorm// folder, and persists the id in state.json * without touching currentStage (brainstorm is orthogonal to the stage * machine — see skills/senai-brainstorm.md). The folder is created on * demand by the next recordDiscussion call; this helper only registers * the id and writes state. */ export declare function startBrainstorm(cwd: string, seed: string, state: SenaiState): SenaiState; /** Mark the understanding as user-confirmed (the hard lock release for the * new brainstorm flow). Also records the mission type picked in UNDERSTAND. */ export declare function confirmUnderstanding(cwd: string, state: SenaiState, missionType: MissionType): SenaiState; /** Persist the scan kinds selected at the scan-plan gate. */ export declare function setScansSelected(cwd: string, state: SenaiState, scans: ScanType[]): SenaiState; /** Record the explore decide door (go / clarify / kill). Only meaningful for * explore missions; guardApproveReadiness requires it before finalize. */ export declare function setExploreDecision(cwd: string, state: SenaiState, decision: ExploreDecision): SenaiState; /** End the brainstorm session: clears the brainstorm session fields so the * next /senai-brainstorm mints a fresh id instead of resuming a finished * one. Called at /senai-brainstorm entry when the previous explore mission * already finalized (plan-road missions keep their id for /senai-plan * adoption). */ export declare function endBrainstormSession(cwd: string, state: SenaiState): SenaiState; /** Add or update one brainstorm question (matched by id) and persist. * The DISCUSS loop calls this after every state change so the per-question * ledger survives restarts. */ export declare function upsertBrainstormQuestion(cwd: string, state: SenaiState, question: BrainstormQuestion): SenaiState; /** Increment the dispatch counter for the active brainstorm and persist. * Callers should ALSO call guardDispatchCount BEFORE invoking this — the * guard refuses when the next count would exceed BRAINSTORM_DISPATCH_CAP. * This helper assumes the caller has already validated. */ export declare function incrementBrainstormDispatchCount(cwd: string, state: SenaiState): SenaiState; /** Persist a reference to a brainstorm brief that fed the active run. * Called from /senai-plan when it adopts a brainstorm run id. The brief * has already been copied into the run folder by that point. */ export declare function setBrainstormBriefPath(cwd: string, state: SenaiState, missionBriefPath: string, discussionDocPath?: string): SenaiState; export declare function advanceStage(cwd: string, state: SenaiState, nextStage: Stage, stageResult?: string): { ok: true; state: SenaiState; } | { ok: false; reason: string; }; export declare function resetState(cwd: string): void; /** Append a finalized discussion event and bump the discussions counter. * Pure state helper; the caller writes the file system changes before * calling this, so on a partial failure the brief/transcript are on disk * but the counter is stale (doctor warns on that). */ export declare function recordDiscussion(cwd: string, state: SenaiState, event: DiscussionEvent): { ok: true; state: SenaiState; } | { ok: false; reason: string; }; /** Persist a reference to the pre-run brief that fed the active run. * Called from /senai-plan when it consumes a pre-run discussion. */ export declare function setMissionBriefPath(cwd: string, state: SenaiState, missionBriefPath: string): SenaiState;