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.
@@ -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.
@@ -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
+ }