external-review 1.0.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 +21 -0
- package/README.md +311 -0
- package/bin/external-review.mjs +582 -0
- package/docs/PLAYBOOK.md +234 -0
- package/docs/PRIVACY.md +182 -0
- package/examples/prompts/data-integrity.txt +49 -0
- package/examples/prompts/second-opinion.txt +41 -0
- package/examples/prompts/user-journey.txt +47 -0
- package/package.json +44 -0
- package/skills/external-review/SKILL.md +198 -0
package/docs/PLAYBOOK.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Playbook
|
|
2
|
+
|
|
3
|
+
Everything below came out of running this against a production app and being
|
|
4
|
+
wrong in interesting ways. It is ordered by how much time each item saves.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. The signature
|
|
9
|
+
|
|
10
|
+
Every real defect a second model found shared one shape:
|
|
11
|
+
|
|
12
|
+
> **The codebase already argued the correct rule somewhere else, and had not
|
|
13
|
+
> applied it here.**
|
|
14
|
+
|
|
15
|
+
Concretely, from one codebase in one day:
|
|
16
|
+
|
|
17
|
+
| The rule, stated | Where it was not applied |
|
|
18
|
+
|---|---|
|
|
19
|
+
| "a non-bool value reads as false instead of throwing and **dropping the whole bike**" | Two hard casts, two lines above |
|
|
20
|
+
| The sync path gated a one-time calibration on an adoption stamp | The link path gated it on "is the id null", so every reconnect recalibrated |
|
|
21
|
+
| A helper whose own doc says "never silently substitute another value" | Seven call sites that substituted zero |
|
|
22
|
+
| "back up the live document, but **only if it is good**" — argued in full | Two sibling stores that backed it up unconditionally, promoting corruption over the last good copy |
|
|
23
|
+
| "a bad tier entry costs that tier's line, never the record" | The object that *owns* the tiers hard-cast and lost the record |
|
|
24
|
+
|
|
25
|
+
Put this sentence in every review prompt. It converts an unbounded search into a
|
|
26
|
+
bounded one: enumerate the codebase's stated intentions, then check each one's
|
|
27
|
+
siblings.
|
|
28
|
+
|
|
29
|
+
**How to hunt it yourself:** grep for comments that explain *why* — "must",
|
|
30
|
+
"never", "otherwise", "this is why". Each one is a rule. For each rule, find the
|
|
31
|
+
other places of that shape and check them.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Four ways a vacuity check lies
|
|
36
|
+
|
|
37
|
+
You fixed something, wrote a regression test, and it passes. Before you believe
|
|
38
|
+
it, revert the fix and confirm the test fails. That check itself fails in four
|
|
39
|
+
ways, all observed:
|
|
40
|
+
|
|
41
|
+
### The revert did not apply
|
|
42
|
+
A scripted `replace` whose pattern did not match silently no-ops. The run
|
|
43
|
+
reports "all passed", which reads as *the test is vacuous* when in fact the fix
|
|
44
|
+
was never removed.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
old = "..."
|
|
48
|
+
assert old in source, "REVERT DID NOT APPLY" # ← this line
|
|
49
|
+
source = source.replace(old, new, 1)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### It applied in the wrong place
|
|
53
|
+
A blind replace hitting the first of four identical lines in a large file. You
|
|
54
|
+
reverted something, just not the thing under test. Assert on a unique enough
|
|
55
|
+
anchor, or assert the resulting line count changed as expected.
|
|
56
|
+
|
|
57
|
+
### The run failed for the wrong reason
|
|
58
|
+
A compile error or a missing fixture is not your assertion failing. Check the
|
|
59
|
+
failure output names your test and your expectation — not the compiler.
|
|
60
|
+
|
|
61
|
+
### The test never reached the defect
|
|
62
|
+
The most common and the most convincing. The fixture looked right, the code path
|
|
63
|
+
was never entered, and the assertion was true for an unrelated reason.
|
|
64
|
+
|
|
65
|
+
Two real examples:
|
|
66
|
+
|
|
67
|
+
- A test for a "cancelled animation leaves the highlight stale" bug asserted on
|
|
68
|
+
the **page position**, which moves whether or not the bug is present. It had to
|
|
69
|
+
assert on the **highlight**.
|
|
70
|
+
- A test for "this value must not be shifted" set up a case where *no* shift
|
|
71
|
+
happened at all, so the code path under test was skipped entirely and the value
|
|
72
|
+
was untouched either way.
|
|
73
|
+
|
|
74
|
+
**Guard:** make the precondition the test's first assertion.
|
|
75
|
+
|
|
76
|
+
```dart
|
|
77
|
+
expect(pinBefore, closeTo(30, 0.01),
|
|
78
|
+
reason: 'precondition: the pin is live, or this test proves nothing');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Know its limit: this catches a fixture that never reaches the defect. It does
|
|
82
|
+
**not** catch a wrong *second* step — a re-run at the same timestamp that takes a
|
|
83
|
+
different branch, say. Reverting the fix is the half that always works.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 2b. A secret scanner's test fixtures look exactly like secrets
|
|
88
|
+
|
|
89
|
+
Worth knowing before you write them. The first commit of this repo's own scanner
|
|
90
|
+
tests was rejected by GitHub push protection: the fixture contained a
|
|
91
|
+
Stripe-key-shaped string, which is precisely what the test needs to contain.
|
|
92
|
+
|
|
93
|
+
The wrong fix is the allowlist link in the rejection message. It teaches the
|
|
94
|
+
repository to ignore that class of finding, and the next one might be real.
|
|
95
|
+
|
|
96
|
+
The right fix is to assemble the literal at runtime so it never appears in the
|
|
97
|
+
source:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
const stripe = ['sk', 'live', '51H8xQ2eZvKYlo2Cabcdefghijklmnop'].join('_');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Same reasoning as §3 below: a fixture that trains a safety mechanism to stay
|
|
104
|
+
quiet is worse than no fixture.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 3. Your existing tests may pin the bug as correct
|
|
109
|
+
|
|
110
|
+
Two separate codebases had a test whose "corrupt record" fixture was really a
|
|
111
|
+
*recoverable* record with one mistyped field:
|
|
112
|
+
|
|
113
|
+
```dart
|
|
114
|
+
{'id': 'bad', 'year': 'not-a-number'} // ← throws, so the record is skipped
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The test asserted that this record gets **dropped**. It was green for months. It
|
|
118
|
+
was asserting that recoverable rider data gets deleted.
|
|
119
|
+
|
|
120
|
+
When you make decoding more tolerant, tests like this fail — and the reflex is to
|
|
121
|
+
"fix the test". Stop and ask which behaviour is right. Then repoint the fixture
|
|
122
|
+
at something genuinely undecodable (no id at all), so the skip path stays covered,
|
|
123
|
+
and add a test for the opposite direction.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 4. Assert intact, not present
|
|
128
|
+
|
|
129
|
+
`expect(bike.components, hasLength(1))` passes for a component that survived
|
|
130
|
+
decode as a husk with every field defaulted. That is the same data loss in a
|
|
131
|
+
different shape.
|
|
132
|
+
|
|
133
|
+
Assert the *irreplaceable* field — the baseline, the service date, the price.
|
|
134
|
+
And where an autosave can make a loss permanent, drive the full round trip:
|
|
135
|
+
|
|
136
|
+
```dart
|
|
137
|
+
final once = Model.fromJson(damaged);
|
|
138
|
+
final twice = Model.fromJson(once.toJson());
|
|
139
|
+
expect(twice.components.single.serviceKmBaseline, 300);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 5. Prompt shapes that worked
|
|
145
|
+
|
|
146
|
+
Scope to **one subsystem per pass**, and always include:
|
|
147
|
+
|
|
148
|
+
- Point at the invariant docs by name; say a violation is usually a real bug.
|
|
149
|
+
- The signature from §1, verbatim.
|
|
150
|
+
- What to EXCLUDE, so passes do not overlap.
|
|
151
|
+
- Required output per finding: severity, `file:line`, a concrete failure sequence
|
|
152
|
+
**with specific values**, why existing tests miss it, the minimal fix.
|
|
153
|
+
- "Verify each claim against the code and quote the lines."
|
|
154
|
+
- "No style, naming or refactor opinions."
|
|
155
|
+
- "If a subsystem is sound, say so in one line rather than padding."
|
|
156
|
+
- "A confident wrong finding is worse than no finding."
|
|
157
|
+
- A cap: "stop at your 10 strongest." Quality tails off badly past that.
|
|
158
|
+
|
|
159
|
+
### Always ask for "HELD UP"
|
|
160
|
+
|
|
161
|
+
A section listing what it checked and found **correct**, one line each.
|
|
162
|
+
|
|
163
|
+
This is your calibration instrument. If it independently re-derives things you
|
|
164
|
+
know to be true, the findings are worth acting on. If it asserts something you
|
|
165
|
+
know is false, discount the whole pass. On one run the held-up section
|
|
166
|
+
independently traced a compliance firewall and confirmed no restricted data
|
|
167
|
+
reached an AI path — which was worth more than any single finding.
|
|
168
|
+
|
|
169
|
+
### Ask for "POLISH" on user-facing areas
|
|
170
|
+
|
|
171
|
+
Up to five concrete UX gaps with `file:line`: a state with no affordance, an
|
|
172
|
+
action with no feedback, a number with no unit, an empty state that says nothing.
|
|
173
|
+
Different question, different answers.
|
|
174
|
+
|
|
175
|
+
### Scope by journey, not by directory
|
|
176
|
+
|
|
177
|
+
"The first-run path: install → connect → first item → its photo" finds what
|
|
178
|
+
"review `lib/features/`" does not. Directory-shaped passes miss the handoffs
|
|
179
|
+
between modules, which is where bugs live. Feature-shaped passes cross them by
|
|
180
|
+
construction.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 6. Reading findings
|
|
185
|
+
|
|
186
|
+
Real outcomes from real passes:
|
|
187
|
+
|
|
188
|
+
- **Refuted by reading the code.** The finding described a fall-through that did
|
|
189
|
+
not exist. Cost: ten minutes. Worth it.
|
|
190
|
+
- **Wrong premise, real bug.** It named the wrong function as the trigger. The
|
|
191
|
+
bug was real via a different path. **Read past the premise.**
|
|
192
|
+
- **First half wrong, second half a P0.** It claimed a fix had never been applied
|
|
193
|
+
(it had — the model read a stale copy), and the *rest* of the same finding
|
|
194
|
+
described a genuine data-loss path nobody had noticed.
|
|
195
|
+
- **Stale line numbers are a tell** that a finding was written against an old
|
|
196
|
+
tree and never re-checked.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 7. Practicalities
|
|
201
|
+
|
|
202
|
+
- **Sync to a dated directory**, never to a long-lived checkout — it may carry
|
|
203
|
+
another session's uncommitted work, and you will review the wrong tree.
|
|
204
|
+
- **A pass will wander** into sibling checkouts if they exist on the machine.
|
|
205
|
+
Put "work only inside the current working directory" in the prompt, and check
|
|
206
|
+
the first lines of the output to see which tree it actually read.
|
|
207
|
+
- **Check the working directory of every pass you launch, not just the first.**
|
|
208
|
+
This bit hard. Launching two in one shell line:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
cd ~/review-dir && nohup runner … & nohup runner … & # WRONG
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The `&&` binds to the FIRST command only. The second runs in your home
|
|
215
|
+
directory, finds a stale long-lived checkout next door, and reviews *that* —
|
|
216
|
+
producing findings against code you fixed hours ago, with no error anywhere.
|
|
217
|
+
The tell is in stderr: a correct pass reads `lib/foo.dart`, a wandering one
|
|
218
|
+
reads `myproject/lib/foo.dart`. Wrap each launch in its own subshell:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
(cd ~/review-dir && nohup runner … &)
|
|
222
|
+
(cd ~/review-dir && nohup runner … &)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Related: never sync to a long-lived checkout in the first place. If the only
|
|
226
|
+
copy on the machine is the dated one you just made, a wandering pass has
|
|
227
|
+
nowhere stale to wander to.
|
|
228
|
+
- **Run two or three concurrently** on different scopes; a pass is mostly waiting
|
|
229
|
+
on an API.
|
|
230
|
+
- **`stderr` is the liveness signal** — it shows which files are being read.
|
|
231
|
+
- **Reviewing your own session's recent work is the highest-value use.** That is
|
|
232
|
+
exactly where your blind spots are. Several of the worst defects found this way
|
|
233
|
+
were in code written hours earlier, including a comment that promised the exact
|
|
234
|
+
behaviour its own code failed to deliver.
|
package/docs/PRIVACY.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# What leaves your machine
|
|
2
|
+
|
|
3
|
+
A review means sending source code to a third-party API. That is the deal, and
|
|
4
|
+
it should be an informed one rather than a hopeful one.
|
|
5
|
+
|
|
6
|
+
## What is sent
|
|
7
|
+
|
|
8
|
+
**Your source files, in the clear, over TLS, to whichever provider serves the
|
|
9
|
+
model you chose.** The runner reads files in the directory you point it at and
|
|
10
|
+
puts their contents in the prompt. There is no filtering, no obfuscation, and no
|
|
11
|
+
way to review code without sending it.
|
|
12
|
+
|
|
13
|
+
Also sent: your prompt, and — for the CLI's own commands — nothing but the model
|
|
14
|
+
id you asked about.
|
|
15
|
+
|
|
16
|
+
## What is not sent
|
|
17
|
+
|
|
18
|
+
- Anything matched by the sync exclusions (see below).
|
|
19
|
+
- Your API key never leaves your machine except as an `Authorization` header to
|
|
20
|
+
OpenRouter, which is what it is for.
|
|
21
|
+
- This tool has no telemetry. It makes no network calls except to
|
|
22
|
+
`openrouter.ai` and, for `sync`, to the host you name.
|
|
23
|
+
|
|
24
|
+
## Deciding whether that is acceptable
|
|
25
|
+
|
|
26
|
+
Run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
external-review providers <model-id>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For every machine that may serve your request, this prints:
|
|
33
|
+
|
|
34
|
+
- the operator's **headquarters**,
|
|
35
|
+
- its published **datacenter regions**,
|
|
36
|
+
- links to its **actual privacy policy and terms**.
|
|
37
|
+
|
|
38
|
+
These are facts published by OpenRouter, not a rating. This tool does not tell
|
|
39
|
+
you which providers are trustworthy, because that is not a technical question
|
|
40
|
+
and the answer differs per user. What it does is make sure you are not guessing.
|
|
41
|
+
|
|
42
|
+
Things worth knowing while you decide:
|
|
43
|
+
|
|
44
|
+
**A model is routed per request.** If it lists four endpoints, any of the four
|
|
45
|
+
may serve a given call. To pin one, use OpenRouter's `provider.order` routing
|
|
46
|
+
or pick a model with a single endpoint.
|
|
47
|
+
|
|
48
|
+
**"Free" is a business model, and on OpenRouter the price is your data.** This
|
|
49
|
+
is stronger than a caveat and it is the single most important thing on this
|
|
50
|
+
page.
|
|
51
|
+
|
|
52
|
+
To use free models at all you must enable, in Settings → Privacy:
|
|
53
|
+
|
|
54
|
+
- *"Enable free endpoints that may train on inputs"*
|
|
55
|
+
- *"Enable free endpoints that may publish prompts"*
|
|
56
|
+
|
|
57
|
+
Without them, free models return
|
|
58
|
+
`404: No endpoints available matching your guardrail restrictions and data
|
|
59
|
+
policy`. So if free models work for you, those toggles are on, and **the code
|
|
60
|
+
you send is permitted to be trained on and published.**
|
|
61
|
+
|
|
62
|
+
**Stealth models are the same deal, more so.** A "stealth" or "cloaked" model is
|
|
63
|
+
an unreleased model shipped under an anonymous name to gather real-world usage.
|
|
64
|
+
That is the entire point: prompts and completions are logged and used to improve
|
|
65
|
+
it. They are attractive for review work — often frontier-class, large context,
|
|
66
|
+
free — and they are the least private option on the menu.
|
|
67
|
+
|
|
68
|
+
**Zero Data Retention is the actual control.** ZDR means a provider will not
|
|
69
|
+
store your data for any period, and cannot train on it. Enable it three ways:
|
|
70
|
+
|
|
71
|
+
- account-wide, in privacy settings, globally or per model group;
|
|
72
|
+
- per API key, as a guardrail, which is how you give a teammate a key that
|
|
73
|
+
cannot leak;
|
|
74
|
+
- per request: `"zdr": true` in the provider preferences.
|
|
75
|
+
|
|
76
|
+
The cost is real: ZDR **removes endpoints**, including most or all free ones,
|
|
77
|
+
and it does not cover plugins such as web search, which carry their own
|
|
78
|
+
policies.
|
|
79
|
+
|
|
80
|
+
So the decision is simple to state, if not to make:
|
|
81
|
+
|
|
82
|
+
| If the code is… | Use |
|
|
83
|
+
|---|---|
|
|
84
|
+
| open source, or you do not mind it training a model | free endpoints — that is the trade |
|
|
85
|
+
| private but not contractually restricted | a paid endpoint whose terms you have read |
|
|
86
|
+
| under NDA, customer contract, or a residency rule | ZDR, or a model you host yourself |
|
|
87
|
+
|
|
88
|
+
Do not let "it is only a code review" carry the decision. A review prompt
|
|
89
|
+
contains more of your source, in one place, than almost anything else you send
|
|
90
|
+
anywhere.
|
|
91
|
+
|
|
92
|
+
**Self-hosting removes the question.** A local model via Ollama or vLLM, or a
|
|
93
|
+
model on infrastructure you control, sends nothing anywhere. It costs you
|
|
94
|
+
capability, and for reviewing a subsystem that tradeoff is often fine.
|
|
95
|
+
|
|
96
|
+
**Your obligations are yours.** If the code is under an NDA, a customer contract,
|
|
97
|
+
export control, or a regulation with data-residency requirements, none of the
|
|
98
|
+
above substitutes for checking. Ask, do not assume.
|
|
99
|
+
|
|
100
|
+
## Secrets
|
|
101
|
+
|
|
102
|
+
The most likely accident is not the model reading your code — you meant that.
|
|
103
|
+
It is a credential riding along inside it.
|
|
104
|
+
|
|
105
|
+
`external-review sync` excludes the usual shapes:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
.env .env.* *.pem *.der *.key *.jks *.keystore *.p12
|
|
109
|
+
*.mobileprovision id_rsa* *.crt
|
|
110
|
+
secrets.* *.secrets.* credentials.* service-account*.json
|
|
111
|
+
.git/ node_modules/ build/ dist/ target/ .venv/ __pycache__/
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then it **verifies over SSH that those paths are absent from the copy**, because
|
|
115
|
+
an exclusion pattern that silently failed to match is the entire risk, and
|
|
116
|
+
`rsync` exits 0 either way.
|
|
117
|
+
|
|
118
|
+
**The default list cannot know about your repo.** Real example: a project's list
|
|
119
|
+
covered `*.jks` and `strava.config.json` but not `garmin/developer_key.pem` — a
|
|
120
|
+
code-signing key that would let someone publish releases as that developer. It
|
|
121
|
+
was only caught because a second person reviewed the list.
|
|
122
|
+
|
|
123
|
+
So: before the first sync, list your own credential paths and add them with
|
|
124
|
+
`--exclude`. Then read the verification output rather than assuming it passed.
|
|
125
|
+
|
|
126
|
+
If a secret does reach a review copy, treat it as disclosed: delete the copy,
|
|
127
|
+
rotate the credential, and do not rely on the provider's retention policy to
|
|
128
|
+
make it un-disclosed.
|
|
129
|
+
|
|
130
|
+
## Running the review on a VM or VPS
|
|
131
|
+
|
|
132
|
+
Worth doing. Also worth being precise about, because the reason people usually
|
|
133
|
+
give for it is the one thing it does not help with.
|
|
134
|
+
|
|
135
|
+
**It does NOT reduce what the model sees.** The prompt is byte-for-byte the
|
|
136
|
+
same whether the runner executes on your laptop or on a box in a datacenter.
|
|
137
|
+
If the endpoint may train on your code, it may train on it either way. Anyone
|
|
138
|
+
telling you a VM makes the *disclosure* safer is confusing two different risks.
|
|
139
|
+
|
|
140
|
+
**What it genuinely does:**
|
|
141
|
+
|
|
142
|
+
**1. It contains the runner, which is the under-discussed risk.** A review
|
|
143
|
+
runner is a third-party binary executing an agentic loop with filesystem access
|
|
144
|
+
and, usually, shell access. It reads whatever it decides it needs. On your
|
|
145
|
+
laptop that neighbourhood includes `~/.ssh`, your cloud credentials, your
|
|
146
|
+
browser profile, your password-manager exports, your other clients' repositories
|
|
147
|
+
and every uncommitted branch you have. On a throwaway VM it includes a dated
|
|
148
|
+
copy of one project.
|
|
149
|
+
|
|
150
|
+
That is not a hypothetical about malice — it is about scope. Agentic tools
|
|
151
|
+
wander. On a real run, a pass launched from the wrong working directory found a
|
|
152
|
+
sibling checkout and reviewed *that* instead, unprompted and without error. It
|
|
153
|
+
had no reason to and no instruction to. It just could.
|
|
154
|
+
|
|
155
|
+
**2. It keeps your API key off your daily machine.** The key lives on the box
|
|
156
|
+
that uses it. A key on a laptop is a key in a laptop backup.
|
|
157
|
+
|
|
158
|
+
**3. It forces a clean, dated, minimal copy.** You review exactly what you
|
|
159
|
+
synced — not your working tree with its uncommitted experiments, its `.env.local`
|
|
160
|
+
and its half-finished spike branch. This is also the only way `scan` and the
|
|
161
|
+
exclusion verification can mean anything, because they run against a snapshot
|
|
162
|
+
rather than a moving target.
|
|
163
|
+
|
|
164
|
+
**4. It makes the blast radius disposable.** If something goes wrong — a secret
|
|
165
|
+
synced by mistake, a runner that misbehaves — you destroy the VM. You cannot
|
|
166
|
+
destroy your laptop.
|
|
167
|
+
|
|
168
|
+
**How to set it up sensibly**
|
|
169
|
+
|
|
170
|
+
- A cheap VPS or a local VM is fine. It needs a shell, `rsync` and the runner;
|
|
171
|
+
it does not need to be fast, because the work happens at the API.
|
|
172
|
+
- Give it **no standing credentials** beyond the model API key. No cloud
|
|
173
|
+
provider keys, no deploy keys, no production access.
|
|
174
|
+
- Sync to a **dated directory per review**, never to a long-lived checkout.
|
|
175
|
+
Delete it afterwards. A stale checkout on the review box is how a pass ends up
|
|
176
|
+
reviewing code you fixed hours ago and reporting it as broken.
|
|
177
|
+
- Do not develop on it. The moment it becomes a second workstation it has the
|
|
178
|
+
same neighbourhood problem as the first one.
|
|
179
|
+
|
|
180
|
+
**When it is not worth it:** reviewing open-source code you would publish
|
|
181
|
+
anyway. The runner-containment argument still applies, but the stakes are low
|
|
182
|
+
enough that a local run in a clean checkout is a reasonable trade.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
HARD CONSTRAINT: work ONLY inside the current working directory. Never read or
|
|
2
|
+
reference anything outside it. If a file you want is not here, reason from what
|
|
3
|
+
is and say what you could not check.
|
|
4
|
+
|
|
5
|
+
You are a senior engineer reviewing this codebase for defects that LOSE, CORRUPT
|
|
6
|
+
or SILENTLY MISREPORT the user's data. Read-only: report, do not edit files.
|
|
7
|
+
|
|
8
|
+
Read the project's instruction files first (CLAUDE.md, AGENTS.md, README,
|
|
9
|
+
CONTRIBUTING - whichever exist). They state this codebase's invariants, and a
|
|
10
|
+
violation of one is usually a real bug.
|
|
11
|
+
|
|
12
|
+
THE HIGHEST-YIELD THING TO HUNT: a rule stated and argued in ONE place, and
|
|
13
|
+
silently not applied in its sibling case. Find a comment that explains WHY
|
|
14
|
+
something must be done a certain way - "must", "never", "otherwise", "this is
|
|
15
|
+
why" - then check every OTHER site of that same shape. In this codebase's
|
|
16
|
+
history that pattern alone accounts for most of the real data-loss bugs.
|
|
17
|
+
|
|
18
|
+
SCOPE: <fill in: the specific directories or subsystem>
|
|
19
|
+
|
|
20
|
+
EXCLUDE (already audited, do not spend budget there): <fill in, or "nothing">
|
|
21
|
+
|
|
22
|
+
WHAT TO HUNT, in priority order:
|
|
23
|
+
1. PERSISTENCE. Any path where a record is dropped, truncated, or overwritten by
|
|
24
|
+
a worse copy. Decoding that throws and takes a whole record with it when one
|
|
25
|
+
field was recoverable. A backup slot that can be filled with a corrupt value.
|
|
26
|
+
A write that reports success it did not achieve.
|
|
27
|
+
2. MIGRATIONS AND SCHEMA. Anything that must be idempotent across
|
|
28
|
+
decode -> encode -> decode and is not. A key that inflates or drops on a
|
|
29
|
+
re-read. Tolerance in one direction and a hard cast in the other.
|
|
30
|
+
3. CONCURRENCY. A read-modify-write across an await. Two writers to one record
|
|
31
|
+
with no ordering. A cached snapshot written back over a newer value.
|
|
32
|
+
4. USER INPUT. A typed value that is silently substituted, dropped, or
|
|
33
|
+
reinterpreted - especially numbers, dates, and anything locale-dependent.
|
|
34
|
+
5. DESTRUCTIVE ACTIONS. Delete, replace, reset, disconnect: is each confirmable
|
|
35
|
+
or reversible, does any remove more than asked, does undo restore what was
|
|
36
|
+
actually removed?
|
|
37
|
+
|
|
38
|
+
For EVERY finding: SEVERITY (P0 data loss / P1 wrong data shown / P2 other),
|
|
39
|
+
file:line, a CONCRETE failure sequence with specific starting state and specific
|
|
40
|
+
values, why existing tests miss it, and the minimal fix.
|
|
41
|
+
|
|
42
|
+
Then a section "HELD UP": what you checked and found genuinely correct, one line
|
|
43
|
+
each. Be specific - this is how your findings get calibrated, and a held-up list
|
|
44
|
+
that independently re-derives known-good facts is what makes the rest credible.
|
|
45
|
+
|
|
46
|
+
Rules: verify each claim against the code and quote the lines. No style, naming
|
|
47
|
+
or refactor opinions. If an area is sound, say so in one line rather than
|
|
48
|
+
padding. A confident wrong finding is worse than no finding. Rank by severity,
|
|
49
|
+
stop at your 10 strongest.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
HARD CONSTRAINT: work ONLY inside the current working directory.
|
|
2
|
+
|
|
3
|
+
You are reviewing code that another AI session wrote or changed VERY RECENTLY,
|
|
4
|
+
and you are the second opinion on its own work. This is the highest-value use of
|
|
5
|
+
an independent model: it is precisely where the first one's blind spots are.
|
|
6
|
+
|
|
7
|
+
Read the project's instruction files first.
|
|
8
|
+
|
|
9
|
+
THE CHANGES: <fill in - paste the diff, name the commits, or list the files>
|
|
10
|
+
|
|
11
|
+
WHAT THE AUTHOR BELIEVED: <fill in - paste the commit messages or the summary it
|
|
12
|
+
gave. You are checking whether the code does what these claim.>
|
|
13
|
+
|
|
14
|
+
BE ADVERSARIAL ABOUT THE NEW CODE SPECIFICALLY. In particular:
|
|
15
|
+
|
|
16
|
+
1. DOES IT DO WHAT ITS OWN COMMENTS SAY? A comment promising behaviour the code
|
|
17
|
+
does not deliver is this codebase's most common defect shape, and freshly
|
|
18
|
+
written code is where it appears. Check each claim against the lines under it.
|
|
19
|
+
2. SECOND-ORDER EFFECTS. A fix that changes when something runs, what survives a
|
|
20
|
+
failure, or what a later step observes. Ask specifically: did this change
|
|
21
|
+
remove an accident that was hiding a different bug? Did it make a previously
|
|
22
|
+
unreachable path reachable?
|
|
23
|
+
3. THE SIBLING SITES. If the fix applied a rule in one place, find every other
|
|
24
|
+
place of that shape and check whether it was applied there too. A partial
|
|
25
|
+
sweep is the normal outcome and it looks identical to a complete one.
|
|
26
|
+
4. THE TESTS THAT SHIPPED WITH IT. For each new test, ask: would this fail if the
|
|
27
|
+
fix were reverted? Does the fixture actually reach the code path? Does it
|
|
28
|
+
assert the record is INTACT or merely present? Does an existing fixture now
|
|
29
|
+
pin the old broken behaviour as correct?
|
|
30
|
+
5. WHAT THE CHANGE DID NOT COVER. The case the author did not think of - the
|
|
31
|
+
other axis, the other branch, the null, the concurrent writer.
|
|
32
|
+
|
|
33
|
+
For EVERY finding: SEVERITY, file:line, a CONCRETE failure sequence, why the new
|
|
34
|
+
tests miss it, and the minimal fix.
|
|
35
|
+
|
|
36
|
+
Then a section "HELD UP": which of the author's claims you verified as true, one
|
|
37
|
+
line each. Be specific about what you checked - this is what makes the findings
|
|
38
|
+
worth acting on.
|
|
39
|
+
|
|
40
|
+
Rules: verify each claim against the code and quote the lines. No style opinions.
|
|
41
|
+
A confident wrong finding is worse than no finding. Stop at your 10 strongest.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
HARD CONSTRAINT: work ONLY inside the current working directory. Never read or
|
|
2
|
+
reference anything outside it.
|
|
3
|
+
|
|
4
|
+
You are a senior engineer reviewing ONE USER JOURNEY end to end. Scope by what a
|
|
5
|
+
person actually does, not by directory - the handoffs between modules are where
|
|
6
|
+
the bugs are, and a directory-shaped review never crosses them.
|
|
7
|
+
|
|
8
|
+
Read the project's instruction files first. They state the invariants.
|
|
9
|
+
|
|
10
|
+
THE JOURNEY: <fill in, e.g. "fresh install -> sign in -> create the first item
|
|
11
|
+
-> attach a photo to it -> see it in the list">
|
|
12
|
+
|
|
13
|
+
Follow it through every file it touches, in order, including the seams between
|
|
14
|
+
them.
|
|
15
|
+
|
|
16
|
+
Read the project's instruction files first, and hunt above all for: a rule
|
|
17
|
+
stated and argued in ONE place and silently not applied in its sibling case.
|
|
18
|
+
|
|
19
|
+
WHAT TO HUNT:
|
|
20
|
+
1. ABANDONMENT. What is left behind when the user backs out halfway, loses
|
|
21
|
+
connectivity, or kills the app mid-step? A half-created record, an orphaned
|
|
22
|
+
file, a one-shot flag consumed before the thing it guards actually happened,
|
|
23
|
+
a credential stored with nothing to use it on.
|
|
24
|
+
2. LOST INPUT. Any value typed or picked that can be silently discarded - a
|
|
25
|
+
sheet dismissed mid-edit, a field never committed on close, an await after
|
|
26
|
+
which the writer is gone.
|
|
27
|
+
3. STATE THAT LIES. A value read once and never refreshed; a summary of a
|
|
28
|
+
heterogeneous list that shows one item's setting while writing to all of
|
|
29
|
+
them; an optimistic update never rolled back on failure.
|
|
30
|
+
4. FABRICATION. Any place a number or status is shown that the system does not
|
|
31
|
+
actually know - a zero rendered where "unknown" is the truth, an estimate
|
|
32
|
+
presented as a measurement, a default substituted for a failed parse.
|
|
33
|
+
5. THE FIRST-RUN SPECIFICS. Anything that behaves differently on a fresh
|
|
34
|
+
install, after a reinstall, or on a device where a platform capability is
|
|
35
|
+
missing entirely.
|
|
36
|
+
|
|
37
|
+
For EVERY finding: SEVERITY, file:line, a CONCRETE sequence of user actions
|
|
38
|
+
leading to a specific wrong outcome, why existing tests miss it, the minimal fix.
|
|
39
|
+
|
|
40
|
+
Then TWO more sections:
|
|
41
|
+
- "HELD UP": what you checked and found correct, one line each.
|
|
42
|
+
- "POLISH": up to 5 concrete UX gaps this journey has - a state with no
|
|
43
|
+
affordance, an action with no feedback, a number with no unit, an empty state
|
|
44
|
+
that says nothing useful. Each with file:line and one sentence of rationale.
|
|
45
|
+
|
|
46
|
+
Rules: verify each claim against the code and quote the lines. No style opinions.
|
|
47
|
+
A confident wrong finding is worse than no finding. Stop at your 10 strongest.
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "external-review",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Review your code with a second, independent model - and know what it costs, where your source goes, and which findings to believe. Ships a Claude Code skill.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"external-review": "bin/external-review.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"skills",
|
|
12
|
+
"docs",
|
|
13
|
+
"examples",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"code-review",
|
|
22
|
+
"ai",
|
|
23
|
+
"claude",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"skill",
|
|
26
|
+
"openrouter",
|
|
27
|
+
"llm",
|
|
28
|
+
"static-analysis",
|
|
29
|
+
"second-opinion",
|
|
30
|
+
"privacy"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/yevgavrikov/claude-external-review.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/yevgavrikov/claude-external-review/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/yevgavrikov/claude-external-review#readme",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "node --test \"test/**/*.test.mjs\""
|
|
43
|
+
}
|
|
44
|
+
}
|