@qvac/harness 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/.claude/skills/pr-review/SKILL.md +237 -40
  2. package/dist/harness.bundle +10 -9
  3. package/dist-lib/index.js +58 -58
  4. package/dist-lib/lib/audio-transcode-bare.js +2 -2
  5. package/dist-lib/lib/capabilities/completion.js +30 -30
  6. package/dist-lib/lib/capabilities/download.js +2 -2
  7. package/dist-lib/lib/capabilities/speech-text.js +1 -1
  8. package/dist-lib/lib/capabilities/tool-prompt-log.js +1 -1
  9. package/dist-lib/lib/capabilities/tool-selection.js +2 -2
  10. package/dist-lib/lib/capabilities/tool-turns.js +1 -1
  11. package/dist-lib/lib/capabilities/usage-breakdown.js +1 -1
  12. package/dist-lib/lib/capabilities/voice.js +3 -3
  13. package/dist-lib/lib/connect.js +11 -11
  14. package/dist-lib/lib/context-window.js +1 -1
  15. package/dist-lib/lib/docx/index.js +3 -3
  16. package/dist-lib/lib/hardware/capability-probe.js +3 -3
  17. package/dist-lib/lib/hardware/gpu-probe-addon.js +2 -2
  18. package/dist-lib/lib/hardware/gpu-probe-entry.js +1 -1
  19. package/dist-lib/lib/helpers/markup-stream-filter.js +1 -1
  20. package/dist-lib/lib/helpers/run-outcome.js +1 -1
  21. package/dist-lib/lib/helpers/spawn-path.d.ts +1 -1
  22. package/dist-lib/lib/helpers/thinking.js +2 -2
  23. package/dist-lib/lib/helpers/tool-intent.js +1 -1
  24. package/dist-lib/lib/helpers/tool-label.js +3 -3
  25. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -1
  26. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -1
  27. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -1
  28. package/dist-lib/lib/hooks/built-in/egress.js +2 -2
  29. package/dist-lib/lib/hooks/built-in/index.js +3 -3
  30. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -1
  31. package/dist-lib/lib/hooks/pipeline.js +4 -4
  32. package/dist-lib/lib/hooks/registry.js +1 -1
  33. package/dist-lib/lib/hooks/run.js +1 -1
  34. package/dist-lib/lib/models/model-catalog.js +4 -4
  35. package/dist-lib/lib/models/model-delete.js +1 -1
  36. package/dist-lib/lib/models/model-handle.js +1 -1
  37. package/dist-lib/lib/models/model-list.js +1 -1
  38. package/dist-lib/lib/models/model-pool.js +8 -8
  39. package/dist-lib/lib/models/model-source.js +1 -1
  40. package/dist-lib/lib/models/music-catalog.d.ts +1 -1
  41. package/dist-lib/lib/no-redirect-fetch-bare.js +3 -3
  42. package/dist-lib/lib/no-redirect-fetch-impl.js +1 -1
  43. package/dist-lib/lib/no-redirect-fetch-node.js +2 -2
  44. package/dist-lib/lib/resolution-detector.js +1 -1
  45. package/dist-lib/lib/sdk-loader.js +2 -2
  46. package/dist-lib/lib/sdk-storage.js +1 -1
  47. package/dist-lib/lib/serve.js +10 -10
  48. package/dist-lib/lib/spawn.js +3 -3
  49. package/dist-lib/lib/staging.js +1 -1
  50. package/dist-lib/lib/tools/catalog.js +1 -1
  51. package/dist-lib/lib/tools/create-documents.js +2 -2
  52. package/dist-lib/lib/tools/edit-documents.js +2 -2
  53. package/dist-lib/lib/tools/edit-image.js +2 -2
  54. package/dist-lib/lib/tools/exec/adapters/python/attachments.js +2 -2
  55. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +1 -1
  56. package/dist-lib/lib/tools/exec/adapters/python/loader.js +1 -1
  57. package/dist-lib/lib/tools/exec/adapters/python/memfs.js +2 -2
  58. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +6 -6
  59. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +3 -3
  60. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +5 -5
  61. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +1 -1
  62. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +3 -3
  63. package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +4 -4
  64. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +1 -1
  65. package/dist-lib/lib/tools/exec/obsidian/cli-process.js +2 -2
  66. package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
  67. package/dist-lib/lib/tools/exec/obsidian/command.js +1 -1
  68. package/dist-lib/lib/tools/exec/obsidian/route.js +7 -7
  69. package/dist-lib/lib/tools/exec/obsidian/vault-create.js +2 -2
  70. package/dist-lib/lib/tools/exec/obsidian/vault-fs.js +6 -6
  71. package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +2 -2
  72. package/dist-lib/lib/tools/exec/obsidian/vault-read.js +1 -1
  73. package/dist-lib/lib/tools/exec/obsidian/vault-write.js +2 -2
  74. package/dist-lib/lib/tools/exec/runner.js +1 -1
  75. package/dist-lib/lib/tools/exec.js +13 -13
  76. package/dist-lib/lib/tools/file-glob.js +3 -3
  77. package/dist-lib/lib/tools/fs/local.js +1 -1
  78. package/dist-lib/lib/tools/fs/remote.js +1 -1
  79. package/dist-lib/lib/tools/fs/walk.js +2 -2
  80. package/dist-lib/lib/tools/generate-image.js +4 -4
  81. package/dist-lib/lib/tools/generate-music.js +6 -6
  82. package/dist-lib/lib/tools/grep.js +6 -6
  83. package/dist-lib/lib/tools/http-request/truncate-response.js +1 -1
  84. package/dist-lib/lib/tools/http-request.js +9 -9
  85. package/dist-lib/lib/tools/mcp/utils.js +1 -1
  86. package/dist-lib/lib/tools/mcp-call.js +8 -8
  87. package/dist-lib/lib/tools/operations/builders.d.ts +1 -1
  88. package/dist-lib/lib/tools/operations/builders.js +1 -1
  89. package/dist-lib/lib/tools/operations/compile.js +1 -1
  90. package/dist-lib/lib/tools/operations/manifest.d.ts +16 -16
  91. package/dist-lib/lib/tools/operations/manifest.js +1 -1
  92. package/dist-lib/lib/tools/outbound-guard.js +1 -1
  93. package/dist-lib/lib/tools/rag-search.js +1 -1
  94. package/dist-lib/lib/tools/read-files.js +3 -3
  95. package/dist-lib/lib/tools/skill.js +8 -8
  96. package/dist-lib/lib/tools/skills/auth-resolver.js +2 -2
  97. package/dist-lib/lib/tools/skills/bin-check.js +1 -1
  98. package/dist-lib/lib/tools/skills/bundled-dir.js +2 -2
  99. package/dist-lib/lib/tools/skills/catalog.js +10 -10
  100. package/dist-lib/lib/tools/skills/cli-schema-check.js +1 -1
  101. package/dist-lib/lib/tools/skills/cli-schema-load.js +1 -1
  102. package/dist-lib/lib/tools/skills/cli-schema.js +4 -4
  103. package/dist-lib/lib/tools/skills/credential-check.js +3 -3
  104. package/dist-lib/lib/tools/skills/credential-grants.js +1 -1
  105. package/dist-lib/lib/tools/skills/credential-health.js +2 -2
  106. package/dist-lib/lib/tools/skills/frontmatter-parser.js +1 -1
  107. package/dist-lib/lib/tools/skills/index.js +5 -5
  108. package/dist-lib/lib/tools/skills/mention.js +1 -1
  109. package/dist-lib/lib/tools/skills/overlay-crud.js +1 -1
  110. package/dist-lib/lib/tools/skills/setup-routes.js +1 -1
  111. package/dist-lib/lib/tools/skills/skill-settings.js +1 -1
  112. package/dist-lib/lib/tools/skills/skill-state-constants.d.ts +8 -8
  113. package/dist-lib/lib/tools/skills/skill-state.js +9 -9
  114. package/dist-lib/lib/tools/toolsets.d.ts +1 -1
  115. package/dist-lib/lib/tools/url-guard.js +2 -2
  116. package/dist-lib/lib/tools/weather-lookup/place.d.ts +11 -0
  117. package/dist-lib/lib/tools/weather-lookup/place.js +152 -0
  118. package/dist-lib/lib/tools/weather-lookup/report.d.ts +37 -0
  119. package/dist-lib/lib/tools/weather-lookup/report.js +56 -0
  120. package/dist-lib/lib/tools/weather-lookup.d.ts +8 -0
  121. package/dist-lib/lib/tools/weather-lookup.js +110 -0
  122. package/dist-lib/lib/tools/web-fetch.js +10 -10
  123. package/dist-lib/lib/tools/web-search.js +2 -2
  124. package/dist-lib/lib/transport.js +1 -1
  125. package/dist-lib/lib/validating-lookup.js +1 -1
  126. package/dist-lib/lib/web-fetch/embedded-state.js +1 -1
  127. package/dist-lib/lib/web-fetch/extract.js +1 -1
  128. package/dist-lib/lib/web-fetch/images.js +1 -1
  129. package/dist-lib/lib/web-fetch/video.js +1 -1
  130. package/dist-lib/lib/web-search/engine-registry.js +7 -7
  131. package/dist-lib/lib/web-search/engines/aol.js +3 -3
  132. package/dist-lib/lib/web-search/engines/bing.js +4 -4
  133. package/dist-lib/lib/web-search/engines/brave.js +2 -2
  134. package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -3
  135. package/dist-lib/lib/web-search/engines/google.js +4 -4
  136. package/dist-lib/lib/web-search/engines/wikipedia.js +2 -2
  137. package/dist-lib/lib/web-search/engines/yahoo.js +3 -3
  138. package/dist-lib/lib/web-search/fetch-html.js +1 -1
  139. package/dist-lib/lib/web-search/merge-results.js +1 -1
  140. package/dist-lib/lib/web-search/orchestrator.js +3 -3
  141. package/dist-lib/schema/baseline.js +1 -1
  142. package/dist-lib/schema/build.js +3 -3
  143. package/dist-lib/schema/release-tags.d.ts +2 -1
  144. package/dist-lib/schema/release-tags.js +9 -7
  145. package/docs/decisions/2026-09-10-skill-wording-is-measured-offline.md +63 -0
  146. package/docs/decisions/2026-09-16-a-lookup-reports-the-place-it-resolved.md +70 -0
  147. package/docs/decisions/INDEX.md +17 -15
  148. package/package.json +16 -3
  149. package/worker/harness-sidecar.mjs +2 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pr-review
3
- description: Deep-dive review of a pull request in @qvac/harness, or of local uncommitted/branch changes when invoked with `local`. Checks CI, the commit type and title, code quality against the house style, contract and API mismatches, and missing tests. In PR mode it creates PENDING inline comments only after the user confirms them, and never submits the review event. In local mode it prints findings in chat and posts nothing. Use when reviewing a PR, given a PR link, or asked to review the working tree or current branch.
3
+ description: Deep-dive review of a pull request in @qvac/harness, or of local uncommitted/branch changes when invoked with `local`. Checks CI and the base branch, the commit type and title, correctness and state/derived-data bugs, contract and API mismatches, security, code quality against the house style, and missing tests. In PR mode it creates PENDING inline comments only after the user confirms them, and never submits the review event. In local mode it prints findings in chat and posts nothing. Use when reviewing a PR, given a PR link, or asked to review the working tree or current branch.
4
4
  ---
5
5
 
6
6
  # PR review
@@ -41,14 +41,18 @@ PR mode:
41
41
 
42
42
  ```bash
43
43
  gh pr view <num> --repo tetherto/qvac-harness \
44
- --json title,body,author,headRefName,headRefOid,baseRefName,files,additions,deletions,state,isDraft,mergeable
44
+ --json title,body,author,headRefName,headRefOid,baseRefName,files,additions,deletions,state,isDraft,mergeable,statusCheckRollup \
45
+ > /tmp/pr-<num>.json
45
46
  gh pr diff <num> --repo tetherto/qvac-harness --patch > /tmp/pr-<num>.patch
46
47
  ```
47
48
 
48
- Read the patch from the file with Read/Grep rather than re-running `gh` — it is
49
- the same bytes every time and the file is greppable.
49
+ Two calls, then read both files with Read/Grep rather than re-running `gh` — it
50
+ is the same bytes every time and the files are greppable. `statusCheckRollup`
51
+ already carries every check, so **do not run `gh pr checks`**; it buys nothing
52
+ the JSON does not have.
50
53
 
51
- Local mode:
54
+ Local mode. The default scope is everything uncommitted — staged, unstaged and
55
+ untracked:
52
56
 
53
57
  ```bash
54
58
  git diff --staged > /tmp/local-staged.patch
@@ -57,8 +61,17 @@ git status --porcelain=v1
57
61
  git ls-files --others --exclude-standard # untracked, easy to miss
58
62
  ```
59
63
 
60
- For a branch rather than a working tree: `git diff main...HEAD` (three dots —
61
- the merge base, not the tip).
64
+ Scope overrides, when the user asks for one: `--staged` is `git diff --staged`
65
+ alone, `--unstaged` is `git diff` plus untracked, and `--branch [<base>]` is
66
+ `git diff <base>...HEAD` (three dots — the merge base, not the tip), with
67
+ `<base>` defaulting to `main`.
68
+
69
+ Untracked files are reviewed as wholly new content: read them from the working
70
+ tree, they appear in no patch.
71
+
72
+ **If the requested scope is empty, say so and stop.** An empty `--staged` is
73
+ not an invitation to review something else, and it is certainly not an
74
+ invitation to invent findings.
62
75
 
63
76
  ## CI
64
77
 
@@ -79,8 +92,10 @@ local `bun run test` does not:
79
92
  A PR that touches `schema/` and leaves `spec/` stale is the most common red
80
93
  here, and it looks nothing like a test failure.
81
94
 
95
+ Read the result from `statusCheckRollup` in `/tmp/pr-<num>.json`, and pull logs
96
+ only for a job that actually failed:
97
+
82
98
  ```bash
83
- gh pr checks <num> --repo tetherto/qvac-harness
84
99
  gh run view --repo tetherto/qvac-harness --log-failed --job <id> > /tmp/pr-<num>-<id>.log
85
100
  ```
86
101
 
@@ -88,6 +103,18 @@ A red `Integrate` is a blocking finding on its own. Read the failing job's log
88
103
  before writing a single line of review — half of what looks like a design
89
104
  problem is a lint rule.
90
105
 
106
+ ### Base branch
107
+
108
+ `baseRefName` is in the JSON you already fetched. Per CLAUDE.md a PR targets
109
+ `main`; the one exception is a stacked PR, which branches from the PR below it
110
+ and targets that branch.
111
+
112
+ So a base other than `main` is only a finding when the PR is **not** part of a
113
+ stack — check whether the base branch is itself an open PR before raising it.
114
+ If it is, the stack is correct and there is nothing to say. If it is not, that
115
+ is a `[P2]`: the diff will contain the base branch's commits and the PR cannot
116
+ merge as it stands.
117
+
91
118
  ## What to check, in order
92
119
 
93
120
  Stop at the first tier that produces findings worth the author's time; a review
@@ -98,7 +125,38 @@ end to end, including the error path. Look for: a promise not awaited, a
98
125
  listener never removed, a resource opened on one path and closed on another, an
99
126
  error swallowed into a default.
100
127
 
101
- **2. Contract and API.** This is a published package, so an exported signature
128
+ A **regression** belongs here too: a change that reads as additive but alters
129
+ what existing callers get — a signature widened, a default flipped, a branch
130
+ removed, a return value that is semantically different at the same type.
131
+
132
+ **2. State and derived data.** The class of bug a linter cannot see and a diff
133
+ skim misses. Walk any new stateful or arithmetic code against these four.
134
+
135
+ - **Two sources of truth.** The same value in two fields, or the same
136
+ expression computed twice, in one payload. Before writing the comment, read
137
+ the type declaration **and** the consumer: if the fields have distinct
138
+ documented meanings (per-item vs aggregate, raw vs normalised) and a reader
139
+ prefers one, this is a value written to the wrong channel — a contract
140
+ violation — not duplication. Say that, and cite the contract. Only call it
141
+ duplication when nothing distinguishes the fields. The two look identical in
142
+ a diff and have opposite fixes: one keeps both fields and moves a value, the
143
+ other deletes a field. Backwards, and you send the author to delete a channel
144
+ something depends on. `[P2]` either way.
145
+ - **Incremental accumulation.** A running total or offset advanced inside a
146
+ loop, where computing the whole table once would be correct by construction.
147
+ `[P2]` when a later edit could desynchronise it — and the fix shrinks the
148
+ code.
149
+ - **Closure over mutated state.** A callback capturing a variable a later step
150
+ reassigns, especially across an `await`. `[P1]` if a late or reordered
151
+ invocation yields a wrong value; say so plainly, because it will not
152
+ reproduce locally.
153
+ - **Laundered values.** `?? 0`, `|| 0`, `Math.max(x, 0)` turning an impossible
154
+ input into a plausible number instead of letting it fail. `[P2]` unless the
155
+ clamp buys a stated presentational guarantee. Watch an optional `uint` in
156
+ `schema/`: it decodes to `0`, not `null`, so `??` never sees the absent
157
+ value.
158
+
159
+ **3. Contract and API.** This is a published package, so an exported signature
102
160
  is a promise to a consumer.
103
161
 
104
162
  - Anything added to or changed in the export map, `index.ts`, `types/`, or
@@ -108,27 +166,45 @@ is a promise to a consumer.
108
166
  - Does the change match its own type declarations, and do callers in this repo
109
167
  still typecheck?
110
168
 
111
- **3. House style.** Run the changed files against **js-house-style** — that
112
- document, not your own taste. Layering, composition on primitives, module size
113
- and shape, no defensive programming, comment density.
169
+ **4. House style.** Load **js-house-style** and run the changed files against
170
+ it — that document, not your own taste or your memory of it. Layering,
171
+ composition on primitives, module size and shape, no defensive programming,
172
+ comment density. A style point the document does not back is not a finding.
114
173
 
115
- **4. Tests.** A behaviour change with no test is a finding. Name the missing
174
+ **5. Tests.** A behaviour change with no test is a finding. Name the missing
116
175
  case rather than asking for "more tests": the tests live under `test/`, run on
117
176
  both the node and bare lanes, and a fix without a regression guard will come
118
177
  back.
119
178
 
120
- **5. Commit type and title.** Per **pr-create**: is this really a `fix` (a
179
+ **6. Commit type and title.** Per **pr-create**: is this really a `fix` (a
121
180
  defect a consumer of a released version could hit), or is it `chore` on code
122
181
  that never shipped? Is the title imperative and specific? Does the branch name
123
182
  avoid `codex` / `cursor` / `claude`?
124
183
 
125
- **6. Scope.** Does the PR do one thing? A behaviour change mixed with an
184
+ **7. Scope.** Does the PR do one thing? A behaviour change mixed with an
126
185
  unrelated rename is two PRs — say so, and say which commit to split out.
127
186
 
128
- **7. Decision records.** Does the change contradict something in
129
- `docs/decisions/`? If it does, the record is updated or superseded in the same
130
- PR, not left to rot. Does it settle something non-obvious with no record at all?
131
- Say which, and what the record would have to state.
187
+ **8. Security.** Not a sweep of the file — only what this diff introduces. An
188
+ API key, token or secret in code, a log line or an error message; a tool result,
189
+ a fetched page or a model completion treated as an instruction rather than as
190
+ data; a path or command assembled from something the model produced; unsafe
191
+ deserialization of an hrpc payload; a new dependency pulled in for something
192
+ small.
193
+
194
+ This package runs untrusted content by design — web fetches, Python in Pyodide,
195
+ MCP servers, the filesystem tool — so the question is never "could input be
196
+ hostile" but "what does this change let hostile input reach". Where it matters,
197
+ `[P1]`.
198
+
199
+ **9. Decision records.** Only when the diff **contradicts** a record in
200
+ `docs/decisions/` that you have actually read — then the record is updated or
201
+ superseded in the same PR rather than left to rot, and that is a `[P2]`.
202
+
203
+ A missing record is not a finding. Do not open `docs/decisions/INDEX.md`
204
+ looking for something to say, do not ask for an ADR because a change "feels
205
+ architectural", and never make "no decision record" the only thing a review
206
+ found. If you genuinely think a lasting choice went unrecorded, say it once in
207
+ the chat overview as a question, never inline.
132
208
 
133
209
  ### Do not over-engineer the review
134
210
 
@@ -156,17 +232,38 @@ corner nobody chose.
156
232
  Each finding: the file and line, what breaks, and the concrete scenario that
157
233
  breaks it. No finding without a failure mode you can state.
158
234
 
159
- Drop anything you cannot defend as changing the code, the reviewer's decision,
160
- or a consumer's experience. Style opinions the house style does not back are
161
- noise. Say so plainly when the diff is clean: "no `[P1]` or `[P2]`
162
- findings" is a complete review.
163
-
164
235
  Lead with the findings, ordered by severity.
165
236
 
237
+ ### Only raise what is worth the author's time
238
+
239
+ A review is not required to produce findings. **"Nothing to flag — the diff is
240
+ clean" is a complete, successful review**, and a good PR reviewed honestly is
241
+ the expected outcome most of the time. Never go hunting for something to say
242
+ because the output looks empty; an invented finding costs the author more than
243
+ a silent review ever could.
244
+
245
+ Every finding passes this bar before it is written down:
246
+
247
+ - You can name **what breaks**, and the concrete path that reaches it.
248
+ - Acting on it **changes the code, the reviewer's decision, or a consumer's
249
+ experience**.
250
+ - It is backed by the house style or a real contract, not your taste.
251
+
252
+ Drop everything else. Not "mention it as a `[P3]`" — **drop it**. A finding you
253
+ cannot defend at `[P2]` is not thereby a `[P3]`; it is noise wearing a smaller
254
+ tag. `[P3]` is for a real, small, correct observation, not a holding pen for
255
+ weak `[P1]`/`[P2]`s.
256
+
257
+ The failure mode this repo has actually seen: a clean PR comes back with four
258
+ `[P3]`s about naming, a speculative race nobody can reach, and a request for a
259
+ decision record. That review is worse than no review — it trains the author to
260
+ skim the next one, and the real finding gets skimmed with it.
261
+
166
262
  ### Severity tags
167
263
 
168
- Tag every finding `[P1]`, `[P2]` or `[P3]`. Use the tag, not a word, so two
169
- reviews of the same repo rank things the same way.
264
+ Tag every finding `[P1]`, `[P2]` or `[P3]` **in the chat overview**, using the
265
+ tag rather than a word, so two reviews of the same repo rank things the same
266
+ way. The tag never appears on the PR — see "Comment style".
170
267
 
171
268
  - `[P1]` — almost-certain bug, data corruption, security issue, CI breaker, or a
172
269
  break of behaviour a consumer relies on. In the overview, and **proposed inline
@@ -183,35 +280,135 @@ nits inline teaches the author to skim the next one.
183
280
 
184
281
  **Severity is what the code does today, not what it might do.** A finding whose
185
282
  only support is a scenario nobody has hit is not a `[P2]`: either find the path
186
- that reaches it, or drop it. Zero findings is a legitimate result.
283
+ that reaches it, or drop it — do not park it at `[P3]` instead. Zero findings is
284
+ a legitimate result.
187
285
 
188
286
  If the user says this is a re-review, say explicitly which earlier concerns are
189
287
  resolved and which still stand.
190
288
 
289
+ ### The overview
290
+
291
+ The user's checkout is usually on a different branch or commit than the PR, so
292
+ a bare line number is worth nothing to them. Every `[P1]` and `[P2]` in the
293
+ overview therefore carries two things: a **3–8 line excerpt read at the head
294
+ SHA**, and a **link to that line on the PR**.
295
+
296
+ The excerpt comes from
297
+ `gh api -H "Accept: application/vnd.github.raw" repos/tetherto/qvac-harness/contents/<path>?ref=<headRefOid>`,
298
+ never from the local tree. The link is the file-diff anchor:
299
+
300
+ `https://github.com/tetherto/qvac-harness/pull/<num>/files#diff-<sha256(path)>R<line>`
301
+
302
+ ```bash
303
+ printf '%s' 'lib/chat/compact.js' | shasum -a 256 | awk '{print $1}'
304
+ ```
305
+
306
+ If the anchor will not compute, fall back to
307
+ `https://github.com/tetherto/qvac-harness/blob/<headRefOid>/<path>#L<line>`.
308
+
309
+ ````markdown
310
+ ## PR #<num> — review overview
311
+
312
+ <one line: what this PR does>
313
+
314
+ ### CI / base
315
+
316
+ <one line; omit entirely when both are clean>
317
+
318
+ ### Findings
319
+
320
+ - **[P1]** <short title> — [`<path>:<line>`](link)
321
+
322
+ <one sentence, plus the repro when there is one>
323
+
324
+ ```js
325
+ <3-8 line excerpt at the head SHA>
326
+ ```
327
+
328
+ - **[P3]** <short title> — [`<path>:<line>`](link) — <one sentence, no excerpt>
329
+
330
+ ### Re-review delta
331
+
332
+ <only when the user said this is a re-review>
333
+
334
+ - Resolved: <…>
335
+ - Still open: <…>
336
+ ````
337
+
338
+ One sentence per finding here; the inline comment carries the reasoning. A
339
+ clean review is this same block with `Findings` reading "nothing to flag".
340
+
341
+ Local mode uses the same shape, headed `## Local review — <scope>`, with plain
342
+ `path:line` instead of links and the excerpt read from the working tree.
343
+
344
+ ## Comment style
345
+
346
+ Write like a colleague leaving a note, not a static analyser filing a report.
347
+ If a human reviewer would not say it out loud, do not write it.
348
+
349
+ - **Brief.** A few lines, one point per comment. If it takes three paragraphs
350
+ to justify, it probably is not a finding.
351
+ - **Ask, don't lecture.** "Can this reuse the existing helper?" beats a
352
+ paragraph on why the new one duplicates knowledge the store already owns.
353
+ - **Link what you refer to.** An existing function, type or rule you want
354
+ reused gets a line link — `[name](<url>#L120)`. A reference the author has to
355
+ hunt for is one they skip.
356
+ - **Show the change in code.** A short before/after, or a `suggestion` block
357
+ whenever the fix is mechanical enough to apply as-is.
358
+ - **Never put a severity marker in a comment body.** No `[P1]`/`[P2]`/`[P3]`,
359
+ no "blocker", no "nit:". Severity is how _you_ decided what to raise; it
360
+ lives in the chat overview and nowhere else. On the PR it reads as grading
361
+ the author and it ages badly once the code moves. If something is minor, say
362
+ it in plain words — "not urgent", "while you're in there".
363
+ - One concrete repro line for a `[P1]`/`[P2]` when one exists. Not a test plan.
364
+ - No praise padding, no "LGTM but…", no restating what the diff does.
365
+ - **Sign off every comment with 🤖 on its own last line**, so it is
366
+ unmistakable that the review came from an AI. Every inline comment, no
367
+ exceptions — the emoji alone, nothing else on the line.
368
+
191
369
  ## Posting (PR mode only)
192
370
 
193
- 1. Print every proposed comment in chat first: file, line, severity, body.
194
- The default set is the `[P1]` and `[P2]` findings; name any `[P3]` you are
195
- holding back so the user can pull it in.
371
+ 1. Print every proposed comment in chat first, as it will be posted: file,
372
+ line, then the verbatim body. Carry the severity in the chat line **outside**
373
+ the body. The default set is the `[P1]` and `[P2]` findings; name any `[P3]`
374
+ you are holding back so the user can pull it in. If there is nothing at
375
+ `[P1]`/`[P2]`, say so and stop — never assemble a payload out of `[P3]`s to
376
+ have something to post.
196
377
  2. Ask the user to confirm the exact set. Wait for a clear yes.
197
- 3. Create the review as **PENDING**, with no event:
378
+ 3. Write `/tmp/pr-<num>-review.json` from the confirmed set **only**. If the
379
+ confirmed set is empty, stop — never post an empty review.
380
+ 4. Pre-flight, before the POST:
381
+ - The comment count matches the confirmed set **exactly**. If it does not,
382
+ stop and reconcile; do not post the larger set and mention it after.
383
+ - Every `line` is a **post-PR line** — the `+` side of the patch, as
384
+ numbered in the file at the head SHA. A line the patch does not touch is a
385
+ `422` that rejects the whole review, so anchor to a touched line or move
386
+ the point to the chat overview.
387
+ - Every body ends with a `🤖` line, and none contains `[P1]`, `[P2]`, `[P3]`,
388
+ "nit" or "blocker". Fix the bodies yourself; do not ask the user.
389
+ - Show the bodies as **rendered Markdown**, one per `file:line`, never raw
390
+ JSON — the user is approving what the author will read.
391
+ 5. Create the review as **PENDING**, with no event:
198
392
 
199
393
  ```bash
200
394
  gh api repos/tetherto/qvac-harness/pulls/<num>/reviews \
201
- --method POST --input /tmp/review.json
395
+ --method POST --input /tmp/pr-<num>-review.json
202
396
  ```
203
397
 
204
- where `review.json` has `commit_id` (the `headRefOid` you fetched) and a
205
- `comments` array of `{path, line, side, body}`, and **no `event` field**. A body
206
- with `event` submits the review; leaving it out is what makes it pending.
398
+ where the payload has `commit_id` (the `headRefOid` you fetched) and a
399
+ `comments` array of `{path, line, side, body}`, and **no `event` field**. A
400
+ payload with `event` submits the review; leaving it out is what makes it
401
+ pending. No top-level `body` either, unless the user asks for one — the inline
402
+ comments are the review.
207
403
 
208
- 4. Hand the user the link and stop:
209
- `https://github.com/tetherto/qvac-harness/pull/<num>#pullrequestreview-<id>`
404
+ If the user edits the selection after the preview, rewrite the file before
405
+ running the command.
210
406
 
211
- If a line is not in the diff, GitHub rejects the comment. Anchor to a line the
212
- patch actually touches, or raise it in the chat overview instead.
407
+ 6. Hand the user the link and stop:
408
+ `https://github.com/tetherto/qvac-harness/pull/<num>#pullrequestreview-<id>`
213
409
 
214
410
  ## Sibling skills
215
411
 
216
412
  - Writing the PR being reviewed → **pr-create**.
217
- - The rules tier 3 reviews against → **js-house-style**.
413
+ - The rules tier 4 reviews against → **js-house-style**. Load it; do not review
414
+ from memory of it.