/** * The body of a projected issue. * * v1.21.0 emitted a fixed six-line notice carrying no item prose at all, on the grounds * that projecting prose risked marker injection, mention notifications, and the 65,536 * character field limit. The hazards were real; the conclusion was not. What shipped was a * redirect — thirty issues on a board with thirty copies of one paragraph, unreadable to * exactly the person the feature was for: somebody triaging in the tracker who will not * clone the repository. `richer-issue-bodies` answers all three hazards instead of avoiding * them: `sanitize.ts` neutralises comments and autolink triggers code-awarely, and the * budget below bounds the field. * * Two structural decisions carry the rest of the design. * * **The region is delimited, and everything outside it is somebody else's.** A body is * regenerated on every sync, so it must be possible to say precisely which bytes the * projection owns. Text before `BODY_BEGIN` or after `BODY_END` survives untouched, which * is what makes a projected issue still usable as a tracker: a reader can add a note to the * description and keep it. * * **The body is rewritten, not written once.** v1.21.0 froze it at creation, reasoning that * an update "would delete the context around [comments]". That does not hold — GitHub issue * comments are separate entities from the body, and a body edit keeps its own revision * history — and the cost of freezing is that every issue created before this change stays * thin forever while every later item edit makes its issue quietly wrong. The rejected * alternative was a one-off migration command; this project has a recorded lesson that a * capability needing a manual post-deploy command will be un-run. * * Nothing volatile may enter this module's output. A timestamp, a commit sha or a metric * would make the rendered region differ from the stored one on every single run, so every * item would plan `update-body` forever and the projection would never converge — the same * defect class as `discover.ts`'s labels-are-objects bug. That is why the region is a pure * function of the item and the repository coordinates, and why a test pins that this file * imports neither `node:fs` nor `node:child_process`. * * Requirements: richer-issue-bodies/AC-1 … richer-issue-bodies/AC-4, * richer-issue-bodies/AC-6, richer-issue-bodies/AC-7, richer-issue-bodies/AC-16, * richer-issue-bodies/AC-17, richer-issue-bodies/AC-21 … richer-issue-bodies/AC-24, * richer-issue-bodies/AC-28 * * @module */ import type { ProjectableItem } from './types.js'; /** Start of the projection-owned region. */ export declare const BODY_BEGIN = ""; /** End of the projection-owned region. */ export declare const BODY_END = ""; /** * The ceiling this module will not cross. * * GitHub's own limit is 65,536. The margin is not decoration: the largest real item in this * project's roadmap is 44,481 characters, so nothing today comes close, and the headroom is * what keeps a future item from failing a write *after* consent was given — the one moment * in the projection where a failure is expensive. */ export declare const MAX_BODY_LENGTH = 60000; /** Repository coordinates, for permalinks. */ export interface RepoCoords { owner: string; name: string; } export interface BodyContext { /** null when coordinates could not be resolved — paths then render unlinked. */ repo: RepoCoords | null; /** Item id → repo-relative path, for reference links. */ itemPaths: ReadonlyMap; } /** * A permalink to a file on the default branch. * * `blob/HEAD/` rather than a resolved branch name, and this was measured rather than * reasoned: on 2026-09-07, `github.com/cli/cli/blob/HEAD/README.md` returned 200 while * `blob/main/README.md` returned 404, because that repository's default branch is `trunk`. * Hardcoding `main` would ship broken links to every project that renamed its default * branch. `HEAD` also needs no extra `gh` call and puts no remote state in the body. */ export declare function fileUrlFor(repo: RepoCoords, relPath: string): string; /** * The projection-owned region, delimiters included. * * Facts first, because a triager reads the top of a body the way they read the top of an * item. The marker sits *inside* the region, so replacing the region can never orphan or * duplicate it. */ export declare function renderGeneratedRegion(item: ProjectableItem, ctx: BodyContext): string; /** * The region bytes of an existing body, or null when it carries no region. * * Delimiters included, so the value can be compared directly against * `renderGeneratedRegion`'s output — comparing the insides would let a delimiter change go * undetected. */ /** * Why the body's region cannot be trusted, or null when it has at most one of each * delimiter. Counted rather than located: `indexOf` picks the first terminator, which is * exactly the forged one. */ export declare function regionDelimiterProblem(body: string): string | null; export declare function extractRegion(body: string): string | null; /** * v1.21.0's body, reproduced exactly. * * **Frozen history, not a template.** Its only job is to be recognised so it can be * replaced, so it must keep emitting the bytes v1.21.0 emitted even as the real renderer * changes. A test pins it as a literal for that reason: edit it and issues created before * this change stop being recognised, and every one of them gains a new region below a stale * notice instead of replacing it. */ export declare function renderLegacyIssueBody(item: ProjectableItem): string; /** * The body to write: a fresh region merged into whatever the issue already has. * * Four cases, first match wins: * * 1. **Both delimiters present** — replace the span between them and keep everything else. * This is the steady state, and the reason a human's note survives a sync. * 2. **Exactly the legacy notice** — the region alone. An issue created by v1.21.0 stops * being thin. * 3. **Legacy notice plus other text** — replace only the recognised notice. Whatever * somebody added stays. * 4. **Anything else** — append the region below the existing body, after stripping any * marker naming *this* item, so the body ends up with exactly one. * * Case 4 deliberately leaves markers naming *other* ids alone. Those are the ambiguity * `indexByMarker` exists to refuse, and quietly deleting one would hide a genuine * two-issue collision rather than resolve it. */ export declare function mergeBody(existing: string, region: string, itemId: string): string; //# sourceMappingURL=body.d.ts.map