@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.
Files changed (87) hide show
  1. package/README.md +257 -65
  2. package/package.json +6 -6
  3. package/presets/alchemy/apis/match-remote-errors-by-tag.md +6 -2
  4. package/presets/alchemy/apis/schemas-at-trust-boundaries.md +6 -2
  5. package/presets/alchemy/data/commit-migrations-then-deploy.md +6 -2
  6. package/presets/alchemy/data/hyperdrive-direct-origin.md +6 -2
  7. package/presets/alchemy/data/migrations-use-direct-url.md +6 -2
  8. package/presets/alchemy/data/never-disable-tls-verification.md +6 -2
  9. package/presets/alchemy/data/parameterized-sql.md +6 -2
  10. package/presets/alchemy/data/private-endpoint-at-runtime.md +6 -2
  11. package/presets/alchemy/data/single-migration-owner.md +6 -2
  12. package/presets/alchemy/durable/idempotent-event-handlers.md +6 -2
  13. package/presets/alchemy/durable/io-inside-steps.md +6 -2
  14. package/presets/alchemy/durable/no-self-triggering-bucket-writes.md +6 -2
  15. package/presets/alchemy/durable/stable-step-names.md +6 -2
  16. package/presets/alchemy/durable/webhook-state-is-truth.md +6 -2
  17. package/presets/alchemy/providers/diff-guards-unresolved-props.md +6 -2
  18. package/presets/alchemy/providers/idempotent-delete.md +8 -3
  19. package/presets/alchemy/providers/lazy-layer-construction.md +6 -2
  20. package/presets/alchemy/providers/reconcile-from-observed-state.md +6 -2
  21. package/presets/alchemy/resources/anchor-paths-with-import-meta.md +6 -2
  22. package/presets/alchemy/resources/idempotent-actions-and-seeds.md +6 -2
  23. package/presets/alchemy/resources/previews-reference-shared-resources.md +6 -2
  24. package/presets/alchemy/resources/protect-irreplaceable-data.md +6 -2
  25. package/presets/alchemy/resources/remote-state-for-ci.md +6 -2
  26. package/presets/alchemy/resources/stable-logical-ids.md +6 -2
  27. package/presets/alchemy/resources/stage-unique-physical-names.md +6 -2
  28. package/presets/alchemy/runtime/bind-tag-not-implementation.md +6 -2
  29. package/presets/alchemy/runtime/deterministic-config-defaults.md +6 -2
  30. package/presets/alchemy/runtime/disposables-in-request-scope.md +6 -2
  31. package/presets/alchemy/runtime/lambda-no-post-response-work.md +6 -2
  32. package/presets/alchemy/runtime/no-per-request-work-at-construction.md +6 -2
  33. package/presets/alchemy/runtime/resolve-config-and-bindings-at-construction.md +6 -2
  34. package/presets/alchemy/secrets/let-alchemy-mint-infra-secrets.md +6 -2
  35. package/presets/alchemy/secrets/never-logged-returned-or-output.md +6 -2
  36. package/presets/alchemy/secrets/none-in-browser-env.md +6 -2
  37. package/presets/alchemy/secrets/redacted-until-use.md +6 -2
  38. package/presets/alchemy/secrets/unwrap-before-compare-or-send.md +6 -2
  39. package/presets/alchemy/security/authorize-public-endpoints.md +6 -2
  40. package/presets/alchemy/security/github-secret-not-variable.md +6 -2
  41. package/presets/alchemy/security/identity-from-verified-session.md +6 -2
  42. package/presets/alchemy/security/keep-auth-protections-on.md +6 -2
  43. package/presets/alchemy/security/least-privilege-bindings.md +6 -2
  44. package/presets/alchemy/security/least-privilege-ci-credentials.md +6 -2
  45. package/presets/alchemy/security/lookup-failure-is-not-anonymous.md +6 -2
  46. package/presets/effect/basics/external-calls-are-resilient.md +6 -2
  47. package/presets/effect/basics/gen-for-sequencing.md +6 -2
  48. package/presets/effect/basics/instrument-with-pipe.md +6 -2
  49. package/presets/effect/config/business-logic-depends-on-config-service.md +6 -2
  50. package/presets/effect/config/secrets-are-redacted.md +6 -2
  51. package/presets/effect/config/tests-provide-values-directly.md +7 -2
  52. package/presets/effect/config/validate-with-schema.md +6 -2
  53. package/presets/effect/data/brand-meaningful-primitives.md +6 -2
  54. package/presets/effect/data/variants-are-tagged-unions.md +6 -2
  55. package/presets/effect/errors/catch-defects-at-boundaries-only.md +6 -2
  56. package/presets/effect/errors/defects-for-bugs.md +6 -2
  57. package/presets/effect/errors/wrap-external-errors.md +6 -2
  58. package/presets/effect/services/dependencies-through-layers.md +6 -2
  59. package/presets/effect/services/fresh-layer-per-test.md +7 -2
  60. package/presets/effect/services/memoize-parameterized-layers.md +6 -2
  61. package/presets/effect/services/no-mutable-state.md +6 -2
  62. package/presets/effect/services/operations-have-no-requirements.md +6 -2
  63. package/presets/effect/services/test-layers-are-in-memory.md +7 -2
  64. package/presets/effect/testing/test-clock-for-time.md +7 -2
  65. package/skills/adhere/SKILL.md +40 -12
  66. package/skills/adhere-fix/SKILL.md +174 -0
  67. package/src/cli.ts +51 -14
  68. package/src/comments.ts +36 -0
  69. package/src/config.ts +98 -14
  70. package/src/contradictions.ts +13 -3
  71. package/src/excerpt.ts +11 -1
  72. package/src/index.ts +1 -1
  73. package/src/init.ts +119 -12
  74. package/src/main.ts +0 -1
  75. package/src/markdown.ts +77 -14
  76. package/src/mechanical.ts +2 -2
  77. package/src/models/Audit.ts +2 -0
  78. package/src/presets.ts +9 -3
  79. package/src/pricing.ts +14 -0
  80. package/src/rules.ts +43 -1
  81. package/src/services/AdhereConfig.ts +40 -14
  82. package/src/services/Jev.ts +17 -0
  83. package/src/services/SourceWalker.ts +32 -10
  84. package/src/suppress.ts +113 -0
  85. package/src/wording.ts +2 -2
  86. package/src/workflows/audit.ts +137 -27
  87. package/src/workflows/format.ts +62 -15
@@ -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: the probability is an accent, and
31
- * the description a cool near-white, set here since a theme's own white can
32
- * be gray. The code's are the terminal's own colors (`3N`), so the user's
33
- * theme picks shades that read on its background; none is magenta, which
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("×", "error"),
95
+ span(finding.level === "warning" ? "⚠" : "×", finding.level),
93
96
  span(" "),
94
- span(finding.rule, "error"),
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
- `Send ${counted(plan.requests, "request", "requests")} to Jev?`;
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 {