urtext 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/dist/analyze/blast-radius.d.ts +28 -0
  4. package/dist/analyze/blast-radius.js +163 -0
  5. package/dist/analyze/canonical.d.ts +27 -0
  6. package/dist/analyze/canonical.js +74 -0
  7. package/dist/analyze/citations.d.ts +256 -0
  8. package/dist/analyze/citations.js +945 -0
  9. package/dist/analyze/effects.d.ts +15 -0
  10. package/dist/analyze/effects.js +255 -0
  11. package/dist/analyze/fact.d.ts +42 -0
  12. package/dist/analyze/fact.js +46 -0
  13. package/dist/analyze/guards.d.ts +70 -0
  14. package/dist/analyze/guards.js +211 -0
  15. package/dist/analyze/index.d.ts +26 -0
  16. package/dist/analyze/index.js +52 -0
  17. package/dist/analyze/program.d.ts +15 -0
  18. package/dist/analyze/program.js +229 -0
  19. package/dist/analyze/surface.d.ts +48 -0
  20. package/dist/analyze/surface.js +396 -0
  21. package/dist/bin.d.ts +2 -0
  22. package/dist/bin.js +12 -0
  23. package/dist/cli.d.ts +110 -0
  24. package/dist/cli.js +502 -0
  25. package/dist/extract/diff.d.ts +35 -0
  26. package/dist/extract/diff.js +116 -0
  27. package/dist/extract/git.d.ts +12 -0
  28. package/dist/extract/git.js +247 -0
  29. package/dist/extract/index.d.ts +4 -0
  30. package/dist/extract/index.js +57 -0
  31. package/dist/extract/intent.d.ts +64 -0
  32. package/dist/extract/intent.js +238 -0
  33. package/dist/extract/scope.d.ts +160 -0
  34. package/dist/extract/scope.js +284 -0
  35. package/dist/extract/symbols.d.ts +24 -0
  36. package/dist/extract/symbols.js +230 -0
  37. package/dist/interpret/client.d.ts +27 -0
  38. package/dist/interpret/client.js +80 -0
  39. package/dist/interpret/index.d.ts +41 -0
  40. package/dist/interpret/index.js +86 -0
  41. package/dist/interpret/prompt.d.ts +23 -0
  42. package/dist/interpret/prompt.js +128 -0
  43. package/dist/interpret/schema.d.ts +74 -0
  44. package/dist/interpret/schema.js +103 -0
  45. package/dist/report/conceal.d.ts +63 -0
  46. package/dist/report/conceal.js +129 -0
  47. package/dist/report/coverage.d.ts +43 -0
  48. package/dist/report/coverage.js +56 -0
  49. package/dist/report/html.d.ts +4 -0
  50. package/dist/report/html.js +634 -0
  51. package/dist/report/markdown.d.ts +2 -0
  52. package/dist/report/markdown.js +168 -0
  53. package/dist/report/model.d.ts +303 -0
  54. package/dist/report/model.js +289 -0
  55. package/dist/report/pdf.d.ts +2 -0
  56. package/dist/report/pdf.js +217 -0
  57. package/dist/report/terminal.d.ts +2 -0
  58. package/dist/report/terminal.js +206 -0
  59. package/dist/report/write.d.ts +105 -0
  60. package/dist/report/write.js +160 -0
  61. package/dist/score/index.d.ts +94 -0
  62. package/dist/score/index.js +572 -0
  63. package/dist/score/reach.d.ts +126 -0
  64. package/dist/score/reach.js +320 -0
  65. package/dist/score/reconcile.d.ts +52 -0
  66. package/dist/score/reconcile.js +208 -0
  67. package/dist/types.d.ts +221 -0
  68. package/dist/types.js +10 -0
  69. package/fonts/DejaVuSans-Bold.ttf +0 -0
  70. package/fonts/DejaVuSans-Oblique.ttf +0 -0
  71. package/fonts/DejaVuSans.ttf +0 -0
  72. package/fonts/DejaVuSansMono.ttf +0 -0
  73. package/fonts/LICENSE +187 -0
  74. package/package.json +44 -0
@@ -0,0 +1,168 @@
1
+ import { EMPTY_LENS_COPY, LENSES, location, plainText, } from "./model.js";
2
+ /**
3
+ * The Markdown surface, a walker over the report model — GitHub-flavored,
4
+ * written for the places a review gets pasted: PRs, issues, chat, downstream
5
+ * tools. Every sentence, tier, glyph, lens routing, ordering, and disclosure
6
+ * rendered here is decided by `buildReportModel`; what this file owns is
7
+ * format mechanics — headings, blockquote prefixes, and the code fences
8
+ * around excerpts.
9
+ *
10
+ * The escaping policy is deliberately minimal: prose is never entity- or
11
+ * backslash-escaped, so an author's `*` may render as emphasis — acceptable,
12
+ * because the text stays legible and unaltered either way, and a report full
13
+ * of visible backslashes would not. What is never acceptable is untrusted
14
+ * text becoming document STRUCTURE, and each construct here closes that door
15
+ * mechanically rather than by escaping: heading and body-paragraph text is
16
+ * collapsed to one line (a newline would end the heading — or begin a fresh
17
+ * line free to open any Markdown structure — and promote the rest to
18
+ * top-level text), blockquote content has every line prefixed so nothing inside can
19
+ * step out of the quote, and excerpts sit inside fences long enough that no
20
+ * backtick run they contain can close them — see
21
+ * `test/report/markdown.test.ts`, "escalates the fence past any backtick run
22
+ * in the excerpt". Concealment arrives from the model as segments; this flat
23
+ * surface joins them through `plainText`, which brackets each label, and a
24
+ * bracketed label with no `(` after it is literal text to Markdown.
25
+ *
26
+ * Unlike the HTML — whose narrative pane repeats every finding and whose
27
+ * filtered panes show some twice — this surface is a linear document, so it
28
+ * partitions: each finding appears exactly once, under its own `lens`, in
29
+ * model order. The model's walker rule allows precisely this (see
30
+ * `ReportModel.findings`: a walker "may group by `lens` or `subject` but
31
+ * preserves this order within a group and may never drop a finding"), and
32
+ * `test/report/markdown.test.ts`, "never drops a finding: every headline the
33
+ * model carries appears exactly once", pins it. `ReachView` site lists are
34
+ * this surface's one density cut, exactly as they are the terminal's: a
35
+ * standalone reach finding's title and body already state the reference
36
+ * count, and the full site list stays on the HTML surface a reader opens for
37
+ * depth.
38
+ */
39
+ /** The floor under every fence; escalation only ever adds to it. */
40
+ const MIN_FENCE = 3;
41
+ /**
42
+ * Collapses text to one line. Headings need it because a raw newline would
43
+ * end the heading early. Body paragraphs need it for the inverse reason: a
44
+ * newline mid-paragraph begins a fresh line, and a fresh line is free to
45
+ * open any Markdown structure — a heading, a blockquote, a fence — turning
46
+ * paragraph prose into document skeleton. Upstream happens to compose
47
+ * bodies single-line today (`typeToString` emits one line), but nothing
48
+ * pins that, so the door is closed here, mechanically, like every other
49
+ * construct's. A space is the right joiner because a paragraph is one
50
+ * block by definition: whatever soft wrapping the text carried, it reads
51
+ * as one flow. See `test/report/markdown.test.ts`, "keeps a newline inside
52
+ * a body paragraph from starting a Markdown structure line".
53
+ */
54
+ function inline(text) {
55
+ return text.replace(/\s*\r?\n\s*/g, " ");
56
+ }
57
+ /**
58
+ * Paragraphs as one blockquote, every line prefixed and a bare marker
59
+ * between paragraphs — so no line of model-carried text, whatever it starts
60
+ * with, can render outside the quote it was attributed under.
61
+ */
62
+ function quote(paragraphs) {
63
+ return paragraphs
64
+ .filter((p) => p !== "")
65
+ .map((p) => p
66
+ .split(/\r?\n/)
67
+ .map((line) => `> ${line}`.trimEnd())
68
+ .join("\n"))
69
+ .join("\n>\n");
70
+ }
71
+ /** The fence info string: TypeScript's two extensions, else none — the whole table the spec names. */
72
+ function language(file) {
73
+ if (file.endsWith(".tsx"))
74
+ return "tsx";
75
+ if (file.endsWith(".ts"))
76
+ return "ts";
77
+ return "";
78
+ }
79
+ /**
80
+ * A fence one backtick longer than the longest run inside the excerpt, never
81
+ * shorter than MIN_FENCE. The excerpt is the one place this document quotes
82
+ * text an adversary can author outright, and a run matching the fence would
83
+ * close the block early — promoting the rest of the excerpt from quoted code
84
+ * to live Markdown, headings and all.
85
+ */
86
+ function fenceFor(code) {
87
+ let longest = 0;
88
+ for (const run of code.match(/`+/g) ?? []) {
89
+ longest = Math.max(longest, run.length);
90
+ }
91
+ return "`".repeat(Math.max(MIN_FENCE, longest + 1));
92
+ }
93
+ function evidenceBlocks(ref) {
94
+ const code = plainText(ref.excerpt);
95
+ const fence = fenceFor(code);
96
+ return [location(ref), `${fence}${language(ref.file)}\n${code}\n${fence}`];
97
+ }
98
+ /**
99
+ * The only way model-authored text reaches this document: one blockquote
100
+ * whose first line is the attribution, then the prose, then the trust
101
+ * caveat. All three come from the one `ModelNoteView` (see `./model.js`,
102
+ * where their inseparability is argued), so there is no code path that
103
+ * renders one without the others. Like the HTML and unlike the terminal,
104
+ * the block is not gated on a recorded model name — a model-tier finding's
105
+ * whole body is model prose, and suppressing it would leave a headline with
106
+ * nothing under it, so the model's UNNAMED_MODEL fallback attribution shows
107
+ * instead, visibly incomplete rather than absent.
108
+ */
109
+ function modelNoteQuote(note) {
110
+ return quote([`unverified · ${note.model}`, plainText(note.text), note.caution]);
111
+ }
112
+ function findingBlocks(finding) {
113
+ const blocks = [
114
+ `### ${finding.glyph} ${inline(plainText(finding.headline))} [${finding.tier}]` +
115
+ (finding.beyondIntent ? ` (${finding.beyondIntent})` : ""),
116
+ ];
117
+ // One walk covers every tier: a model-tier finding arrives with no body
118
+ // paragraphs and its whole prose in `modelNote`, an inferred finding keeps
119
+ // its analyzer paragraphs and carries the claim's reasoning beside them,
120
+ // and a claim-free finding has no note at all.
121
+ for (const paragraph of finding.body) {
122
+ blocks.push(inline(plainText(paragraph)));
123
+ }
124
+ if (finding.modelNote) {
125
+ blocks.push(modelNoteQuote(finding.modelNote));
126
+ }
127
+ for (const ref of finding.evidence) {
128
+ blocks.push(...evidenceBlocks(ref));
129
+ }
130
+ return blocks;
131
+ }
132
+ export function renderMarkdown(model) {
133
+ const blocks = ["# urtext review", model.scope];
134
+ // The disclosures, every one, ahead of the first lens heading: a reader
135
+ // has to know what the review could not see before reading the list and
136
+ // concluding nothing else was found. Blockquotes, so they read as the
137
+ // report speaking about itself, set apart from the findings.
138
+ if (model.provenance) {
139
+ blocks.push(quote([model.provenance]));
140
+ }
141
+ if (model.notes.length > 0) {
142
+ // Gated on the model's `notes` exactly: non-empty means the review is
143
+ // partial and this surface must say so, in the same sentence the HTML's
144
+ // banner leads with.
145
+ blocks.push(quote(["**This review is partial.**", ...model.notes]));
146
+ }
147
+ if (model.coverageNote) {
148
+ blocks.push(quote([model.coverageNote]));
149
+ }
150
+ if (model.filterNote) {
151
+ blocks.push(quote([model.filterNote]));
152
+ }
153
+ if (model.beyondIntentLegend) {
154
+ blocks.push(quote([model.beyondIntentLegend]));
155
+ }
156
+ for (const { key, label } of LENSES) {
157
+ blocks.push(`## ${label}`);
158
+ const inLens = model.findings.filter((f) => f.lens === key);
159
+ if (inLens.length === 0) {
160
+ blocks.push(EMPTY_LENS_COPY);
161
+ continue;
162
+ }
163
+ for (const finding of inLens) {
164
+ blocks.push(...findingBlocks(finding));
165
+ }
166
+ }
167
+ return blocks.filter((b) => b !== "").join("\n\n") + "\n";
168
+ }
@@ -0,0 +1,303 @@
1
+ import { type ConcealSegment } from "./conceal.js";
2
+ import type { Changeset, Finding, Tier } from "../types.js";
3
+ export { plainText } from "./conceal.js";
4
+ export type { ConcealSegment } from "./conceal.js";
5
+ /**
6
+ * The one report model every output surface walks. The terminal and HTML
7
+ * renderers each walked the findings themselves and shared honesty copy
8
+ * piecemeal, and two drift bugs shipped from exactly that split — the
9
+ * terminal missing the concealment defense the HTML had, and the HTML
10
+ * missing the filter disclosure the terminal had. Everything a surface may
11
+ * say about *content* — which findings, in what order, at what tier, with
12
+ * which disclosures — is decided here, once; a renderer applies format
13
+ * mechanics (escaping, wrapping, typesetting) and its own phrasing of the
14
+ * same truth, never a decision of its own.
15
+ *
16
+ * Concealment happens here, while the model is built, and it is structural:
17
+ * content-bearing fields are `ConcealSegment` arrays in which a concealed
18
+ * character is a segment of its own, so a walker can style it without ever
19
+ * parsing labels back out of flattened text — a flattened label cannot be
20
+ * told apart from source code that literally spells it. Identifier-shaped
21
+ * fields (file paths, the range label, the coverage note, warnings, the
22
+ * model name) stay `labelConcealed` strings, a ruled exception recorded in
23
+ * the design spec's addendum. Either way no raw concealing character
24
+ * survives into the model — see `test/report/model.test.ts`, "carries no
25
+ * raw concealing character anywhere: titles, bodies, headlines, file paths,
26
+ * the range label, warnings, the model name, and the coverage note". Where
27
+ * a sentence is shared verbatim by the existing surfaces it is composed
28
+ * here (or reached through `./coverage.js`, which keeps its composing
29
+ * functions); where the surfaces phrase one truth differently, the model
30
+ * carries the pieces and each walker keeps its phrasing.
31
+ */
32
+ export interface ReportMeta {
33
+ model?: string;
34
+ /**
35
+ * Every reason this run fell short of its full pipeline, in the order they
36
+ * happened: a dead analyzer, a skipped interpretation stage, a report that
37
+ * could not be written. One list, because they are one thing to a reader —
38
+ * and because a second field for the skipped stage meant `review` in
39
+ * `../cli.ts` filled both and the banner printed that line twice.
40
+ */
41
+ warnings: string[];
42
+ /**
43
+ * How many claim-free standalone reach rows reconcile's filter removed.
44
+ * Not folded into `warnings`: the filter running as designed is not a
45
+ * shortfall, and its disclosure must not trip the "This review is
46
+ * partial." banner. Composed into `ReportModel.filterNote` with
47
+ * `suppressionNote`, the same sentence both surfaces print.
48
+ */
49
+ suppressed?: number;
50
+ }
51
+ export type Lens = "narrative" | "effects" | "surface";
52
+ /**
53
+ * What a finding is about — finer than `Lens`, and carried beside it because
54
+ * the HTML report needs the distinction the lens alone erases: its effects
55
+ * pane splits "effect" from "guard" into separate sections and shows
56
+ * "surface" findings a second time under Contracts. See
57
+ * `test/report/model.test.ts`, "keeps the finer subject beside the lens, so
58
+ * a walker can split effects from guards and show contracts in both panes".
59
+ * "reach" and "citation" have no filtered lens of their own — a standalone
60
+ * reach finding and a citation finding each appear in the narrative only,
61
+ * which the effects pane's note says out loud.
62
+ */
63
+ export type Subject = "effect" | "guard" | "surface" | "reach" | "citation";
64
+ export interface EvidenceView {
65
+ file: string;
66
+ line: number;
67
+ /**
68
+ * Concealment-segmented source text; flat surfaces join it with
69
+ * `plainText`, and renderers add only format escaping.
70
+ */
71
+ excerpt: ConcealSegment[];
72
+ /**
73
+ * Which revision `line` counts in, copied from `EvidenceRef`: a before-side
74
+ * line very often points somewhere unrelated in the working tree, and every
75
+ * surface owes the reader that warning. Omitted means the after side.
76
+ */
77
+ side?: "before" | "after";
78
+ }
79
+ export interface ReachView {
80
+ /** Deduped count, exactly as rank computed it. */
81
+ references: number;
82
+ /** Deduped sites, capped at REACH_SITES_SHOWN exactly as the HTML is today. */
83
+ sites: EvidenceView[];
84
+ /** Count of collected-but-not-shown sites. */
85
+ overflow: number;
86
+ }
87
+ export interface ModelNoteView {
88
+ /** Never empty: an unnamed model renders as UNNAMED_MODEL, the existing fallback copy. */
89
+ model: string;
90
+ /** The model's prose, concealment-segmented. */
91
+ text: ConcealSegment[];
92
+ /**
93
+ * The trust caveat rendered with the prose — which of the two fixed
94
+ * sentences depends on whether the prose stands alone or explains an
95
+ * analyzer's finding. Carried in the note rather than chosen by a
96
+ * renderer, so no surface can pick the weaker caveat for the stronger
97
+ * claim.
98
+ */
99
+ caution: string;
100
+ }
101
+ export interface FindingView {
102
+ id: string;
103
+ tier: Tier;
104
+ /** TIER_GLYPH[tier], chosen here, once. */
105
+ glyph: string;
106
+ /** Kind-prefix routing lives here, once. */
107
+ lens: Lens;
108
+ /** See `Subject`; absent for a standalone model claim or an unprefixed id. */
109
+ subject?: Subject;
110
+ /**
111
+ * `file:line — title`, with a before-side anchor marked — segmented from
112
+ * the raw composition, so a concealing character in the path or title is
113
+ * a segment here even though `file` below is a flattened string. Joined
114
+ * with `plainText` it reads exactly as the terminal prints it. The pieces
115
+ * below let the HTML compose its own split headline without re-deciding
116
+ * any of them.
117
+ */
118
+ headline: ConcealSegment[];
119
+ /** Concealment-segmented title, lowercase-led as `toFinding` composes it. */
120
+ title: ConcealSegment[];
121
+ file: string;
122
+ line: number;
123
+ /**
124
+ * The anchor's side, from the first evidence ref — a fact's file and line
125
+ * are derived from that ref (see `makeFact`), so its side annotation
126
+ * applies to the headline too. A model-tier finding carries no evidence
127
+ * and no side, which is correct rather than incidental: its location is
128
+ * the model's own, in the after revision.
129
+ */
130
+ side?: "before" | "after";
131
+ /**
132
+ * Analyzer-authored paragraphs, each concealment-segmented. Empty for a
133
+ * model-tier finding: its whole body is model prose, and all model prose
134
+ * lives in `modelNote` so no surface can render it without attribution —
135
+ * see `test/report/model.test.ts`, "moves model-tier prose into the
136
+ * attributed model note, leaving no bare body".
137
+ */
138
+ body: ConcealSegment[][];
139
+ /** Prose and attribution inseparable, as today. */
140
+ modelNote?: ModelNoteView;
141
+ /**
142
+ * Every ref, uncapped: the HTML shows all of them. The terminal's
143
+ * shorter list is presentation density, applied by that walker.
144
+ */
145
+ evidence: EvidenceView[];
146
+ reach?: ReachView;
147
+ /**
148
+ * BEYOND_INTENT_MARK, present only when the claim behind this finding set
149
+ * `beyondIntent`. Carries the words rather than a boolean so no renderer
150
+ * composes them; absent or the mark, never a "not marked" string. See
151
+ * `test/report/model.test.ts`, "carries the mark's words, composed here so
152
+ * no renderer composes them".
153
+ */
154
+ beyondIntent?: string;
155
+ }
156
+ export interface ReportModel {
157
+ /** "44 files, 2384 lines changed · vs master" — the terminal's wording. */
158
+ scope: string;
159
+ /** The scope pieces, for the HTML's own phrasing of the same numbers. */
160
+ fileCount: number;
161
+ /** Insertions and deletions both: summing only the after side reported nothing changed for a pure deletion. */
162
+ lineCount: number;
163
+ rangeLabel: string;
164
+ counts: {
165
+ verified: number;
166
+ inferred: number;
167
+ model: number;
168
+ };
169
+ /**
170
+ * Present only under the gate both surfaces use today: a model name AND at
171
+ * least one inferred/model finding. A tier badge asserts a machine looked,
172
+ * and that assertion is only checkable if the reader knows which machine.
173
+ * See `test/report/model.test.ts`, "gates provenance on a model name AND a
174
+ * model-derived tier".
175
+ */
176
+ provenance?: string;
177
+ /**
178
+ * The recorded model name, labeled, absent when the run never named one.
179
+ * Deliberately carried beside the gated `provenance`: the terminal
180
+ * suppresses claim prose entirely when no model is named, and a walker
181
+ * cannot apply that gate from `provenance` alone once every finding is
182
+ * verified.
183
+ */
184
+ modelName?: string;
185
+ /**
186
+ * Every reason the review fell short, in order — analyzer failures,
187
+ * skipped interpretation, then the untracked-file note. Non-empty means
188
+ * the surface must say the review is partial.
189
+ */
190
+ notes: string[];
191
+ /**
192
+ * The deleted-file coverage note from `deletedFilesNote`; absent when no
193
+ * TypeScript file was deleted. Deliberately NOT in `notes`: deleting a
194
+ * TypeScript file is routine, and a partial-review banner that fires on
195
+ * every such diff is a banner a reader learns to skip. See
196
+ * `test/report/model.test.ts`, "carries each disclosure exactly once, in
197
+ * the field renderers must read it from".
198
+ */
199
+ coverageNote?: string;
200
+ /**
201
+ * Composed by `suppressionNote`; absent when nothing was suppressed.
202
+ * Deliberately NOT in `notes`: the filter running as designed is not a
203
+ * shortfall and must not trip partial-review copy.
204
+ */
205
+ filterNote?: string;
206
+ /**
207
+ * BEYOND_INTENT_MEANING, present exactly when at least one finding carries
208
+ * the mark. Deliberately NOT in `notes`: a badge doing its job is not a
209
+ * shortfall, and it must not trip partial-review copy — the same rule
210
+ * `filterNote` and `coverageNote` are separate fields for. See
211
+ * `test/report/model.test.ts`, "keeps the legend out of notes, so a badge
212
+ * doing its job never trips partial-review copy".
213
+ */
214
+ beyondIntentLegend?: string;
215
+ /**
216
+ * In rank order; renderers must not reorder — see
217
+ * `test/report/model.test.ts`, "preserves rank order exactly". A walker
218
+ * may group by `lens` or `subject` but preserves this order within a
219
+ * group and may never drop a finding.
220
+ */
221
+ findings: FindingView[];
222
+ }
223
+ /**
224
+ * The order every surface lists the tiers in: strongest evidence first.
225
+ * Owned here so no walker can shuffle its counts, chips, or legend into a
226
+ * sequence the other surfaces do not use.
227
+ */
228
+ export declare const TIER_ORDER: readonly ["verified", "inferred", "model"];
229
+ /**
230
+ * The lens display order and headings, owned here for the same reason
231
+ * `TIER_ORDER` owns the tier sequence: the HTML's tab strip and the
232
+ * Markdown's section headings both read from this one constant, and two
233
+ * walkers with private copies of an order are two orders waiting to diverge.
234
+ */
235
+ export declare const LENSES: readonly [{
236
+ readonly key: "narrative";
237
+ readonly label: "Narrative";
238
+ }, {
239
+ readonly key: "effects";
240
+ readonly label: "Effects & contracts";
241
+ }, {
242
+ readonly key: "surface";
243
+ readonly label: "API surface";
244
+ }];
245
+ /**
246
+ * What an empty lens says: a sentence about the filter, never about the
247
+ * code. A lens is a view over findings the model classified by id prefix,
248
+ * and if that classification ever stops matching what the analyzers emit,
249
+ * the empty pane is what a user sees — so it must not be able to claim
250
+ * nothing changed while a removed guard sits ranked first elsewhere in the
251
+ * report. Shared by the HTML's empty effects pane and the Markdown's empty
252
+ * sections; a surface may append its own pointer to where the findings are,
253
+ * but the filter-shaped sentence itself is single-sourced here.
254
+ */
255
+ export declare const EMPTY_LENS_COPY = "Nothing in this range matched this view.";
256
+ /**
257
+ * What a findings-free run says — one sentence about the analyzers, never
258
+ * about the code being fine. Shared by the surfaces that state it whole (the
259
+ * terminal and the PDF); the Markdown has no single no-findings line, its
260
+ * lens sections each carry EMPTY_LENS_COPY instead. Owned here for the same
261
+ * reason as EMPTY_LENS_COPY: two walkers with private copies of a sentence
262
+ * are two sentences waiting to diverge.
263
+ */
264
+ export declare const NO_FINDINGS_COPY = "No findings. Nothing in this change tripped an analyzer.";
265
+ /**
266
+ * `path:line`, or `path:line (before)` when the line number counts in the
267
+ * before revision rather than the working tree — see `EvidenceView.side`
268
+ * for why the reader is owed that marker. Shared by the flat surfaces
269
+ * (terminal, Markdown, PDF), which had verbatim private copies; the HTML
270
+ * composes its own marked-up location and is deliberately not a consumer.
271
+ */
272
+ export declare function location(ref: {
273
+ file: string;
274
+ line: number;
275
+ side?: "before" | "after";
276
+ }): string;
277
+ /** The badge every surface shows on a marked finding. Composed here, once. */
278
+ export declare const BEYOND_INTENT_MARK = "beyond stated intent";
279
+ /**
280
+ * What the badge means, stated once per report rather than once per finding.
281
+ * Names the commit messages as the source and says what the comparison is not,
282
+ * because the badge alone reads stronger than the evidence behind it.
283
+ */
284
+ export declare const BEYOND_INTENT_MEANING = "\u201Cbeyond stated intent\u201D means the commit messages in this range do not account for what the change does there. It compares the change against its own description, not against anything a person actually asked for.";
285
+ /** The marks both surfaces already print, kept identical so every surface reads as one tool. */
286
+ export declare const TIER_GLYPH: Record<Tier, string>;
287
+ /** The tier's display word — "model-only", not the bare tier value, where a reader sees it. */
288
+ export declare const TIER_WORD: Record<Tier, string>;
289
+ export declare const TIER_MEANING: Record<Tier, string>;
290
+ /**
291
+ * The name attached to model prose when the run recorded none. A run that
292
+ * produced model-tier findings without a model name is a bug upstream, not a
293
+ * licence to show the prose bare: the fallback keeps the attribution present
294
+ * and visibly incomplete rather than absent.
295
+ */
296
+ export declare const UNNAMED_MODEL = "an unnamed model";
297
+ /** The caveat beside prose the model authored with no fact underneath it. */
298
+ export declare const MODEL_CAUTION_STANDALONE = "Nothing mechanical corroborates this. Treat it as a lead to check, not a result.";
299
+ /** The caveat beside a claim's explanation of an analyzer's finding. */
300
+ export declare const MODEL_CAUTION_CLAIM = "The finding above is an analyzer's. This explanation of why it matters is not.";
301
+ /** How many referencing sites a finding lists before it stops listing them. */
302
+ export declare const REACH_SITES_SHOWN = 5;
303
+ export declare function buildReportModel(changeset: Changeset, findings: Finding[], meta: ReportMeta): ReportModel;