/** * Roadmap layout: where items live, and how to read and move one * (roadmap-revamp/AC-2, AC-6, AC-30, AC-32). * * Shared by the two consumers that must agree byte-for-byte about the layout — * the `roadmap-done` lifecycle action and the `roadmap-layout` doctor migration. * Duplicating the directory names or the frontmatter surgery in both would make * the migration and the action capable of disagreeing about what a valid item is. * * Status is the containing directory and nothing else (DD-1). Nothing in this * module reads a `status` field, and `writeHistoryEntry` never writes one. * * @module */ /** The five status directories, in lifecycle order. */ export declare const ROADMAP_STATUSES: readonly ["inbox", "backlog", "in-progress", "done", "hold"]; export type RoadmapStatus = (typeof ROADMAP_STATUSES)[number]; /** Repo-relative roadmap root. */ export declare const ROADMAP_DIR = ".aidlc/roadmap"; /** Repo-relative directory the roadmap replaces. */ export declare const LEGACY_IDEAS_DIR = ".aidlc/ideas"; /** `YYYYMMDD-slug.md` — the only filename shape an item may have. */ export declare const ITEM_FILENAME_RE: RegExp; /** * The primary checkout's copy of the project. * * Items always live in the primary checkout, never a worktree's copy: a backlog * written inside a feature worktree is stranded on that branch until it merges, * which for an inbox is indistinguishable from losing it. The project's copy, not the * checkout's top: a project below the repository root keeps its roadmap under its own * `.aidlc/`, and the top has none (repo-root-is-not-the-project/AC-6). Outside a git * repo, `cwd` is all there is. * * Still the primary checkout, found from the git common dir as `gitCommonDir` resolves * it, and never the nearest `.aidlc/` the project-root walk finds from a worktree: that * rule would strand an item on the worktree's branch (cwd-project-root/AC-18). `cwd` is * the resolved project root, as every caller passes it. */ export declare function primaryCheckout(cwd: string): string; /** Absolute roadmap root for the primary checkout containing `cwd`. */ export declare function roadmapRoot(cwd: string): string; /** * Every roadmap root a *lookup* should consider: this working tree first, then * the primary checkout. Deduped, so the ordinary single-checkout case is one root. * * Capture still writes to `roadmapRoot` alone — an item written inside a worktree * is stranded on that branch, and for an inbox that is indistinguishable from * losing it. Reading is the opposite problem (automation-verdicts/AC-4): an * instance finishing on a worktree branch has its item on that branch, while the * primary checkout sits on some *other* instance's branch and cannot see it. * `roadmap-done` looked only at the primary checkout, found nothing, and reported * success — twice in three days, over a clean `✓ Instance is complete`. * * This working tree comes first because the finishing branch is the more specific * answer: an item promoted on this branch is the copy about to be committed. * * Neither root derives from user input — one is the caller's resolved project * root, the other is `primaryProjectRoot()`, from git — so there is no traversal * vector to contain here. The filter is existence and absoluteness, nothing more; a * containment check that cannot fire would be one more guard seen only passing. * * Deduped by REALPATH, not by string. `primaryProjectRoot` realpaths its result while * `cwd` arrives as given, so on macOS the same directory reaches this function as * both `/var/…` and `/private/var/…`. Comparing the strings would leave two * entries for one directory in the ordinary single-checkout case — every item * found twice, and `roadmap-done`'s duplicate warning firing on every completion. */ export declare function roadmapRoots(cwd: string): string[]; /** Absolute path of one status directory. */ export declare function statusDir(cwd: string, status: RoadmapStatus): string; /** One item, as much of it as the layout cares about. */ export interface RoadmapItem { /** Absolute path on disk. */ file: string; /** Filename, e.g. `20260814-cost-tracking.md`. */ filename: string; /** Derived from the containing directory, never from frontmatter. */ status: RoadmapStatus; id: string | null; title: string | null; promotedTo: string | null; /** Full file contents, so a caller can rewrite without a second read. */ raw: string; } /** * Parse an item's frontmatter block, or null when there is none. * * Returns the raw text of the block as well as the parsed object so a caller can * edit the text in place rather than re-serializing — see `writeHistoryEntry`. */ export declare function frontmatter(raw: string): { text: string; start: number; end: number; data: Record; } | null; /** * Everything after an item's frontmatter block — the body, verbatim. * * Layout knowledge, so it lives beside `frontmatter` rather than being re-derived by each * consumer that wants the prose. An item with no parseable frontmatter has no separable * body, and returning the whole file would silently publish the YAML; the honest answer is * the empty string, and callers already have to handle an item that will not parse. */ export declare function itemBody(raw: string): string; /** Read one item. Returns null when the file is unreadable or has no frontmatter. */ export declare function readItem(file: string, status: RoadmapStatus): RoadmapItem | null; /** Why one item file will not yield frontmatter. */ export type ItemParseFailure = 'unreadable' | 'no-frontmatter' | 'unterminated-frontmatter' | 'invalid-yaml'; /** One item that cannot be read, with everything a reporter or repairer needs. */ export interface UnparseableItem { status: RoadmapStatus; filename: string; /** Absolute path on disk. */ file: string; /** `/` — the form used in doctor output. */ label: string; reason: ItemParseFailure; /** Parser message for `invalid-yaml`, else null. */ detail: string | null; } /** * Every roadmap item whose frontmatter will not parse, across all statuses. * * This is the *shared* detection the two reporting callers need. It deliberately * re-reads and re-parses rather than going through `readItem`, whose `null` * return cannot distinguish "unparseable" from "absent" — and that conflation is * the whole defect: `listStatus` skips nulls, so a malformed item is missing from * browse, from `findAllByPromotedTo`, from the `roadmap-done` action and from * `aidlc doctor`, with nothing anywhere saying so. Five of this project's first 49 * items were invisible for up to ten days, every one a backtick-initial title. * * `readItem`'s signature is left alone on purpose. Widening it to * `ok | reason` would touch every caller to buy reporting that only two of them * want; naming the failures once, here, buys the same reporting for the price of * one function. Items are walked in sorted order so output never depends on * directory read order. */ export declare function unparseableItems(cwd: string): UnparseableItem[]; /** * Quote an item's unquoted `title:` value, or null when that is not the problem. * * The only mechanical repair worth having, because it is the only failure the * capture flow actually produces: agents name a code defect the natural way, with * the identifier in backticks, and YAML reserves `` ` `` as an indicator so a * plain scalar cannot start with one. The line is rewritten as text rather than * round-tripped through the parser — the document does not parse, so there is * nothing to round-trip, and rebuilding it would rewrite fields this has no * business touching. * * Returns null unless quoting the title makes the whole block parse. That check * is the guard against a confident wrong repair: a file broken somewhere else, * or broken in the title *and* elsewhere, is left for a human and reported. */ export declare function quoteItemTitle(raw: string): string | null; /** * Every item in one status directory, sorted by filename. * * Sorted so output never depends on directory read order — the rule * `operational/packagers.ts` records for the same reason. */ export declare function listStatus(cwd: string, status: RoadmapStatus): RoadmapItem[]; /** * `listStatus` against an explicit roadmap root. * * Split out so a lookup can walk several roots (`roadmapRoots`) while every * existing caller keeps primary-checkout-only semantics unchanged. */ export declare function listStatusAt(root: string, status: RoadmapStatus): RoadmapItem[]; /** * Every DISTINCT item whose `promoted_to` names `instance`, across every root and * status. * * All five statuses rather than only `in-progress` because an item moved by hand * should still be findable — the point of a lookup keyed on `promoted_to` is that * it does not depend on the item being where we expect. All roots for the reason * on `roadmapRoots`. * * **Deduped by filename, and that is the whole subtlety.** Two roots of one * repository normally hold the SAME item: a worktree branched from `main`, or one * that has merged `main` back, has its own copy of every committed item. Counting * raw matches would make `length > 1` true on every ordinary worktree completion, * so `roadmap-done` would warn about a duplicate every single time — noise * indistinguishable from the real thing, which is precisely the failure mode this * lookup was widened to fix. * * Two DIFFERENT files naming the same instance are two items, and both are * returned. Several items may share one instance, and every caller files or * reports all of them (roadmap-done-one-item/AC-1, AC-5). There is deliberately no * one-item variant: `findByPromotedTo` returned `[0]`, and that shape is what left * the second item in `in-progress/` under a success line. * * Returns matches in root order, this working tree first. */ export declare function findAllByPromotedTo(cwd: string, instance: string): RoadmapItem[]; /** * Append a `{ status, at }` entry to an item's `history` list, textually. * * Deliberately a text edit rather than parse-mutate-reserialize. The project's * YAML serializer sorts keys alphabetically, which would reorder every item's * frontmatter and break the fixed key order AC-4 requires — and it would rewrite * lines this change has no business touching, turning a one-line append into a * whole-file diff. * * Handles the three shapes `history` can be in: a block list with entries, an * inline empty list, and a bare key with nothing under it. * * An optional `reason` is appended to the entry (inbox-intake-bar/AC-13). It is what * makes parked work distinguishable from dropped work without a sixth status directory * or a new frontmatter field: `hold/` still means "not now", and the reason says which * kind. Because `history` already sits in the frontmatter, browse reads it without * opening a body — the frontmatter-only rule that keeps browse cheap survives. * * The reason is emitted as a **double-quoted scalar, unconditionally**. This is the * `title` rule one level down and for the same reason: the entry is a YAML flow mapping, * so an unquoted reason containing `,`, `}` or `: ` ends the entry early and takes the * whole item's frontmatter with it — and "parked — gated on the accounts table, see * item-x" is exactly the string most likely to contain a comma. Quoting always removes * the class; quoting when it looks risky does not, because a comma does not look risky. * * @returns The rewritten contents, or null when there is no frontmatter to edit. */ export declare function writeHistoryEntry(raw: string, status: RoadmapStatus, at: string, reason?: string): string | null; /** * Move an item into `status`, appending the matching history entry. * * The write happens before the rename so a failure leaves the item where it was * with its old history, rather than moved with no record of the move. * * The destination is derived from **the item's own roadmap root**, not from * `cwd`. Once a lookup can span roots (`roadmapRoots`, automation-verdicts/AC-4), * `statusDir(cwd, ...)` would relocate a worktree item into the primary * checkout — a deletion on one branch and an untracked file in another tree, * which is a worse outcome than the stranding it was meant to fix. An item's * root is `//`, so it is two levels up from `item.file`. * * `cwd` is kept only as the fallback for the (impossible-in-practice) case of an * item path with no status directory above it, and so the signature stays put * for existing callers. For them the two agree by construction: their items came * from `listStatus(cwd, ...)`. * * @param at - ISO date (`YYYY-MM-DD`) for the history entry. * @param reason - Optional; required by the skills for a move into `hold/`, where it * says whether the item is parked or declined (inbox-intake-bar/AC-15). Optional here * because this function moves items in every direction, and the rule about `hold/` * belongs to the flow that decided to park something, not to the file mover. * * Writes the item and nothing else (html-roadmap-view/AC-35). It used to rebuild two * committed Mermaid graphs as well, so every promotion left two shared generated files * modified in whichever checkout ran it; `aidlc roadmap view` now draws the roadmap on * demand, outside the working tree. * * @returns The new absolute path. */ export declare function moveItem(item: RoadmapItem, cwd: string, status: RoadmapStatus, at: string, reason?: string): string; /** * The roadmap root an item currently lives under, or null when its path does not * have the expected `//` shape. */ export declare function itemRoadmapRoot(item: RoadmapItem): string | null; /** Today's date as `YYYY-MM-DD`, the form every roadmap date field uses. */ export declare function isoDate(now?: Date): string; //# sourceMappingURL=paths.d.ts.map