/** * CLEO-native changeset writer — dual-write to file + SSoT. * * Bridges the file-on-disk audit trail (`.changeset/*.md`, kept for human PR * review surface) with the canonical docs SSoT blob store. Every successful * write lands in BOTH places or NEITHER: * * 1. Renders the {@link ChangesetEntry} as `---` fenced YAML frontmatter * plus the optional markdown body (`notes`). * 2. Writes `.changeset/.md` to disk via tmp-then-rename for atomicity. * 3. Stores the SAME bytes as a content-addressed blob via the attachment * store with `extras: { type: 'changeset', slug: '' }`. * * If step 3 fails the file from step 2 is removed. If step 2 succeeds but the * SSoT row is partially written, the attachment ref is dereferenced. This * keeps the two surfaces eventually-consistent under crash semantics. * * The slug is validated against the `changeset` kind's `entityIdPattern` * (`/^t\d+-[a-z0-9-]+$/`) from {@link DocKindRegistry}. Invalid slugs return * `E_SLUG_PATTERN_MISMATCH` so the operator can correct the input. * * @epic T9793 (E-DOCS-CHANGESET-INTEGRATION) * @task T9793 * @see ADR-068 — DB Charter: changeset bytes live in manifest.db blob store * @see docs-taxonomy.ts — `changeset` kind registry entry */ import { type ChangesetEntry } from '@cleocode/contracts'; /** * Options accepted by {@link writeChangesetEntry}. */ export interface WriteChangesetOptions { /** * Absolute path to the project root containing `.changeset/`. * * Resolved to `.changeset/.md` for the file write and used to * locate the attachment SSoT (`.cleo/attachments/index.db`). */ readonly projectRoot: string; /** * Identity to record on the SSoT row. * * Defaults to `'cleo-changeset'`. Surfaces in `attachment_refs.attachedBy` * so operators can audit which CLI surface created the entry. */ readonly attachedBy?: string; } /** * Result of a successful dual-write. */ export interface WriteChangesetResult { /** Absolute path to the written `.changeset/.md`. */ readonly filePath: string; /** Slug used (matches the filename without `.md`). */ readonly slug: string; /** Attachment ID (`att_`) of the SSoT blob. */ readonly attachmentId: string; /** SHA-256 of the bytes written to BOTH surfaces. */ readonly sha256: string; /** Owner task ID — the first task in the entry's `tasks` array. */ readonly ownerId: string; } /** * Discriminated error result for {@link writeChangesetEntry}. * * `E_SLUG_RESERVED` (T10388, Saga T10288, Epic T10289) is surfaced by the * central slug-allocator chokepoint when another writer has already claimed * the slug — either in the SAME process (in-process reserved set) or another * process (DB-level `uniq_attachments_slug` UNIQUE INDEX). The `suggestions` * field carries exactly 3 free alternatives derived by * `deriveSlugSuggestionsForAllocator`. The `aliases` field retains * `'E_SSOT_WRITE_FAILED'` for one-release back-compat — downstream consumers * grepping for the legacy code can match `aliases.includes(...)`. */ export type WriteChangesetError = { readonly code: 'E_SLUG_PATTERN_MISMATCH'; readonly message: string; readonly example?: string; } | { readonly code: 'E_INVALID_ENTRY'; readonly message: string; } | { readonly code: 'E_FILE_WRITE_FAILED'; readonly message: string; } | { readonly code: 'E_SLUG_RESERVED'; readonly message: string; readonly suggestions: readonly string[]; readonly aliases: readonly string[]; } | { readonly code: 'E_SSOT_WRITE_FAILED'; readonly message: string; }; /** * Discriminated union returned by {@link writeChangesetEntry}. */ export type WriteChangesetOutcome = { readonly ok: true; readonly result: WriteChangesetResult; } | { readonly ok: false; readonly error: WriteChangesetError; }; /** * Render a {@link ChangesetEntry} as the canonical `---`-fenced markdown form. * * The output matches what {@link parseChangesetFile} round-trips: identical * fields, identical YAML scalar style for arrays, identical body trim rules. * * @internal */ export declare function renderChangesetMarkdown(entry: ChangesetEntry): string; /** * Dual-write a changeset entry to BOTH `.changeset/.md` AND the docs * SSoT blob store. Either both writes succeed, or neither persists. * * The owner task ID for the SSoT row is the FIRST entry in `entry.tasks` — * subsequent task IDs ride along in the changeset's `tasks` field but the * blob is anchored to one owner (the convention used elsewhere by the docs * domain). Aggregators read the full `tasks` array from the parsed markdown. * * @param entry - Pre-validated changeset entry (schema-validated again here * to keep this function safe to call with raw user input). * @param opts - Project root + optional `attachedBy` identity. * @returns Discriminated outcome with `result` on success or `error` on any * failure. NEVER throws — file or SSoT errors map to `E_*` codes so callers * can surface them as LAFS envelopes. * * @example * ```ts * const outcome = await writeChangesetEntry( * { * id: 't9793-changeset-ssot', * tasks: ['T9793'], * kind: 'feat', * summary: 'Changeset DocKind becomes SSoT-first via dual-write.', * }, * { projectRoot: '/path/to/repo' }, * ); * if (outcome.ok) { * console.log(outcome.result.filePath); * console.log(outcome.result.attachmentId); * } * ``` */ export declare function writeChangesetEntry(entry: ChangesetEntry, opts: WriteChangesetOptions): Promise; //# sourceMappingURL=writer.d.ts.map