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.
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/dist/analyze/blast-radius.d.ts +28 -0
- package/dist/analyze/blast-radius.js +163 -0
- package/dist/analyze/canonical.d.ts +27 -0
- package/dist/analyze/canonical.js +74 -0
- package/dist/analyze/citations.d.ts +256 -0
- package/dist/analyze/citations.js +945 -0
- package/dist/analyze/effects.d.ts +15 -0
- package/dist/analyze/effects.js +255 -0
- package/dist/analyze/fact.d.ts +42 -0
- package/dist/analyze/fact.js +46 -0
- package/dist/analyze/guards.d.ts +70 -0
- package/dist/analyze/guards.js +211 -0
- package/dist/analyze/index.d.ts +26 -0
- package/dist/analyze/index.js +52 -0
- package/dist/analyze/program.d.ts +15 -0
- package/dist/analyze/program.js +229 -0
- package/dist/analyze/surface.d.ts +48 -0
- package/dist/analyze/surface.js +396 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +12 -0
- package/dist/cli.d.ts +110 -0
- package/dist/cli.js +502 -0
- package/dist/extract/diff.d.ts +35 -0
- package/dist/extract/diff.js +116 -0
- package/dist/extract/git.d.ts +12 -0
- package/dist/extract/git.js +247 -0
- package/dist/extract/index.d.ts +4 -0
- package/dist/extract/index.js +57 -0
- package/dist/extract/intent.d.ts +64 -0
- package/dist/extract/intent.js +238 -0
- package/dist/extract/scope.d.ts +160 -0
- package/dist/extract/scope.js +284 -0
- package/dist/extract/symbols.d.ts +24 -0
- package/dist/extract/symbols.js +230 -0
- package/dist/interpret/client.d.ts +27 -0
- package/dist/interpret/client.js +80 -0
- package/dist/interpret/index.d.ts +41 -0
- package/dist/interpret/index.js +86 -0
- package/dist/interpret/prompt.d.ts +23 -0
- package/dist/interpret/prompt.js +128 -0
- package/dist/interpret/schema.d.ts +74 -0
- package/dist/interpret/schema.js +103 -0
- package/dist/report/conceal.d.ts +63 -0
- package/dist/report/conceal.js +129 -0
- package/dist/report/coverage.d.ts +43 -0
- package/dist/report/coverage.js +56 -0
- package/dist/report/html.d.ts +4 -0
- package/dist/report/html.js +634 -0
- package/dist/report/markdown.d.ts +2 -0
- package/dist/report/markdown.js +168 -0
- package/dist/report/model.d.ts +303 -0
- package/dist/report/model.js +289 -0
- package/dist/report/pdf.d.ts +2 -0
- package/dist/report/pdf.js +217 -0
- package/dist/report/terminal.d.ts +2 -0
- package/dist/report/terminal.js +206 -0
- package/dist/report/write.d.ts +105 -0
- package/dist/report/write.js +160 -0
- package/dist/score/index.d.ts +94 -0
- package/dist/score/index.js +572 -0
- package/dist/score/reach.d.ts +126 -0
- package/dist/score/reach.js +320 -0
- package/dist/score/reconcile.d.ts +52 -0
- package/dist/score/reconcile.js +208 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.js +10 -0
- package/fonts/DejaVuSans-Bold.ttf +0 -0
- package/fonts/DejaVuSans-Oblique.ttf +0 -0
- package/fonts/DejaVuSans.ttf +0 -0
- package/fonts/DejaVuSansMono.ttf +0 -0
- package/fonts/LICENSE +187 -0
- 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;
|