@sitar_fiercer4c/skills 0.1.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 (50) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +75 -0
  3. package/bin/install.js +45 -0
  4. package/package.json +29 -0
  5. package/skills/architecture-walkthrough/SKILL.md +223 -0
  6. package/skills/architecture-walkthrough/references/sections.md +29 -0
  7. package/skills/architecture-walkthrough/scripts/check_structure.py +200 -0
  8. package/skills/autotest-webapp-ui/SKILL.md +58 -0
  9. package/skills/backend-code-review/SKILL.md +386 -0
  10. package/skills/backend-code-review/references/report-format.md +333 -0
  11. package/skills/backend-code-review/scripts/list_routes.py +269 -0
  12. package/skills/backend-code-review/scripts/sweep.py +550 -0
  13. package/skills/backend-code-review/scripts/verify_citations.py +201 -0
  14. package/skills/be-brief/SKILL.md +18 -0
  15. package/skills/clarke-list-excel/SKILL.md +51 -0
  16. package/skills/clarke-list-excel/references/output-schema.md +125 -0
  17. package/skills/clarke-list-excel/scripts/clarke_common.py +251 -0
  18. package/skills/clarke-list-excel/scripts/clarke_extract.py +487 -0
  19. package/skills/clarke-list-excel/scripts/load_clarke.py +322 -0
  20. package/skills/clarke-list-excel/scripts/run_all.py +63 -0
  21. package/skills/datalab-api/SKILL.md +163 -0
  22. package/skills/datalab-api/references/parameters-and-payload.md +121 -0
  23. package/skills/datalab-api/references/table-selection.md +35 -0
  24. package/skills/datalab-api/scripts/datalab_tables.py +365 -0
  25. package/skills/find-test-seam/SKILL.md +41 -0
  26. package/skills/frontend-code-review/SKILL.md +247 -0
  27. package/skills/frontend-code-review-2/SKILL.md +192 -0
  28. package/skills/frontend-code-review-2/scripts/fetch_pr_comments.py +65 -0
  29. package/skills/frontend-code-review-2/scripts/render_report.py +139 -0
  30. package/skills/murtaza-breif/SKILL.md +143 -0
  31. package/skills/murtaza-breif/scripts/save_brief.py +128 -0
  32. package/skills/pdf-to-json/SKILL.md +42 -0
  33. package/skills/pdf-to-json/references/output-schema.md +168 -0
  34. package/skills/pdf-to-json/scripts/extract_figures.py +319 -0
  35. package/skills/pdf-to-json/scripts/load_mongo.py +287 -0
  36. package/skills/pdf-to-json/scripts/pdf_extract.py +1313 -0
  37. package/skills/record-api-traffic/SKILL.md +434 -0
  38. package/skills/record-api-traffic/references/reading-recordings.md +224 -0
  39. package/skills/record-api-traffic/scripts/check-schema.mjs +184 -0
  40. package/skills/record-api-traffic/scripts/dump-quotation.mjs +67 -0
  41. package/skills/record-api-traffic/scripts/dump-source-excel.mjs +75 -0
  42. package/skills/record-api-traffic/scripts/lib/repo.mjs +109 -0
  43. package/skills/record-api-traffic/scripts/preflight.py +528 -0
  44. package/skills/record-api-traffic/scripts/record-api-traffic.py +720 -0
  45. package/skills/refac-wrt-business-goal/SKILL.md +305 -0
  46. package/skills/refac-wrt-business-goal/references/critic.md +170 -0
  47. package/skills/system-resource-triage/SKILL.md +180 -0
  48. package/skills/system-resource-triage/scripts/reap.sh +116 -0
  49. package/skills/system-resource-triage/scripts/triage.sh +111 -0
  50. package/skills/using-git-worktrees/SKILL.md +167 -0
@@ -0,0 +1,386 @@
1
+ ---
2
+ name: backend-code-review
3
+ description: Review an MVCS backend's HTTP routes against the repo's own code-rules.md, producing a severity-graded report organised by the layering that rules file defines. Use this skill whenever the user asks to review, audit, or critique backend code against architecture or code rules — "review the routes", "check this against code-rules", "audit the API surface", "does this follow our MVCS structure", "is the routes layer clean" — and whenever they point at a code-rules.md, an architecture-rules file, or a routes/ directory and ask what is wrong with it. Also use it when the user asks for a security or layering pass over an Express API, or asks which endpoints lack auth, validation, or rate limiting. Only the routes layer has an established methodology; see Scope.
4
+ ---
5
+
6
+ # Backend code review
7
+
8
+ ## What this produces
9
+
10
+ One markdown file: a review of a backend's HTTP routes, judged against that repo's own
11
+ `code-rules.md`, organised by the layering that file defines. Severity counts and a route
12
+ inventory at the top, per-file and per-route finding tables in the middle, a prioritised fix
13
+ list at the bottom.
14
+
15
+ `references/report-format.md` is the output contract — read it before writing anything. It
16
+ carries the skeleton, the severity model, the table columns, and worked example rows showing the
17
+ level of specificity expected.
18
+
19
+ Write the file, report the path, stop. Do not also summarise the findings in chat: the report is
20
+ long and a chat summary either duplicates it or quietly becomes the thing the user reads instead,
21
+ which defeats the point of a document they can return to and check.
22
+
23
+ ## Scope
24
+
25
+ **Only the routes layer has an established methodology.** It was worked out against a real
26
+ codebase and revised until a reader was satisfied with it.
27
+
28
+ The other sections a `code-rules.md` typically names — services, controllers, models, config,
29
+ utils, validators, middleware — do not have one yet. If asked to review those, say so plainly and
30
+ offer to work one out together, the way the routes methodology was worked out. Then stop.
31
+
32
+ Improvising a methodology and shipping it under this skill's name is worse than an honest gap.
33
+ The user cannot tell, from the output, which parts of the approach were validated and which were
34
+ invented on the spot, so the unvetted section contaminates their trust in the reviewed one.
35
+
36
+ This does not mean ignoring those layers. Reviewing a route *requires* reading the service it
37
+ calls — see "Trace every route to its floor". The boundary is about which layer the findings are
38
+ organised around and which layer gets a systematic pass, not about which files you may open.
39
+
40
+ ## The protocol
41
+
42
+ ### 1. Read `code-rules.md` first, before any code
43
+
44
+ It is the specification, not background. It gives you two things:
45
+
46
+ - **The section list**, in order — these become the report's H2 headings. Take them from the file,
47
+ not from a fixed list, so a repo naming `jobs` or `adapters` gets those headings.
48
+ - **The rule for each layer** — what routes are allowed to do, what belongs in controllers versus
49
+ services, what validators are for, and any explicit DOS/DONTS.
50
+
51
+ Every finding names the rule it is judged against. That is what makes this a review against *this
52
+ repo's stated architecture* rather than a stream of generic advice the user did not ask for. A
53
+ criticism that cannot be tied to a rule, and is not a security or correctness issue, is a
54
+ preference — leave it out.
55
+
56
+ If there is no rules file, ask for one or ask which conventions to review against. Do not
57
+ substitute your own idea of good layering; the user chose theirs.
58
+
59
+ Also check whether this code has been reviewed before — an existing review document, a decisions
60
+ record, PR threads on a previous round. Maintainers reject findings for reasons that are not
61
+ visible in the code, and a reviewer who re-raises a settled point spends credibility needed for
62
+ the real findings. Where a prior decision exists, honour it or argue against it explicitly; do
63
+ not silently re-litigate it.
64
+
65
+ ### 2. Build the route inventory mechanically
66
+
67
+ ```bash
68
+ python3 scripts/list_routes.py <backend>/src
69
+ ```
70
+
71
+ This resolves each route's **real** path by combining the router's mount prefix from the app
72
+ entrypoint with the path in the route file, and it flags routes defined inline in the entrypoint
73
+ and middleware applied by path prefix.
74
+
75
+ Run it rather than assembling the inventory by hand. Deriving paths by reading is where reviews
76
+ go wrong before they start, in two specific ways:
77
+
78
+ - **Doc comments naming a route's path drift from the mount point and are frequently wrong.** In
79
+ one backend, nine comments across a single controller named a URL segment that did not exist —
80
+ the router was mounted a level up from what every comment claimed. A whole review was drafted
81
+ from those comments before anyone opened the entrypoint.
82
+ - **Hand-counting gets the aggregate wrong.** The derived statistics — how many routes carry a
83
+ validator, how many carry auth — are the lines a reader is most likely to quote, and the most
84
+ embarrassing to get wrong. A hand-built inventory of one backend reported four routes with
85
+ validators; the script found three, because a route with `auth` middleware had been miscounted.
86
+
87
+ Treat the output as a scaffold, not a finding. Regex parsing misses dynamic mounts, conditionally
88
+ registered routes, and re-exported routers. Confirm each row against the file as you trace it.
89
+
90
+ Two things in the script's output are findings in their own right, worth checking before you go
91
+ further:
92
+
93
+ - **Routes defined inline in the entrypoint**, marked `(inline)`. `code-rules.md` files that name
94
+ a `routes` section almost always mean endpoints live there. An inline handler typically has no
95
+ controller, no validator, and its own ad-hoc error envelope that bypasses the error middleware.
96
+ - **Middleware applied by path prefix**, listed separately. `app.use('/some/path', limiter)` is
97
+ invisible when reading the route file, and it matches all methods and all subpaths — so a
98
+ limiter meant for `POST /login` also throttles `GET /login/anything`.
99
+
100
+ **Then walk the inventory once for cost, before you start tracing.** Take the full route list —
101
+ every row, in order — and mark each one that reaches a third-party billed call, a subprocess, an
102
+ LLM or OCR service, or heavy CPU. Cross that column against the limiter column. This is a pass
103
+ over a list, not a recall exercise, and it has to be done that way: a review that names the
104
+ throttling gaps it happened to notice reads as an exhaustive list to the person acting on it, so
105
+ the endpoint it skipped is the one that stays unprotected. One review missed
106
+ `POST /api/messages/:messageId/attachments/:attachmentId/quotation` — a paid extraction call —
107
+ entirely, while correctly listing four cheaper siblings.
108
+
109
+ ### 3. Sweep the tree for what reading cannot see
110
+
111
+ ```bash
112
+ python3 scripts/sweep.py <backend>/src --repo-root <backend>
113
+ ```
114
+
115
+ Tracing a route down through its layers is how you find what a single file does wrong. It is
116
+ structurally blind to a second class of finding, the kind that exists only across files: an
117
+ environment variable declared in `.env` that nothing reads, a helper defined identically in two
118
+ config modules, an export with no importer, a block of code pasted at three sites. Nobody spots
119
+ those by reading carefully, because every individual file looks fine. You spot them by counting.
120
+
121
+ This matters more than it sounds. An earlier version of this skill relied on careful reading
122
+ alone and rediscovered a *different subset* of these findings on each run — the same review, run
123
+ twice, would report the unused credentials one time and the duplicated helper the next. Coverage
124
+ that depends on what you happened to notice is sampling, not review. The sweep makes it
125
+ repeatable.
126
+
127
+ Five sections come back:
128
+
129
+ - **`ENV`** — every `process.env` read with its site, against what the `.env` files declare.
130
+ `DECLARED-NEVER-READ` is often a stale credential sitting in a checked-in file with no consumer.
131
+ `READ-NEVER-DECLARED` is a tunable no one can discover. `READS-OUTSIDE-CONFIG` is the layering
132
+ question, if the rules file gives config ownership of environment access.
133
+ - **`EXPORTS`** — for each exported identifier, its in-file reference count and its external
134
+ importers. The distinction between the two numbers is the whole point; see "no importer is not
135
+ the same as no caller" below. A separate `same-name-elsewhere` line lists files that merely
136
+ contain the word — that is a duplicate-definition lead, not an importer.
137
+ - **`REQUEST INPUTS`** — every request-derived key name in the tree, each marked `MASKED` or
138
+ `UNMASKED` against the masking array the sweep finds, plus the sites that log or pass a whole
139
+ `req.query`/`req.body` object.
140
+ - **`DUPES`** — the same identifier defined at top level in more than one file.
141
+ - **`BLOCKS`** — normalised line windows appearing verbatim in more than one place.
142
+
143
+ Every row is a lead, not a finding. The parsing is regex-based, so it mis-handles dynamic property
144
+ access, re-exports, and identifiers built from strings, and a name appearing in two files is
145
+ sometimes two unrelated things. Open the file and confirm before any of it goes in the report.
146
+
147
+ **`REQUEST INPUTS` exists because the natural grep misses keys.** A masking list is only correct
148
+ if it covers every sensitive key the app actually receives, and that set is not greppable: a
149
+ callback handler often never writes `req.query.code` at all — the controller passes the whole
150
+ `req.query` object into a service, which destructures `{ state, code, error }` out of its own
151
+ parameter, a file and a layer away. The sweep follows that one hop. In one backend this was the
152
+ difference between reporting two unmasked keys and reporting three; the third, `t`, was a signed
153
+ image-proxy token read on a different route entirely. Take the enumeration from this output rather
154
+ than from what you remember reading.
155
+
156
+ **Then cross-reference your own notes.** Some of the best findings are two ordinary observations
157
+ that only mean something together, and depth-first reading files them in separate sections where
158
+ they never meet. One review noted that a config module's header comment cites another file as the
159
+ model lazy-environment pattern, and noted elsewhere that this same file reads its environment at
160
+ module load — the bug the comment holds up as the fix. Both facts were in the document; the
161
+ finding was not. Before writing, reread what you have collected and look for pairs like that: a
162
+ comment that claims something you disproved elsewhere, a helper recommended in one section that
163
+ you flagged in another, a guard present on one route and absent on its sibling.
164
+
165
+ ### 4. Trace every route to its floor
166
+
167
+ For each route, follow the whole chain: route → middleware → validator → controller → service(s)
168
+ → utils and models. Judge each layer against its own rule.
169
+
170
+ **Violations hide one layer below where they appear.** A controller can look perfectly thin while
171
+ the service it delegates to does another layer's job — reaching into a model it should not own,
172
+ or running two hundred lines of input sanitisation that the rules file says belongs in
173
+ `validators/`. A review that reads the route file and the controller and stops will miss most of
174
+ what is actually there, and will read as superficial to anyone who knows the code.
175
+
176
+ This is the expensive step and there is no shortcut. Budget for it.
177
+
178
+ When two routes share a handler shape — paired exports, paired list endpoints — read both rather
179
+ than assuming symmetry. Near-duplicate handlers are where copy-paste divergence lives, and the
180
+ duplication itself is usually worth a finding.
181
+
182
+ ### 5. Judge, and record what holds
183
+
184
+ Severity model:
185
+
186
+ | Marker | Means |
187
+ |---|---|
188
+ | 🔴 | Rule violation, security defect, or correctness bug |
189
+ | 🟡 | Rule drift, duplication, fragile coupling |
190
+ | ⚠️ | Observation, nit, inconsistency |
191
+ | ✅ | Verified correct — worth keeping as-is |
192
+
193
+ **Before you write 🔴 or 🟡 on any row, answer this out loud in your notes:**
194
+
195
+ > **Does the harm require a fact that is not in this repository?**
196
+
197
+ Facts that are not in the repository include: the `NODE_ENV` value the deployment actually sets,
198
+ the number of proxies in front of the app, whether a permissive default someone explicitly wrote
199
+ down was intended, which hosts are on the allow-list at the load balancer.
200
+
201
+ If the answer is **yes**, the row is capped at ⚠️ and phrased as **"confirm X"**, never "change X".
202
+ If the answer is **no** — the harm follows from code that is in front of you, at any deployment —
203
+ grade it on its merits.
204
+
205
+ Worked through the cases this exists for:
206
+
207
+ | Candidate | Requires an outside fact? | Grade |
208
+ |---|---|---|
209
+ | TOTP printed to stdout behind a `NODE_ENV !== 'production'` gate | Yes — what `NODE_ENV` is set to | ⚠️ "confirm production sets it" |
210
+ | `CORS_ORIGIN='*'` written into both `.env` and `.env.example` | Yes — whether the deployed value is the example one | ⚠️ "confirm the deployed origin" |
211
+ | `app.set('trust proxy', 1)` | Yes — how many proxies are actually in front | ⚠️ "confirm the hop count" |
212
+ | One global secret signs password-reset tokens | No — true at every deployment | 🔴 |
213
+ | `/api/health/officecli` reachable unauthenticated | No — no middleware on the route, full stop | 🔴 |
214
+ | One `JWT_SECRET` signs both session and image-proxy tokens | No — visible in the two signing sites | 🔴 |
215
+
216
+ The reason this is a gate and not advice: the rest of this skill sharpens security instincts hard,
217
+ and a paragraph asking for restraint loses to them every time. The question has a yes/no answer
218
+ and a forced consequence, so it survives the pull. A reader who dismisses three re-opened
219
+ decisions reads the fourth finding — the real one — with less attention, and that is the cost.
220
+
221
+ A ⚠️ "confirm" row is not a weaker finding. It is the accurate one: you found something whose
222
+ safety depends on a value you cannot see, and the useful output is to name the value.
223
+
224
+ **A `✅` is a claim that you checked the thing that could go wrong.** Not that the code reads well,
225
+ and not that the mechanism is clever. The happy path working is not evidence; it is the default.
226
+ So say which failure path you followed — the catch block, the second concurrent caller, the
227
+ missing-config case, the malformed input — and what you found there. If you did not follow one,
228
+ that is not a `✅`. Leave it out. Silence is honest; a `✅` is a promise.
229
+
230
+ The reason to be strict here is that `✅` is sold to the reader as "examined and sound", so a wrong
231
+ one is worse than no row at all — it actively stops the next person from looking. Real examples
232
+ from a review of this kind: a streaming proxy marked `✅` for "handling its failure mode well",
233
+ where the mechanism was indeed careful but the `catch` around it collapsed credential, network and
234
+ permission errors alike into `404 Image not found`. A counter marked `✅` for sound concurrency
235
+ reasoning, where the increment was genuinely atomic but the caller sat on a `GET` path and threw
236
+ the reserved number away on every conflict. In both, the praise was accurate about the mechanism
237
+ and blind to the code immediately around it. A `✅` covers what you examined and nothing adjacent.
238
+
239
+ `✅` rows still matter and should be there. A review listing only faults leaves the reader unable
240
+ to tell "examined and sound" from "not examined", and that ambiguity is worst exactly where the
241
+ stakes are highest. Record the non-obvious things you actually probed: a security control you
242
+ tried to get past, a guard you confirmed runs before the expensive work, an idempotency mechanism
243
+ you traced through a retry.
244
+
245
+ ## What to look for below the route file
246
+
247
+ Route files are short and mostly fine. The findings are underneath. This list is where the
248
+ non-obvious ones came from — treat it as prompts to check, not a checklist to fill.
249
+
250
+ - **Writes on a `GET` path.** Lazy backfills, self-healing migrations, cache warming, counters
251
+ consumed to build a display value. `GET` is meant to be safe and idempotent; concurrent reads
252
+ then race each other into writes, and work reserved on a read path is usually work discarded.
253
+ - **Whole documents serialised to the client.** A `.lean()` or a raw `res.json(doc)` bypasses the
254
+ model's own `toJSON`, so fields that transform strips — buffers, hashes, internal state — go out
255
+ on the wire. Check what the model deletes and whether the query path honours it.
256
+ - **One secret signing two different token types.** Session tokens and image-proxy or download
257
+ tokens signed with the same key. Often safe only by payload-shape coincidence — one verifier
258
+ requires a field the other's tokens lack. That is not a documented boundary, so a future
259
+ payload change breaks it silently.
260
+ - **Credentials in logs.** Request loggers that dump `req.query` capture OAuth authorization
261
+ codes on callback routes and signed tokens on proxy routes. Check whether the masking helper
262
+ the codebase already has is actually applied at the outermost layer, not just deep in a service.
263
+ Take the unmasked key list from `sweep.py --section request-inputs`, not from reading — the keys
264
+ that leak are exactly the ones no grep finds, because they are destructured a layer away from
265
+ where the request object is read.
266
+ - **Error handlers that flatten distinct failures into one status.** A `catch` mapping everything
267
+ to `404` or `400` hides credential and network failures from whoever has to debug them. Check
268
+ what the handler discriminates on, not just that it has a handler.
269
+ - **Validation living in services.** Length caps, allow-lists, bounds checks, recipient parsing
270
+ inside a service function, when the rules file has a `validators` section. Note where it is and
271
+ how much of it there is; the volume is the argument.
272
+ - **Expensive or paid-API endpoints left unthrottled** — the cost/limiter cross-product from
273
+ step 2. Also check whether a `force`-style query flag bypasses an idempotency short-circuit,
274
+ which converts a bounded operation into unbounded spend.
275
+ - **A helper that exists to consolidate a pattern, bypassed by one caller.** Codebases grow a
276
+ `withX` wrapper and then leave the oldest call site hand-rolling the same sequence. The
277
+ wrapper's own comment often claims universal use — check it.
278
+ - **Redundant round-trips to an external API** inside a single request, usually from a controller
279
+ calling two service functions that each independently fetch the same remote object.
280
+ - **Auth applied inconsistently across sibling routes** — and, where a route deliberately has no
281
+ auth middleware, what actually authorises it. A signed token in a query string can be a correct
282
+ design; say so and explain the trail rather than reporting it as missing auth.
283
+
284
+ ## Verify before you write
285
+
286
+ Every claim is checkable, and a reader who finds one wrong claim discounts the rest of the
287
+ report. Four failure modes to guard against specifically:
288
+
289
+ **Comments are claims, not facts.** An export block commented "exported for unit testing" is a
290
+ hypothesis. Grep for the identifiers across `src`, tests, and scripts before repeating it — in one
291
+ backend none of the six such exports was imported anywhere, so the comment was simply false.
292
+
293
+ **"No importer" is not the same as "no caller."** That same finding once produced advice that
294
+ would have broken the file: the review concluded the six exports were dead and said deleting them
295
+ was safe, when every one had four or five callers *inside its own module*. They were over-exported,
296
+ not unused. The repair for an identifier with no external importer is to narrow the export list;
297
+ the repair for one with no references anywhere is to delete the definition. `sweep.py` reports
298
+ both counts side by side precisely because the two look identical from outside the file and the
299
+ remedies are opposite. Check the in-file count before you recommend a deletion.
300
+
301
+ **Sameness is measurable, so measure it.** "Identical", "the same twelve phrases", "differs only
302
+ by X" — these are the claims a duplication finding rests on, and they are the reader's entire
303
+ reason to consolidate. Diff the two sites rather than eyeballing them. A review that reported two
304
+ keyword lists as covering the same phrases, when one had eleven entries and the other twelve, gave
305
+ its reader a merge that would silently drop a case. The same goes for arithmetic in prose: if you
306
+ write "15 of 31", the remainder is 16, and a reader who spots the slip stops trusting the counts
307
+ that matter.
308
+
309
+ **Follow the value to its sink before reporting an injection or traversal.** A path segment
310
+ interpolated into a key looks alarming; if the result is compared for equality against a
311
+ signature-verified payload, it is closed, and reporting it as traversable is the kind of error
312
+ that costs the reader's trust in everything else. Trace it, then either report it as a real
313
+ finding or record it as a `✅` with the reason it holds.
314
+
315
+ Cite `file.js:LINE` for everything, and confirm the line still holds what you say before it goes
316
+ in. Duplication findings cite every site, with line ranges — "this is duplicated" with one
317
+ citation is unactionable and unverifiable.
318
+
319
+ **A citation carries two claims, and checking one does not check the other.** The line number is
320
+ one claim; the assertion riding on it is the other. Confirming that `excel.service.js:10–32`
321
+ exists is not confirming that the function there has no `return results;` — in the review that
322
+ made exactly that mistake, `return results;` was line 33, one past the cited range, and the range
323
+ itself stopped mid-function with an unclosed brace. Nothing about the line check would catch it,
324
+ because the line check passed. So verify the claim against the construct, then cite it.
325
+
326
+ This is sharpest on **negative claims** — "no return", "never calls", "lacks a guard", "no
327
+ importer". An absence is only observable over a complete construct: if your range ends before the
328
+ closing brace, the thing you say is missing may be on the next line. Cite the construct's full
329
+ extent, and quote the closing lines or the command that establishes the absence.
330
+
331
+ ### The last thing you do before the report is finished
332
+
333
+ ```bash
334
+ python3 scripts/verify_citations.py <report>.md --root <backend> --problems
335
+ ```
336
+
337
+ It resolves every `file:LINE` and `file:LINE–LINE` in the document, prints the real source back,
338
+ and flags three things: `NOT-FOUND` (the path or line does not resolve), `AMBIGUOUS` (a bare
339
+ filename matching several files — say which), and `TRUNCATED-CONSTRUCT` (a range on a row that
340
+ claims an absence, where the braces do not balance across it).
341
+
342
+ Every flag is a row to reopen. `TRUNCATED-CONSTRUCT` in particular is not a formatting nit; it is
343
+ the script telling you the evidence for that row does not cover the claim. Some will be fine on
344
+ inspection — a row citing "9 sites across `:19–176`" is a span, not a construct — but you have to
345
+ look, because this is the one failure mode that reads as authoritative when it is fabricated.
346
+
347
+ Run it against the finished document, fix what it finds, and run it again.
348
+
349
+ ## Writing the findings
350
+
351
+ Each finding carries four things in order: a **bold claim in a few words**, the evidence, the
352
+ consequence, and the repair. The reader is scanning a wide table; the bold clause is what they
353
+ read first and it has to carry the point on its own.
354
+
355
+ **The repair is the part reviews drop, and it is the part the reader needs most.** You have just
356
+ spent an hour tracing this code. They have not. A finding that stops at the consequence hands the
357
+ hardest question — what do we actually do — back to the person with the least context to answer
358
+ it, and a finding nobody can act on is one nobody can schedule or argue with either. Name the
359
+ function to extract, the module to create, the import to delete, the signature to change, the
360
+ config key to add. "This is duplicated across three files" is a diagnosis; "extract it to
361
+ `utils/fileName.util.js` and import it at all three" is a review.
362
+
363
+ Where the right repair is genuinely contested, say so *and still give the reader something*: name
364
+ the two options and what each costs, then say which one the rules file favours. That is a repair.
365
+ "Whether it should move is a judgement call" is where the reviewer stopped working.
366
+
367
+ The prioritised fix list at the bottom is not a substitute. Readers work from the tables, and a
368
+ row whose fix lives forty lines away in another section reads as a complaint.
369
+
370
+ **Repair the whole finding, not the instance you noticed.** If the claim is that a block appears
371
+ at three sites, the repair addresses all three — a fix that folds two of them leaves the third
372
+ behind and reads as complete, so nobody comes back for it. If the finding names three concerns in
373
+ one place — a value read, validated, and normalised in the wrong layer — the repair moves all
374
+ three. Partial repairs are how the leftover site survives the cleanup.
375
+
376
+ Prefer naming a place in the same codebase that already does the thing correctly over
377
+ recommending an abstract fix. If one module uses a dedicated secret and another reuses the shared
378
+ one, the fix is "follow `oauthState.js`" — concrete, obviously correct, and already approved by
379
+ whoever wrote it.
380
+
381
+ State consequences in terms of what it costs: unbounded spend, an hour of unrevocable access, a
382
+ credential in a log file. Not "this is bad practice".
383
+
384
+ Close with the prioritised fix list. The tables are organised for lookup, not for action — rank
385
+ the fixes by exploitability and blast radius, so an unauthenticated remote issue outranks an
386
+ internal layering violation even when both are 🔴.