/** * Release operations backed by the canonical `releases` SQLite table. * * Originally migrated from `.cleo/releases.json` into `release_manifests` * by T5580. T9686-B2 unified `release_manifests` into the new `releases` * table (T9508) and dropped the legacy table — every read/write here now * targets the single canonical `releases` table via Drizzle ORM. * * T9756 (T9738-D / A4) eliminated the dual PK shape introduced by T9686-B2. * Every row — legacy-migrated AND new-pipeline — now uses the uniform * `:` PK shape. The migration at * `20260520163500_t9756-uniform-releases-pk/` rewrites historical * `legacy:` rows in place. New writes (via {@link prepareRelease} * and {@link migrateReleasesFromJson}) derive `` from * {@link generateProjectHash} so all rows share one shape. * * Provenance discrimination is no longer carried by the PK prefix. The * `tasksJson` column (NOT NULL on legacy rows, NULL on new-pipeline rows) * serves as the column-level discriminator consumed by * {@link releasesRowToManifest}. * * The status enum on the unified table is the union of both lifecycles: * New T9492: planned / pr-opened / pr-merged / published / reconciled * Legacy T5580: prepared / committed / tagged / pushed * Shared terminals: rolled_back / failed / cancelled * * @task T5580 * @task T4788 * @task T9686 (unification) * @task T9756 (uniform PK shape) */ import { createPage } from '../pagination.js'; import type { ReleaseChannel } from './channel.js'; import type { PushMode } from './release-config.js'; /** * Status values admitted by the unified `releases` table (T9686-B2). * * The status value itself discriminates the source pipeline — no separate * `pipeline` column is needed. * * - **New T9492 pipeline**: `planned | pr-opened | pr-merged | published | * reconciled` — set by `cleo release plan` / `open` / `reconcile`. * - **Legacy T5580 pipeline**: `prepared | committed | tagged | pushed` — * set by `cleo release prepare` / `commit` / `tag` / `push`. (The historic * `draft` value is retained for backward compatibility with any callers * that branch on it, but no live row in the production DB has ever held * `draft`.) * - **Shared terminals**: `rolled_back | failed | cancelled`. * * Consumers MUST handle both lifecycles. Code that needs to know which * pipeline owns a row can branch on `manifest.source` ('new' | 'legacy'), * derived from the row PK at read time. * * @task T9686 */ export type ReleaseManifestStatus = 'draft' | 'prepared' | 'committed' | 'tagged' | 'pushed' | 'rolled_back' | 'planned' | 'pr-opened' | 'pr-merged' | 'published' | 'reconciled' | 'failed' | 'cancelled'; /** Release manifest structure. */ export interface ReleaseManifest { version: string; status: ReleaseManifestStatus; createdAt: string; preparedAt?: string; committedAt?: string; taggedAt?: string; pushedAt?: string; tasks: string[]; notes?: string; changelog?: string; previousVersion?: string; commitSha?: string; gitTag?: string; /** * Provenance discriminator added by T9686. * * - `'new'` — row came from the `releases` table (new pipeline). * - `'legacy'` — row came from the `release_manifests` table (pre-T9492). * * Callers that only care about the legacy shape can ignore this field; it is * always populated by reads through `releases_view`. * * @task T9686 */ source?: 'new' | 'legacy'; } export interface ReleaseListOptions { status?: ReleaseManifest['status']; limit?: number; offset?: number; } /** Task record shape needed for release operations. */ export interface ReleaseTaskRecord { id: string; title: string; status: string; parentId?: string; completedAt?: string | null; labels?: string[]; /** Structured task type — 'epic' | 'task' | 'subtask'. Used for changelog filtering and categorization. */ type?: string; /** Task description. Used to enrich changelog entries when meaningfully different from the title. */ description?: string; } /** * Prepare a release (create a release manifest entry). * @task T4788 */ export declare function prepareRelease(version: string, tasks: string[] | undefined, notes: string | undefined, loadTasksFn: () => Promise, cwd?: string): Promise<{ version: string; status: string; tasks: string[]; taskCount: number; }>; /** * List all releases from the canonical `releases` table (T9686-B2 unified). * * Reads directly from the unified `releases` table — no view, no UNION, * single source of truth. New-pipeline rows and legacy-migrated rows are * returned in the same shape; the `source` discriminator is derived from * the row's `id` prefix (`legacy:` vs `:`). * * @task T4788 * @task T9686 */ export declare function listReleases(optionsOrCwd?: ReleaseListOptions | string, cwd?: string): Promise<{ releases: Array<{ version: string; status: string; createdAt: string; taskCount: number; source?: 'new' | 'legacy'; }>; total: number; filtered: number; latest?: string; page: ReturnType; }>; /** * Show release details from the canonical `releases` table (T9686-B2 unified). * * Single-table read, no view. Surfaces both new-pipeline rows (`status` in * planned/pr-opened/pr-merged/published/reconciled) and legacy-migrated * rows (`status` in prepared/committed/tagged/pushed/rolled_back). Throws * when the version is not present in the unified table. * * @task T4788 * @task T9686 */ export declare function showRelease(version: string, cwd?: string): Promise; /** * Mark release as committed (metadata only). * @task T4788 */ export declare function commitRelease(version: string, cwd?: string): Promise<{ version: string; status: string; committedAt: string; }>; /** * Mark release as tagged (metadata only). * @task T4788 */ export declare function tagRelease(version: string, cwd?: string): Promise<{ version: string; status: string; taggedAt: string; }>; /** * Run release validation gates. * @task T4788 * @task T5586 */ export declare function runReleaseGates(version: string, loadTasksFn: () => Promise, cwd?: string, opts?: { dryRun?: boolean; }): Promise<{ version: string; allPassed: boolean; gates: Array<{ name: string; status: 'passed' | 'failed'; message: string; }>; passedCount: number; failedCount: number; metadata: ReleaseGateMetadata; }>; /** * Cancel and remove a release in draft or prepared state. * Only releases that have not yet been committed to git can be cancelled. * For committed/tagged/pushed releases, use rollbackRelease() instead. * * @task T5602 */ export declare function cancelRelease(version: string, projectRoot?: string): Promise<{ success: boolean; message: string; version: string; }>; /** * Rollback a release. * @task T4788 */ export declare function rollbackRelease(version: string, reason?: string, cwd?: string): Promise<{ version: string; previousStatus: string; status: string; reason: string; }>; /** * Metadata captured during gate evaluation, returned alongside gate results. * Downstream (engine layer) uses this to determine PR vs direct push. */ export interface ReleaseGateMetadata { /** npm dist-tag channel resolved from the current branch. */ channel: ReleaseChannel; /** Whether the target branch requires a PR (branch protection detected or mode='pr'). */ requiresPR: boolean; /** Branch that should be targeted for this release type. */ targetBranch: string; /** Branch the repo is currently on. */ currentBranch: string; } /** Push policy configuration from config.release.push. */ export interface PushPolicy { enabled?: boolean; remote?: string; requireCleanTree?: boolean; allowedBranches?: string[]; /** Push mode override: 'direct' | 'pr' | 'auto' (default: 'direct'). */ mode?: PushMode; /** Override PR target branch (default: auto-detected from GitFlow config). */ prBase?: string; } /** * Push release to remote via git. * * Respects config.release.push policy: * - remote: override default remote (fallback to 'origin') * - requireCleanTree: verify git working tree is clean before push * - allowedBranches: verify current branch is in the allowed list * - enabled: if false and no explicit push flag, caller should skip * * @task T4788 * @task T4276 */ export declare function pushRelease(version: string, remote?: string, cwd?: string, opts?: { explicitPush?: boolean; mode?: PushMode; prBase?: string; epicId?: string; guided?: boolean; }): Promise<{ version: string; status: string; remote: string; pushedAt: string; requiresPR?: boolean; }>; /** * Update release status after push, with optional provenance fields. * @task T4788 * @task T5580 */ export declare function markReleasePushed(version: string, pushedAt: string, cwd?: string, provenance?: { commitSha?: string; gitTag?: string; }): Promise; /** * Mark a release as shipped (pushed) and write task→commit provenance rows * into `task_commits` unconditionally. * * Post-retirement semantics (SPEC-T9345 §12.2 · T9541): * - The `CLEO_PROVENANCE_DUAL_WRITE` env var is retired — new-table writes * are now unconditional. The legacy `release_manifests` row is preserved * (F12 backward-compat per ADR-073). * - Every task ID in `release_manifests.tasksJson` gets a corresponding row * in `task_commits` using the provided `commitSha`, * `link_kind='implements'` and `link_source='manual'`. * - On the first invocation per project after the upgrade, a sentinel flag * and audit log entry are written to record retirement (best-effort). * * @param version - Release version string (e.g. `v2026.6.0`). * @param pushedAt - ISO-8601 timestamp of the push event. * @param cwd - Optional project root override. * @param provenance - Optional commit SHA and git tag. * @returns Promise resolving to the number of `task_commits` rows inserted. * * @task T9510 * @task T9541 * @see SPEC-T9345 §8.3 * @see SPEC-T9345 §12.2 */ export declare function markReleaseShipped(version: string, pushedAt: string, cwd?: string, provenance?: { commitSha?: string; gitTag?: string; }): Promise<{ taskCommitsInserted: number; }>; /** * One-time migration: read .cleo/releases.json and insert each release into * the release_manifests table. Renames the file to releases.json.migrated on success. * * @task T5580 */ export declare function migrateReleasesJsonToSqlite(projectRoot?: string): Promise<{ migrated: number; }>; //# sourceMappingURL=release-manifest.d.ts.map