import type { CliMessages } from './i18n/index.js'; import type { Revision } from './git.js'; import type { BaselineBreach, BaselineComparison, Locale, PromptComparison, PromptProfile, RuleLevel } from '@trazum/core'; import type { AgainstDriver, BillLevers, CacheEconomics, ContextPressure, RepriceReport, UsageProfileReport } from '@trazum/core'; /** * Markdown for the places a pull request is actually read. * * One renderer, two destinations. The GitHub step summary and a PR comment want * the same numbers with different framing, and the numbers come from the same * verdicts the terminal report prints — so a discrepancy between what a * reviewer reads on the pull request and what the job log said is impossible by * construction rather than by care. * * Nothing here knows the name `GITHUB_STEP_SUMMARY`. The CLI writes a file; the * Action decides what that file is for. That keeps `trazum` a tool you can run * on your laptop and read the output of. */ /** GitHub rejects a comment body over 65,536 characters. */ export declare const MAX_COMMENT_CHARS = 60000; /** A step summary is capped at 1 MiB. Well under it, and honest when it trims. */ export declare const MAX_SUMMARY_CHARS = 900000; /** * A value fit to sit in a table cell. * * Paths come from a repository, and on a pull request that means from whoever * opened it. `prompts/a|b``c\|d.txt` is a legal POSIX filename, and each of * those characters breaks a markdown table in its own way. * * **This emits `` with HTML entities rather than a backtick span, and the * reason is that the entity version has no failure mode to reason about.** The * first version did the obvious thing — wrap in backticks, escape `|` as `\|`, * widen the fence past the longest backtick run — and CodeQL was right to flag * it: it did not handle a backslash. Given `a\|b.txt` it emitted `` `a\\|b.txt` ``, * and whether that survives depends on whether the row splitter reads `\\|` as * an escaped pipe or as an escaped backslash followed by a live one. It happens * to work in cmark-gfm today. An escaper whose correctness rests on that is not * an escaper. * * With entities there is **no `|` character in the output at all**, so the row * cannot split under any scanner; backticks inside `` are literal, so the * fence arithmetic disappears; and a backslash needs no treatment. Three hazard * classes collapse into one rule: encode `&`, `<`, `>` and `|`. * * Newlines still have to go — anything vertical ends the row — so they become a * single space. */ export declare function mdCell(value: string): string; /** * A value fit to sit inline in prose. * * For values that are *words* — a model's display name. Paths go through * `mdCell`, because a path is code and a code span is both safer and less ugly: * escaping every `.` and `-` turned `a.txt` into `a\.txt`, which renders * correctly and reads like a bug to anyone who sees the source. * * So the escaped set is only what can change meaning **mid-line**: emphasis, * code spans, links, autolinks and table cells. `#`, `-`, `+` and `.` are * block-level constructs that need to start a line to mean anything, and the * newline collapse above guarantees this value never does. */ export declare function mdText(value: string): string; /** * Untrusted **prose** in a table cell. * * `mdCell` is for values that are code — a path, a sha — and it says so by * wrapping them in ``. A commit subject and an author's name are neither. * Rendering `David Muñoz Rey` in a table typesets somebody's name as * a code span, and `fix: the rules only trimmed in two languages` * does the same to a sentence. Both were wrong in the first draft of the blame * report, and only visible once it was rendered. * * The safety is `mdCell`'s, unchanged, for the same reason: **entities, so there * is no `|` in the output at all** and the row cannot split under any scanner. * `mdText`'s backslash escaping is complete and would also survive a cell, but it * puts the correctness on a reader's ability to see that `\\\|` is an escaped * backslash followed by an escaped pipe. Nothing here should need that. * * Then the inline-markdown set on top, which `mdCell` does not need because * backticks make its content literal. A subject reading `fix *everything*` would * otherwise arrive in italics, and two backticks in one would open a code span — * cosmetic rather than dangerous, and still not what the author wrote. */ export declare function mdTextCell(value: string): string; /** Truncates a body to fit, saying so rather than trailing off. */ export declare function fitWithin(body: string, limit: number, notice: string): string; export interface MarkdownFileVerdict { path: string; tokens: number; /** null when no budget covers this file. */ maxTokens: number | null; /** The config pattern the budget came from, if any. */ pattern: string | null; /** Only set when the file is over budget. */ optimizedTokens: number | null; } /** * The cost diff, when the run had a baseline to compare against. * * Everything here is already computed by the time the report is rendered; this * carries it rather than recomputing, so the comment on a pull request and the * exit code can never disagree about whether the branch got more expensive. */ export interface BaselineMarkdown { comparison: BaselineComparison; breached: BaselineBreach[]; /** Recomputed monthly cost, and whether it is comparable to the baseline's. */ money: { before: number; after: number; comparable: boolean; }; /** The file to re-record, named so the reader can act without looking it up. */ path: string; } export interface CheckMarkdownInput { /** The directory or file the run was pointed at. */ target: string; verdicts: MarkdownFileVerdict[]; level: RuleLevel; tokenSource: 'heuristic' | 'external'; /** * The widest band across the prompts this report covers. * * Widest and never averaged: a report over a prose prompt and a CSV covers * text the estimator is 6% and 33% out on, and a figure between them * describes neither. The caller holds the texts and computes it. */ band: number; /** True when a walk limit stopped the run early. */ truncated: boolean; /** Absent when no baseline governed the run. */ baseline?: BaselineMarkdown; t: CliMessages; } export declare function renderCheckMarkdown(input: CheckMarkdownInput): string; export interface DiffMarkdownInput { comparison: PromptComparison; beforePath: string; afterPath: string; /** True when the figures came from the optimised text rather than as written. */ optimized: boolean; locale: Locale; t: CliMessages; } /** * The diff report as markdown. * * Carries the sign convention into the heading, because this is the one place a * reader arrives with no context: every number is `after - before`, and positive * means worse. Getting that wrong in a PR comment would be worse than not * commenting. */ export declare function renderDiffMarkdown(input: DiffMarkdownInput): string; /** * Wraps a report for a pull request comment. * * **Collapsed when there is nothing wrong**, and that is the decision worth * defending. A green table that stays green on every push is the thing a * maintainer learns to skip — and once they skip it, they skip the red one too. * Expanded means something needs reading. * * The marker is an HTML comment, invisible in the rendered comment and stable * across pushes, so the poster can find its own previous comment and replace it * rather than adding another. `key` separates two runs that legitimately post * about different things in the same pull request. */ export declare function wrapForComment(body: string, options: { marker: string; ok: boolean; title: string; collapsedNote: string; trimNotice: string; }): string; /** * The invisible anchor a comment is found by on the next push. * * The key reaches an HTML comment, so it is reduced to alphanumerics and single * separators: runs collapse, edges are trimmed, and a key with nothing usable in * it falls back to `default`. That leaves no `--` in the output at all, which * takes the whole `-->` question off the table rather than reasoning about * whether a particular arrangement of dashes happens to be safe. */ export declare function commentMarker(key: string): string; export interface RankMarkdownRow { path: string; profile: PromptProfile; /** Tokens the deterministic rules would take, at the level asked for. */ recoverable: number; /** What those tokens cost per month under the usage profile. */ recoverableUsd: number; } export interface RankMarkdownInput { /** The directory the run was pointed at. */ root: string; ranked: readonly RankMarkdownRow[]; level: RuleLevel; modelDisplayName: string; callsPerMonth: number; /** True when a walk limit stopped the run early. */ truncated: boolean; /** Source files with no marker, skipped rather than aborting the run. */ skipped: number; t: CliMessages; } /** * The ranking as markdown. * * Every string but the heading comes from `t.rank`, the same object the terminal * report reads. That is not tidiness — a second copy of "there is no score" is a * second thing to keep true, and the first time somebody softens one of these * sentences they will soften the copy they happened to be looking at. * * **Money and tokens stay in adjacent columns**, as in the terminal, and for the * reason the terminal has them: four prompts reading `$0.25` looked like four * equivalent jobs when three of them recovered a single token. A pull request * comment is where that misreading would do the most damage, because nobody * reading one has the file open. */ export declare function renderRankMarkdown(input: RankMarkdownInput): string; export interface BlameMarkdownRow { revision: Revision; /** `null` when the file did not exist at that commit, or held no marked prompt. */ tokens: number | null; /** Tokens added since the previous (older) revision. `null` for the first. */ delta: number | null; /** The name the file had at that commit, when it differs from today's. */ name: string | null; } export interface BlameMarkdownInput { repoPath: string; rows: readonly BlameMarkdownRow[]; truncated: boolean; /** The priced movement across the history, when a model was resolved. */ netCost: { amount: string; modelDisplayName: string; callsPerMonth: number; } | null; /** This file's own measured band, computed by the caller that holds its text. */ band: number; t: CliMessages; } /** * The token history as markdown. * * A rise is bold and a fall is not, which is the same asymmetry the terminal * makes with colour: growth is the thing somebody has to act on, and a report * that shouts equally about both trains the reader to ignore it. * * **Author and subject are the least trusted values this repository renders.** * They come from commit metadata, which on a pull request from a fork is written * by whoever opened it, and they land in a table on a page maintainers read. Both * go through `mdCell`, which emits entities rather than escapes — so there is no * `|` in the output to split a row and no backtick arithmetic to get wrong. */ export declare function renderBlameMarkdown(input: BlameMarkdownInput): string; export interface ProfileMarkdownInput { report: UsageProfileReport; levers: BillLevers; cache: CacheEconomics; t: CliMessages; /** * The gate verdicts, when any gate was armed. * * They reached the terminal on stderr and stopped there, so a CI run * summary carried the whole report and not the one sentence explaining why * the build was red — the reader had to open the raw log to find it. These * arrive already rendered by the caller, which owns the thresholds and the * copy; this is a rendering and must not decide anything a gate decides. */ gates?: { failed: boolean; lines: string[]; }; /** * `--markdown-summary`: the short form, for a pull-request body or a weekly * note rather than a full report. * * The person who owns the budget usually does not run the CLI, and handing * them the whole report is handing them a document to skim — where the one * figure that changed is as easy to miss as it was in the terminal. The * summary states what changed, the single lever worth the most, and stops. * * It is a *view*, never a different set of figures: every number in it is * taken from the same report the full rendering uses, so a reader who opens * both cannot find them disagreeing. */ summary?: boolean; /** * The `--since`/`--until` values as the user typed them, when a window was * applied. Passed through rather than re-derived from `timeWindow`'s epoch * bounds, because a bare `--until 2026-08-14` includes that whole day — * rendering the internal exclusive bound would print the *next* day and * disagree with the terminal about which window this was. */ window?: { since: string; until: string; }; /** * Passed only when the price table is old enough to matter, so the * threshold lives once, beside the terminal's. Rendered loud: staleness * does not name its own size the way a skipped line does. */ stalePricing?: { date: string; days: number; }; /** * The comparison, when `--against` was given — the section the terminal * has had since 1.11 and the markdown did not, so a CI summary reporting * on two logs showed only one of them. The drivers arrive computed (core's * `driversBetween`) rather than derived here: the sign convention has one * implementation, and this is a rendering. */ /** * The repricing, when `--what-if` was given. Arrives computed from core's * `repriceProfile` rather than derived here: three surfaces must not * disagree about what a move would cost, and this is a rendering. */ whatIf?: RepriceReport | null; /** * The largest call against each model's window, computed once in the CLI — * like `whatIf` — because the ratio's denominator lives in the (possibly * overlaid) catalogue and a summary must not re-derive it differently. */ pressure?: ContextPressure[]; against?: { previousTotalUsd: number; previousCalls: number; labelDrivers: AgainstDriver[]; modelDrivers: AgainstDriver[]; /** True when both spans are known and intersect. */ overlap: { from: string; to: string; } | null; /** Nothing in the previous log could be priced — its own answer. */ nothingPriced: boolean; }; } /** * `trazum profile` as GitHub-flavoured markdown, for a job summary or a * pull-request comment. * * The terminal report is the source of truth and this reuses its message * catalogue line for line, because two renderings of the same finding drift the * moment they are worded twice — the sign conventions here (`positive means * worse` on the cache delta, ceilings that must be named as ceilings) have each * already produced a bug when restated by hand. * * A finding that only exists in a terminal is a finding the reader's tooling * never surfaces; this is the other half of the `--json` lesson, for humans * reading CI instead of machines. */ export declare function renderProfileMarkdown(input: ProfileMarkdownInput): string; //# sourceMappingURL=markdown.d.ts.map