connections-arkitect 0.3.4
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 +104 -0
- package/arkitect.config.example.json +10 -0
- package/bin/arkitect.mjs +135 -0
- package/package.json +42 -0
- package/spine/justify-existence.example.json +8 -0
- package/src/checks/agnostic/rules-docs.mjs +92 -0
- package/src/checks/code/dependency-freshness.mjs +82 -0
- package/src/checks/code/no-secrets-committed.mjs +112 -0
- package/src/checks/code/oversized-files.mjs +74 -0
- package/src/checks/code/sibling-consensus.mjs +69 -0
- package/src/checks/code/tsconfig-conformance.mjs +98 -0
- package/src/checks/code/veteran-would-mock.mjs +699 -0
- package/src/checks/hosted/db-justify-existence.mjs +237 -0
- package/src/checks/hosted/db-required-roles.mjs +76 -0
- package/src/core/config.mjs +50 -0
- package/src/core/discovery.mjs +41 -0
- package/src/core/finding.mjs +40 -0
- package/src/core/fs-walk.mjs +24 -0
- package/src/core/hosted/judge.mjs +42 -0
- package/src/core/hosted/spine.mjs +63 -0
- package/src/core/hosted/vault-exec.mjs +61 -0
- package/src/core/index.mjs +15 -0
- package/src/core/init.mjs +64 -0
- package/src/core/oracle/family-conformance.mjs +77 -0
- package/src/core/project-detect.mjs +126 -0
- package/src/core/runner.mjs +369 -0
- package/src/core/sarif.mjs +63 -0
- package/src/engines/surface/surface-size-coverage-engine.mjs +199 -0
- package/src/update/self-update.mjs +90 -0
- package/your-checks/README.md +33 -0
- package/your-checks/code/example-check.mjs +24 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
// CORE check — "Things a 30-year veteran coder would make fun of."
|
|
2
|
+
//
|
|
3
|
+
// A taxonomy of the small, unambiguous embarrassments that survive review because each one is
|
|
4
|
+
// individually harmless: debug droppings, 2010-era fossils, copy-paste artifacts, silent error
|
|
5
|
+
// swallows, redundant boolean gymnastics. None of them break the build; all of them tell a
|
|
6
|
+
// visiting greybeard exactly how much the codebase is skimmed rather than read.
|
|
7
|
+
//
|
|
8
|
+
// Two tiers, matching the runner's gating model:
|
|
9
|
+
// - error → definitive, zero-tolerance (the check FAILS): each of these was at ZERO when the
|
|
10
|
+
// rule landed, so any hit is a fresh regression, never archaeology.
|
|
11
|
+
// - warning/info → the thermometer tier: real smells worth harvesting worst-first, but a living
|
|
12
|
+
// codebase carries some; they never fail the check.
|
|
13
|
+
//
|
|
14
|
+
// Detection is a light JS lexer, not naive line-grep, because the classic false positives are
|
|
15
|
+
// exactly the interesting ones:
|
|
16
|
+
// - `var` inside a template literal is usually a DELIBERATE embedded ES5 snippet (tracking
|
|
17
|
+
// pixels, CloudFront Functions, iframe embed codes) — template-literal content is blanked.
|
|
18
|
+
// - patterns quoted inside strings, comments, or regex literals are not code — blanked.
|
|
19
|
+
// - minified vendored bundles (isomorphic-git etc.) are not YOUR code — skipped, counted.
|
|
20
|
+
// - an empty `catch` / `.catch(() => {})` that carries a comment — inside the braces, on the
|
|
21
|
+
// same line, or attached directly above the statement — is a documented decision; only the
|
|
22
|
+
// wordless swallow is flagged.
|
|
23
|
+
//
|
|
24
|
+
// Considered and deliberately NOT rules (so nobody re-proposes them):
|
|
25
|
+
// - TODO/FIXME/HACK — owned by the `stale-todos` check.
|
|
26
|
+
// - `await new Promise(r => setTimeout(r, n))` — legitimate backoff/polling is mechanically
|
|
27
|
+
// indistinguishable from sleep-based race-dodging; a rule would be mostly false positives.
|
|
28
|
+
// - `setTimeout(fn, 0)` — sometimes the correct macrotask deferral in UI code.
|
|
29
|
+
// - `x == null` — the one idiomatic loose equality (null-or-undefined).
|
|
30
|
+
// - complexity / file size / duplication — owned by the codeRisk group.
|
|
31
|
+
//
|
|
32
|
+
// bun packages/connections-arkitect/bin/arkitect.mjs --root . --check veteran-would-mock
|
|
33
|
+
import { readFileSync } from "node:fs";
|
|
34
|
+
import { relative, extname, basename, dirname } from "node:path";
|
|
35
|
+
import { createFinding } from "../../core/finding.mjs";
|
|
36
|
+
import { walkFiles } from "../../core/fs-walk.mjs";
|
|
37
|
+
|
|
38
|
+
// ── Lexer: blank everything that is not code ─────────────────────────────────
|
|
39
|
+
// Produces two same-length views of the source (offsets preserved, newlines kept):
|
|
40
|
+
// code — code only; strings, template literals, comments, regex bodies → spaces
|
|
41
|
+
// comment — comment CONTENT only (line + block); everything else → spaces
|
|
42
|
+
// For .vue files only <script> block contents are lexed; the rest stays blank.
|
|
43
|
+
|
|
44
|
+
const REGEX_KEYWORDS = new Set([
|
|
45
|
+
"return",
|
|
46
|
+
"typeof",
|
|
47
|
+
"case",
|
|
48
|
+
"in",
|
|
49
|
+
"of",
|
|
50
|
+
"instanceof",
|
|
51
|
+
"new",
|
|
52
|
+
"delete",
|
|
53
|
+
"void",
|
|
54
|
+
"do",
|
|
55
|
+
"else",
|
|
56
|
+
"yield",
|
|
57
|
+
"await",
|
|
58
|
+
"throw",
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
function regexCanStart(prevSig, lastWord) {
|
|
62
|
+
if (REGEX_KEYWORDS.has(lastWord)) return true;
|
|
63
|
+
// After an identifier, number, `)` or `]` a slash is division, not a regex.
|
|
64
|
+
return !/[A-Za-z0-9_$)\]]/.test(prevSig);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function scriptRanges(raw) {
|
|
68
|
+
const ranges = [];
|
|
69
|
+
const open = /<script\b[^>]*>/gi;
|
|
70
|
+
let m;
|
|
71
|
+
while ((m = open.exec(raw))) {
|
|
72
|
+
const start = m.index + m[0].length;
|
|
73
|
+
const close = raw.indexOf("</script", start);
|
|
74
|
+
const end = close === -1 ? raw.length : close;
|
|
75
|
+
ranges.push([start, end]);
|
|
76
|
+
open.lastIndex = end;
|
|
77
|
+
}
|
|
78
|
+
return ranges;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildViews(raw, isVue = false) {
|
|
82
|
+
const n = raw.length;
|
|
83
|
+
const code = new Array(n);
|
|
84
|
+
const comment = new Array(n);
|
|
85
|
+
for (let i = 0; i < n; i++) {
|
|
86
|
+
const keep = raw[i] === "\n" ? "\n" : " ";
|
|
87
|
+
code[i] = keep;
|
|
88
|
+
comment[i] = keep;
|
|
89
|
+
}
|
|
90
|
+
const ranges = isVue ? scriptRanges(raw) : [[0, n]];
|
|
91
|
+
for (const [start, end] of ranges) lexInto(raw, start, end, code, comment);
|
|
92
|
+
return { code: code.join(""), comment: comment.join("") };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function lexInto(raw, start, end, code, comment) {
|
|
96
|
+
let mode = "code";
|
|
97
|
+
const frames = [{ interp: false, depth: 0 }]; // brace-tracking frames; `${` pushes an interp frame
|
|
98
|
+
const templateReturns = []; // mode to return to when a template literal closes
|
|
99
|
+
let prevSig = "";
|
|
100
|
+
let lastWord = "";
|
|
101
|
+
for (let i = start; i < end; i++) {
|
|
102
|
+
const ch = raw[i];
|
|
103
|
+
const next = i + 1 < end ? raw[i + 1] : "";
|
|
104
|
+
if (mode === "code") {
|
|
105
|
+
if (ch === "/" && next === "/") {
|
|
106
|
+
mode = "line";
|
|
107
|
+
i++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (ch === "/" && next === "*") {
|
|
111
|
+
mode = "block";
|
|
112
|
+
i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (ch === "'") {
|
|
116
|
+
mode = "sq";
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (ch === '"') {
|
|
120
|
+
mode = "dq";
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (ch === "`") {
|
|
124
|
+
templateReturns.push("code");
|
|
125
|
+
mode = "tpl";
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (ch === "/" && regexCanStart(prevSig, lastWord)) {
|
|
129
|
+
mode = "regex";
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (ch === "{") {
|
|
133
|
+
frames[frames.length - 1].depth++;
|
|
134
|
+
} else if (ch === "}") {
|
|
135
|
+
const top = frames[frames.length - 1];
|
|
136
|
+
if (top.interp && top.depth === 0) {
|
|
137
|
+
frames.pop();
|
|
138
|
+
mode = "tpl";
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (top.depth > 0) top.depth--;
|
|
142
|
+
}
|
|
143
|
+
code[i] = ch;
|
|
144
|
+
if (!/\s/.test(ch)) {
|
|
145
|
+
prevSig = ch;
|
|
146
|
+
lastWord = /[A-Za-z0-9_$]/.test(ch) ? lastWord + ch : "";
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (mode === "line") {
|
|
151
|
+
if (ch === "\n") mode = "code";
|
|
152
|
+
else comment[i] = ch;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (mode === "block") {
|
|
156
|
+
if (ch === "*" && next === "/") {
|
|
157
|
+
mode = "code";
|
|
158
|
+
i++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (ch !== "\n") comment[i] = ch;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (mode === "sq" || mode === "dq") {
|
|
165
|
+
if (ch === "\\") {
|
|
166
|
+
i++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if ((mode === "sq" && ch === "'") || (mode === "dq" && ch === '"') || ch === "\n") mode = "code";
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (mode === "tpl") {
|
|
173
|
+
if (ch === "\\") {
|
|
174
|
+
i++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (ch === "`") {
|
|
178
|
+
mode = templateReturns.pop() || "code";
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (ch === "$" && next === "{") {
|
|
182
|
+
frames.push({ interp: true, depth: 0 });
|
|
183
|
+
mode = "code";
|
|
184
|
+
i++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (mode === "regex") {
|
|
190
|
+
if (ch === "\\") {
|
|
191
|
+
i++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === "[") {
|
|
195
|
+
mode = "regexClass";
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (ch === "/" || ch === "\n") mode = "code";
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (mode === "regexClass") {
|
|
202
|
+
if (ch === "\\") {
|
|
203
|
+
i++;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (ch === "]") mode = "regex";
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Rules ────────────────────────────────────────────────────────────────────
|
|
213
|
+
// view: which lens the pattern runs against. `guardComment`: a brace-span match is skipped when the
|
|
214
|
+
// raw span carries a comment (a documented swallow is a decision, not a smell).
|
|
215
|
+
|
|
216
|
+
const CODE_RULES = [
|
|
217
|
+
{
|
|
218
|
+
id: "debugger-statement",
|
|
219
|
+
severity: "error",
|
|
220
|
+
re: /(?<![.\w$])debugger\b/g,
|
|
221
|
+
what: "a `debugger` statement is committed",
|
|
222
|
+
fix: "Delete it.",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: "var-declaration",
|
|
226
|
+
severity: "error",
|
|
227
|
+
extKey: "varDeclarationExtensions",
|
|
228
|
+
re: /(?<![.\w$])var\s+[A-Za-z_$]/g,
|
|
229
|
+
what: "`var` in TypeScript/Vue source — hoisting roulette, retired by `let`/`const` in 2015",
|
|
230
|
+
fix: "Use `const` (or `let` when reassigned). Embedded ES5 snippets belong in template literals, which this rule already ignores.",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "eval-call",
|
|
234
|
+
severity: "error",
|
|
235
|
+
re: /(?<![.\w$])eval\s*\(/g,
|
|
236
|
+
what: "`eval()` — the veteran does not even make fun of this one, he just leaves",
|
|
237
|
+
fix: "There is always another way. Find it.",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
id: "document-write",
|
|
241
|
+
severity: "error",
|
|
242
|
+
re: /(?<![.\w$])document\.write(?:ln)?\s*\(/g,
|
|
243
|
+
what: "`document.write` on the live document — a 1998 idiom that nukes the page",
|
|
244
|
+
fix: "Render through the framework (a child window's print document, e.g. `printWindow.document.write`, is fine and not matched).",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "xml-http-request",
|
|
248
|
+
severity: "error",
|
|
249
|
+
re: /\bnew\s+XMLHttpRequest\b/g,
|
|
250
|
+
what: "hand-rolled XMLHttpRequest in the fetch era",
|
|
251
|
+
fix: "Use fetch (or the shared HTTP client).",
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "nan-comparison",
|
|
255
|
+
severity: "error",
|
|
256
|
+
re: /[=!]==?\s*NaN\b|\bNaN\s*[=!]==?/g,
|
|
257
|
+
what: "comparing against NaN — always false, the bug every veteran was bitten by exactly once",
|
|
258
|
+
fix: "Use Number.isNaN(value).",
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "self-alias-fossil",
|
|
262
|
+
severity: "error",
|
|
263
|
+
re: /\b(?:const|let|var)\s+(?:self|that|_this)\s*=\s*this\b/g,
|
|
264
|
+
what: "`self = this` — the pre-arrow-function fossil",
|
|
265
|
+
fix: "Use an arrow function; lexical `this` has existed since 2015.",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "empty-else-block",
|
|
269
|
+
severity: "error",
|
|
270
|
+
re: /\belse\s*\{\s*\}/g,
|
|
271
|
+
guardComment: true,
|
|
272
|
+
what: "an empty `else {}` — code that says something and then says nothing",
|
|
273
|
+
fix: "Delete the else branch.",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: "redundant-boolean-ternary",
|
|
277
|
+
severity: "error",
|
|
278
|
+
re: /\?\s*true\s*:\s*false\b|\?\s*false\s*:\s*true\b/g,
|
|
279
|
+
what: "`cond ? true : false` — a boolean laundered through a ternary",
|
|
280
|
+
fix: "Use the condition itself (negate it for the inverted form).",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "date-gettime",
|
|
284
|
+
severity: "error",
|
|
285
|
+
re: /new\s+Date\s*\(\s*\)\s*\.\s*getTime\s*\(\s*\)/g,
|
|
286
|
+
what: "`new Date().getTime()` — allocating an object to ask the clock",
|
|
287
|
+
fix: "Date.now().",
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: "deprecated-substr",
|
|
291
|
+
severity: "error",
|
|
292
|
+
re: /\.substr\s*\(/g,
|
|
293
|
+
what: "`.substr()` — deprecated for so long its MDN page is mostly an apology",
|
|
294
|
+
fix: "Use .slice() (mind the second argument: substr takes a LENGTH, slice takes an END index).",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: "foreach-async",
|
|
298
|
+
severity: "error",
|
|
299
|
+
re: /\.forEach\s*\(\s*async\b/g,
|
|
300
|
+
what: "`.forEach(async …)` — fire-and-forget promises nobody awaits; errors vanish",
|
|
301
|
+
fix: "Use `for … of` with await, or Promise.all(items.map(async …)).",
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "console-log-frontend",
|
|
305
|
+
severity: "error",
|
|
306
|
+
markerKey: "frontendPathMarkers",
|
|
307
|
+
re: /\bconsole\s*\.\s*log\s*\(/g,
|
|
308
|
+
what: "console.log debug droppings in shipped frontend code",
|
|
309
|
+
fix: "Delete it. (console.error/warn for real error surfacing and console.debug — the devtools-filtered verbose channel — are deliberate APIs and not matched; server-side CloudWatch logging is out of scope.)",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "native-dialog",
|
|
313
|
+
severity: "error",
|
|
314
|
+
markerKey: "frontendPathMarkers",
|
|
315
|
+
re: /\bwindow\s*\.\s*(?:alert|confirm|prompt)\s*\(|(?<![.\w$])alert\s*\(/g,
|
|
316
|
+
what: "window.alert/confirm/prompt in a polished SPA — the browser's 1995 chrome over your design system",
|
|
317
|
+
fix: "Use the shared dialog/confirm component. (The one sanctioned wrapper that FALLS BACK to window.confirm is declared in config `sanctionedHomes`.)",
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: "empty-catch-swallow",
|
|
321
|
+
severity: "warning",
|
|
322
|
+
re: /\bcatch\s*(?:\(\s*[\w$]*\s*\))?\s*\{\s*\}/g,
|
|
323
|
+
guardComment: true,
|
|
324
|
+
what: "an empty catch with no comment — the error goes to die in silence and nobody agreed to it",
|
|
325
|
+
fix: "Handle it, rethrow it, or leave a comment stating WHY swallowing is correct here (a commented empty catch is not flagged).",
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "swallowed-promise-catch",
|
|
329
|
+
severity: "warning",
|
|
330
|
+
re: /\.catch\s*\(\s*\(\s*\)\s*=>\s*\{\s*\}\s*\)|\.catch\s*\(\s*function\s*\(\s*\)\s*\{\s*\}\s*\)/g,
|
|
331
|
+
guardComment: true,
|
|
332
|
+
what: "`.catch(() => {})` — a rejection muzzled with no explanation",
|
|
333
|
+
fix: "Handle it, or leave a comment in the handler stating why dropping it is correct (a commented handler is not flagged).",
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "json-deep-clone",
|
|
337
|
+
severity: "warning",
|
|
338
|
+
re: /JSON\s*\.\s*parse\s*\(\s*JSON\s*\.\s*stringify\s*\(/g,
|
|
339
|
+
guardComment: true,
|
|
340
|
+
what: "JSON.parse(JSON.stringify(…)) deep clone — drops undefined/functions/Dates and walks the object twice",
|
|
341
|
+
fix: "structuredClone(value) — unless the JSON round-trip's lossiness is the point (stripping reactivity/functions); then say so in a comment and keep it (a commented clone is not flagged).",
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: "math-random-token",
|
|
345
|
+
severity: "warning",
|
|
346
|
+
re: /Math\s*\.\s*random\s*\(\s*\)\s*\.\s*toString\s*\(\s*36\b/g,
|
|
347
|
+
what: "Math.random().toString(36) hand-rolled IDs — collision-prone and non-cryptographic",
|
|
348
|
+
fix: "crypto.randomUUID(), or the shared random helper if the workspace has one.",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "loose-boolean-equality",
|
|
352
|
+
severity: "warning",
|
|
353
|
+
re: /(?<![=!<>])[=!]=(?!=)\s*(?:true|false)\b/g,
|
|
354
|
+
what: "loose `== true` / `!= false` — coercion gymnastics around a value that is already a boolean",
|
|
355
|
+
fix: "Use the value directly, or strict equality when it is genuinely nullable.",
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: "double-cast",
|
|
359
|
+
severity: "info",
|
|
360
|
+
extKey: "doubleCastExtensions",
|
|
361
|
+
re: /\bas\s+unknown\s+as\b/g,
|
|
362
|
+
what: "`as unknown as` double-cast — the type system asked a question and was told to shut up",
|
|
363
|
+
fix: "Prefer a real type guard or a narrower seam type; keep only genuinely unavoidable boundary casts.",
|
|
364
|
+
},
|
|
365
|
+
];
|
|
366
|
+
|
|
367
|
+
const RAW_RULES = [
|
|
368
|
+
{
|
|
369
|
+
id: "merge-conflict-marker",
|
|
370
|
+
severity: "error",
|
|
371
|
+
re: /^(?:<{7} |>{7} )/gm,
|
|
372
|
+
what: "a committed merge-conflict marker — the codebase equivalent of walking out with toilet paper on your shoe",
|
|
373
|
+
fix: "Resolve the conflict.",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "lorem-ipsum-placeholder",
|
|
377
|
+
severity: "error",
|
|
378
|
+
re: /lorem\s+ipsum/gi,
|
|
379
|
+
what: "lorem-ipsum placeholder copy in committed source",
|
|
380
|
+
fix: "Write the real copy (i18n-keyed where the surface is localized).",
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
// Filename smells — checked against EVERY walked file (dotfiles included), content never read.
|
|
385
|
+
const ARTIFACT_TESTS = [
|
|
386
|
+
{ re: /\.(?:bak|orig|rej|swp|swo)$/i, label: "editor/merge backup extension" },
|
|
387
|
+
{ re: /^(?:\.DS_Store|Thumbs\.db|desktop\.ini)$/i, label: "OS metadata litter" },
|
|
388
|
+
{ re: / copy(?: ?\d+)?\.[^.]+$/i, label: "finder/explorer duplicate" },
|
|
389
|
+
{ re: / \(\d+\)\.[^.]+$/, label: "download-dedupe duplicate" },
|
|
390
|
+
{ re: /[._-]old\.[^.]+$/i, label: "parked old version" },
|
|
391
|
+
{ re: /~$/, label: "editor backup tilde" },
|
|
392
|
+
];
|
|
393
|
+
|
|
394
|
+
// Commented-out-code shape test. Deliberately conservative: pragmas, JSDoc `*` lines, URLs,
|
|
395
|
+
// arrow-prose (route tables, flow descriptions) and bare key=value shorthand (runbook env vars,
|
|
396
|
+
// parameter reference tables) never count; only runs of clearly code-shaped comment lines do.
|
|
397
|
+
// Assignment/call shapes must carry the statement's own trailing `;` — prose that merely contains
|
|
398
|
+
// code-ish punctuation does not (the 2026-07-05 skeptic pass: every FP was punctuation-shaped prose).
|
|
399
|
+
const COMMENT_PRAGMA = /^(?:eslint|prettier|biome|oxlint|stylelint|c8 |istanbul|v8 |@ts-|ts-|@vite|vite-|webpack|#|!|\*)/i;
|
|
400
|
+
const COMMENT_CODE_SHAPES = [
|
|
401
|
+
/^(?:const |let |var |if\s*\(|for\s*\(|while\s*\(|switch\s*\(|return[ ;(]|await |import |export |function |case |break;|continue;|try\b|catch\s*\(|\}\s*else\b|else\s*\{)/,
|
|
402
|
+
/^[\w$.[\]"']+\s*[-+*/|&]?=[^=>].*;\s*$/,
|
|
403
|
+
/^[\w$]+(?:\.[\w$]+)*\([^()]*\);\s*$/,
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
function isCodeShapedComment(text) {
|
|
407
|
+
const t = text.trim();
|
|
408
|
+
if (t.length < 3) return false;
|
|
409
|
+
if (COMMENT_PRAGMA.test(t)) return false;
|
|
410
|
+
if (/https?:\/\//.test(t)) return false;
|
|
411
|
+
if (/[→⇒]/.test(t)) return false;
|
|
412
|
+
if (/\b(?:TODO|FIXME|HACK|NOTE|e\.g\.|i\.e\.)\b/i.test(t)) return false;
|
|
413
|
+
return COMMENT_CODE_SHAPES.some((re) => re.test(t));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function lineStartsOf(text) {
|
|
417
|
+
const starts = [0];
|
|
418
|
+
for (let i = 0; i < text.length; i++) if (text[i] === "\n") starts.push(i + 1);
|
|
419
|
+
return starts;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function lineAt(starts, offset) {
|
|
423
|
+
let lo = 0;
|
|
424
|
+
let hi = starts.length - 1;
|
|
425
|
+
while (lo < hi) {
|
|
426
|
+
const mid = (lo + hi + 1) >> 1;
|
|
427
|
+
if (starts[mid] <= offset) lo = mid;
|
|
428
|
+
else hi = mid - 1;
|
|
429
|
+
}
|
|
430
|
+
return lo + 1;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function looksMinified(raw, fileName) {
|
|
434
|
+
if (/\.min\.(?:js|mjs|cjs)$/i.test(fileName)) return true;
|
|
435
|
+
const lines = raw.split("\n");
|
|
436
|
+
let maxLen = 0;
|
|
437
|
+
for (const l of lines) if (l.length > maxLen) maxLen = l.length;
|
|
438
|
+
if (maxLen > 2500) return true;
|
|
439
|
+
return lines.length >= 8 && raw.length / lines.length > 240;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function compileList(values) {
|
|
443
|
+
return (values || []).map((v) => new RegExp(v));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export const audit = {
|
|
447
|
+
id: "veteran-would-mock",
|
|
448
|
+
title: "Things a 30-year veteran coder would make fun of",
|
|
449
|
+
category: "maintainability",
|
|
450
|
+
domain: "code",
|
|
451
|
+
requires: {},
|
|
452
|
+
defaultConfig: {
|
|
453
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue"],
|
|
454
|
+
varDeclarationExtensions: [".ts", ".tsx", ".vue"],
|
|
455
|
+
doubleCastExtensions: [".ts", ".tsx", ".vue"],
|
|
456
|
+
// Rules that only make sense in browser-shipped code fire only under these path markers.
|
|
457
|
+
frontendPathMarkers: ["/web/src/"],
|
|
458
|
+
// Per-rule canonical homes (declared, not silenced): the ONE shared wrapper allowed to fall
|
|
459
|
+
// back to window.confirm, the ONE shared random helper, etc. Shape: { "<rule-id>": ["rel/path"] }.
|
|
460
|
+
sanctionedHomes: {},
|
|
461
|
+
// Path substrings excluded from CONTENT rules (e.g. an in-tree copy of this very check corpus,
|
|
462
|
+
// which quotes the smells it hunts). Filename-artifact rules still apply everywhere.
|
|
463
|
+
skipPathMarkers: [],
|
|
464
|
+
testDirSegments: ["__tests__", "__mocks__", "test", "tests", "testing", "e2e", "fixtures", "playground"],
|
|
465
|
+
testFilePatterns: ["\\.(spec|test)\\.[a-z]+$", "\\.d\\.ts$", "(test|spec)-(helpers|harness)", "-harness\\."],
|
|
466
|
+
commentedCodeMinLines: 4,
|
|
467
|
+
maxListedPerRule: 50,
|
|
468
|
+
outputPath: "tmp/audits/VETERAN_WOULD_MOCK.md",
|
|
469
|
+
},
|
|
470
|
+
async run(ctx) {
|
|
471
|
+
const { root, checkConfig: cfg } = ctx;
|
|
472
|
+
const findings = [];
|
|
473
|
+
const extSet = new Set(cfg.extensions);
|
|
474
|
+
const testFileRes = compileList(cfg.testFilePatterns);
|
|
475
|
+
const testDirSet = new Set(cfg.testDirSegments);
|
|
476
|
+
const skippedMinified = [];
|
|
477
|
+
const filesByDir = new Map(); // dir → Set of basenames, for the numbered-clone rule
|
|
478
|
+
let scanned = 0;
|
|
479
|
+
|
|
480
|
+
const isTestPath = (rel) => {
|
|
481
|
+
const segments = rel.split("/");
|
|
482
|
+
if (segments.some((s) => testDirSet.has(s))) return true;
|
|
483
|
+
return testFileRes.some((re) => re.test(rel));
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// Pass 1 — filename artifacts, dotfiles included (content never read).
|
|
487
|
+
for (const abs of walkFiles(root, { includeDotfiles: true, ignore: new Set([".claude"]) })) {
|
|
488
|
+
const name = basename(abs);
|
|
489
|
+
for (const t of ARTIFACT_TESTS) {
|
|
490
|
+
if (t.re.test(name)) {
|
|
491
|
+
findings.push(
|
|
492
|
+
createFinding({
|
|
493
|
+
id: "committed-backup-artifact",
|
|
494
|
+
title: "Committed backup/copy artifact",
|
|
495
|
+
severity: "error",
|
|
496
|
+
file: relative(root, abs).replace(/\\/g, "/"),
|
|
497
|
+
message: `\`${name}\` (${t.label}) — version control IS the backup; parked copies are what the veteran screenshots for the group chat.`,
|
|
498
|
+
fix: "Delete it; git remembers.",
|
|
499
|
+
}),
|
|
500
|
+
);
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Pass 2 — content rules.
|
|
507
|
+
for (const abs of walkFiles(root)) {
|
|
508
|
+
const ext = extname(abs);
|
|
509
|
+
if (!extSet.has(ext)) continue;
|
|
510
|
+
const rel = relative(root, abs).replace(/\\/g, "/");
|
|
511
|
+
if (cfg.skipPathMarkers.some((m) => rel.includes(m))) continue;
|
|
512
|
+
if (isTestPath(rel)) continue;
|
|
513
|
+
|
|
514
|
+
let raw;
|
|
515
|
+
try {
|
|
516
|
+
raw = readFileSync(abs, "utf8");
|
|
517
|
+
} catch {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const fileName = basename(abs);
|
|
521
|
+
if (looksMinified(raw, fileName)) {
|
|
522
|
+
skippedMinified.push(rel);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
scanned++;
|
|
526
|
+
|
|
527
|
+
const dir = dirname(rel);
|
|
528
|
+
let names = filesByDir.get(dir);
|
|
529
|
+
if (!names) filesByDir.set(dir, (names = new Set()));
|
|
530
|
+
names.add(fileName);
|
|
531
|
+
|
|
532
|
+
const starts = lineStartsOf(raw);
|
|
533
|
+
const rawLines = raw.split("\n");
|
|
534
|
+
const push = (rule, offset) => {
|
|
535
|
+
const line = lineAt(starts, offset);
|
|
536
|
+
const snippet = (rawLines[line - 1] || "").trim().slice(0, 100);
|
|
537
|
+
findings.push(
|
|
538
|
+
createFinding({
|
|
539
|
+
id: rule.id,
|
|
540
|
+
title: rule.what,
|
|
541
|
+
severity: rule.severity,
|
|
542
|
+
file: rel,
|
|
543
|
+
line,
|
|
544
|
+
message: `${rule.what} — \`${snippet}\``,
|
|
545
|
+
fix: rule.fix,
|
|
546
|
+
}),
|
|
547
|
+
);
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
for (const rule of RAW_RULES) {
|
|
551
|
+
rule.re.lastIndex = 0;
|
|
552
|
+
let m;
|
|
553
|
+
while ((m = rule.re.exec(raw))) push(rule, m.index);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const views = buildViews(raw, ext === ".vue");
|
|
557
|
+
const sanctionedHomes = cfg.sanctionedHomes || {};
|
|
558
|
+
for (const rule of CODE_RULES) {
|
|
559
|
+
if (rule.extKey && !cfg[rule.extKey].includes(ext)) continue;
|
|
560
|
+
if (rule.markerKey && !cfg[rule.markerKey].some((mk) => rel.includes(mk))) continue;
|
|
561
|
+
if ((sanctionedHomes[rule.id] || []).includes(rel)) continue;
|
|
562
|
+
rule.re.lastIndex = 0;
|
|
563
|
+
let m;
|
|
564
|
+
while ((m = rule.re.exec(views.code))) {
|
|
565
|
+
// A guarded rule is skipped when a comment documents the decision: in the RAW match
|
|
566
|
+
// span, on the rest of its line, or attached directly above the statement (up to 3
|
|
567
|
+
// lines up, a blank line breaks attachment) — `catch { /* best-effort */ }`,
|
|
568
|
+
// `x.catch(() => {}); // reason`, and a comment block written above a fire-and-forget
|
|
569
|
+
// are all documented decisions, not smells (the 2026-07-05 skeptic pass found the
|
|
570
|
+
// above-the-statement form is how this codebase actually documents them).
|
|
571
|
+
if (rule.guardComment) {
|
|
572
|
+
const lineEnd = raw.indexOf("\n", m.index + m[0].length);
|
|
573
|
+
const span = raw.slice(m.index, lineEnd === -1 ? raw.length : lineEnd);
|
|
574
|
+
if (/\/\/|\/\*/.test(span)) continue;
|
|
575
|
+
const matchLine = lineAt(starts, m.index);
|
|
576
|
+
let documentedAbove = false;
|
|
577
|
+
for (let k = matchLine - 2; k >= 0 && k >= matchLine - 4; k--) {
|
|
578
|
+
const above = (rawLines[k] || "").trim();
|
|
579
|
+
if (!above) break;
|
|
580
|
+
if (/^(?:\/\/|\/\*|\*)/.test(above)) {
|
|
581
|
+
documentedAbove = true;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (documentedAbove) continue;
|
|
586
|
+
}
|
|
587
|
+
push(rule, m.index);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Commented-out code: runs of ≥ N consecutive code-shaped comment lines.
|
|
592
|
+
const commentLines = views.comment.split("\n");
|
|
593
|
+
let runStart = -1;
|
|
594
|
+
let runLen = 0;
|
|
595
|
+
const flushRun = (endIdx) => {
|
|
596
|
+
// An explicitly labelled example/usage block directly above the run is documentation
|
|
597
|
+
// (integration scaffolds, runbooks), not dead code — the label states the intent.
|
|
598
|
+
const labelledExample = [1, 2, 3].some((k) => {
|
|
599
|
+
const above = commentLines[runStart - k];
|
|
600
|
+
return typeof above === "string" && /\b(?:example|usage)\b/i.test(above);
|
|
601
|
+
});
|
|
602
|
+
if (runLen >= cfg.commentedCodeMinLines && !labelledExample) {
|
|
603
|
+
const line = runStart + 1;
|
|
604
|
+
findings.push(
|
|
605
|
+
createFinding({
|
|
606
|
+
id: "commented-out-code",
|
|
607
|
+
title: "Commented-out code block",
|
|
608
|
+
severity: "warning",
|
|
609
|
+
file: rel,
|
|
610
|
+
line,
|
|
611
|
+
message: `${runLen} consecutive lines of commented-out code starting at line ${line} — dead code with a paint job; git already remembers it.`,
|
|
612
|
+
fix: "Delete the block (restore it from history if it is ever needed).",
|
|
613
|
+
}),
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
runStart = -1;
|
|
617
|
+
runLen = 0;
|
|
618
|
+
void endIdx;
|
|
619
|
+
};
|
|
620
|
+
for (let i = 0; i < commentLines.length; i++) {
|
|
621
|
+
if (isCodeShapedComment(commentLines[i])) {
|
|
622
|
+
if (runStart === -1) runStart = i;
|
|
623
|
+
runLen++;
|
|
624
|
+
} else if (runStart !== -1) {
|
|
625
|
+
flushRun(i);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (runStart !== -1) flushRun(commentLines.length);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Pass 3 — numbered file clones (utils2.ts beside utils.ts).
|
|
632
|
+
for (const [dir, names] of filesByDir) {
|
|
633
|
+
for (const name of names) {
|
|
634
|
+
const m = /^(.*[a-z])(\d+)(\.[a-z]+)$/i.exec(name);
|
|
635
|
+
if (!m) continue;
|
|
636
|
+
const [, stem, , ext] = m;
|
|
637
|
+
if (stem.length < 4) continue;
|
|
638
|
+
if (/(?:oauth|http|https|utf|sha|md|base|es|v)$/i.test(stem)) continue;
|
|
639
|
+
if (!names.has(`${stem}${ext}`)) continue;
|
|
640
|
+
findings.push(
|
|
641
|
+
createFinding({
|
|
642
|
+
id: "numbered-file-clone",
|
|
643
|
+
title: "Numbered file clone",
|
|
644
|
+
severity: "warning",
|
|
645
|
+
file: `${dir === "." ? "" : `${dir}/`}${name}`.replace(/\\/g, "/"),
|
|
646
|
+
message: `\`${name}\` sits beside \`${stem}${ext}\` — versioning by filename suffix; the number says "we gave up on naming".`,
|
|
647
|
+
fix: "Merge them or give the second file a name that states its actual difference.",
|
|
648
|
+
}),
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const errors = findings.filter((f) => f.severity === "error").length;
|
|
654
|
+
const warnings = findings.filter((f) => f.severity === "warning").length;
|
|
655
|
+
const infos = findings.length - errors - warnings;
|
|
656
|
+
return {
|
|
657
|
+
failed: errors > 0,
|
|
658
|
+
findings,
|
|
659
|
+
outputPath: cfg.outputPath,
|
|
660
|
+
jsonPayload: { scanned, skippedMinified, errors, warnings, infos },
|
|
661
|
+
report: renderReport({ findings, scanned, skippedMinified, maxListed: cfg.maxListedPerRule }),
|
|
662
|
+
};
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
function renderReport({ findings, scanned, skippedMinified, maxListed }) {
|
|
667
|
+
const lines = [
|
|
668
|
+
"# Things a 30-year veteran coder would make fun of",
|
|
669
|
+
"",
|
|
670
|
+
"_Small, unambiguous embarrassments: debug droppings, 2010 fossils, copy-paste artifacts, silent swallows._",
|
|
671
|
+
"_Errors gate (each rule was at zero when it landed — a hit is a fresh regression). Warnings/info are the_",
|
|
672
|
+
"_thermometer tier: harvest worst-first, never expect zero._",
|
|
673
|
+
"",
|
|
674
|
+
`- Files scanned: ${scanned}`,
|
|
675
|
+
`- Minified/vendored bundles skipped (not your code): ${skippedMinified.length}${skippedMinified.length ? ` — ${skippedMinified.join(", ")}` : ""}`,
|
|
676
|
+
`- Findings: ${findings.length}`,
|
|
677
|
+
"",
|
|
678
|
+
];
|
|
679
|
+
if (!findings.length) {
|
|
680
|
+
lines.push("## Clean — the veteran walks through and finds nothing to screenshot.");
|
|
681
|
+
return `${lines.join("\n")}\n`;
|
|
682
|
+
}
|
|
683
|
+
const byRule = new Map();
|
|
684
|
+
for (const f of findings) {
|
|
685
|
+
const list = byRule.get(f.id) || [];
|
|
686
|
+
list.push(f);
|
|
687
|
+
byRule.set(f.id, list);
|
|
688
|
+
}
|
|
689
|
+
const sevRank = { error: 0, warning: 1, info: 2 };
|
|
690
|
+
const groups = [...byRule.entries()].sort((a, b) => sevRank[a[1][0].severity] - sevRank[b[1][0].severity] || b[1].length - a[1].length);
|
|
691
|
+
for (const [ruleId, items] of groups) {
|
|
692
|
+
lines.push(`## ${ruleId} — ${items.length} (${items[0].severity})`, "");
|
|
693
|
+
if (items[0].fix) lines.push(`_Fix: ${items[0].fix}_`, "");
|
|
694
|
+
for (const f of items.slice(0, maxListed)) lines.push(`- \`${f.file}${f.line ? `:${f.line}` : ""}\` — ${f.message}`);
|
|
695
|
+
if (items.length > maxListed) lines.push(`- …and ${items.length - maxListed} more (full list in findings JSON — no silent cap)`);
|
|
696
|
+
lines.push("");
|
|
697
|
+
}
|
|
698
|
+
return `${lines.join("\n")}\n`;
|
|
699
|
+
}
|