@drkmttr/adhere 0.11.0 → 0.13.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 (102) hide show
  1. package/README.md +184 -37
  2. package/package.json +9 -9
  3. package/presets/effect/basics/{instrument-with-pipe.md → instrument-with-pipe/RULE.md} +1 -1
  4. package/presets/effect/config/{business-logic-depends-on-config-service.md → business-logic-depends-on-config-service/RULE.md} +3 -3
  5. package/presets/effect/config/{secrets-are-redacted.md → secrets-are-redacted/RULE.md} +3 -3
  6. package/presets/effect/config/{validate-with-schema.md → validate-with-schema/RULE.md} +4 -4
  7. package/skills/adhere/SKILL.md +206 -118
  8. package/skills/adhere-fix/SKILL.md +9 -8
  9. package/skills/adhere-setup/SKILL.md +244 -0
  10. package/src/cli.ts +114 -25
  11. package/src/config.ts +50 -3
  12. package/src/index.ts +2 -0
  13. package/src/init.ts +193 -27
  14. package/src/install.ts +158 -0
  15. package/src/main.ts +1 -1
  16. package/src/rules.ts +135 -56
  17. package/src/services/AdhereConfig.ts +2 -2
  18. package/src/services/Credentials.ts +4 -4
  19. package/src/services/Jev.http.ts +76 -10
  20. package/src/services/Jev.ts +39 -10
  21. package/src/workflows/audit.ts +23 -12
  22. package/src/workflows/format.ts +172 -35
  23. /package/presets/alchemy/apis/{match-remote-errors-by-tag.md → match-remote-errors-by-tag/RULE.md} +0 -0
  24. /package/presets/alchemy/apis/{schemas-at-trust-boundaries.md → schemas-at-trust-boundaries/RULE.md} +0 -0
  25. /package/presets/alchemy/data/{commit-migrations-then-deploy.md → commit-migrations-then-deploy/RULE.md} +0 -0
  26. /package/presets/alchemy/data/{hyperdrive-direct-origin.md → hyperdrive-direct-origin/RULE.md} +0 -0
  27. /package/presets/alchemy/data/{migrations-use-direct-url.md → migrations-use-direct-url/RULE.md} +0 -0
  28. /package/presets/alchemy/data/{never-disable-tls-verification.md → never-disable-tls-verification/RULE.md} +0 -0
  29. /package/presets/alchemy/data/{private-endpoint-at-runtime.md → private-endpoint-at-runtime/RULE.md} +0 -0
  30. /package/presets/alchemy/data/{single-migration-owner.md → single-migration-owner/RULE.md} +0 -0
  31. /package/presets/alchemy/durable/{idempotent-event-handlers.md → idempotent-event-handlers/RULE.md} +0 -0
  32. /package/presets/alchemy/durable/{io-inside-steps.md → io-inside-steps/RULE.md} +0 -0
  33. /package/presets/alchemy/durable/{no-self-triggering-bucket-writes.md → no-self-triggering-bucket-writes/RULE.md} +0 -0
  34. /package/presets/alchemy/durable/{stable-step-names.md → stable-step-names/RULE.md} +0 -0
  35. /package/presets/alchemy/durable/{webhook-state-is-truth.md → webhook-state-is-truth/RULE.md} +0 -0
  36. /package/presets/alchemy/providers/{diff-guards-unresolved-props.md → diff-guards-unresolved-props/RULE.md} +0 -0
  37. /package/presets/alchemy/providers/{idempotent-delete.md → idempotent-delete/RULE.md} +0 -0
  38. /package/presets/alchemy/providers/{lazy-layer-construction.md → lazy-layer-construction/RULE.md} +0 -0
  39. /package/presets/alchemy/providers/{reconcile-from-observed-state.md → reconcile-from-observed-state/RULE.md} +0 -0
  40. /package/presets/alchemy/resources/{anchor-paths-with-import-meta.md → anchor-paths-with-import-meta/RULE.md} +0 -0
  41. /package/presets/alchemy/resources/{idempotent-actions-and-seeds.md → idempotent-actions-and-seeds/RULE.md} +0 -0
  42. /package/presets/alchemy/resources/{previews-reference-shared-resources.md → previews-reference-shared-resources/RULE.md} +0 -0
  43. /package/presets/alchemy/resources/{protect-irreplaceable-data.md → protect-irreplaceable-data/RULE.md} +0 -0
  44. /package/presets/alchemy/resources/{remote-state-for-ci.md → remote-state-for-ci/RULE.md} +0 -0
  45. /package/presets/alchemy/resources/{stable-logical-ids.md → stable-logical-ids/RULE.md} +0 -0
  46. /package/presets/alchemy/resources/{stage-unique-physical-names.md → stage-unique-physical-names/RULE.md} +0 -0
  47. /package/presets/alchemy/runtime/{bind-tag-not-implementation.md → bind-tag-not-implementation/RULE.md} +0 -0
  48. /package/presets/alchemy/runtime/{deterministic-config-defaults.md → deterministic-config-defaults/RULE.md} +0 -0
  49. /package/presets/alchemy/runtime/{disposables-in-request-scope.md → disposables-in-request-scope/RULE.md} +0 -0
  50. /package/presets/alchemy/runtime/{lambda-no-post-response-work.md → lambda-no-post-response-work/RULE.md} +0 -0
  51. /package/presets/alchemy/runtime/{no-per-request-work-at-construction.md → no-per-request-work-at-construction/RULE.md} +0 -0
  52. /package/presets/alchemy/runtime/{resolve-config-and-bindings-at-construction.md → resolve-config-and-bindings-at-construction/RULE.md} +0 -0
  53. /package/presets/alchemy/secrets/{let-alchemy-mint-infra-secrets.md → let-alchemy-mint-infra-secrets/RULE.md} +0 -0
  54. /package/presets/alchemy/secrets/{none-in-browser-env.md → none-in-browser-env/RULE.md} +0 -0
  55. /package/presets/alchemy/secrets/{redacted-until-use.md → redacted-until-use/RULE.md} +0 -0
  56. /package/presets/alchemy/secrets/{unwrap-before-compare-or-send.md → unwrap-before-compare-or-send/RULE.md} +0 -0
  57. /package/presets/alchemy/security/{authorize-public-endpoints.md → authorize-public-endpoints/RULE.md} +0 -0
  58. /package/presets/alchemy/security/{github-secret-not-variable.md → github-secret-not-variable/RULE.md} +0 -0
  59. /package/presets/alchemy/security/{identity-from-verified-session.md → identity-from-verified-session/RULE.md} +0 -0
  60. /package/presets/alchemy/security/{keep-auth-protections-on.md → keep-auth-protections-on/RULE.md} +0 -0
  61. /package/presets/alchemy/security/{least-privilege-bindings.md → least-privilege-bindings/RULE.md} +0 -0
  62. /package/presets/alchemy/security/{least-privilege-ci-credentials.md → least-privilege-ci-credentials/RULE.md} +0 -0
  63. /package/presets/alchemy/security/{lookup-failure-is-not-anonymous.md → lookup-failure-is-not-anonymous/RULE.md} +0 -0
  64. /package/presets/effect/basics/{gen-for-sequencing.md → gen-for-sequencing/RULE.md} +0 -0
  65. /package/presets/effect/config/{tests-provide-values-directly.md → tests-provide-values-directly/RULE.md} +0 -0
  66. /package/presets/effect/data/{brand-meaningful-primitives.md → brand-meaningful-primitives/RULE.md} +0 -0
  67. /package/presets/effect/data/{variants-are-tagged-unions.md → variants-are-tagged-unions/RULE.md} +0 -0
  68. /package/presets/effect/errors/{catch-defects-at-boundaries-only.md → catch-defects-at-boundaries-only/RULE.md} +0 -0
  69. /package/presets/effect/errors/{defects-for-bugs.md → defects-for-bugs/RULE.md} +0 -0
  70. /package/presets/effect/errors/{wrap-external-errors.md → wrap-external-errors/RULE.md} +0 -0
  71. /package/presets/effect/services/{dependencies-through-layers.md → dependencies-through-layers/RULE.md} +0 -0
  72. /package/presets/effect/services/{fresh-layer-per-test.md → fresh-layer-per-test/RULE.md} +0 -0
  73. /package/presets/effect/services/{memoize-parameterized-layers.md → memoize-parameterized-layers/RULE.md} +0 -0
  74. /package/presets/effect/services/{no-mutable-state.md → no-mutable-state/RULE.md} +0 -0
  75. /package/presets/effect/services/{operations-have-no-requirements.md → operations-have-no-requirements/RULE.md} +0 -0
  76. /package/presets/effect/services/{test-layers-are-in-memory.md → test-layers-are-in-memory/RULE.md} +0 -0
  77. /package/presets/effect/testing/{test-clock-for-time.md → test-clock-for-time/RULE.md} +0 -0
  78. /package/presets/react/effects/{clean-up-what-they-start.md → clean-up-what-they-start/RULE.md} +0 -0
  79. /package/presets/react/effects/{event-logic-in-handlers.md → event-logic-in-handlers/RULE.md} +0 -0
  80. /package/presets/react/effects/{external-stores-use-sync-external-store.md → external-stores-use-sync-external-store/RULE.md} +0 -0
  81. /package/presets/react/effects/{fetches-ignore-stale-responses.md → fetches-ignore-stale-responses/RULE.md} +0 -0
  82. /package/presets/react/effects/{notify-parents-in-handlers.md → notify-parents-in-handlers/RULE.md} +0 -0
  83. /package/presets/react/server/{no-private-data-to-client.md → no-private-data-to-client/RULE.md} +0 -0
  84. /package/presets/react/server/{server-functions-authorize-and-validate.md → server-functions-authorize-and-validate/RULE.md} +0 -0
  85. /package/presets/react/state/{no-contradictory-state.md → no-contradictory-state/RULE.md} +0 -0
  86. /package/presets/react/state/{no-props-copied-into-state.md → no-props-copied-into-state/RULE.md} +0 -0
  87. /package/presets/react/state/{store-ids-not-copies.md → store-ids-not-copies/RULE.md} +0 -0
  88. /package/presets/security/{constant-time-secret-compare.md → constant-time-secret-compare/RULE.md} +0 -0
  89. /package/presets/security/{no-secrets-in-output.md → no-secrets-in-output/RULE.md} +0 -0
  90. /package/presets/security/{no-untrusted-input-in-commands-or-paths.md → no-untrusted-input-in-commands-or-paths/RULE.md} +0 -0
  91. /package/presets/security/{parameterized-queries.md → parameterized-queries/RULE.md} +0 -0
  92. /package/presets/typescript/async/{concurrent-independent-work.md → concurrent-independent-work/RULE.md} +0 -0
  93. /package/presets/typescript/async/{network-calls-have-timeouts.md → network-calls-have-timeouts/RULE.md} +0 -0
  94. /package/presets/typescript/design/{no-argument-mutation.md → no-argument-mutation/RULE.md} +0 -0
  95. /package/presets/typescript/errors/{no-swallowed-errors.md → no-swallowed-errors/RULE.md} +0 -0
  96. /package/presets/typescript/errors/{release-on-every-path.md → release-on-every-path/RULE.md} +0 -0
  97. /package/presets/typescript/testing/{assert-outcomes.md → assert-outcomes/RULE.md} +0 -0
  98. /package/presets/typescript/testing/{independent-tests.md → independent-tests/RULE.md} +0 -0
  99. /package/presets/typescript/types/{assertions-only-when-proven.md → assertions-only-when-proven/RULE.md} +0 -0
  100. /package/presets/typescript/types/{derive-dont-restate.md → derive-dont-restate/RULE.md} +0 -0
  101. /package/presets/typescript/types/{unrepresentable-invalid-states.md → unrepresentable-invalid-states/RULE.md} +0 -0
  102. /package/presets/typescript/types/{validate-external-data.md → validate-external-data/RULE.md} +0 -0
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_SUFFICIENCY_THRESHOLD, DEFAULT_THRESHOLD } from "#config.ts";
1
2
  import { type Kind, tokenize } from "#highlight.ts";
2
3
  import { shownId } from "#rules.ts";
3
4
  import type { AuditPlan, AuditResult, FileDone, Finding } from "#workflows/audit.ts";
@@ -11,11 +12,15 @@ type Role =
11
12
  | "error"
12
13
  | "warning"
13
14
  | "probability"
15
+ | "low"
16
+ | "below"
14
17
  | "description"
15
18
  | "path"
16
19
  | "lineNumber"
17
20
  | "underline"
18
21
  | "label"
22
+ | "legend"
23
+ | "quiet"
19
24
  | Kind;
20
25
 
21
26
  interface Span {
@@ -30,20 +35,28 @@ type Line = ReadonlyArray<Span>;
30
35
  * palette (`38;5;N`) or truecolor (`38;2;R;G;B`), bold with a trailing `;1`,
31
36
  * and a dim line number. Of the header, only the `×` and the rule are red, or
32
37
  * a warning's `⚠` and rule amber, since a whole line of red is hard to read:
33
- * the probability is an accent, and the description a cool near-white, set
34
- * here since a theme's own white can be gray. The code's are the terminal's
35
- * own colors (`3N`), so the user's theme picks shades that read on its
36
- * background; none is magenta, which would run into the underline.
38
+ * the description is a cool near-white, set here since a theme's own white
39
+ * can be gray. A score's three colors are one set, of like lightness, so
40
+ * they read as a scale, and none is the red of the `×`. Its labels, and the
41
+ * legend's meanings, are dim. The legend's words are bold in the terminal's
42
+ * own color, so they do not read as the hint's label. The code's are the
43
+ * terminal's own colors (`3N`), so the
44
+ * user's theme picks shades that read on its background; none is magenta,
45
+ * which would run into the underline.
37
46
  */
38
47
  const THEME: Readonly<Record<Role, string>> = {
39
48
  error: "38;2;164;20;71;1",
40
49
  warning: "38;2;214;154;0;1",
41
- probability: "38;5;156",
50
+ probability: "38;2;152;195;121",
51
+ low: "38;2;229;192;123",
52
+ below: "38;2;224;108;117",
42
53
  description: "38;2;230;230;255",
43
54
  path: "38;2;5;125;160;1",
44
55
  lineNumber: "2",
45
56
  underline: "38;2;255;0;175",
46
57
  label: "38;2;242;205;205",
58
+ legend: "1",
59
+ quiet: "2",
47
60
  comment: "2",
48
61
  string: "32",
49
62
  constant: "33",
@@ -90,15 +103,60 @@ const displayPath = (file: string, root: string | undefined): string =>
90
103
  const counted = (count: number, one: string, many: string): string =>
91
104
  `${count} ${count === 1 ? one : many}`;
92
105
 
93
- const header = (finding: Finding): Line => [
94
- span(" "),
95
- span(finding.level === "warning" ? "⚠" : "×", finding.level),
96
- span(" "),
97
- span(shownId({ id: finding.rule, preset: finding.preset }), finding.level),
98
- span(" ("),
99
- span(finding.probability.toFixed(2), "probability"),
100
- span("): "),
101
- span(finding.description, "description"),
106
+ /** A score as a header shows it, in hundredths, so two compare as they read. */
107
+ const hundredths = (value: number): number => Math.round(value * 100);
108
+
109
+ /**
110
+ * Where a confidence stops being far below a threshold and where it starts
111
+ * being well above it, in hundredths. Between the two it is near the
112
+ * threshold: within a quarter of the room above it, on either side, so the
113
+ * band narrows as the threshold rises and leaves room above for scores to be
114
+ * well clear of it. Scores that gather in the band, reported or not, turn on
115
+ * where the threshold sits, a sign it may be too low or too high.
116
+ */
117
+ const bandsOf = (threshold: number): { readonly near: number; readonly clear: number } => {
118
+ const at = hundredths(threshold);
119
+ const margin = (100 - at) / 4;
120
+ return { near: Math.max(0, Math.ceil(at - margin)), clear: Math.ceil(at + margin) };
121
+ };
122
+
123
+ /**
124
+ * A score in one of three ranges set by its threshold: amber near it, red
125
+ * below that, the accent above. A confidence's threshold is its rule's, and
126
+ * a red one shows only in a run that prints every judgment. A context's is
127
+ * the sufficiency threshold, and a red one marks the finding to doubt first.
128
+ */
129
+ const score = (value: number, threshold: number): Span => {
130
+ const { near, clear } = bandsOf(threshold);
131
+ const shown = hundredths(value);
132
+ return span(value.toFixed(2), shown < near ? "below" : shown < clear ? "low" : "probability");
133
+ };
134
+
135
+ const lacksContext = (finding: Finding, sufficiencyThreshold: number): boolean =>
136
+ finding.context !== undefined && finding.context < sufficiencyThreshold;
137
+
138
+ /**
139
+ * The rule and its description, as `vp lint` heads a diagnostic, then the
140
+ * scores on a line of their own, where they start in one place whatever the
141
+ * rule's length. Their labels are dim, so only the numbers carry color.
142
+ */
143
+ const header = (finding: Finding, sufficiencyThreshold: number): ReadonlyArray<Line> => [
144
+ [
145
+ span(" "),
146
+ span(finding.level === "warning" ? "⚠" : "×", finding.level),
147
+ span(" "),
148
+ span(shownId({ id: finding.rule, preset: finding.preset }), finding.level),
149
+ span(": "),
150
+ span(finding.description, "description"),
151
+ ],
152
+ [
153
+ span(" "),
154
+ span("confidence ", "quiet"),
155
+ score(finding.probability, finding.threshold),
156
+ ...(finding.context === undefined
157
+ ? []
158
+ : [span(" · context ", "quiet"), score(finding.context, sufficiencyThreshold)]),
159
+ ],
102
160
  ];
103
161
 
104
162
  /**
@@ -151,19 +209,14 @@ const excerpt = (finding: Finding, root: string | undefined): ReadonlyArray<Line
151
209
 
152
210
  /**
153
211
  * The warning on a finding whose file may not show enough to check its rule,
154
- * with Jev's probability that it does not, and help: how to add what the
155
- * code relies on as a note Jev reads, in the form the README asks for.
212
+ * which its context score in the header says how far, and help: how to add
213
+ * what the code relies on as a note Jev reads, in the form the README asks for.
156
214
  */
157
- const insufficient = (finding: Finding): ReadonlyArray<Line> =>
158
- finding.insufficiency === undefined
215
+ const insufficient = (finding: Finding, sufficiencyThreshold: number): ReadonlyArray<Line> =>
216
+ !lacksContext(finding, sufficiencyThreshold)
159
217
  ? []
160
218
  : [
161
- [
162
- span(" warning: ", "warning"),
163
- span(
164
- `this file may not show enough to check this rule (Jev: ${finding.insufficiency.toFixed(2)} that it does not)`,
165
- ),
166
- ],
219
+ [span(" warning: ", "warning"), span("this file may not show enough to check this rule")],
167
220
  ...hanging(span(" help: ", "label"), [
168
221
  [span("add what the code relies on outside this file as a note Jev reads:")],
169
222
  highlighted("// @adhere <the fact>, and how you know it")[0] ?? [],
@@ -183,17 +236,91 @@ const hint = (finding: Finding): ReadonlyArray<Line> => {
183
236
 
184
237
  /**
185
238
  * One diagnostic, in the frame `vp lint` prints on a terminal: a header with
186
- * the rule in red, the section of the file Jev points at with the lines it
187
- * names in pink, a warning when the file may not show enough to decide, and
188
- * the code to write as the hint.
239
+ * the rule in red and its scores, the description, the section of the file
240
+ * Jev points at with the lines it names in pink, a warning when the file may
241
+ * not show enough to decide, and the code to write as the hint.
189
242
  */
190
- const frame = (finding: Finding, root: string | undefined): ReadonlyArray<Line> => [
191
- header(finding),
243
+ const frame = (
244
+ finding: Finding,
245
+ root: string | undefined,
246
+ sufficiencyThreshold: number,
247
+ ): ReadonlyArray<Line> => [
248
+ ...header(finding, sufficiencyThreshold),
192
249
  ...excerpt(finding, root),
193
- ...insufficient(finding),
250
+ ...insufficient(finding, sufficiencyThreshold),
194
251
  ...hint(finding),
195
252
  ];
196
253
 
254
+ /** A bound of a range: hundredths as a score shows them, and 0 and 1 without decimals. */
255
+ const bound = (value: number): string =>
256
+ value === 0 ? "0" : value === 100 ? "1" : (value / 100).toFixed(2);
257
+
258
+ /** The scores from one number of hundredths through another: `0–0.74`, or `0.85–1`. */
259
+ const range = (from: number, to: number): string => `${bound(from)}–${bound(to)}`;
260
+
261
+ /**
262
+ * A legend row's cells for a threshold: the scores far below it, the
263
+ * threshold, and the scores well above it, as in `0–0.74 < 0.8 < 0.85–1`.
264
+ * The scores between the two ranges are the ones near the threshold, so it
265
+ * and each `<` are in their color, and the ranges in theirs. A threshold too low to
266
+ * have scores far below it has no first range, and no `<` after one.
267
+ */
268
+ const cellsOf = (threshold: number): ReadonlyArray<Span> => {
269
+ const { near, clear } = bandsOf(threshold);
270
+ return [
271
+ span(near > 0 ? range(0, near - 1) : "", "below"),
272
+ span(near > 0 ? "<" : "", "low"),
273
+ span(String(hundredths(threshold) / 100), "low"),
274
+ span("<", "low"),
275
+ span(range(clear, 100), "probability"),
276
+ ];
277
+ };
278
+
279
+ /**
280
+ * What the scores under a header mean, once under the findings, as a table:
281
+ * a row for each score with its ranges around its threshold, then what the
282
+ * score is, dim. The cells stand in columns, so a color lines up down the
283
+ * rows. A confidence's threshold is the run's; a rule with a threshold of
284
+ * its own has its ranges around that. A context's is the sufficiency
285
+ * threshold.
286
+ */
287
+ const legend = (
288
+ result: AuditResult,
289
+ threshold: number,
290
+ sufficiencyThreshold: number,
291
+ ): ReadonlyArray<Line> => {
292
+ const rows = [
293
+ ["confidence", cellsOf(threshold), "Jev's probability that the file breaks the rule"],
294
+ [
295
+ "context",
296
+ cellsOf(sufficiencyThreshold),
297
+ "its probability that the file shows enough to decide",
298
+ ],
299
+ ] as const;
300
+ const padding = (text: string, width: number): Span => span(" ".repeat(width - text.length));
301
+ const labels = Math.max(...rows.map(([label]) => label.length));
302
+ const widthOf = (column: number): number =>
303
+ Math.max(...rows.map(([, cells]) => cells[column]?.text.length ?? 0));
304
+ return result.findings.length === 0
305
+ ? []
306
+ : [
307
+ ...rows.map(([label, cells, meaning]) => [
308
+ span(" "),
309
+ span(label, "legend"),
310
+ padding(label, labels),
311
+ span(" "),
312
+ ...cells.flatMap((cell, column) => [
313
+ span(" "),
314
+ cell,
315
+ padding(cell.text, widthOf(column)),
316
+ ]),
317
+ span(" "),
318
+ span(meaning, "quiet"),
319
+ ]),
320
+ [],
321
+ ];
322
+ };
323
+
197
324
  /** The findings counted by level: `1 error`, or `2 errors and 1 warning` once there are warnings. */
198
325
  const found = (findings: ReadonlyArray<Finding>): string => {
199
326
  const warnings = findings.filter((finding) => finding.level === "warning").length;
@@ -239,19 +366,29 @@ export interface RenderOptions {
239
366
  /** Color the frame the way `vp lint` does on a terminal, and highlight the code. */
240
367
  readonly color?: boolean;
241
368
  readonly root?: string;
369
+ /** The run's threshold, for the legend: lint reports no finding at or below it. Defaults to 0.8. */
370
+ readonly threshold?: number;
371
+ /** Below this, a finding's context is low and it carries a warning. Defaults to 0.6. */
372
+ readonly sufficiencyThreshold?: number;
242
373
  }
243
374
 
244
375
  /**
245
376
  * The report, in the default `vp lint` layout: each diagnostic followed by a
246
- * blank line, then the counts. Color is on when the caller is writing to a
247
- * terminal.
377
+ * blank line, then the legend for their scores, then the counts. Color is on
378
+ * when the caller is writing to a terminal.
248
379
  */
249
- export const render = (result: AuditResult, options: RenderOptions = {}): ReadonlyArray<string> =>
250
- [
251
- ...result.findings.flatMap((finding) => [...frame(finding, options.root), []]),
380
+ export const render = (result: AuditResult, options: RenderOptions = {}): ReadonlyArray<string> => {
381
+ const sufficiencyThreshold = options.sufficiencyThreshold ?? DEFAULT_SUFFICIENCY_THRESHOLD;
382
+ return [
383
+ ...result.findings.flatMap((finding) => [
384
+ ...frame(finding, options.root, sufficiencyThreshold),
385
+ [],
386
+ ]),
387
+ ...legend(result, options.threshold ?? DEFAULT_THRESHOLD, sufficiencyThreshold),
252
388
  ...summary(result),
253
389
  ...blockedFiles(result, options.root),
254
390
  ].map((line) => serialize(line, options.color === true));
391
+ };
255
392
 
256
393
  /**
257
394
  * What a run will do, before it asks: the files and rules found, the checks