/** * Shared types for the roadmap → GitHub Issues projection. * * The projection is one-way: disk is the source of truth and the tracker is a * rendering of it. Two consequences show up throughout these types. * * First, `sync` writes nothing to disk (design DD-1). There is no `remote:` * frontmatter field and no cache: the durable link is the item `id`, carried in a * body marker on the issue. Creating an issue and writing a local link would be two * writes to two systems, and every failure of the second produces a duplicate on the * next run. * * Second, an outcome is a *value*, not an action. `planProjection` returns a plan of * outcomes and the command layer decides what to do with it, which is what makes the * whole projection testable with no network and no `gh` binary. * * Requirements: roadmap-issue-projection/AC-14, roadmap-issue-projection/AC-46 * * @module */ import type { RoadmapStatus } from '../paths.js'; /** * What the projection concluded about one item, or one issue. * * A closed set. The design review found that an open-ended vocabulary made two * criteria unachievable — "the second plan contains only X or Y" cannot be checked * against a list nobody wrote down. */ export type Outcome = 'create' | 'update-title' | 'update-labels' | 'update-body' | 'close' | 'unchanged' | 'skip' | 'state-divergent' | 'region-corrupt' | 'ineligible-with-issue' | 'orphan-missing' | 'label-without-marker' | 'milestone-create' | 'milestone-update' | 'milestone-close' | 'milestone-reopen' | 'update-milestone' | 'milestone-orphan'; /** The outcomes that write. Everything else is a report. */ export declare const WRITE_OUTCOMES: readonly Outcome[]; /** * Outcomes that suppress every write for their item (design DE-18). * * `state-divergent` means a human closed the issue while the item is still live. * Reopening it would relocate the two-writer loop rather than remove it, and * retitling it would write to an issue somebody deliberately closed. So a terminal * report is the *only* line its item emits. */ export declare const TERMINAL_REPORTS: readonly Outcome[]; /** * Execution order within one item: edit while the issue is still open, then close. The * milestone is set after `create`, because it needs the number `create` returns * (github-milestones/AC-7). */ export declare const WRITE_ORDER: readonly Outcome[]; /** True when this outcome performs a `gh` write. */ export declare function isWrite(outcome: Outcome): boolean; /** * The frontmatter facts a reader with no checkout cannot otherwise see. * * Every field is nullable and `null` means *not recorded*. It is never coerced to an * empty string or a guess: an item with no `author` is a normal item, and rendering * `not recorded` tells a reader something true, where a blank tells them nothing and an * invented value tells them something false. */ export interface ItemFacts { createdAt: string | null; author: string | null; source: string | null; promotedTo: string | null; /** Item ids, in file order. Empty when the key is absent or holds no usable ids. */ dependsOn: readonly string[]; } /** An item that can be projected: parseable, with an id. */ export interface ProjectableItem { id: string; /** Falls back to the filename slug when frontmatter carries no title. */ title: string; status: RoadmapStatus; filename: string; /** Repo-relative path, for the issue body. */ relPath: string; /** Everything below the frontmatter, verbatim. Projected after sanitization. */ body: string; facts: ItemFacts; } /** * Why an item cannot be projected. * * The two classes are distinguished because their remedies differ and only one of * them has a tool. `aidlc doctor` repairs an unquoted title; nothing reports an item * that parses fine with no `id`, so pointing a user at `doctor` for that class would * send them to a command that says nothing (design DE-14). */ export type UnprojectableClass = 'unparseable' | 'missing-id'; export interface UnprojectableRef { /** `/` — the form doctor output uses. */ label: string; status: RoadmapStatus; cls: UnprojectableClass; reason: string; } /** * One issue as the projection needs it. * * `labels` is already flattened to names. `gh issue list --json labels` returns * objects (`{id,name,description,color}`), and treating them as strings would make * every status-label comparison fail — so every item would plan `update-labels` on * every run, forever, and the projection would never converge (design DE-9). */ export interface RemoteIssue { number: number; title: string; state: 'OPEN' | 'CLOSED'; labels: readonly string[]; body: string; /** * The milestone number, null for none. Absent when discovery did not ask for it — which it * does only while milestones are on, so the off argv is today's (github-milestones/AC-15). */ milestone?: number | null; } /** * A milestone as a plan line carries it: the wanted title, description and state, and the * GitHub number when one already exists. Null `number` means this run creates it. */ export interface MilestoneRef { id: string; title: string; description: string; number: number | null; state: 'open' | 'closed'; } /** One line of a plan. */ export interface PlanLine { outcome: Outcome; /** null on issue-only lines (`orphan-missing`, `label-without-marker`). */ item: ProjectableItem | null; issueNumber: number | null; /** Short reason, e.g. `not-backfilled`, or the divergence in words. */ detail: string | null; addLabels: readonly string[]; removeLabels: readonly string[]; /** * The exact bytes to send, on `create` and `update-body`; null on every other outcome. * * Decided in the pure planner rather than in `execute.ts` (design DD-6). The merge that * produces it has four branches and has to preserve a human's additions, which is * exactly the kind of logic that should not live in the one module capable of * irreversible writes — and here it is testable with no `gh` at all. */ newBody: string | null; /** * On a `milestone-*` line, the milestone. On `update-milestone`, the one to set, or null to * clear it. Optional so a plan with milestones off is shaped exactly as before. */ milestone?: MilestoneRef | null; } export interface Plan { /** * Printed in the plan header *and* the confirmation prompt. A projection aimed at * the wrong repository is only reversible before the first write, so the resolved * target is stated at both moments a human can still stop it. */ repoLabel: string; /** `owner/name`, or null when `gh` resolves the current repository. */ repo: string | null; lines: readonly PlanLine[]; /** Only outcomes actually present appear as keys. */ counts: Readonly>>; writeCount: number; } //# sourceMappingURL=types.d.ts.map