@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.
- package/LICENSE +5 -0
- package/README.md +75 -0
- package/bin/install.js +45 -0
- package/package.json +29 -0
- package/skills/architecture-walkthrough/SKILL.md +223 -0
- package/skills/architecture-walkthrough/references/sections.md +29 -0
- package/skills/architecture-walkthrough/scripts/check_structure.py +200 -0
- package/skills/autotest-webapp-ui/SKILL.md +58 -0
- package/skills/backend-code-review/SKILL.md +386 -0
- package/skills/backend-code-review/references/report-format.md +333 -0
- package/skills/backend-code-review/scripts/list_routes.py +269 -0
- package/skills/backend-code-review/scripts/sweep.py +550 -0
- package/skills/backend-code-review/scripts/verify_citations.py +201 -0
- package/skills/be-brief/SKILL.md +18 -0
- package/skills/clarke-list-excel/SKILL.md +51 -0
- package/skills/clarke-list-excel/references/output-schema.md +125 -0
- package/skills/clarke-list-excel/scripts/clarke_common.py +251 -0
- package/skills/clarke-list-excel/scripts/clarke_extract.py +487 -0
- package/skills/clarke-list-excel/scripts/load_clarke.py +322 -0
- package/skills/clarke-list-excel/scripts/run_all.py +63 -0
- package/skills/datalab-api/SKILL.md +163 -0
- package/skills/datalab-api/references/parameters-and-payload.md +121 -0
- package/skills/datalab-api/references/table-selection.md +35 -0
- package/skills/datalab-api/scripts/datalab_tables.py +365 -0
- package/skills/find-test-seam/SKILL.md +41 -0
- package/skills/frontend-code-review/SKILL.md +247 -0
- package/skills/frontend-code-review-2/SKILL.md +192 -0
- package/skills/frontend-code-review-2/scripts/fetch_pr_comments.py +65 -0
- package/skills/frontend-code-review-2/scripts/render_report.py +139 -0
- package/skills/murtaza-breif/SKILL.md +143 -0
- package/skills/murtaza-breif/scripts/save_brief.py +128 -0
- package/skills/pdf-to-json/SKILL.md +42 -0
- package/skills/pdf-to-json/references/output-schema.md +168 -0
- package/skills/pdf-to-json/scripts/extract_figures.py +319 -0
- package/skills/pdf-to-json/scripts/load_mongo.py +287 -0
- package/skills/pdf-to-json/scripts/pdf_extract.py +1313 -0
- package/skills/record-api-traffic/SKILL.md +434 -0
- package/skills/record-api-traffic/references/reading-recordings.md +224 -0
- package/skills/record-api-traffic/scripts/check-schema.mjs +184 -0
- package/skills/record-api-traffic/scripts/dump-quotation.mjs +67 -0
- package/skills/record-api-traffic/scripts/dump-source-excel.mjs +75 -0
- package/skills/record-api-traffic/scripts/lib/repo.mjs +109 -0
- package/skills/record-api-traffic/scripts/preflight.py +528 -0
- package/skills/record-api-traffic/scripts/record-api-traffic.py +720 -0
- package/skills/refac-wrt-business-goal/SKILL.md +305 -0
- package/skills/refac-wrt-business-goal/references/critic.md +170 -0
- package/skills/system-resource-triage/SKILL.md +180 -0
- package/skills/system-resource-triage/scripts/reap.sh +116 -0
- package/skills/system-resource-triage/scripts/triage.sh +111 -0
- 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()
|