@drkmttr/adhere 0.9.4 → 0.10.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/README.md +257 -65
- package/package.json +6 -6
- package/presets/alchemy/apis/match-remote-errors-by-tag.md +6 -2
- package/presets/alchemy/apis/schemas-at-trust-boundaries.md +6 -2
- package/presets/alchemy/data/commit-migrations-then-deploy.md +6 -2
- package/presets/alchemy/data/hyperdrive-direct-origin.md +6 -2
- package/presets/alchemy/data/migrations-use-direct-url.md +6 -2
- package/presets/alchemy/data/never-disable-tls-verification.md +6 -2
- package/presets/alchemy/data/parameterized-sql.md +6 -2
- package/presets/alchemy/data/private-endpoint-at-runtime.md +6 -2
- package/presets/alchemy/data/single-migration-owner.md +6 -2
- package/presets/alchemy/durable/idempotent-event-handlers.md +6 -2
- package/presets/alchemy/durable/io-inside-steps.md +6 -2
- package/presets/alchemy/durable/no-self-triggering-bucket-writes.md +6 -2
- package/presets/alchemy/durable/stable-step-names.md +6 -2
- package/presets/alchemy/durable/webhook-state-is-truth.md +6 -2
- package/presets/alchemy/providers/diff-guards-unresolved-props.md +6 -2
- package/presets/alchemy/providers/idempotent-delete.md +8 -3
- package/presets/alchemy/providers/lazy-layer-construction.md +6 -2
- package/presets/alchemy/providers/reconcile-from-observed-state.md +6 -2
- package/presets/alchemy/resources/anchor-paths-with-import-meta.md +6 -2
- package/presets/alchemy/resources/idempotent-actions-and-seeds.md +6 -2
- package/presets/alchemy/resources/previews-reference-shared-resources.md +6 -2
- package/presets/alchemy/resources/protect-irreplaceable-data.md +6 -2
- package/presets/alchemy/resources/remote-state-for-ci.md +6 -2
- package/presets/alchemy/resources/stable-logical-ids.md +6 -2
- package/presets/alchemy/resources/stage-unique-physical-names.md +6 -2
- package/presets/alchemy/runtime/bind-tag-not-implementation.md +6 -2
- package/presets/alchemy/runtime/deterministic-config-defaults.md +6 -2
- package/presets/alchemy/runtime/disposables-in-request-scope.md +6 -2
- package/presets/alchemy/runtime/lambda-no-post-response-work.md +6 -2
- package/presets/alchemy/runtime/no-per-request-work-at-construction.md +6 -2
- package/presets/alchemy/runtime/resolve-config-and-bindings-at-construction.md +6 -2
- package/presets/alchemy/secrets/let-alchemy-mint-infra-secrets.md +6 -2
- package/presets/alchemy/secrets/never-logged-returned-or-output.md +6 -2
- package/presets/alchemy/secrets/none-in-browser-env.md +6 -2
- package/presets/alchemy/secrets/redacted-until-use.md +6 -2
- package/presets/alchemy/secrets/unwrap-before-compare-or-send.md +6 -2
- package/presets/alchemy/security/authorize-public-endpoints.md +6 -2
- package/presets/alchemy/security/github-secret-not-variable.md +6 -2
- package/presets/alchemy/security/identity-from-verified-session.md +6 -2
- package/presets/alchemy/security/keep-auth-protections-on.md +6 -2
- package/presets/alchemy/security/least-privilege-bindings.md +6 -2
- package/presets/alchemy/security/least-privilege-ci-credentials.md +6 -2
- package/presets/alchemy/security/lookup-failure-is-not-anonymous.md +6 -2
- package/presets/effect/basics/external-calls-are-resilient.md +6 -2
- package/presets/effect/basics/gen-for-sequencing.md +6 -2
- package/presets/effect/basics/instrument-with-pipe.md +6 -2
- package/presets/effect/config/business-logic-depends-on-config-service.md +6 -2
- package/presets/effect/config/secrets-are-redacted.md +6 -2
- package/presets/effect/config/tests-provide-values-directly.md +7 -2
- package/presets/effect/config/validate-with-schema.md +6 -2
- package/presets/effect/data/brand-meaningful-primitives.md +6 -2
- package/presets/effect/data/variants-are-tagged-unions.md +6 -2
- package/presets/effect/errors/catch-defects-at-boundaries-only.md +6 -2
- package/presets/effect/errors/defects-for-bugs.md +6 -2
- package/presets/effect/errors/wrap-external-errors.md +6 -2
- package/presets/effect/services/dependencies-through-layers.md +6 -2
- package/presets/effect/services/fresh-layer-per-test.md +7 -2
- package/presets/effect/services/memoize-parameterized-layers.md +6 -2
- package/presets/effect/services/no-mutable-state.md +6 -2
- package/presets/effect/services/operations-have-no-requirements.md +6 -2
- package/presets/effect/services/test-layers-are-in-memory.md +7 -2
- package/presets/effect/testing/test-clock-for-time.md +7 -2
- package/skills/adhere/SKILL.md +40 -12
- package/skills/adhere-fix/SKILL.md +174 -0
- package/src/cli.ts +51 -14
- package/src/comments.ts +36 -0
- package/src/config.ts +98 -14
- package/src/contradictions.ts +13 -3
- package/src/excerpt.ts +11 -1
- package/src/index.ts +1 -1
- package/src/init.ts +119 -12
- package/src/main.ts +0 -1
- package/src/markdown.ts +77 -14
- package/src/mechanical.ts +2 -2
- package/src/models/Audit.ts +2 -0
- package/src/presets.ts +9 -3
- package/src/pricing.ts +14 -0
- package/src/rules.ts +43 -1
- package/src/services/AdhereConfig.ts +40 -14
- package/src/services/Jev.ts +17 -0
- package/src/services/SourceWalker.ts +32 -10
- package/src/suppress.ts +113 -0
- package/src/wording.ts +2 -2
- package/src/workflows/audit.ts +137 -27
- package/src/workflows/format.ts +62 -15
package/src/workflows/format.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Kind, tokenize } from "#highlight.ts";
|
|
2
|
+
import { shownId } from "#rules.ts";
|
|
2
3
|
import type { AuditPlan, AuditResult, FileDone, Finding } from "#workflows/audit.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -8,6 +9,7 @@ import type { AuditPlan, AuditResult, FileDone, Finding } from "#workflows/audit
|
|
|
8
9
|
*/
|
|
9
10
|
type Role =
|
|
10
11
|
| "error"
|
|
12
|
+
| "warning"
|
|
11
13
|
| "probability"
|
|
12
14
|
| "description"
|
|
13
15
|
| "path"
|
|
@@ -26,15 +28,16 @@ type Line = ReadonlyArray<Span>;
|
|
|
26
28
|
/**
|
|
27
29
|
* SGR parameters per role. The frame's are set colors, from the 256-color
|
|
28
30
|
* palette (`38;5;N`) or truecolor (`38;2;R;G;B`), bold with a trailing `;1`,
|
|
29
|
-
* and a dim line number. Of the header, only the `×` and the rule are red,
|
|
30
|
-
* since a whole line of red is hard to read:
|
|
31
|
-
* the
|
|
32
|
-
* be gray. The code's are the terminal's
|
|
33
|
-
* theme picks shades that read on its
|
|
34
|
-
* would run into the underline.
|
|
31
|
+
* and a dim line number. Of the header, only the `×` and the rule are red, or
|
|
32
|
+
* 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.
|
|
35
37
|
*/
|
|
36
38
|
const THEME: Readonly<Record<Role, string>> = {
|
|
37
39
|
error: "38;2;164;20;71;1",
|
|
40
|
+
warning: "38;2;214;154;0;1",
|
|
38
41
|
probability: "38;5;156",
|
|
39
42
|
description: "38;2;230;230;255",
|
|
40
43
|
path: "38;2;5;125;160;1",
|
|
@@ -89,9 +92,9 @@ const counted = (count: number, one: string, many: string): string =>
|
|
|
89
92
|
|
|
90
93
|
const header = (finding: Finding): Line => [
|
|
91
94
|
span(" "),
|
|
92
|
-
span("×",
|
|
95
|
+
span(finding.level === "warning" ? "⚠" : "×", finding.level),
|
|
93
96
|
span(" "),
|
|
94
|
-
span(finding.rule,
|
|
97
|
+
span(shownId({ id: finding.rule, preset: finding.preset }), finding.level),
|
|
95
98
|
span(" ("),
|
|
96
99
|
span(finding.probability.toFixed(2), "probability"),
|
|
97
100
|
span("): "),
|
|
@@ -151,6 +154,13 @@ const frame = (finding: Finding, root: string | undefined): ReadonlyArray<Line>
|
|
|
151
154
|
...hint(finding),
|
|
152
155
|
];
|
|
153
156
|
|
|
157
|
+
/** The findings counted by level: `1 error`, or `2 errors and 1 warning` once there are warnings. */
|
|
158
|
+
const found = (findings: ReadonlyArray<Finding>): string => {
|
|
159
|
+
const warnings = findings.filter((finding) => finding.level === "warning").length;
|
|
160
|
+
const errors = counted(findings.length - warnings, "error", "errors");
|
|
161
|
+
return warnings === 0 ? errors : `${errors} and ${counted(warnings, "warning", "warnings")}`;
|
|
162
|
+
};
|
|
163
|
+
|
|
154
164
|
const summary = (result: AuditResult): ReadonlyArray<Line> => {
|
|
155
165
|
const counts = [
|
|
156
166
|
counted(result.files, "file", "files"),
|
|
@@ -159,11 +169,9 @@ const summary = (result: AuditResult): ReadonlyArray<Line> => {
|
|
|
159
169
|
...(result.skipped > 0 ? [`${result.skipped} skipped`] : []),
|
|
160
170
|
...(result.waiting > 0 ? [`${result.waiting} waiting`] : []),
|
|
161
171
|
...(result.blocked.length > 0 ? [`${result.blocked.length} blocked`] : []),
|
|
172
|
+
...(result.suppressed > 0 ? [`${result.suppressed} suppressed`] : []),
|
|
162
173
|
];
|
|
163
|
-
return [
|
|
164
|
-
[span(`Found ${counted(result.findings.length, "error", "errors")}.`)],
|
|
165
|
-
[span(`${counts.join(", ")}.`)],
|
|
166
|
-
];
|
|
174
|
+
return [[span(`Found ${found(result.findings)}.`)], [span(`${counts.join(", ")}.`)]];
|
|
167
175
|
};
|
|
168
176
|
|
|
169
177
|
/**
|
|
@@ -213,7 +221,7 @@ export const render = (result: AuditResult, options: RenderOptions = {}): Readon
|
|
|
213
221
|
/** What a run was asked to keep to, for the plan to say. */
|
|
214
222
|
export interface RunLimits {
|
|
215
223
|
readonly filter?: ReadonlyArray<string>;
|
|
216
|
-
readonly rpm?: number;
|
|
224
|
+
readonly rpm?: number | undefined;
|
|
217
225
|
}
|
|
218
226
|
|
|
219
227
|
/** About how long `requests` take at `rpm`: seconds under a minute, minutes past it. */
|
|
@@ -224,6 +232,41 @@ const durationAt = (requests: number, rpm: number): string => {
|
|
|
224
232
|
: counted(Math.round(seconds / 60), "minute", "minutes");
|
|
225
233
|
};
|
|
226
234
|
|
|
235
|
+
/** A number to one decimal place, without a trailing .0: 3.2, 43. */
|
|
236
|
+
const tenths = (n: number): string => Number(n.toFixed(1)).toLocaleString("en-US");
|
|
237
|
+
|
|
238
|
+
/** A token count as the plan says it: 612, 41,000, 3.2 million, 43 million, 1.1 billion. */
|
|
239
|
+
const tokensSaid = (tokens: number): string =>
|
|
240
|
+
tokens >= 1e9
|
|
241
|
+
? `${tenths(tokens / 1e9)} billion`
|
|
242
|
+
: tokens >= 1e6
|
|
243
|
+
? `${tenths(tokens / 1e6)} million`
|
|
244
|
+
: tokens >= 1e3
|
|
245
|
+
? (Math.round(tokens / 1e3) * 1e3).toLocaleString("en-US")
|
|
246
|
+
: tokens.toLocaleString("en-US");
|
|
247
|
+
|
|
248
|
+
/** An estimated cost as the plan says it: under $0.01, about $0.14, about $1,234. */
|
|
249
|
+
const dollars = (amount: number): string =>
|
|
250
|
+
amount < 0.01
|
|
251
|
+
? "under $0.01"
|
|
252
|
+
: amount < 100
|
|
253
|
+
? `about $${amount.toFixed(2)}`
|
|
254
|
+
: `about $${Math.round(amount).toLocaleString("en-US")}`;
|
|
255
|
+
|
|
256
|
+
/** What judging the plan costs at the model's price, when adhere knows it. */
|
|
257
|
+
const costOf = (plan: AuditPlan): number | undefined =>
|
|
258
|
+
plan.price === undefined ? undefined : (plan.tokens / 1e6) * plan.price;
|
|
259
|
+
|
|
260
|
+
/** The plan's input tokens and their cost; locating findings comes on top, since it depends on the findings. */
|
|
261
|
+
const costLine = (plan: AuditPlan): string => {
|
|
262
|
+
const cost = costOf(plan);
|
|
263
|
+
const priced =
|
|
264
|
+
cost === undefined || plan.price === undefined
|
|
265
|
+
? ""
|
|
266
|
+
: `: ${dollars(cost)} at $${plan.price} per million`;
|
|
267
|
+
return `Those carry about ${tokensSaid(plan.tokens)} input tokens${priced}, and more for locating findings.`;
|
|
268
|
+
};
|
|
269
|
+
|
|
227
270
|
export const describePlan = (plan: AuditPlan, limits: RunLimits = {}): ReadonlyArray<string> => {
|
|
228
271
|
const cached = plan.cached > 0 ? `, ${plan.cached} cached` : "";
|
|
229
272
|
const skipped =
|
|
@@ -260,12 +303,16 @@ export const describePlan = (plan: AuditPlan, limits: RunLimits = {}): ReadonlyA
|
|
|
260
303
|
return [
|
|
261
304
|
found,
|
|
262
305
|
`Judging ${rest} takes ${counted(plan.requests, "request", "requests")} to Jev, plus 1 or more for each file with a finding.${pace}${waiting}`,
|
|
306
|
+
...(plan.tokens > 0 ? [costLine(plan)] : []),
|
|
263
307
|
];
|
|
264
308
|
};
|
|
265
309
|
|
|
266
310
|
/** The question before a run sends anything. */
|
|
267
|
-
export const sendQuestion = (plan: AuditPlan): string =>
|
|
268
|
-
|
|
311
|
+
export const sendQuestion = (plan: AuditPlan): string => {
|
|
312
|
+
const cost = costOf(plan);
|
|
313
|
+
const priced = cost === undefined || plan.tokens === 0 ? "" : `, ${dollars(cost)}`;
|
|
314
|
+
return `Send ${counted(plan.requests, "request", "requests")} to Jev${priced}?`;
|
|
315
|
+
};
|
|
269
316
|
|
|
270
317
|
/** A run's progress so far: every file it finished, with the requests and findings they added up to. */
|
|
271
318
|
export interface Progress {
|