@preventive/triage 1.0.0-alpha.27 → 1.0.0-alpha.29

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 (83) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/client/linked-findings.js +1 -1
  3. package/common/bundle-metadata.js +12 -10
  4. package/common/code-language.js +17 -2
  5. package/common/default-scan-models.ts +3 -3
  6. package/common/finding-id.js +1 -1
  7. package/common/managed/report-content.ts +1 -1
  8. package/common/managed/report-filter.ts +1 -1
  9. package/out/client-managed-import.js +16 -16
  10. package/out/client-managed.js +93 -68
  11. package/out/graph.js +11 -11
  12. package/out/index.html +1 -1
  13. package/out/prism.js +2 -2
  14. package/out/stasis.svg +0 -25
  15. package/out/terminal.js +163 -163
  16. package/out/view.css +1 -1
  17. package/out/view.js +223 -190
  18. package/package.json +21 -28
  19. package/server-e2e/static.ts +3 -4
  20. package/server-managed/bundle-advisories.ts +53 -0
  21. package/server-managed/bundle-advisory-inventory.ts +75 -0
  22. package/server-managed/bundle-build-diagnostics.d.ts +10 -0
  23. package/server-managed/bundle-build-diagnostics.js +23 -0
  24. package/server-managed/bundle-build-leases.ts +41 -0
  25. package/server-managed/bundle-build-worker.js +54 -0
  26. package/server-managed/bundle-build.ts +168 -0
  27. package/server-managed/bundle-cache.ts +22 -13
  28. package/server-managed/bundle-summary-cache.ts +27 -9
  29. package/server-managed/bundle.ts +66 -1
  30. package/server-managed/cache-storage.ts +4 -0
  31. package/server-managed/content-access.ts +1 -1
  32. package/server-managed/db-methods.ts +122 -20
  33. package/server-managed/db-neon.ts +121 -32
  34. package/server-managed/db-schema.ts +2 -0
  35. package/server-managed/db.ts +5 -1
  36. package/server-managed/github-app.ts +10 -15
  37. package/server-managed/github-errors.ts +21 -0
  38. package/server-managed/github-issues.ts +1 -1
  39. package/server-managed/github-metadata.ts +34 -1
  40. package/server-managed/github-pulls.ts +142 -30
  41. package/server-managed/http.ts +244 -131
  42. package/server-managed/index.ts +28 -11
  43. package/server-managed/management-catalog.ts +45 -0
  44. package/server-managed/object-storage-vercel.ts +1 -1
  45. package/server-managed/public-workspace.ts +20 -14
  46. package/server-managed/report-migration.ts +1 -1
  47. package/server-managed/report-sources.ts +14 -4
  48. package/server-managed/repository-browser.ts +3 -0
  49. package/server-managed/repository-discovery.ts +1 -1
  50. package/server-managed/revisions.ts +35 -0
  51. package/server-managed/sql-postgres.ts +1 -0
  52. package/server-managed/sql.ts +14 -3
  53. package/server-managed/storage-db.ts +12 -5
  54. package/server-managed/storage-maintenance.ts +47 -22
  55. package/server-managed/storage-payload.ts +16 -4
  56. package/server-managed/storage-stores.ts +2 -2
  57. package/server-managed/storage.ts +6 -4
  58. package/server-managed/team-feed.ts +50 -5
  59. package/server-managed/team-reports.ts +85 -22
  60. package/server-managed/workspace-shares.ts +15 -3
  61. package/report/index.js +0 -260
  62. package/report/src/finding-id.js +0 -80
  63. package/report/src/finding.js +0 -314
  64. package/report/src/labels.js +0 -35
  65. package/report/src/md-structure.js +0 -471
  66. package/report/src/md-text.js +0 -167
  67. package/report/src/meta.js +0 -76
  68. package/report/src/parse-codex.js +0 -149
  69. package/report/src/parse-deepsec.js +0 -199
  70. package/report/src/parse-deepview-fields.js +0 -375
  71. package/report/src/parse-deepview-md.js +0 -186
  72. package/report/src/parse-md-id.js +0 -137
  73. package/report/src/parse-md.js +0 -323
  74. package/report/src/parse-piolium-id.js +0 -79
  75. package/report/src/parse-piolium-rows.js +0 -126
  76. package/report/src/parse-piolium-tokens.js +0 -185
  77. package/report/src/parse-piolium.js +0 -399
  78. package/report/src/security.js +0 -63
  79. package/report/src/severity.js +0 -18
  80. package/report/src/utf8.js +0 -21
  81. package/report/src/write-md-finding.js +0 -273
  82. package/report/src/write-md.js +0 -291
  83. /package/{LICENSE → LICENSE-MIT} +0 -0
@@ -1,80 +0,0 @@
1
- // Finding ids, stamped by the analyzer onto its JSON output and filled
2
- // in by the viewer for findings that arrive without one. Web Crypto is
3
- // the common surface — `crypto.subtle` exists in modern Node and in
4
- // secure browser contexts — so one implementation runs in both.
5
- //
6
- // Two reports from the same source give a finding the same id; an edit
7
- // to its description or its source invalidates it.
8
-
9
- import { encodeUtf8 } from './utf8.js'
10
-
11
- function toHex(bytes) {
12
- return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')
13
- }
14
-
15
- // The file-content hash the JSON output format uses. sha512 because the
16
- // id below hashes a string that already includes it, so a collision here
17
- // would propagate into an id collision. Padded base64 with the SRI-style
18
- // tag. `btoa` rather than `Uint8Array#toBase64`, still flagged in Node.
19
- export async function computeFileHash(source) {
20
- const bytes = typeof source === 'string' ? encodeUtf8(source) : source
21
- const digest = new Uint8Array(await crypto.subtle.digest('SHA-512', bytes))
22
- return `sha512-${btoa(String.fromCodePoint(...digest))}`
23
- }
24
-
25
- // A fingerprint hashed into a v4-shaped UUID: derived, not random, but
26
- // the shape lets a downstream tool treat it as an opaque id.
27
- async function fingerprintToId(fingerprint) {
28
- const bytes = encodeUtf8(JSON.stringify(fingerprint))
29
- const digest = await crypto.subtle.digest('SHA-256', bytes)
30
- const u = new Uint8Array(digest, 0, 16)
31
- // version 4: 0100xxxx
32
- u[6] = (u[6] & 0x0f) | 0x40
33
- // variant 1: 10xxxxxx
34
- u[8] = (u[8] & 0x3f) | 0x80
35
- const hex = toHex(u)
36
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`
37
- }
38
-
39
- // Stable per-finding id from the (severity, description, fileHash) triple
40
- // the analyzer emits. fileHash being undefined is fine — JSON.stringify
41
- // drops undefined keys, so a finding with no hash keys off the pair and
42
- // re-runs over the same source yield the same ids.
43
- export function findingId(severity, description, fileHash) {
44
- return fingerprintToId({ severity, description, fileHash })
45
- }
46
-
47
- // An id derived from a finding, on the first discriminator it carries.
48
- // null when `crypto.subtle` is unavailable (some `file://` setups), so
49
- // the caller can fall back to a session-local id — the UI still works,
50
- // without persistent triage on those findings.
51
- //
52
- // In order:
53
- // - _idBasis — a FROZEN fingerprint a parser stamped, used verbatim;
54
- // it exists so a change to the rendered description
55
- // can't re-key stored triage (parse-md-id.js).
56
- // - fileHash — as `findingId` above.
57
- // - location — a markdown import's url, also stable.
58
- // - file/line — last resort for a JSON finding with neither: not what
59
- // the spec prescribes, but better than collapsing two
60
- // unrelated findings onto one id.
61
- export async function deriveFindingId(f) {
62
- if (typeof crypto?.subtle?.digest !== 'function') return null
63
- const fingerprint = fingerprintOf(f)
64
- try {
65
- return await fingerprintToId(fingerprint)
66
- } catch {
67
- return null
68
- }
69
- }
70
-
71
- // The choice above as the object that gets hashed. Key order is part of
72
- // the id — JSON.stringify keeps insertion order — so every shape lists
73
- // severity and description first.
74
- export function fingerprintOf(f) {
75
- if (f._idBasis) return f._idBasis
76
- const { severity, description } = f
77
- if (f.fileHash) return { severity, description, fileHash: f.fileHash }
78
- if (f.location) return { severity, description, location: f.location }
79
- return { severity, description, file: f.file, line: f.line }
80
- }
@@ -1,314 +0,0 @@
1
- // What one finding IS, read off the parsed object: the severity it
2
- // displays under, the revalidation stamp it carries, the run it came
3
- // from, the export marker to strip, how its text splits into a name and
4
- // a body. The viewer and write-md.js beside it both read a parser's
5
- // object through these; format.js re-exports every name. Pure — no DOM,
6
- // no app state, nothing above `report/` — so a viewer switch arrives as
7
- // an argument (`displayedSeverity`, `runMetaLine`).
8
-
9
- import { fenceRanges, inFence } from './md-structure.js'
10
- import { SOURCE_LABELS } from './labels.js'
11
- import { canonicalSeverity } from './severity.js'
12
-
13
- // Severity ranking — higher = more severe. Two stacks: vulnerabilities
14
- // (critical → low) over bug-class findings (high_bug → bug), with
15
- // informational at the bottom; only DeepSec emits the bug tiers. A new
16
- // tier goes here, and every other list keys off SEVERITIES below.
17
- export const SEVERITY_ORDER = {
18
- critical: 6, high: 5, medium: 4, low: 3,
19
- high_bug: 2, bug: 1, informational: 0,
20
- }
21
- // Highest-to-lowest iteration order.
22
- export const SEVERITIES = ['critical', 'high', 'medium', 'low', 'high_bug', 'bug', 'informational']
23
-
24
- // ── Corrected severity ───────────────────────────────────────────────
25
- // `correctedSeverity`, with a free-text `correctedSeverityReason`, is a
26
- // report's own re-rating of the analyzer's `severity`, and PER-REPORT
27
- // where `severity` is not: the id hashes `severity`, so a dedupe keeps
28
- // each occurrence's corrected value on the survivor in
29
- // `f._correctedByReport`.
30
- //
31
- // Every display, count and sort resolves severity through the helpers
32
- // below; IDENTITY — the id fingerprint, dedupe keys — stays raw.
33
-
34
- // Only a known tier counts: an unrecognised one would sort to rank 0 and
35
- // render an uncolored badge, so the intrinsic severity stands instead.
36
- function validCorrected(corrected) {
37
- corrected = canonicalSeverity(corrected)
38
- return corrected != null && corrected in SEVERITY_ORDER ? corrected : null
39
- }
40
-
41
- // The finding's own effective severity. It carries its own report's
42
- // correction — it IS that report's finding — so no report key is needed;
43
- // divergence across reports comes from correctedVariants.
44
- export function effectiveSeverity(f) {
45
- return validCorrected(f?.correctedSeverity) ?? canonicalSeverity(f?.severity)
46
- }
47
-
48
- // True when the finding carries a valid correction that actually changes
49
- // the tier — the trigger for the dual badge / reason affordance.
50
- export function hasSeverityCorrection(f) {
51
- const c = validCorrected(f?.correctedSeverity)
52
- return c != null && c !== canonicalSeverity(f?.severity)
53
- }
54
-
55
- // Every display, count and sort site reads severity through this with the
56
- // current lens (`state.severityMode`), not off `f.severity`.
57
- export function displayedSeverity(f, mode) {
58
- return mode === 'original' ? canonicalSeverity(f?.severity) : effectiveSeverity(f)
59
- }
60
-
61
- // The per-report map of a deduped survivor, but only where the reports
62
- // disagree — the "varies across reports" hint. null otherwise.
63
- export function correctedVariants(f) {
64
- const byReport = f?._correctedByReport
65
- if (!byReport) return null
66
- const tiers = new Set(Object.values(byReport).map((v) => canonicalSeverity(v?.severity)))
67
- return tiers.size > 1 ? byReport : null
68
- }
69
-
70
- // ── Revalidation ─────────────────────────────────────────────────────
71
- // A second pass over a finding. `revalidate` is what it concluded —
72
- // `confirmed` (the finding stands), `partial` (part of it does),
73
- // `refuted` (it doesn't), `unreachable` (nothing can reach the code),
74
- // `unknown` (it couldn't tell) — with its reasoning in
75
- // `revalidateVerdict` and, for a refutation, `revalidateRecommendation`.
76
- // `revalidation` is the odd one out: the row that IS the pass rather
77
- // than one it judged, carrying no verdict. `revalidateSource` names
78
- // WHOSE pass said so, keyed like a report's `source`, and earns its keep
79
- // when dedup carries a stamp onto another report's finding.
80
- //
81
- // One of these words or nothing — an unrecognised value is no stamp.
82
- export const REVALIDATE_KINDS = ['revalidation', 'refuted', 'unreachable', 'confirmed', 'partial', 'unknown']
83
- const REVALIDATE_SET = new Set(REVALIDATE_KINDS)
84
-
85
- // The outcome as the DATA has it, '' when there is none; the viewer's
86
- // `revalidateKind` is this behind the layer switch.
87
- //
88
- // Read as written: `Refuted ` is not this field's value. A drifted
89
- // spelling is folded back where a person can edit one — a DOCUMENT
90
- // (parse-deepview-fields.js readRevalidation) — and past that boundary
91
- // case-folding it would say the opposite of what the analyzer wrote.
92
- export function revalidateKindOf(f) {
93
- return REVALIDATE_SET.has(f?.revalidate) ? f.revalidate : ''
94
- }
95
-
96
- // Does this finding belong to the APP layer — the code as the
97
- // application runs it — rather than the source underneath? Another
98
- // product's does by construction: its producer looked at the
99
- // application, and a `source` in labels.js is exactly "not DeepView's own
100
- // dump". DeepView's own are source-layer, except the row that IS its
101
- // revalidation pass, which describes the run and not a line of code.
102
- //
103
- // `source` comes in separately because callers resolve it against the
104
- // report's marker first. The sole definition of the split; readers take
105
- // it off the finding as `isApp`, stamped once.
106
- export function isAppFinding(f, source = f?.source) {
107
- return Object.hasOwn(SOURCE_LABELS, source) || revalidateKindOf(f) === 'revalidation'
108
- }
109
-
110
- // Security involvement is independent of the App/code layer and the displayed
111
- // (possibly corrected) severity. External reports use their original severity,
112
- // not the synthetic report-level `type: security` supplied by their parsers.
113
- // `security: false` opts out of the analyzer default, not the other OR rules.
114
- export function isSecurityFinding(f, source = f?._source ?? f?.source) {
115
- const external = typeof source === 'string' && source !== '' && source !== 'deepview'
116
- const analyzer = external ? source : f?.analyzer ?? f?.type ?? f?._analyzer
117
- return f?.security === true
118
- || ((analyzer === 'security' || analyzer === 'dependencies') && f?.security !== false)
119
- || (external && ['critical', 'high', 'medium', 'low'].includes(f?.severity))
120
- }
121
-
122
- // ── Run meta ─────────────────────────────────────────────────────────
123
- export function prettyModel(model) {
124
- if (!model) return model
125
- return model.replace(/^[^/]+\//u, '').replace(/^claude-/u, '').replaceAll('-', ' ')
126
- }
127
-
128
- // The run a finding came from, as one line — analyzer type, model,
129
- // effort, exports mode, ` · `-joined, absent fields elided. Every
130
- // surface that prints it (card, table row, bundle source rows, the
131
- // markdown export) comes here, so the field list and separator can't
132
- // drift apart.
133
- //
134
- // The revalidation row names itself right after the mode it ran in
135
- // (`security · revalidate · opus 5 · …`) — that row only, not a verdict
136
- // row the pass produced. `revalidation` is whether the layer is
137
- // applied; off, the pass's name goes with the rest of it.
138
- export function runMetaLine(f, revalidation = true) {
139
- const pass = revalidation && revalidateKindOf(f) === 'revalidation' ? 'revalidate' : ''
140
- return [f?.type, pass, prettyModel(f?.model), f?.effort, f?.exportsMode]
141
- .filter(Boolean).join(' · ')
142
- }
143
-
144
- // ── Export markers ───────────────────────────────────────────────────
145
- // Isolate mode injects `[export: <name>]` markers and a `(<name>): `
146
- // lead-in so a merged per-file response stays traceable to individual
147
- // exports (src/isolate.js). Once post-process has lifted the name into
148
- // `f.exportName` / `f.methodName`, the inline copy only repeats it, so a
149
- // marker or prefix naming either field comes off; one naming something
150
- // else is context ("this export affects <other>") and stays.
151
- //
152
- // In isolate mode a leading marker or `(<any>): [export: <any>] ` prefix
153
- // comes off whatever it names, since that can be a sibling export. That
154
- // pass runs FIRST, or the per-name strip could decapitate the prefix and
155
- // leave the `(…): ` lead-in stranded.
156
- export function stripExportMarker(text, f) {
157
- if (!text) return text
158
- let result = text
159
- if (f?.exportsMode === 'isolate') {
160
- // One level of nested `()` — `` (first branch of `bar()`) ``.
161
- // Deeper nesting doesn't match at all, leaving the prose intact
162
- // rather than over-stripping it.
163
- result = result.replace(/^\((?:[^()]|\([^()]*\))*\): \[export:\s*\w+\] /u, '')
164
- result = result.replace(/^\[export:\s*\w+\] /u, '')
165
- }
166
- const names = [f?.exportName, f?.methodName].filter(Boolean)
167
- .map((name) => name.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&'))
168
- // Every marker first, then the prefixes. The two passes are ordered,
169
- // not merely grouped: a `(Foo): ` prefix can sit BEHIND a marker
170
- // naming the OTHER name — `[export: bar] (Foo): …` — and the prefix
171
- // strip only ever looks at the front of the text, so a per-name pass
172
- // that checked the prefix before the other name's marker came off
173
- // would leave it there.
174
- for (const name of names) result = result.replaceAll(new RegExp(`\\[export:\\s*${name}\\]\\s*`, 'gu'), '')
175
- for (const name of names) result = result.replace(new RegExp(`^\\(\`?${name}\`?\\): `, 'u'), '')
176
- return result
177
- }
178
-
179
- // The export/method location as a label: `exportName.methodName` for a
180
- // class export with a specific method, one name when they agree, '' when
181
- // there is neither.
182
- export function findingDisplayName(f) {
183
- const e = f?.exportName
184
- const m = f?.methodName
185
- if (e && m && e !== m) return `${e}.${m}`
186
- return e || m || ''
187
- }
188
-
189
- // ── Finding title ────────────────────────────────────────────────────
190
- // What the finding is CALLED: the `title` field when a report has one,
191
- // else the description's first line — where every markdown import puts
192
- // the finding's heading, and where a JSON finding's one-paragraph
193
- // description stands in for a name. Every surface that names a finding
194
- // comes through here, so a report's own title reaches all of them or
195
- // none. The export marker comes off first, being chrome.
196
- export function firstLine(text) {
197
- if (!text) return ''
198
- for (const line of text.split('\n')) {
199
- if (line.trim()) return line.trim()
200
- }
201
- return ''
202
- }
203
-
204
- // The `title` a finding carries, trimmed — '' when there is none a
205
- // reader can use. The three readers below all open on it.
206
- function ownTitle(f) {
207
- return typeof f?.title === 'string' ? f.title.trim() : ''
208
- }
209
-
210
- export function findingTitle(f) {
211
- return ownTitle(f) || firstLine(stripExportMarker(f?.description, f))
212
- }
213
-
214
- // Title + body for a heading-over-body layout. With a `title` field the
215
- // split is already made and the description is all body — except a first
216
- // line REPEATING the title, which would stutter under it.
217
- //
218
- // Without one, the first line is the title, but only over a non-empty
219
- // body: a single-line description stays whole rather than being bolded,
220
- // and one that OPENS on a fence keeps its first line, since lifting it
221
- // out would leave the block unopened and render its code as prose.
222
- export function splitDescription(f) {
223
- const text = stripExportMarker(f?.description, f) || ''
224
- const own = ownTitle(f)
225
- if (own) {
226
- const body = text.trim()
227
- const nl = body.indexOf('\n')
228
- const first = (nl < 0 ? body : body.slice(0, nl)).trim()
229
- if (first !== own) return { title: own, body }
230
- return { title: own, body: nl < 0 ? '' : body.slice(nl + 1).replace(/^\s+/u, '') }
231
- }
232
- if (!text) return { title: '', body: '' }
233
- const nl = text.indexOf('\n')
234
- if (nl < 0) return { title: '', body: text }
235
- // A fence opening at index 0 — the same reading codeBlockSegments
236
- // gives it (format.js).
237
- if (fenceRanges(text)[0]?.[0] === 0) return { title: '', body: text }
238
- const body = text.slice(nl + 1).replace(/^\s+/u, '')
239
- if (!body) return { title: '', body: text }
240
- return { title: text.slice(0, nl).trim(), body }
241
- }
242
-
243
- // The description with the name in front of it — the shape a format
244
- // without a `title` field already writes, for the surfaces that show one
245
- // blob per finding rather than a heading over a body.
246
- export function titledDescription(f) {
247
- const own = ownTitle(f)
248
- if (!own) return stripExportMarker(f?.description, f) || ''
249
- const { body } = splitDescription(f)
250
- return body ? `${own}\n\n${body}` : own
251
- }
252
-
253
- // ── Description sections ─────────────────────────────────────────────
254
- // A description body split into the sections the report wrote it in: a
255
- // paragraph OPENING with `**Label:**` is one. Every parser emits its
256
- // narrative fields that way, whatever the report called them, so keying
257
- // off the markup rather than a list of label words picks all of them up.
258
- //
259
- // Everything else is prose, and consecutive unlabelled paragraphs stay
260
- // in ONE block so their spacing survives. `[{ label, body }]` in
261
- // document order, `label` null for prose.
262
- const SECTION_LABEL_RE = /^\*\*([^*\n]+):\*\*[ \t]*/u
263
-
264
- // Blank lines, but only OUTSIDE a fence. A snippet's own blank line
265
- // would tear the block in two, leaving each half with one bare fence
266
- // marker and neither rendering as code.
267
- function paragraphs(text) {
268
- const ranges = fenceRanges(text)
269
- if (ranges.length === 0) return text.split(/\n{2,}/u)
270
- const parts = []
271
- let last = 0
272
- for (const m of text.matchAll(/\n{2,}/gu)) {
273
- if (inFence(ranges, m.index)) continue
274
- parts.push(text.slice(last, m.index))
275
- last = m.index + m[0].length
276
- }
277
- parts.push(text.slice(last))
278
- return parts
279
- }
280
-
281
- export function descriptionSections(body) {
282
- const sections = []
283
- for (const para of paragraphs(body || '')) {
284
- if (!para.trim()) continue
285
- const m = SECTION_LABEL_RE.exec(para)
286
- if (m) {
287
- sections.push({ label: m[1].trim(), body: para.slice(m[0].length).trim() })
288
- continue
289
- }
290
- const open = sections.at(-1)
291
- if (open && open.label === null) open.body += `\n\n${para}`
292
- else sections.push({ label: null, body: para })
293
- }
294
- return sections
295
- }
296
-
297
- // ── Locations and evidence ───────────────────────────────────────────
298
- // `file:line`, the line dropped when there isn't a finite one ('?' on
299
- // imports that carry none). Takes a finding or an evidence row — both
300
- // carry the pair and print it alike — and passes `line` through raw, so
301
- // a range (`10-20`) survives whole.
302
- export function locationLabel(x) {
303
- return Number.isFinite(parseInt(x?.line, 10)) ? `${x.file}:${x.line}` : (x?.file ?? '')
304
- }
305
-
306
- // An evidence row's note: `text` is what parse-md.js writes from the
307
- // lines under a reference, `observation` what a JSON report may call the
308
- // same thing. No producer emits both, but a row carrying both reads as
309
- // the observation over the text rather than one silently winning.
310
- // Non-string values are ignored — this reads whatever JSON arrives.
311
- export function evidenceNote(row) {
312
- const str = (v) => (typeof v === 'string' ? v : '')
313
- return `${str(row?.observation)}\n${str(row?.text)}`.trim()
314
- }
@@ -1,35 +0,0 @@
1
- // The words the markdown writer uses for the app's enumerations, one
2
- // table per dimension. The viewer's prose surfaces read them too — the
3
- // export dialog, the analyzer dropdown, the page header — so a filter
4
- // the dialog lists and the header line in the file can't disagree.
5
-
6
- import { canonicalSeverity } from './severity.js'
7
-
8
- export const SEVERITY_LABELS = {
9
- critical: 'Critical', high: 'High', medium: 'Medium', low: 'Low',
10
- high_bug: 'High bug', bug: 'Bug', informational: 'Informational',
11
- }
12
-
13
- export const TRIAGE_LABELS = {
14
- inprogress: 'In progress', fixed: 'Fixed', invalid: 'Invalid',
15
- deleted: 'Deleted', ignored: 'Ignored',
16
- }
17
-
18
- export const COLOR_LABELS = { red: 'Red', blue: 'Blue', green: 'Green', gray: 'Gray', none: 'Unmarked' }
19
-
20
- // The producer behind a report's `source` marker, one per format the
21
- // library reads. The analyzer's own dump carries no marker and is
22
- // described by its run meta instead.
23
- export const SOURCE_LABELS = {
24
- 'claude-security': 'Claude Security',
25
- 'codex-security': 'Codex Security',
26
- 'deepsec': 'DeepSec',
27
- 'piolium': 'Piolium',
28
- }
29
-
30
- // An unknown tier prints as itself rather than vanishing: a report can
31
- // invent one, and the reader is better served by the word than by a
32
- // blank.
33
- export function severityLabel(severity) {
34
- return SEVERITY_LABELS[canonicalSeverity(severity)] ?? String(severity ?? '')
35
- }