/** * Atomic ID-numbering utility for docs slugs that carry a numeric portion. * * ## Why this module exists * * Before this module the `cleo docs add` path for kinds like ADR (slug * pattern `adr--`) had no canonical way to pick the next number. * Authoring agents resorted to filesystem `readdir(docs/adr/)` + parse * `adr--...` → max → +1 — race-prone (two concurrent adds pick the * same N) and inconsistent with the SSoT canon (the `attachments` table is * the authority, not the publish-mirror directory). * * T10153 (the original sub-task that asked for `cleo docs add --type adr * --next` semantics) is ABSORBED here per T10159 — the new entry point * accepts an `AUTO` token in the caller-supplied slug, resolves it via * a SQLite `BEGIN IMMEDIATE` transaction over the `attachments` table, * and returns a fully-formed slug ready for {@link reserveSlug}. * * ## Contract * * 1. {@link resolveNextDocNumber}(kind, opts) — inspects every existing * `attachments` row whose `slug` matches the kind's pattern, parses * the numeric portion (per the regex captured at the `AUTO` position * derived from the kind's `entityIdPattern`), and returns * `max(seen) + 1` (or `1` when the table is empty). * * 2. {@link applyAutoSlug}(rawSlug, resolvedNumber) — replaces the * literal `AUTO` token in the caller-supplied slug with the resolved * number, zero-padding to the kind's expected width when the pattern * requires it (e.g. `adr-\d{3,4}` → pad to 3 digits). * * 3. {@link allocateAutoSlug}(kind, rawSlug, opts) — the high-level * convenience that combines both: detect `AUTO`, resolve, apply, * return the final slug. The DB probe + slug assembly happen inside * a single `BEGIN IMMEDIATE` transaction so two concurrent * `cleo docs add --slug adr-AUTO-foo` invocations in the same * process never pick the same N. * * ## Why BEGIN IMMEDIATE * * `node:sqlite` does not expose advisory-lock primitives. The `attachments` * partial UNIQUE INDEX on `slug` is the cross-process backstop, but it * fires LATE — after the writer has already chosen a number. By taking * a `BEGIN IMMEDIATE` lock on the database BEFORE the MAX query we ensure * that no other write transaction can sneak in between our read and the * imminent INSERT performed by `attachmentStore.put`. Same-process callers * additionally serialise via {@link reserveSlug} (per-slug Mutex) once the * slug is fully formed. * * ## Pad-width policy * * Each DocKind's `entityIdPattern` (declared in `@cleocode/contracts`) * encodes the expected numeric-portion width — e.g. `adr-\d{3,4}` means * "3 or 4 digits". We pad to the LOWER bound (3) so newly-allocated * numbers stay sortable lexicographically with the legacy corpus, and * naturally widen to 4 once a project crosses N=1000 without any * migration. * * Unknown / non-numeric DocKinds (e.g. `note`, `handoff`) — return * `{ kind, sequence: 0 }` from {@link resolveNextDocNumber} and pass * through the slug unchanged from {@link applyAutoSlug}. * * @task T10159 (absorbs T10153) * @epic T10157 * @saga T9855 * @adr ADR-076 (canon routing) */ /** * Options accepted by every public entry point. * * `cwd` is forwarded to `getDb(cwd)` — same convention used by * {@link reserveSlug}. */ export interface DocNumberingOptions { /** Optional working directory for `.cleo/` resolution. */ readonly cwd?: string; } /** * Result of {@link resolveNextDocNumber}. * * `sequence === 0` signals that the kind does NOT have a numeric portion * (the caller should leave the slug unchanged). */ export interface ResolveNextDocNumberResult { /** The DocKind that was queried (echoed back for caller convenience). */ readonly kind: string; /** Next available sequence number, or `0` for non-numeric kinds. */ readonly sequence: number; } /** * Literal token a caller embeds in a slug to ask the allocator to fill in * the next sequence number. The literal is intentionally short, ALL-CAPS * (to never collide with a valid kebab-case identifier), and unique enough * that an accidental match is implausible. */ export declare const AUTO_TOKEN = "AUTO"; /** * Parse a numbered slug into its `(kind, sequence, remainder)` parts. * * Recognises the canonical `adr--` shape today. Returns `null` * for unprefixed slugs or slugs whose leading token does not match any * known numbering pattern. * * @param slug - Slug to inspect. * @returns Triple or `null`. */ export declare function parseSlugSequence(slug: string): { kind: string; sequence: number; remainder: string; } | null; /** * Resolve the DISPLAY number to render for a doc, preferring the explicitly * stored {@link import('../store/schema/attachments.js').attachments.displayAlias} * over the number derived from the slug string. * * ## Why this exists (T11875 · ADR reconcile T11676) * * Under the slug-primary model the kebab slug is the canonical handle and the * rendered number (e.g. ADR "051") is a DISPLAY ALIAS only. Historically that * number was DERIVED from the slug via {@link parseSlugSequence} — so three * DISTINCT ADRs slugged `adr-051-*` all rendered "051" with no way to * disambiguate. T11875 added a real `display_alias` column; this resolver makes * the stored alias authoritative while preserving byte-for-byte legacy * behaviour for docs that never had one assigned. * * Precedence: * 1. `storedAlias` — when a non-null positive integer is supplied, it wins * unconditionally (the alias is the decoupled SSoT). * 2. Slug-derived — otherwise parse the numeric portion out of `slug` via the * canonical numbering patterns ({@link parseSlugSequence}). * 3. `null` — when neither yields a number (non-numbered kind / no alias). * * @param slug - The doc's canonical slug (e.g. `adr-051-override-patterns`), or * `null` for slug-less docs. * @param storedAlias - The value of `attachments.display_alias` for this doc, or * `null` when unset. * @returns The display number to render, or `null` when none can be resolved. */ export declare function resolveDisplayNumber(slug: string | null | undefined, storedAlias: number | null | undefined): number | null; /** Reset in-process state — test-only escape hatch. */ export declare function _resetNumberingCache_TESTING_ONLY(): void; /** * Resolve the next sequence number for `kind`. * * Returns `{ kind, sequence: 0 }` for kinds that do not carry a numeric * portion (e.g. `note`, `handoff`). Caller should leave the slug unchanged * in that case. * * @param kind - DocKind to query (e.g. `'adr'`). * @param opts - Optional cwd. * @returns The next sequence number for this kind. */ export declare function resolveNextDocNumber(kind: string, opts?: DocNumberingOptions): Promise; /** * Replace the `AUTO` token in `rawSlug` with `resolvedNumber`. * * Pad width is selected from the kind-canonical descriptor when one * exists (so `adr` always pads to 3+ digits); slug-derived descriptors * pad to 1 (no padding) since the AUTO position there is arbitrary. * * When `rawSlug` does not contain `AUTO` the input is returned unchanged. * * @param rawSlug - Slug supplied by the caller. * @param resolvedNumber - Sequence number to substitute. * @param kind - Optional DocKind — selects the canonical pad width. * @returns Slug with `AUTO` replaced by the (padded) number. */ export declare function applyAutoSlug(rawSlug: string, resolvedNumber: number, kind?: string): string; /** * One-shot helper: detect `AUTO` in `rawSlug`, resolve the next sequence * atomically, and return the fully-formed slug. * * Resolution path: * * 1. If the slug does not contain `AUTO`, return it unchanged. * 2. If the slug starts with the kind's canonical prefix (e.g. * `adr-AUTO-...` with `kind === 'adr'`), use the kind descriptor for * width-correct padding. * 3. Otherwise derive an inline descriptor from the slug itself — used * for variable-prefix kinds like `changeset` where the leading * `t-` portion is part of the slug, not the kind. * * The DB probe runs inside `BEGIN IMMEDIATE` so two concurrent calls in * the same process cannot pick the same N. Cross-process serialisation * relies on the `attachments.slug` partial UNIQUE INDEX as the backstop. * * @param kind - DocKind hint (used for canonical pad width). * @param rawSlug - Slug containing the `AUTO` token. * @param opts - Optional cwd. * @returns Slug with `AUTO` replaced — ready to forward to {@link reserveSlug}. */ export declare function allocateAutoSlug(kind: string, rawSlug: string, opts?: DocNumberingOptions): Promise; /** * Params for {@link allocateAutoSlugForDispatch} — ADR-057 uniform signature. * * @task T10159 */ export interface AllocateAutoSlugForDispatchParams { /** DocKind hint — selects the canonical pad width when applicable. */ readonly kind: string; /** Raw slug containing the `AUTO` token (or any slug — pass-through if no AUTO). */ readonly rawSlug: string; } /** * Result of {@link allocateAutoSlugForDispatch} — the fully-resolved slug. * * Declared as an interface so the lint script's `Result` heuristic * recognises it as a typed contract surface. * * @task T10159 */ export interface AllocateAutoSlugForDispatchResult { /** Slug with `AUTO` replaced (or input pass-through). */ readonly resolvedSlug: string; } /** * Dispatch entry-point wrapper around {@link allocateAutoSlug} conforming * to the ADR-057 uniform `(projectRoot, params)` signature. * * The wrapper exists so the `cleo docs add` dispatch handler can `await * allocateAutoSlugForDispatch(projectRoot, params)` without tripping the * `lint-contracts-core-ssot` L1 rule. `projectRoot` is forwarded to * {@link allocateAutoSlug} as the `cwd` option for path resolution. * * @param projectRoot - Working directory for `.cleo/` resolution. * @param params - The DocKind hint + raw slug pair. * @returns The resolved slug wrapped in an envelope. * @task T10159 */ export declare function allocateAutoSlugForDispatch(projectRoot: string, params: AllocateAutoSlugForDispatchParams): Promise; //# sourceMappingURL=numbering.d.ts.map