/** * Changeset frontmatter parser — pure-function variant of * {@link parseChangesetFile} that operates on an in-memory string body rather * than a filesystem path. * * ## Why a second parser? * * {@link parseChangesetFile} is the canonical reader used by the linter and * the release-plan aggregator: it accepts a path, reads the file, splits * frontmatter, and re-validates the slug against the filename. That contract * fits the read side perfectly. * * The write side ({@link writeChangesetEntry} delegation from * `cleo docs add --type changeset`) needs a slightly different shape: * 1. The input bytes already live in memory (the dispatch handler just * read them via `fs/promises.readFile`). * 2. The slug comes from the CLI `--slug` flag — there is no file on disk * yet, so the filename-vs-id cross-check inside `parseChangesetFile` * cannot apply. * 3. Missing frontmatter MUST be reported as a structured `{ error }` so * the caller can map it to the `E_REQUIRES_CHANGESET_VERB` envelope * that hints the operator at `cleo changeset add`. Throwing here would * force the caller into try/catch + string-match — every callsite * already discriminates on tagged unions and we want to keep that * consistency. * * Both parsers consume the same {@link ChangesetEntrySchema}, so the * structural contract is shared — there is no divergence in what counts * as a valid entry. * * @task T10367 * @epic T10290 * @saga T10288 */ import { type ChangesetEntry } from '@cleocode/contracts'; /** * Discriminated outcome of {@link parseChangesetFrontmatter}. * * On success the `entry` is a fully-validated {@link ChangesetEntry}. On * failure the `error` discriminator carries enough context for the caller * to map it to a CLI envelope without re-parsing: * * - `'missing-frontmatter'` — file lacks `---` fences entirely (the * most common reason `cleo docs add --type changeset` should redirect * the operator at `cleo changeset add`). * - `'missing-required'` — frontmatter present but at least one * required schema field is absent. `missing` lists the field names * (top-level only). * - `'yaml-invalid'` — frontmatter present but unparseable — * surfaces the underlying parser message + 1-based line offset (if * the YAML parser localised it) for human-friendly diagnostics. * - `'schema-invalid'` — every required field is present but at * least one validation rule failed (e.g. bad `kind`, malformed task * ID). `issues` is the human-readable bullet list. */ export type ParseChangesetFrontmatterResult = { readonly ok: true; readonly entry: ChangesetEntry; } | { readonly ok: false; readonly error: 'missing-frontmatter'; } | { readonly ok: false; readonly error: 'missing-required'; readonly missing: readonly string[]; } | { readonly ok: false; readonly error: 'yaml-invalid'; readonly parserMessage: string; readonly line?: number; } | { readonly ok: false; readonly error: 'schema-invalid'; readonly issues: readonly string[]; }; /** * Parse changeset frontmatter from an in-memory markdown body. * * The body must follow the same `---`-fenced YAML + markdown-body layout * that {@link parseChangesetFile} consumes. The optional markdown body * after the closing fence is merged into the entry as `notes` (only when * the frontmatter did not already set the field). * * Unlike {@link parseChangesetFile} this function does NOT cross-check the * `id` against a filename — the slug comes from the CLI flag and the entry * is what we are about to write. Cross-checking the slug against the * frontmatter `id` is the caller's responsibility (the dispatch handler * does this and emits `E_SLUG_MISMATCH` when they diverge). * * @param raw - Full markdown body (frontmatter + body) as a string. * @returns Discriminated outcome — never throws. * * @task T10367 * * @example * ```ts * const raw = `--- * id: t10367-example * tasks: [T10367] * kind: feat * summary: example entry * --- * Body text here. * `; * const out = parseChangesetFrontmatter(raw); * if (out.ok) { * console.log(out.entry.id); // 't10367-example' * } * ``` */ export declare function parseChangesetFrontmatter(raw: string): ParseChangesetFrontmatterResult; //# sourceMappingURL=parse-frontmatter.d.ts.map