@heyramzi/lint 0.0.0-stage → 1.32.2

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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +277 -2
  3. package/bin/heyramzi-comment-length.js +99 -0
  4. package/bin/heyramzi-commit-msg.js +31 -0
  5. package/bin/heyramzi-file-tokens.js +66 -0
  6. package/bin/heyramzi-inline-path.js +79 -0
  7. package/bin/heyramzi-jev-code.js +107 -0
  8. package/bin/heyramzi-push-diff.js +93 -0
  9. package/bin/heyramzi-repo-rules.js +70 -0
  10. package/bin/heyramzi-slop.js +314 -0
  11. package/bin/heyramzi-svelte-markup.js +50 -0
  12. package/bin/heyramzi-ui-rules.js +56 -0
  13. package/comment-length.js +368 -0
  14. package/commit-msg.js +57 -0
  15. package/config/oxlintrc.json +87 -0
  16. package/data/invisibles.js +218 -0
  17. package/data/jev-bench.js +115 -0
  18. package/data/jev-code-bench.js +313 -0
  19. package/data/slop-words.d.ts +64 -0
  20. package/data/slop-words.js +2681 -0
  21. package/file-tokens.js +122 -0
  22. package/gate-files.js +14 -0
  23. package/index.js +116 -0
  24. package/inline-path.js +90 -0
  25. package/jev-code.js +194 -0
  26. package/jev.js +341 -0
  27. package/package.json +80 -4
  28. package/processors/markdown.js +22 -0
  29. package/push-diff.js +55 -0
  30. package/register.d.ts +43 -0
  31. package/register.js +255 -0
  32. package/repo-rules/packages.js +164 -0
  33. package/repo-rules/paths.js +101 -0
  34. package/repo-rules/toolchain.js +172 -0
  35. package/repo-rules/tree.js +51 -0
  36. package/repo-rules/workflows.js +116 -0
  37. package/repo-rules.js +54 -0
  38. package/rules/convex-ast.js +66 -0
  39. package/rules/convex-index-name.js +40 -0
  40. package/rules/convex-internal-only.js +59 -0
  41. package/rules/convex-no-date-now-in-query.js +48 -0
  42. package/rules/convex-no-deprecated-crons.js +40 -0
  43. package/rules/convex-no-filter.js +41 -0
  44. package/rules/convex-no-get-metadata.js +26 -0
  45. package/rules/convex-no-v-any.js +24 -0
  46. package/rules/convex-react-client-module-scope.js +37 -0
  47. package/rules/convex-require-args.js +39 -0
  48. package/rules/convex-require-returns.js +31 -0
  49. package/rules/convex-runtime-imports.js +48 -0
  50. package/rules/max-function-lines.js +105 -0
  51. package/rules/max-object-methods.js +86 -0
  52. package/rules/no-ai-words.js +78 -0
  53. package/rules/no-app-stores.js +25 -0
  54. package/rules/no-chained-type-assertions.js +91 -0
  55. package/rules/no-class.js +47 -0
  56. package/rules/no-conditional-empty-object-spread.js +64 -0
  57. package/rules/no-deep-phosphor-imports.js +29 -0
  58. package/rules/no-default-export.js +70 -0
  59. package/rules/no-em-dash.js +194 -0
  60. package/rules/no-emoji.js +56 -0
  61. package/rules/no-hardcoded-models.js +63 -0
  62. package/rules/no-inline-path.js +46 -0
  63. package/rules/no-inline-svg.js +67 -0
  64. package/rules/no-known-value-widening.js +289 -0
  65. package/rules/no-legacy-compat.js +35 -0
  66. package/rules/no-overkill-fallback.js +77 -0
  67. package/rules/no-raw-try-catch.js +26 -0
  68. package/rules/no-redundant-prop-fallback.js +153 -0
  69. package/rules/no-relative-imports.js +213 -0
  70. package/rules/no-relative-type-imports.js +40 -0
  71. package/rules/no-rune-outside-svelte-file.js +56 -0
  72. package/rules/no-rune-shadowing-binding.js +85 -0
  73. package/rules/no-skipped-or-focused-tests.js +74 -0
  74. package/rules/no-snake-case-props.js +45 -0
  75. package/rules/no-svelte4-syntax.js +33 -0
  76. package/rules/no-vitest-import-in-vite-plus.js +64 -0
  77. package/rules/props-interface.js +44 -0
  78. package/rules/redirect-external-needs-option.js +45 -0
  79. package/rules/require-safety-comment-for-type-assertion.js +110 -0
  80. package/rules/svelte-section-order.js +115 -0
  81. package/rules/test-file.js +8 -0
  82. package/scripts/count-hits.mjs +213 -0
  83. package/scripts/harvest-voice.mjs +161 -0
  84. package/scripts/jev-bench.mjs +41 -0
  85. package/scripts/jev-code-bench.mjs +61 -0
  86. package/scripts/measure-register.mjs +61 -0
  87. package/slop.d.ts +53 -0
  88. package/slop.js +644 -0
  89. package/svelte-markup.js +107 -0
  90. package/svelte-template.js +258 -0
  91. package/ui-rules.js +324 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * The invisible-character set, and the one home for it.
3
+ */
4
+
5
+ /*
6
+ * These are the characters a language model drags into its output without anybody seeing them: zero-width spaces, no-break spaces, soft hyphens, bidi controls, variation selectors, tag characters, private-use codepoints.
7
+ */
8
+
9
+ /*
10
+ * They survive a copy and paste into a Reddit textarea, a LinkedIn box or a CMS field, and they survive every rewrite, because they are not words.
11
+ */
12
+
13
+ /*
14
+ * They are the one AI tell a regex catches with certainty.
15
+ */
16
+
17
+ /*
18
+ * WHY here and not beside the skill that used to own it: the set was vendored as 835 lines of Python under `humanizer/scripts/unicode-hygiene/`,
19
+ */
20
+
21
+ /*
22
+ * where no publishing code could call it, so nothing on any publish path ever ran it. `slop-words.js` is the sibling file and the precedent: one home, every consumer imports.
23
+ */
24
+
25
+ /*
26
+ * See `slop.js` for the functions that use this.
27
+ */
28
+
29
+ /*
30
+ * This is a check on our own copy before we publish it. Nothing here defeats a provenance system and nothing here proves human authorship. Say neither.
31
+ */
32
+
33
+ /*
34
+ * Character set originates in guillaumemeyer/watermarks-remover, MIT, layer A of `clean-user-facing-text`, and covers all of it: zero-width, bidi, format controls, tag characters, exotic spaces,
35
+ */
36
+
37
+ /*
38
+ * reserved ignorables, noncharacters, private use, variation selectors and the script-specific carriers. Ported to JS 31 Aug 2026 and completed 31 Aug 2026.
39
+ */
40
+
41
+ /*
42
+ * Two upstream halves are deliberately not here. The media half (C2PA, EXIF, SynthID) has nothing to act on: we publish text.
43
+ */
44
+
45
+ /*
46
+ * And layer B paraphrases to defeat statistical token-sampling watermarks, which is the opposite of what this is for.
47
+ */
48
+
49
+ /** Format and invisible controls. Removed outright. */
50
+ export const STRIP_CODEPOINTS = new Set([
51
+ 0x00ad, // soft hyphen
52
+ 0x034f, // combining grapheme joiner
53
+ 0x061c, // Arabic letter mark
54
+ 0x115f, // Hangul choseong filler
55
+ 0x1160, // Hangul jungseong filler
56
+ 0x17b4, // Khmer vowel inherent AQ
57
+ 0x17b5, // Khmer vowel inherent AA
58
+ 0x180b, // Mongolian free variation selector 1
59
+ 0x180c,
60
+ 0x180d,
61
+ 0x180e, // Mongolian vowel separator
62
+ 0x180f, // Mongolian free variation selector 4
63
+ 0x200b, // zero width space
64
+ 0x200c, // zero width non-joiner
65
+ 0x200d, // zero width joiner
66
+ 0x200e, // left-to-right mark
67
+ 0x200f, // right-to-left mark
68
+ 0x202a, // LRE
69
+ 0x202b, // RLE
70
+ 0x202c, // PDF
71
+ 0x202d, // LRO
72
+ 0x202e, // RLO
73
+ 0x2060, // word joiner
74
+ 0x2061, // function application
75
+ 0x2062, // invisible times
76
+ 0x2063, // invisible separator
77
+ 0x2064, // invisible plus
78
+ 0x2065, // unassigned, Default_Ignorable
79
+ 0x2066, // LRI
80
+ 0x2067, // RLI
81
+ 0x2068, // FSI
82
+ 0x2069, // PDI
83
+ 0x206a, // inhibit symmetric swapping
84
+ 0x206b,
85
+ 0x206c,
86
+ 0x206d,
87
+ 0x206e,
88
+ 0x206f,
89
+ 0x3164, // Hangul filler, renders blank
90
+ 0xfeff, // BOM / zero width no-break space
91
+ 0xffa0, // halfwidth Hangul filler, renders blank
92
+ 0xfff9, // interlinear annotation anchor
93
+ 0xfffa,
94
+ 0xfffb,
95
+ ]);
96
+
97
+ /** Spaces that look like U+0020 and are not. Normalised to a plain space. */
98
+ export const SPACE_HOMOGLYPHS = new Map([
99
+ [0x00a0, " "], // no-break space
100
+ [0x1680, " "], // Ogham space mark
101
+ [0x2000, " "], // en quad
102
+ [0x2001, " "], // em quad
103
+ [0x2002, " "], // en space
104
+ [0x2003, " "], // em space
105
+ [0x2004, " "], // three-per-em space
106
+ [0x2005, " "], // four-per-em space
107
+ [0x2006, " "], // six-per-em space
108
+ [0x2007, " "], // figure space
109
+ [0x2008, " "], // punctuation space
110
+ [0x2009, " "], // thin space
111
+ [0x200a, " "], // hair space
112
+ [0x202f, " "], // narrow no-break space
113
+ [0x205f, " "], // medium mathematical space
114
+ [0x3000, " "], // ideographic space
115
+ ]);
116
+
117
+ /** U+2028 and U+2029. A paste target reads them as a line break or not at all. */
118
+ export const SEPARATORS = new Map([
119
+ [0x2028, "\n"],
120
+ [0x2029, "\n\n"],
121
+ ]);
122
+
123
+ /**
124
+ * Zero-width joiner and the two emoji variation selectors.
125
+ */
126
+
127
+ /*
128
+ * Free-floating they are invisible carriers.
129
+ */
130
+
131
+ /*
132
+ * After an emoji base they are part of the visible sequence (a family, a flag, a heart on fire) and stripping them changes what the reader sees.
133
+ */
134
+
135
+ /*
136
+ * `stripInvisibles` keeps them in that position and removes them everywhere else.
137
+ */
138
+ export const EMOJI_GLUE = new Set([0x200d, 0xfe0e, 0xfe0f]);
139
+
140
+ const isPrivateUse = (cp) =>
141
+ (cp >= 0xe000 && cp <= 0xf8ff) ||
142
+ (cp >= 0xf0000 && cp <= 0xffffd) ||
143
+ (cp >= 0x100000 && cp <= 0x10fffd);
144
+
145
+ /** U+FDD0..U+FDEF plus the two codepoints at the end of every plane. */
146
+ const isNoncharacter = (cp) => (cp >= 0xfdd0 && cp <= 0xfdef) || (cp & 0xfffe) === 0xfffe;
147
+
148
+ /** Variation selectors: VS1..VS16 in the BMP, VS17..VS256 in plane 14. */
149
+ const isVariationSelector = (cp) =>
150
+ (cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0xe0100 && cp <= 0xe01ef);
151
+
152
+ /** Tag characters, U+E0001 and U+E0020..U+E007F. A known stego carrier. */
153
+ const isTagChar = (cp) => cp >= 0xe0000 && cp <= 0xe007f;
154
+
155
+ /** U+FFF0..U+FFF8: reserved, Default_Ignorable, and so invisible in every font. */
156
+ const isReservedIgnorable = (cp) => cp >= 0xfff0 && cp <= 0xfff8;
157
+
158
+ /**
159
+ * Format controls belonging to one script, used as carriers in text of another.
160
+ */
161
+
162
+ /*
163
+ * Egyptian hieroglyph quadrat controls, Duployan shorthand overlap controls and the musical beam and phrase controls all render as nothing in Latin text,
164
+ */
165
+
166
+ /*
167
+ * and a model dragging one into a marketing paragraph is a carrier and nothing else.
168
+ */
169
+
170
+ /*
171
+ * WHY it is unconditional here when upstream keeps them beside their own script: this gate runs on our own copy, which is Latin script.
172
+ */
173
+
174
+ /*
175
+ * A hieroglyph document would need the adjacency test; nothing here writes one. Say so before removing this line rather than discovering it on a corpus that does.
176
+ */
177
+ const isScriptCarrier = (cp) =>
178
+ (cp >= 0x13430 && cp <= 0x1343f) ||
179
+ (cp >= 0x1bca0 && cp <= 0x1bca3) ||
180
+ (cp >= 0x1d173 && cp <= 0x1d17a);
181
+
182
+ /** True when the codepoint carries no meaning a reader can see. */
183
+ export function isInvisible(cp) {
184
+ return (
185
+ STRIP_CODEPOINTS.has(cp) ||
186
+ isVariationSelector(cp) ||
187
+ isTagChar(cp) ||
188
+ isNoncharacter(cp) ||
189
+ isPrivateUse(cp) ||
190
+ isReservedIgnorable(cp) ||
191
+ isScriptCarrier(cp) ||
192
+ (cp >= 0xe0080 && cp <= 0xe00ff)
193
+ );
194
+ }
195
+
196
+ /** The label a finding reports, so a fix does not need a codepoint table. */
197
+ export function invisibleKind(cp) {
198
+ if (SPACE_HOMOGLYPHS.has(cp)) return "fake space";
199
+ if (SEPARATORS.has(cp)) return "line separator";
200
+ if (isTagChar(cp)) return "tag character";
201
+ if (isVariationSelector(cp) || (cp >= 0x180b && cp <= 0x180f)) return "variation selector";
202
+ if (
203
+ cp === 0x061c ||
204
+ (cp >= 0x200e && cp <= 0x200f) ||
205
+ (cp >= 0x202a && cp <= 0x202e) ||
206
+ (cp >= 0x2066 && cp <= 0x2069)
207
+ )
208
+ return "bidi control";
209
+ if (cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0x2060 || cp === 0xfeff)
210
+ return "zero-width";
211
+ if (isPrivateUse(cp)) return "private use";
212
+ if (isNoncharacter(cp) || isReservedIgnorable(cp)) return "noncharacter";
213
+ if (isScriptCarrier(cp)) return "script carrier";
214
+ return "invisible control";
215
+ }
216
+
217
+ /** `U+200B` for a log line or a finding. */
218
+ export const hex = (cp) => `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`;
@@ -0,0 +1,115 @@
1
+ // Held-out lines for the Jev shapes: `shape` is the one it should fail, null passes all.
2
+ // Never copy a line from the draft that exposed a shape: scripts/jev-bench.mjs, TECHLOG.md.
3
+
4
+ export const SENTENCES = [
5
+ { shape: "inflated", text: "This payroll update is a game changer for small teams." },
6
+ { shape: "inflated", text: "Our onboarding flow delivers a seamless, delightful experience." },
7
+ { shape: "inflated", text: "Great kitchens run on great habits and a passion for quality." },
8
+ { shape: "inflated", text: "The results speak for themselves and the team is thrilled." },
9
+ { shape: "aphorism", text: "A budget only protects the money you never planned to spend." },
10
+ { shape: "aphorism", text: "A late invoice reaches every project that shares the account." },
11
+ { shape: "aphorism", text: "Hire slow and the team will thank you for it." },
12
+ { shape: "aphorism", text: "A warm-up set earns the heavy set its place." },
13
+ { shape: "vague", text: "Here's the one hiring mistake that costs founders the most." },
14
+ { shape: "vague", text: "The Friday reason your invoices go unpaid." },
15
+ { shape: "vague", text: "There's a simple trick to keeping sauces from splitting." },
16
+ { shape: "vague", text: "Let me walk you through what changed this quarter." },
17
+ {
18
+ shape: "filler",
19
+ text: "It's worth noting that, in many cases, the gym is generally quieter at noon.",
20
+ },
21
+ {
22
+ shape: "filler",
23
+ text: "Great question, I'd be happy to help you figure out the payroll dates.",
24
+ },
25
+ { shape: "filler", text: "Some experts say that you should probably review contracts yearly." },
26
+ {
27
+ shape: "filler",
28
+ text: "At the end of the day, it's really about basically paying people on time.",
29
+ },
30
+ {
31
+ shape: "filler",
32
+ text: "In order to renew your passport, you will need to bring along two photos.",
33
+ },
34
+ { shape: "filler", text: "The landlord is able to raise the rent once each and every year." },
35
+ {
36
+ shape: "filler",
37
+ text: "Due to the fact that the oven runs hot, reduce the temperature down by 10 degrees.",
38
+ },
39
+ {
40
+ shape: "filler",
41
+ text: "We went ahead and moved the team meeting to Thursday afternoon at 3pm in the afternoon.",
42
+ },
43
+ { shape: "contrast", text: "It's not a hiring problem, it's a pricing problem." },
44
+ { shape: "contrast", text: "You can either chase every invoice or you can get paid." },
45
+ { shape: "contrast", text: "From solo bakers to global chains, everyone needs a recipe cost." },
46
+ { shape: "contrast", text: "The menu is short, and by short I mean it has very few dishes." },
47
+ { shape: "pitch", text: "Close the books, and payroll is already done." },
48
+ { shape: "pitch", text: "Book your table and dinner is basically sorted." },
49
+ { shape: "pitch", text: "Upload the invoice and your accounting is already handled." },
50
+ { shape: "pitch", text: "Sit back, and your invoices are already sent." },
51
+ { shape: null, text: "Press stop and the app saves the video to Photos." },
52
+ { shape: null, text: "Send the invoice, then mark it paid when the money arrives." },
53
+ { shape: null, text: "Close the books on the last day of the month, then send payroll." },
54
+ { shape: null, text: "The oven takes 12 minutes to reach 200 degrees." },
55
+ { shape: "personified", text: "The spreadsheet knows exactly who is behind on rent." },
56
+ { shape: "personified", text: "This template gives you your Sunday evenings back." },
57
+ { shape: "personified", text: "Cash flow is the muscle every agency forgets to train." },
58
+ { shape: "personified", text: "Pricing is the lever that moves the whole business." },
59
+ { shape: null, text: "Open Payroll, then Settings, and set the pay date to the 25th." },
60
+ { shape: null, text: "Invoices sent on a Monday were paid 6 days sooner than Friday ones." },
61
+ { shape: null, text: "Salt the pasta water until it tastes like the sea." },
62
+ { shape: null, text: "Book the interview for 30 minutes and send the brief the day before." },
63
+ { shape: null, text: "Add a late fee of 2% to any invoice unpaid after 30 days." },
64
+ { shape: null, text: "Rest 90 seconds between heavy sets, then log the weight." },
65
+ { shape: null, text: "The contract renews on 1 March unless you cancel in writing." },
66
+ { shape: null, text: "Ask the candidate to walk you through the last project they shipped." },
67
+ { shape: null, text: "Watch me set the pay date in Payroll" },
68
+ { shape: null, text: "Bring two photos to renew your passport." },
69
+ { shape: null, text: "The landlord can raise the rent once a year." },
70
+ { shape: null, text: "The oven runs hot, so set it 10 degrees lower." },
71
+ { shape: null, text: "The team meeting moved to Thursday at 3pm." },
72
+ {
73
+ shape: "stilted",
74
+ text: "Diners find their allergens in their own column on the menu, with the codes from the kitchen sheet.",
75
+ },
76
+ {
77
+ shape: "stilted",
78
+ text: "Visitors meet their badge at reception, with the photo from the portal.",
79
+ },
80
+ { shape: "stilted", text: "Staff meet no clock-in button on the old tablets." },
81
+ {
82
+ shape: "stilted",
83
+ text: "Patients find their results in their own section of the portal, with the ranges from the lab doc.",
84
+ },
85
+ { shape: null, text: "Thanks, that clears up the lease question." },
86
+ { shape: null, text: "The menu shows allergens in a separate column." },
87
+ {
88
+ shape: null,
89
+ text: "Visitors pick up their badge at reception. It has the photo from the portal.",
90
+ },
91
+ { shape: null, text: "Once a member reports a locker lost, it's never given out again." },
92
+ { shape: null, text: "The old tablets don't have a clock-in button." },
93
+ { shape: null, text: "Does Friday at 10am still work for you and your accountant?" },
94
+ { shape: null, text: "Thanks for sending the floor plan so fast." },
95
+ { shape: null, text: "We'd love to have you at the tasting next week." },
96
+ ];
97
+
98
+ export const PARAGRAPHS = [
99
+ {
100
+ shape: "rhythm",
101
+ text: "Invoices matter. Deadlines matter. Clients matter. That's why the system matters. And that's why we built it.",
102
+ },
103
+ {
104
+ shape: "rhythm",
105
+ text: "We cut costs. We cut delays. We cut the noise. The result is clarity. The result is growth. The result is freedom.",
106
+ },
107
+ {
108
+ shape: null,
109
+ text: "Send invoices on Monday morning. When we moved ours from Friday, the average wait dropped from 19 days to 13. The one client who still paid late had a 60-day term in the contract, so we changed the term at renewal.",
110
+ },
111
+ {
112
+ shape: null,
113
+ text: "Rest 90 seconds between sets. Log the weight before you rack the bar, because you won't remember it after the third set. If the last rep slows down, keep the same weight next week.",
114
+ },
115
+ ];
@@ -0,0 +1,313 @@
1
+ // Cases for the Jev code rules: `rule` is the one it should fire, null fires none.
2
+ // Tune questions on TRAIN only; HELDOUT is the score that counts (scripts/jev-code-bench.mjs).
3
+
4
+ export const TRAIN = [
5
+ {
6
+ rule: "cast",
7
+ file: "src/lib/orders.ts",
8
+ code: `export function total(res: unknown) {\n const order = res as any;\n return order.items.reduce((s: number, i: any) => s + i.price, 0);\n}`,
9
+ },
10
+ {
11
+ rule: "cast",
12
+ file: "src/routes/+page.server.ts",
13
+ code: `export async function load({ fetch }) {\n const body = await (await fetch("/api/me")).json();\n // @ts-ignore\n return { name: body.profile.name as string };\n}`,
14
+ },
15
+ {
16
+ rule: "compat",
17
+ file: "src/lib/billing.ts",
18
+ code: `export function chargeCustomer(id: string, cents: number) {\n return stripe.charges.create({ customer: id, amount: cents });\n}\n\n/** @deprecated use chargeCustomer */\nexport const charge = chargeCustomer;`,
19
+ },
20
+ {
21
+ rule: "compat",
22
+ file: "src/lib/user.ts",
23
+ code: `export function displayName(u: User) {\n const first = u.firstName ?? u.first_name;\n const last = u.lastName ?? u.last_name;\n return \`\${first} \${last}\`;\n}`,
24
+ },
25
+ {
26
+ rule: "swallowed",
27
+ file: "src/lib/sync.ts",
28
+ code: `export async function syncInvoices() {\n try {\n await pushToXero(await loadInvoices());\n } catch (err) {\n console.log(err);\n }\n}`,
29
+ },
30
+ {
31
+ rule: "swallowed",
32
+ file: "Sources/Store.swift",
33
+ code: `func save(_ note: Note) {\n do {\n try context.save()\n } catch {\n }\n}`,
34
+ },
35
+ {
36
+ rule: "impossible",
37
+ file: "src/lib/cart.ts",
38
+ code: `export function newCart() {\n const items: Item[] = [];\n if (!items) throw new Error("items missing");\n return { items, createdAt: Date.now() };\n}`,
39
+ },
40
+ {
41
+ rule: "impossible",
42
+ file: "src/lib/math.ts",
43
+ code: `export function double(n: number) {\n try {\n return n * 2;\n } catch {\n return 0;\n }\n}`,
44
+ },
45
+ {
46
+ rule: "wrapper",
47
+ file: "src/lib/api.ts",
48
+ code: `export function getProject(id: string) {\n return fetchProject(id);\n}`,
49
+ },
50
+ {
51
+ rule: "wrapper",
52
+ file: "src/lib/log.ts",
53
+ code: `export class Logger {\n info(msg: string) {\n console.info(msg);\n }\n}`,
54
+ },
55
+ {
56
+ rule: "speculative",
57
+ file: "src/lib/slug.ts",
58
+ code: `type SlugOptions = { separator?: string; strategy?: "default" };\n\nexport function slugify(text: string, options: SlugOptions = {}) {\n return text.toLowerCase().trim().replace(/\\s+/g, "-");\n}`,
59
+ },
60
+ {
61
+ rule: "speculative",
62
+ file: "src/lib/greet.ts",
63
+ code: `export function greet<T extends string = string>(name: T, hooks: { before?: () => void } = {}) {\n return \`Hello, \${name}\`;\n}`,
64
+ },
65
+ {
66
+ rule: "naming",
67
+ file: "src/lib/report.ts",
68
+ code: `export async function handleStuff(x: string) {\n const data = await db.query(x);\n const result = data.rows.map((tmp) => tmp.v);\n return result;\n}`,
69
+ },
70
+ {
71
+ rule: "naming",
72
+ file: "src/lib/utils.ts",
73
+ code: `export const helper = (a: Order[], b: number) => a.filter((c) => c.total > b);`,
74
+ },
75
+ {
76
+ rule: "nesting",
77
+ file: "src/lib/access.ts",
78
+ code: `export function canEdit(user, doc) {\n if (user) {\n if (user.active) {\n if (doc.ownerId === user.id) {\n return true;\n }\n }\n }\n return false;\n}`,
79
+ },
80
+ {
81
+ rule: "nesting",
82
+ file: "Sources/Upload.swift",
83
+ code: `func upload(_ file: URL?) {\n if let file {\n if let data = try? Data(contentsOf: file) {\n if data.count > 0 {\n client.send(data)\n }\n }\n }\n}`,
84
+ },
85
+ {
86
+ rule: "narration",
87
+ file: "src/lib/price.ts",
88
+ code: `// Multiply the price by the quantity\nconst subtotal = price * qty;\n// Add the tax to the subtotal\nconst total = subtotal + tax;`,
89
+ },
90
+ {
91
+ rule: "narration",
92
+ file: "src/lib/queue.ts",
93
+ code: `// Changed from setTimeout to setInterval for the new flow\nconst timer = setInterval(drain, 1000);`,
94
+ },
95
+ {
96
+ rule: "leftover",
97
+ file: "src/lib/checkout.ts",
98
+ code: `export function checkout(cart: Cart) {\n console.log("cart here", cart);\n // const old = legacyCheckout(cart);\n // return old;\n return submit(cart);\n}`,
99
+ },
100
+ {
101
+ rule: "leftover",
102
+ file: "src/lib/refund.ts",
103
+ code: `export function refund(orderId: string) {\n // TODO: implement\n return null;\n}`,
104
+ },
105
+ {
106
+ rule: "workaround",
107
+ file: "src/routes/api/route.ts",
108
+ code: `// The route keeps its old name until the rename of the op lands.\nexport const POST = handleOverlay;`,
109
+ },
110
+ {
111
+ rule: "workaround",
112
+ file: "src/lib/pricing.ts",
113
+ code: `export function checkout(plan: Plan) {\n // Quick fix: redirect to signup until auth is wired in\n return redirect(303, "/signup");\n}`,
114
+ },
115
+ {
116
+ rule: "fallback",
117
+ file: "src/lib/posts.ts",
118
+ code: `export function rows(data: Feed) {\n return data.posts ?? data.items ?? data.results ?? [];\n}`,
119
+ },
120
+ {
121
+ rule: null,
122
+ file: "src/lib/ocoya.ts",
123
+ code: `// Named in the 1 Sep changelog, missing from the spec, and answering 404 since 14 Oct.\nexport const ANALYTICS = "GET /analytics/export";`,
124
+ },
125
+ {
126
+ rule: "fallback",
127
+ file: "src/lib/env.ts",
128
+ code: `export const apiKey = process.env.STRIPE_KEY ?? "";\nexport const host = process.env.API_HOST || "http://localhost:3000";`,
129
+ },
130
+ {
131
+ rule: "duplicate",
132
+ file: "src/lib/format.ts",
133
+ code: `export function formatStart(d: Date) {\n const y = d.getFullYear();\n const m = String(d.getMonth() + 1).padStart(2, "0");\n return \`\${y}-\${m}\`;\n}\nexport function formatEnd(d: Date) {\n const y = d.getFullYear();\n const m = String(d.getMonth() + 1).padStart(2, "0");\n return \`\${y}-\${m}\`;\n}`,
134
+ },
135
+ {
136
+ rule: "duplicate",
137
+ file: "src/lib/env.ts",
138
+ code: `const apiUrl = process.env.API_URL;\nif (!apiUrl) throw new Error("API_URL missing");\nconst dbUrl = process.env.DB_URL;\nif (!dbUrl) throw new Error("DB_URL missing");\nconst cdnUrl = process.env.CDN_URL;\nif (!cdnUrl) throw new Error("CDN_URL missing");`,
139
+ },
140
+ {
141
+ rule: "effectSync",
142
+ file: "src/lib/components/Cart.svelte",
143
+ code: `let items = $state([]);\nlet total = $state(0);\n\n$effect(() => {\n total = items.reduce((s, i) => s + i.price, 0);\n});`,
144
+ },
145
+ {
146
+ rule: "effectSync",
147
+ file: "src/routes/+page.svelte",
148
+ code: `let first = $state("");\nlet last = $state("");\nlet full = $state("");\n$effect(() => {\n full = \`\${first} \${last}\`;\n});`,
149
+ },
150
+ {
151
+ rule: "restates",
152
+ file: "src/lib/tax.test.ts",
153
+ code: `test("tax", () => {\n const price = 100;\n expect(addTax(price)).toBe(price * TAX_RATE + price);\n});`,
154
+ },
155
+ {
156
+ rule: "restates",
157
+ file: "src/lib/__tests__/mailer.test.ts",
158
+ code: `test("sends", async () => {\n const send = vi.fn();\n await notify({ send }, "a@b.c");\n expect(send).toHaveBeenCalled();\n});`,
159
+ },
160
+ {
161
+ rule: null,
162
+ file: "src/lib/invoice.ts",
163
+ code: `export function dueDate(issued: Date, termsDays: number): Date {\n const due = new Date(issued);\n due.setDate(due.getDate() + termsDays);\n return due;\n}`,
164
+ },
165
+ {
166
+ rule: null,
167
+ file: "src/routes/api/lead/+server.ts",
168
+ code: `export async function POST({ request }) {\n const parsed = Lead.safeParse(await request.json());\n if (!parsed.success) return json({ error: parsed.error.flatten() }, { status: 400 });\n await leads.insert(parsed.data);\n return json({ ok: true });\n}`,
169
+ },
170
+ {
171
+ rule: null,
172
+ file: "src/lib/components/Badge.svelte",
173
+ code: `let { label, tone = "neutral" }: { label: string; tone?: "neutral" | "warn" } = $props();\nconst classes = $derived(tone === "warn" ? "bg-amber-100" : "bg-zinc-100");`,
174
+ },
175
+ {
176
+ rule: null,
177
+ file: "Sources/Timer.swift",
178
+ code: `@Observable final class Stopwatch {\n private(set) var elapsed: Duration = .zero\n func tick(by step: Duration) { elapsed += step }\n}`,
179
+ },
180
+ {
181
+ rule: null,
182
+ file: "src/lib/retry.ts",
183
+ code: `// Plunk answers 429 for about a minute after a burst, so back off before retrying.\nexport async function withRetry<T>(fn: () => Promise<T>, tries = 3): Promise<T> {\n for (let i = 1; ; i++) {\n try { return await fn(); }\n catch (err) { if (i >= tries) throw err; await sleep(2 ** i * 1000); }\n }\n}`,
184
+ },
185
+ {
186
+ rule: null,
187
+ file: "e2e/checkout.spec.ts",
188
+ code: `test("a paid order shows its receipt", async ({ page }) => {\n await page.goto("/cart");\n await page.getByRole("button", { name: "Pay" }).click();\n await expect(page.getByText("Receipt #")).toBeVisible();\n});`,
189
+ },
190
+ {
191
+ rule: null,
192
+ file: "scripts/build.sh",
193
+ code: `set -euo pipefail\nfor pkg in packages/*; do\n (cd "$pkg" && pnpm build)\ndone`,
194
+ },
195
+ {
196
+ rule: null,
197
+ file: "src/lib/cn.ts",
198
+ code: `const STATUS_LABEL = { draft: "Draft", sent: "Sent", paid: "Paid" } as const;\nexport const statusLabel = (s: keyof typeof STATUS_LABEL) => STATUS_LABEL[s];`,
199
+ },
200
+ ];
201
+
202
+ export const HELDOUT = [
203
+ {
204
+ rule: "cast",
205
+ file: "src/lib/webhook.ts",
206
+ code: `export function handle(evt: Stripe.Event) {\n const session = evt.data.object as unknown as CheckoutSession;\n return session.customer_email!.toLowerCase();\n}`,
207
+ },
208
+ {
209
+ rule: "compat",
210
+ file: "src/lib/index.ts",
211
+ code: `export { renderCard } from "./card";\n// kept so old imports keep working\nexport { renderCard as drawCard } from "./card";`,
212
+ },
213
+ {
214
+ rule: "swallowed",
215
+ file: "src/lib/cache.ts",
216
+ code: `export async function readCache(key: string) {\n try {\n return JSON.parse(await kv.get(key));\n } catch {\n return {};\n }\n}`,
217
+ },
218
+ {
219
+ rule: "impossible",
220
+ file: "src/lib/ids.ts",
221
+ code: `export function makeId() {\n const id = crypto.randomUUID();\n if (typeof id !== "string") return "";\n return id;\n}`,
222
+ },
223
+ {
224
+ rule: "wrapper",
225
+ file: "src/lib/db.ts",
226
+ code: `export const Tasks = {\n list(projectId: string) {\n return repo.listTasks(projectId);\n },\n};`,
227
+ },
228
+ {
229
+ rule: "speculative",
230
+ file: "src/lib/export.ts",
231
+ code: `export function exportCsv(rows: Row[], format: "csv" = "csv", adapter?: ExportAdapter) {\n return rows.map((r) => Object.values(r).join(",")).join("\\n");\n}`,
232
+ },
233
+ {
234
+ rule: "naming",
235
+ file: "src/lib/stats.ts",
236
+ code: `export function calc(arr: Visit[]) {\n const res = arr.filter((v) => v.ok);\n const info = res.length / arr.length;\n return info;\n}`,
237
+ },
238
+ {
239
+ rule: "nesting",
240
+ file: "src/lib/ship.ts",
241
+ code: `function ship(order) {\n if (order) {\n if (order.paid) {\n if (order.address) {\n if (!order.shipped) {\n carrier.book(order);\n }\n }\n }\n }\n}`,
242
+ },
243
+ {
244
+ rule: "narration",
245
+ file: "src/lib/auth.ts",
246
+ code: `// Get the session\nconst session = await getSession();\n// Return the user from the session\nreturn session.user;`,
247
+ },
248
+ {
249
+ rule: "leftover",
250
+ file: "Sources/Feed.swift",
251
+ code: `func refresh() async {\n print("DEBUG items", items.count)\n // items = try await legacyFetch()\n items = try await api.feed()\n}`,
252
+ },
253
+ {
254
+ rule: "workaround",
255
+ file: "src/lib/stream.ts",
256
+ code: `/** Old status name some jobs still send; treated as "running" until they move. */\nexport type JobStatus = "running" | "done" | "generating";`,
257
+ },
258
+ {
259
+ rule: "fallback",
260
+ file: "src/lib/client.ts",
261
+ code: `export function client() {\n const token = env.TOKEN ?? env.API_TOKEN ?? "dev-token";\n return new Api(token);\n}`,
262
+ },
263
+ {
264
+ rule: null,
265
+ file: "src/lib/list.ts",
266
+ code: `export function page(rows: Row[], size = 25) {\n return rows.slice(0, size);\n}`,
267
+ },
268
+ {
269
+ rule: "duplicate",
270
+ file: "src/lib/notify.ts",
271
+ code: `if (user.email) {\n await mail.send({ to: user.email, subject: "Invoice", body: render(invoice) });\n await log.write("mail", user.id);\n}\nif (user.billingEmail) {\n await mail.send({ to: user.billingEmail, subject: "Invoice", body: render(invoice) });\n await log.write("mail", user.id);\n}`,
272
+ },
273
+ {
274
+ rule: "effectSync",
275
+ file: "src/lib/components/Filter.svelte",
276
+ code: `let { rows, query } = $props();\nlet visible = $state([]);\n$effect(() => {\n visible = rows.filter((r) => r.name.includes(query));\n});`,
277
+ },
278
+ {
279
+ rule: "restates",
280
+ file: "src/lib/pricing.spec.ts",
281
+ code: `it("discount", () => {\n const base = 80;\n expect(discounted(base)).toEqual(base - base * DISCOUNT);\n});`,
282
+ },
283
+ {
284
+ rule: null,
285
+ file: "src/lib/slugify.ts",
286
+ code: `export function slugify(title: string): string {\n return title.normalize("NFKD").replace(/[^\\w\\s-]/g, "").trim().toLowerCase().replace(/\\s+/g, "-");\n}`,
287
+ },
288
+ {
289
+ rule: null,
290
+ file: "src/lib/components/Toggle.svelte",
291
+ code: `let { checked = $bindable(false), label }: { checked?: boolean; label: string } = $props();\n$effect(() => {\n localStorage.setItem("toggle", String(checked));\n});`,
292
+ },
293
+ {
294
+ rule: null,
295
+ file: "src/routes/api/upload/+server.ts",
296
+ code: `export async function POST({ request, locals }) {\n if (!locals.user) error(401, "Sign in first");\n const form = await request.formData();\n const file = form.get("file");\n if (!(file instanceof File)) error(400, "No file");\n return json(await storage.put(locals.user.id, file));\n}`,
297
+ },
298
+ {
299
+ rule: null,
300
+ file: "Sources/Parser.swift",
301
+ code: `func parse(_ line: String) throws -> Entry {\n guard let match = line.wholeMatch(of: entryRegex) else { throw ParseError.malformed(line) }\n return Entry(date: match.date, amount: match.amount)\n}`,
302
+ },
303
+ {
304
+ rule: null,
305
+ file: "e2e/signup.spec.ts",
306
+ code: `test("a new user lands on the dashboard", async ({ page }) => {\n await page.goto("/signup");\n await page.getByLabel("Email").fill("new@example.com");\n await page.getByRole("button", { name: "Create account" }).click();\n await expect(page).toHaveURL(/dashboard/);\n});`,
307
+ },
308
+ {
309
+ rule: null,
310
+ file: "src/lib/money.ts",
311
+ code: `// Cents, never floats: 0.1 + 0.2 rounds a Stripe total off by one.\nexport const toCents = (amount: string) => Math.round(Number(amount) * 100);`,
312
+ },
313
+ ];