@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,247 @@
1
+ ---
2
+ name: frontend-code-review
3
+ description: Reviews Next.js App Router frontend code against the project's layered architecture rules in code_rules_frontend.md (api / app / components / lib / types / utils), flagging every misplaced-code violation with the exact rule broken, why it matters, and a concrete fix. Use this whenever the user asks to review frontend code, check a diff or PR against architecture rules, asks "does this follow our conventions / rules", asks where a piece of code belongs, or mentions code_rules_frontend.md or frontend code review — even if they just paste a component, page, or api file and ask what's wrong with it.
4
+ ---
5
+
6
+ # Frontend code review
7
+
8
+ You are reviewing code against a layered architecture. The bugs you are hunting are almost never
9
+ syntax errors — the compiler and the linter already catch those. They are **placement** errors:
10
+ correct code sitting in the wrong layer, where it quietly erodes the boundary that makes the
11
+ codebase navigable. A `formatBytes` defined inside a route file works fine and gets rewritten by
12
+ the next person. An axios call outside `api/` works fine until the day the token policy changes in
13
+ one place and not the other.
14
+
15
+ So read for *location*, not correctness. The question is never "does this run?" but "is this the
16
+ folder where the next person would look for it?"
17
+
18
+ ## Step 1: Establish which layer the code is in
19
+
20
+ If the user gave you a path, use it. If they didn't, infer it and **say what you inferred and why**,
21
+ because every rule below is conditional on the layer — the same `export { x } from "./y"` is a
22
+ firing offence in `api/` and the intended design in `components/ui/`. Getting the layer wrong
23
+ inverts the verdict.
24
+
25
+ Signals: `page.tsx` / `layout.tsx` / a `route.ts` under a URL-shaped folder ⇒ `app`. A default-
26
+ exported React component that isn't a page ⇒ `components`. Plain async functions calling
27
+ `apiClient` ⇒ `api`. Only `interface` / `type` / shape constants ⇒ `types`. Exported pure
28
+ functions ⇒ `utils`.
29
+
30
+ If a file's *contents* argue for a different layer than its *path*, that mismatch is itself the
31
+ finding — that is the whole point of the review.
32
+
33
+ ## Step 2: Check every rule for that layer, plus the cross-cutting ones
34
+
35
+ The full rule catalogue is below. Two disciplines matter more than speed:
36
+
37
+ **Check the rules that pass, not just the ones that fail.** The user needs to know what you looked
38
+ at. A review that lists three problems tells them nothing about the twelve things you didn't
39
+ examine versus the twelve you cleared.
40
+
41
+ **Quote the code you are accusing.** A finding without a line reference and the offending
42
+ expression is an assertion the reader has to re-derive. Cite `file:line` where you have it.
43
+
44
+ ## The rules
45
+
46
+ Rules are grouped by folder, then by DOS (positive obligations) and DONTS (prohibitions). The IDs
47
+ are for citation — use them verbatim in your output so findings are traceable back to the source
48
+ document.
49
+
50
+ ### Architecture context
51
+
52
+ Next.js App Router, layered like the backend's MVCS. `app` is routes + controllers, `api` is
53
+ services, `types` is models, `utils` is utils + validators. The backend's `config` and `middleware`
54
+ layers have no folder here: config lives in `api/axios.ts`, and middleware's job (auth gating) is
55
+ done by `layout.tsx` guards.
56
+
57
+ ### api — one file per backend resource, holding every HTTP call to it
58
+
59
+ Each function wraps one endpoint, returns `res.data`, and is typed with the matching interface from
60
+ `types`. `api/axios.ts` is the shared client: it owns the base URL, attaches the JWT, and owns the
61
+ 401 policy. It is also the only module in the app that reads an environment variable
62
+ (e.g. `NEXT_PUBLIC_API_URL`).
63
+
64
+ - **API-DO1** — Name the function after what the endpoint does, and comment it with the method and
65
+ path it calls. Eg: `// GET /email/inbox`.
66
+ - **API-DO2** — Keep that comment true. A wrong path in a comment is worse than no comment, because
67
+ the next person reasons from it instead of from the code.
68
+ - **API-DO3** — Return `res.data`, not the raw axios response, and type the return with the
69
+ interface from `types`. Leaking the envelope makes every caller unwrap it again.
70
+ - **API-D1** — Don't create or call an axios instance outside this folder. Every request goes
71
+ through `apiClient` so the token interceptor stays the single place auth is attached.
72
+ *Importing axios **types** elsewhere is fine* — `utils/apiError.ts` legitimately needs
73
+ `AxiosError` to narrow an error. The ban is on instances and calls, not on the type namespace.
74
+ - **API-D2** — Don't re-export functions imported from other files, for any reason whatsoever.
75
+ This is absolute inside `api/`. A service that re-exports its neighbour hides which module a
76
+ caller actually depends on.
77
+ - **API-D3** — Don't put React in here. No hooks, no components, no state — these are plain async
78
+ functions. A hook in `api/` cannot be called from a server component or from another hook's
79
+ branch.
80
+ - **API-D4** — Don't read `process.env` outside `api/axios.ts`. One module owns configuration; a
81
+ second reader is a second thing to update when the variable is renamed.
82
+
83
+ ### app — file-system routes
84
+
85
+ A `page.tsx` coordinates: it calls `api` functions, holds the screen's state, and renders
86
+ components. A `layout.tsx` wraps a subtree and is where that subtree's auth guard runs, so pages
87
+ underneath can assume the user is signed in.
88
+
89
+ - **APP-DO1** — Keep a page to coordination. When a page needs to decide something, that decision
90
+ should be a call into `api` or `utils`, not logic written inline. Tax maths, totals, retry
91
+ policy, and payload shaping are decisions.
92
+ - **APP-DO2** — Handle an expired session the same way in every route. If one page signs the user
93
+ out on a 401 and another tells them to reconnect a third-party account, one of them is lying to
94
+ the user.
95
+ - **APP-DO3** — Persist what you let the user change. If a control edits state, that state belongs
96
+ in the save payload — an edit the UI accepts and silently drops on reload is worse than no
97
+ control at all.
98
+ - **APP-D1** — Don't define generic helpers in a route file. Date formatters, byte-size formatters,
99
+ currency formatters and the like belong in `utils`, where the next page can find them instead of
100
+ writing them again.
101
+ - **APP-D2** — Don't reach the network except through `api`. No `fetch`, no axios, no direct URL
102
+ strings in a page.
103
+ - **APP-D3** — Don't leave reusable markup here. A sub-view used by a second route moves to
104
+ `components`.
105
+
106
+ ### components — reusable UI, grouped by feature
107
+
108
+ Feature folders (`auth`, `dashboard`, `email`, `quotation`) plus `ui` for generic primitives. `ui`
109
+ is the generic layer — a `Button` or a `Modal` knows nothing about quotations or Outlook. The
110
+ feature folders may know the domain, and may call `api` when the component owns a self-contained
111
+ piece of the workflow (e.g. a preview modal that loads its own attachment).
112
+
113
+ - **CMP-DO1** — An `index.ts` that re-exports a folder's public components **is allowed here, and
114
+ only here**. It is the folder's front door, not a service hiding its dependencies — which is why
115
+ API-D2 does not apply to it. Eg: `components/ui/index.ts`.
116
+ - **CMP-D1** — Don't read or clear the token in an ordinary component. The one exception is the
117
+ guard shell a layout renders to protect its subtree (eg `components/dashboard/DashboardShell.tsx`).
118
+ Everything else takes a callback from that shell. A component that signs the user out itself, on
119
+ top of the handler it was already given, does the job twice — and the second `removeToken()` runs
120
+ against already-cleared storage, so the bug hides.
121
+ - **CMP-D2** — Don't let a `ui/` primitive learn the domain. A prop named `quotation` or
122
+ `taxRate` on a generic `Modal` means the primitive is no longer reusable.
123
+
124
+ ### lib — app-level modules that are neither a network call nor a helper
125
+
126
+ Today this is only sample data standing in for endpoints that don't exist yet (eg `lib/mockData.ts`).
127
+
128
+ - **LIB-D1** — Don't let anything permanent settle here. When a mock is replaced by a real endpoint
129
+ the fetching moves to `api`, its shapes move to `types`, and the file goes away.
130
+
131
+ ### types — the structure of data crossing the network boundary
132
+
133
+ The mirror of `models` on the backend. One file per backend resource, mirroring that resource's
134
+ response envelope exactly (eg `types/email.types.ts`).
135
+
136
+ - **TYP-DO1** — Keep these honest about what the server actually sends. A field typed as
137
+ always-present that the backend sometimes omits produces a crash the compiler promised could not
138
+ happen.
139
+ - **TYP-D1** — Don't put logic here. Interfaces, type aliases, and constants that describe a default
140
+ shape are fine; **functions and network calls are not**. A type guard, a mapper, a
141
+ `formatX(t: T): string` — all of these are code wearing a types file as a hat, and they belong in
142
+ `utils` (generic) or with the feature (domain).
143
+
144
+ ### utils — generic helpers with nothing to do with business logic
145
+
146
+ Every function is pure, and any function that touches `window` or `localStorage` guards for it
147
+ first so it is safe to import from a server component (eg `utils/auth.ts`).
148
+
149
+ - **UTL-DO1** — Check this folder before writing a helper. Most helpers that get written twice are
150
+ written twice because nobody looked.
151
+ - **UTL-DO2** — Guard `window` / `localStorage` access with `typeof window === "undefined"` so the
152
+ module is importable from a server component.
153
+ - **UTL-D1** — Don't implement the same helper in two places. If a copy already exists, import it or
154
+ move it here — two copies of a formatter drift, and the one that drifts is never the one you are
155
+ reading. A renamed duplicate (`formatDate` vs `formatFullDate` with the same body) is still a
156
+ duplicate.
157
+ - **UTL-D2** — Don't let domain logic accumulate here. A helper that knows what a quotation, a tax
158
+ rate or a supplier column is has stopped being generic, and belongs with the feature it serves.
159
+
160
+ ## Traps — the distinctions that decide most reviews
161
+
162
+ These are the places where a fast read gives the wrong verdict. Work through them before you write.
163
+
164
+ | Looks like a violation | But it's fine when | Rule |
165
+ |---|---|---|
166
+ | `export { Button } from "./Button"` | it's `components/*/index.ts` — the folder's front door | CMP-DO1 beats API-D2 |
167
+ | `import type { AxiosError } from "axios"` | anywhere — it's a type, not an instance | API-D1 permits it |
168
+ | `removeToken()` in a component | it's the layout's guard shell (`DashboardShell.tsx`) | CMP-D1 exception |
169
+ | a `const DEFAULT_X = {...}` in `types/` | it describes a default *shape*, not behaviour | TYP-D1 permits it |
170
+ | a feature component calling `api` | it owns a self-contained piece of the workflow | `components` intro |
171
+
172
+ And the inverse — things that look innocent and are not:
173
+
174
+ - `import axios from "axios"` (default import, not `import type`) in any non-`api` file is API-D1,
175
+ even if the file never fires a request yet. The instance is the violation.
176
+ - A re-export in `api/` is API-D2 **even if the re-exported thing is a type or a constant** — the
177
+ rule says "for any reason whatsoever" and it is the one absolute in the document.
178
+ - A helper in `utils/` that reads generically but takes a domain-shaped argument
179
+ (`(row: QuotationRow)`) is UTL-D2. The name is not the tell; the parameter type is.
180
+ - A page that computes a total inline is APP-DO1 *and* usually APP-D1 — flag whichever fits, don't
181
+ double-count the same lines under two IDs unless they genuinely name different problems.
182
+
183
+ ## Severity
184
+
185
+ Match the repo's existing review convention:
186
+
187
+ | | Meaning |
188
+ |---|---|
189
+ | 🔴 | Rule violation with a live consequence — security, correctness, or a boundary that will break under change |
190
+ | 🟡 | Real violation, contained blast radius — drift, duplication, fragile coupling |
191
+ | ⚠️ | Observation or nit — worth knowing, not worth blocking |
192
+ | ✅ | Checked and clean |
193
+
194
+ Anything touching auth, tokens, or the network boundary starts at 🔴. Placement violations with no
195
+ current consequence (a formatter in the wrong folder) are 🟡. Style preferences are ⚠️.
196
+
197
+ ## Output format
198
+
199
+ Use this structure. Adapt the depth to the size of the input — a ten-line snippet does not need
200
+ five sections — but never drop the coverage table, because "what did you check?" is the question the
201
+ user cannot answer for themselves.
202
+
203
+ ```markdown
204
+ ## Review: <file or diff under review>
205
+
206
+ **Layer:** `<folder>` — <one line: stated by the user, or how you inferred it>
207
+
208
+ ### Findings
209
+
210
+ | Sev | Rule | Location | Finding |
211
+ |-----|------|----------|---------|
212
+ | 🔴 | API-D1 | `line 4` | <what is wrong, in one sentence> |
213
+
214
+ Then, for each finding, a short block:
215
+
216
+ #### 🔴 API-D1 — <short title> (`file:line`)
217
+
218
+ <The offending code, quoted — two or three lines, enough to locate it.>
219
+
220
+ **Why it matters.** <The consequence, concretely. Not "violates the rule" — what breaks, and when.>
221
+
222
+ **Fix.** <A concrete change: which file it moves to, what the call site becomes. Show the code where
223
+ a sentence would be ambiguous.>
224
+
225
+ ### Rules checked and clean
226
+
227
+ | Rule | Verdict |
228
+ |------|---------|
229
+ | API-DO1 method/path comment present | ✅ |
230
+ | API-D2 no re-exports | ✅ |
231
+ ```
232
+
233
+ Some real problems have no rule behind them — a missing null guard, an import that resolves to
234
+ nothing, a typo in a comment. Report them, but put them under a separate **"Outside the rules"**
235
+ heading rather than in the findings table with an empty Rule column. Mixing them in makes the table
236
+ lie about what the architecture document actually requires, and a reader skimming the Rule column
237
+ cannot tell which items they are obliged to fix.
238
+
239
+ If the code is clean, say so per rule category rather than emitting a bare "looks good" — list every
240
+ rule you checked with a ✅ and state plainly that you found no violations. A clean review that shows
241
+ its work is useful; one that doesn't is indistinguishable from a review that wasn't done.
242
+
243
+ If you cannot verify a rule from what you were given — UTL-D1 needs the rest of `utils/` to know
244
+ whether a helper is a duplicate, APP-DO2 needs the other routes to know whether 401 handling is
245
+ consistent — say that explicitly rather than passing it silently. If you have filesystem access,
246
+ read the folder and check properly; only fall back to "not verifiable from this snippet" when you
247
+ genuinely can't look.
@@ -0,0 +1,192 @@
1
+ ---
2
+ name: frontend-code-review-2
3
+ description: Verifies frontend code against a set of PR review comments and reports pass / fail / unclear per comment, with quoted evidence from the code as it exists on disk right now — and changes nothing. Writes a markdown + JSON report. Use this whenever the user asks to check, verify, audit or confirm whether review comments, PR feedback, or review-document findings have actually been addressed — "did we fix these PR comments", "audit the frontend against PR #3", "which of these are still open", "give me a pass/fail report, don't change anything", "verify the fixes landed", "my colleague says these are done, check". Use it even if the user never says the word "audit". This is comment-verification, not fresh review: reach for it when the findings already exist and the question is whether the code satisfies them. Do NOT use it when the user wants the issues actually fixed — this skill deliberately never edits code.
4
+ ---
5
+
6
+ # Frontend code audit (review-only)
7
+
8
+ This is a verification pass, not a review and not a fix. Someone already reviewed the code and left
9
+ comments; someone may since have claimed to address them. Your job is to answer one question per
10
+ comment: **is this true of the code on disk right now?**
11
+
12
+ You are not looking for new problems. The comment list is the spec, and a comment that turns out to
13
+ be wrong is still the thing you report against.
14
+
15
+ The entire value of an audit is that it is trustworthy. A finding you guessed at is worse than no
16
+ finding, because it launders a guess into a checkmark that the next person acts on. So every status
17
+ you write carries quoted code behind it, read from the current tree.
18
+
19
+ ## The one hard rule: don't touch the code
20
+
21
+ Read-only on the source. The only files you write are the two report files.
22
+
23
+ Two reasons, and they both matter more than the convenience of a quick fix. First, an auditor who
24
+ repairs what they audit destroys the evidence — the reader can no longer tell what state the branch
25
+ was actually in, and the report becomes a record of your edits rather than of the code. Second, the
26
+ user has a separate workflow for fixing; mixing the two means an unreviewed change rides in under
27
+ the cover of a report.
28
+
29
+ So when a fix is one line and obvious, that is still a `fail`. Write the evidence, and if it helps,
30
+ one line on what the fix would be. If you notice yourself reaching for Edit or Write on a source
31
+ file, treat that impulse as the signal that you have found a real `fail` and write it down instead.
32
+
33
+ ## Step 1 — Pin down the inputs, and say what you resolved
34
+
35
+ Three things: where the comments come from, what code they are about, where the report goes.
36
+
37
+ - **Comments** — a PR URL or number, or a list the user pastes inline. For a PR, use
38
+ `scripts/fetch_pr_comments.py` rather than hand-rolling `gh` calls; it handles pagination, thread
39
+ replies and path filtering, which are easy to get subtly wrong.
40
+ - **Code under audit** — defaults to
41
+ `/home/dev/workspaces/murtaza-workspaces/murtaza-hotel-project/Frontend`. The review documents the
42
+ comments are anchored to live in that folder's `review/` subdirectory.
43
+ - **Output** — defaults to `<code path>/FRONTEND_AUDIT.md` and `.json`.
44
+
45
+ If the user scoped it ("just the api layer", "only the types comments"), filter by the review
46
+ document path — `Frontend/review/API_REVIEW.md` for api, `APP_REVIEW.md` for app, and so on. State
47
+ the scope and the comment count before you start checking, so a mis-scoped run is caught in one line
48
+ rather than at the end of a long report.
49
+
50
+ ```bash
51
+ python3 scripts/fetch_pr_comments.py --pr 3 --path-prefix Frontend/review/API_REVIEW.md \
52
+ --out /tmp/audit-comments.json
53
+ ```
54
+
55
+ ## Step 2 — Resolve each comment to the code it is about
56
+
57
+ This step decides whether the audit is accurate, and it has a trap in it.
58
+
59
+ **The comment's anchor is not the code.** These comments are attached to lines in review documents
60
+ (`Frontend/review/API_REVIEW.md:44`), which are markdown — usually a row in a findings table or a
61
+ paragraph in a detail section. The comment is *about* a source file that the review document names.
62
+ So read the anchored region of the document to learn which file and symbol is meant, then leave the
63
+ document behind. **You are grading the code, not the review.** A report that quotes the review
64
+ document back at the user has verified nothing.
65
+
66
+ Many comments name the file and symbol themselves ("Delete `formatFullDate` from
67
+ `email/[messageId]/page.tsx`"). Those need no lookup — go straight to the source.
68
+
69
+ **Expect paired duplicates.** A reviewer typically comments once on the summary-table row and again
70
+ on the detail section for the same finding, sometimes in nearly identical words. Report both, since
71
+ each comment id is something a person will search for, but mark the second `duplicate_of` the first.
72
+ Without that, a reader sees eight problems where there were four, and the summary counts mislead.
73
+
74
+ **Some comments reject the finding rather than request a change** — e.g. "no need to store it in the
75
+ backend database, it is only for the user to hide it in the frontend." Here the requested state is
76
+ the current state, so it is a `pass`. Say explicitly that the comment asked for no change and that
77
+ the code matches, because a reader scanning a column of statuses will otherwise assume someone did
78
+ work here.
79
+
80
+ ## Step 3 — Decide the status from the code on disk
81
+
82
+ Re-derive everything. Do not trust the review document's line numbers (they were written against an
83
+ older tree), a commit message, a changelog, an earlier audit, or the user telling you a colleague
84
+ already did it. Those tell you what someone intended; the file tells you what is true. Being told
85
+ the work is done is a reason to look more carefully, not less.
86
+
87
+ | Status | When |
88
+ |---|---|
89
+ | `pass` | The code now satisfies what the comment asked for, in full. |
90
+ | `fail` | The problem is still there, or only part of the request landed. |
91
+ | `unclear` | You genuinely cannot locate the code, or the comment is too vague to have a testable meaning. |
92
+
93
+ **Partial is `fail`, and name the remainder.** This is the status most often got wrong, because a
94
+ half-applied change looks like a fix at the point you happen to look at. A change can land in one
95
+ place and not in its counterpart: the return statement updated but not the function's declared
96
+ return type, the helper extracted but the old copy still exported, the field added but left optional
97
+ when the comment asked for required, one of four call sites migrated. Before you write `pass`, ask
98
+ what else had to move for this change to be complete, and go look at that too. A half-applied fix
99
+ is often worse than none, since it can leave the code not even compiling — and that is exactly the
100
+ thing an audit exists to surface.
101
+
102
+ **`unclear` is honest, and it is expensive.** It hands the work back to the reader, so spend a real
103
+ search first: try the symbol name, the old name, the file the review names, and a repo-wide grep. If
104
+ you can find the code but the comment is a judgement call, don't hide behind `unclear` — give your
105
+ reading and say it is a judgement call.
106
+
107
+ **The trap that produces false passes: a name matching in the wrong place.** Grep tells you a string
108
+ exists somewhere, not that the request was satisfied. `canSendEmail` appearing as an optional field
109
+ on a duplicate interface in `quotation.types.ts` is not the required field the comment asked for on
110
+ `EmailStatusConnected` in `email.types.ts`. Before you write `pass`, confirm all three: the right
111
+ file, the right symbol, the right shape. Grep locates; reading decides.
112
+
113
+ ### Evidence, and its fidelity
114
+
115
+ Every finding carries a quote with a `file:line` from the current tree. This is what makes the
116
+ report checkable by someone who doesn't trust you — and being checkable is the point.
117
+
118
+ - For a `fail`, quote the offending code as it stands.
119
+ - For a `pass`, quote the code that now satisfies the comment.
120
+ - When the pass is an *absence* ("the duplicate is gone"), a quote is impossible, so state the
121
+ search that establishes it and its scope: "`grep -rn "escapeHtml" Frontend/` returns only
122
+ `utils/sanitizeHtml.ts:12`." An unscoped "I couldn't find it" is not evidence.
123
+
124
+ **If you cite a command and its result, the result has to be real.** Writing "grep returns exactly
125
+ one hit" when it returns two is the one error that discredits the whole report — a reader who
126
+ re-runs your command and gets a different answer now has to re-verify every other line you wrote.
127
+ Line numbers and counts drift as you work, so when you are about to state one, re-run the command
128
+ and read the output rather than recalling it from earlier in the session. Nobody minds "returns two
129
+ hits, one of which is a prose comment"; they mind being told something they can disprove in five
130
+ seconds.
131
+
132
+ ## Step 4 — Write the report
133
+
134
+ Build a findings JSON, then render the markdown from it with `scripts/render_report.py`. The script
135
+ computes the counts and the per-file breakdown from the findings themselves, so the summary table
136
+ cannot drift out of step with the detail below it — which is the failure mode when both files are
137
+ written by hand. It also keeps the two files sharing one basename, so the pair is findable.
138
+
139
+ ```bash
140
+ python3 scripts/render_report.py --findings /tmp/findings.json --out-base <output path>/FRONTEND_AUDIT
141
+ ```
142
+
143
+ The findings JSON you write:
144
+
145
+ ```json
146
+ {
147
+ "audit": {
148
+ "source": "https://github.com/owner/repo/pull/3",
149
+ "scope": "Frontend/review/API_REVIEW.md — 8 comments",
150
+ "target": "/abs/path/Frontend",
151
+ "commit": "0a8162d"
152
+ },
153
+ "findings": [
154
+ {
155
+ "comment_id": "3923087999",
156
+ "comment_url": "https://github.com/owner/repo/pull/3#discussion_r3923087999",
157
+ "comment_location": "Frontend/review/API_REVIEW.md:44",
158
+ "comment": "Update the 4 comments to use the correct /api/... paths.",
159
+ "target_files": ["Frontend/api/email.api.ts"],
160
+ "status": "fail",
161
+ "evidence": "email.api.ts:19 still reads `// GET /email/status`; :26, :34, :41 likewise. None carry the /api prefix.",
162
+ "remaining": "All four comments still need the /api prefix.",
163
+ "duplicate_of": null
164
+ }
165
+ ]
166
+ }
167
+ ```
168
+
169
+ `remaining` is for partial work and may be omitted. `duplicate_of` carries the earlier comment id
170
+ when two comments describe one finding.
171
+
172
+ ## Step 5 — Reply in chat with the summary only
173
+
174
+ Three to five lines: the counts, the two file paths, and at most a one-sentence headline (the
175
+ cluster that dominates the failures, or that nothing has been addressed yet). The user asked for a
176
+ report on disk precisely so they don't have to read it in the transcript — reproducing its substance
177
+ in chat makes the deliverable harder to find, not easier, and quietly doubles the length of the
178
+ thing they have to read.
179
+
180
+ Something like:
181
+
182
+ > Audited 8 comments from PR #3 against `Frontend/api` (4 distinct findings; 4 of the comments are
183
+ > duplicate pairs). **0 pass, 8 fail, 0 unclear** — none of the api-layer feedback has landed yet.
184
+ > Report: `Frontend/FRONTEND_AUDIT.md` and `.json`. No source files were modified.
185
+
186
+ ## Bundled scripts
187
+
188
+ - `scripts/fetch_pr_comments.py` — pulls review comments from a PR via `gh`, paginated, optionally
189
+ filtered by file path, and normalises them to `{id, url, path, line, body, in_reply_to}`. Run
190
+ with `--help` for options.
191
+ - `scripts/render_report.py` — turns the findings JSON into `<base>.md` and `<base>.json`, deriving
192
+ the summary counts and the by-file breakdown so the two files always agree.
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+ """Fetch a PR's review comments via `gh`, normalised for auditing.
3
+
4
+ Usage:
5
+ python3 fetch_pr_comments.py --pr 3 [--repo owner/name] \
6
+ [--path-prefix Frontend/review/] [--out comments.json]
7
+
8
+ Emits a JSON list of {id, url, path, line, body, in_reply_to}, ordered by path then line,
9
+ so the audit works through the review document top to bottom.
10
+ """
11
+ import argparse
12
+ import json
13
+ import subprocess
14
+ import sys
15
+
16
+
17
+ def gh_json(args):
18
+ proc = subprocess.run(["gh"] + args, capture_output=True, text=True)
19
+ if proc.returncode != 0:
20
+ sys.exit(f"gh failed: {proc.stderr.strip()}")
21
+ return json.loads(proc.stdout)
22
+
23
+
24
+ def main():
25
+ p = argparse.ArgumentParser()
26
+ p.add_argument("--pr", required=True)
27
+ p.add_argument("--repo", help="owner/name; defaults to the current repo's origin")
28
+ p.add_argument("--path-prefix", default="", help="only keep comments on paths starting with this")
29
+ p.add_argument("--out", help="write JSON here instead of stdout")
30
+ args = p.parse_args()
31
+
32
+ repo = args.repo
33
+ if not repo:
34
+ repo = gh_json(["repo", "view", "--json", "nameWithOwner"])["nameWithOwner"]
35
+
36
+ raw = gh_json(["api", f"repos/{repo}/pulls/{args.pr}/comments", "--paginate"])
37
+
38
+ out = []
39
+ for c in raw:
40
+ path = c.get("path") or ""
41
+ if args.path_prefix and not path.startswith(args.path_prefix):
42
+ continue
43
+ out.append(
44
+ {
45
+ "id": str(c["id"]),
46
+ "url": c.get("html_url"),
47
+ "path": path,
48
+ "line": c.get("line") or c.get("original_line"),
49
+ "body": (c.get("body") or "").strip(),
50
+ "in_reply_to": str(c["in_reply_to_id"]) if c.get("in_reply_to_id") else None,
51
+ }
52
+ )
53
+
54
+ out.sort(key=lambda c: (c["path"], c["line"] or 0))
55
+ text = json.dumps(out, indent=2)
56
+ if args.out:
57
+ with open(args.out, "w") as f:
58
+ f.write(text + "\n")
59
+ print(f"{len(out)} comments -> {args.out}", file=sys.stderr)
60
+ else:
61
+ print(text)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python3
2
+ """Render an audit findings JSON into <base>.md and <base>.json.
3
+
4
+ Counts and the by-file breakdown are derived here rather than written by hand, so the
5
+ summary can never disagree with the detail below it.
6
+
7
+ Usage:
8
+ python3 render_report.py --findings findings.json --out-base /path/FRONTEND_AUDIT
9
+ """
10
+ import argparse
11
+ import json
12
+ from collections import Counter, defaultdict
13
+ from datetime import datetime, timezone
14
+
15
+ ICON = {"pass": "✅", "fail": "❌", "unclear": "❓"}
16
+ ORDER = {"fail": 0, "unclear": 1, "pass": 2}
17
+
18
+
19
+ def main():
20
+ p = argparse.ArgumentParser()
21
+ p.add_argument("--findings", required=True)
22
+ p.add_argument("--out-base", required=True)
23
+ args = p.parse_args()
24
+
25
+ with open(args.findings) as f:
26
+ doc = json.load(f)
27
+
28
+ meta = doc.get("audit", {})
29
+ findings = doc["findings"]
30
+
31
+ for i, f_ in enumerate(findings):
32
+ st = f_.get("status")
33
+ if st not in ICON:
34
+ raise SystemExit(f"finding {i} ({f_.get('comment_id')}): bad status {st!r}")
35
+
36
+ counts = Counter(f_["status"] for f_ in findings)
37
+ summary = {
38
+ "pass": counts.get("pass", 0),
39
+ "fail": counts.get("fail", 0),
40
+ "unclear": counts.get("unclear", 0),
41
+ "total": len(findings),
42
+ "distinct_findings": len([f_ for f_ in findings if not f_.get("duplicate_of")]),
43
+ }
44
+ meta.setdefault("generated_at", datetime.now(timezone.utc).isoformat(timespec="seconds"))
45
+
46
+ out = {"audit": meta, "summary": summary, "findings": findings}
47
+ with open(args.out_base + ".json", "w") as f:
48
+ json.dump(out, f, indent=2)
49
+ f.write("\n")
50
+
51
+ by_file = defaultdict(list)
52
+ for f_ in findings:
53
+ for path in f_.get("target_files") or ["(unresolved)"]:
54
+ by_file[path].append(f_)
55
+
56
+ L = []
57
+ L.append("# Frontend code audit")
58
+ L.append("")
59
+ L.append("Review-only pass. No source files were modified by this audit.")
60
+ L.append("")
61
+ for key, label in (
62
+ ("source", "Comments"),
63
+ ("scope", "Scope"),
64
+ ("target", "Code audited"),
65
+ ("commit", "At commit"),
66
+ ("generated_at", "Generated"),
67
+ ):
68
+ if meta.get(key):
69
+ L.append(f"- **{label}:** {meta[key]}")
70
+ L.append("")
71
+ L.append("## Summary")
72
+ L.append("")
73
+ L.append("| Status | Count |")
74
+ L.append("|---|---|")
75
+ L.append(f"| ✅ pass | {summary['pass']} |")
76
+ L.append(f"| ❌ fail | {summary['fail']} |")
77
+ L.append(f"| ❓ unclear | {summary['unclear']} |")
78
+ L.append(f"| **total comments** | **{summary['total']}** |")
79
+ if summary["distinct_findings"] != summary["total"]:
80
+ L.append(f"| distinct findings (duplicates collapsed) | {summary['distinct_findings']} |")
81
+ L.append("")
82
+
83
+ L.append("## By file")
84
+ L.append("")
85
+ L.append("| File | ✅ | ❌ | ❓ |")
86
+ L.append("|---|---|---|---|")
87
+ for path in sorted(by_file):
88
+ c = Counter(f_["status"] for f_ in by_file[path])
89
+ L.append(f"| `{path}` | {c.get('pass',0)} | {c.get('fail',0)} | {c.get('unclear',0)} |")
90
+ L.append("")
91
+
92
+ L.append("## Findings")
93
+ L.append("")
94
+ L.append("| Status | Comment | Location | Files |")
95
+ L.append("|---|---|---|---|")
96
+ ordered = sorted(findings, key=lambda f_: (ORDER[f_["status"]], f_.get("comment_id") or ""))
97
+ for f_ in ordered:
98
+ files = ", ".join(f"`{x}`" for x in (f_.get("target_files") or [])) or "—"
99
+ dup = " *(dup)*" if f_.get("duplicate_of") else ""
100
+ L.append(
101
+ f"| {ICON[f_['status']]} {f_['status']} | `{f_.get('comment_id','')}`{dup} "
102
+ f"| {f_.get('comment_location','—')} | {files} |"
103
+ )
104
+ L.append("")
105
+
106
+ for f_ in ordered:
107
+ cid = f_.get("comment_id", "")
108
+ url = f_.get("comment_url")
109
+ head = f"### {ICON[f_['status']]} {f_['status'].upper()} — comment "
110
+ head += f"[{cid}]({url})" if url else f"`{cid}`"
111
+ L.append(head)
112
+ L.append("")
113
+ loc = f_.get("comment_location")
114
+ if loc:
115
+ L.append(f"- **Anchored at:** `{loc}`")
116
+ if f_.get("target_files"):
117
+ L.append(f"- **Code audited:** {', '.join('`' + x + '`' for x in f_['target_files'])}")
118
+ if f_.get("duplicate_of"):
119
+ L.append(f"- **Duplicate of:** `{f_['duplicate_of']}` — same finding, restated.")
120
+ L.append("")
121
+ L.append("> " + (f_.get("comment", "").strip().replace("\n", "\n> ") or "_(empty)_"))
122
+ L.append("")
123
+ L.append(f"**Evidence.** {f_.get('evidence', '_none given_')}")
124
+ L.append("")
125
+ if f_.get("remaining"):
126
+ L.append(f"**Still outstanding.** {f_['remaining']}")
127
+ L.append("")
128
+
129
+ with open(args.out_base + ".md", "w") as f:
130
+ f.write("\n".join(L))
131
+
132
+ print(
133
+ f"{summary['total']} comments: {summary['pass']} pass / {summary['fail']} fail / "
134
+ f"{summary['unclear']} unclear -> {args.out_base}.md, {args.out_base}.json"
135
+ )
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()