@qvac/harness 0.2.8 → 0.2.9
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/dist/harness.bundle +65 -12
- package/dist/prebuilds-manifest.json +8 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/lib/capabilities/completion.d.ts +35 -2
- package/dist-lib/lib/capabilities/completion.js +84 -18
- package/dist-lib/lib/connect.js +1 -0
- package/dist-lib/lib/contract-types.d.ts +1 -0
- package/dist-lib/lib/serve.js +1 -0
- package/dist-lib/lib/wire.js +28 -0
- package/dist-lib/schema/harness.js +5 -2
- package/dist-lib/spec/hrpc/messages.js +22 -7
- package/dist-lib/spec/hyperschema/index.js +22 -7
- package/dist-lib/spec/hyperschema/schema.json +14 -1
- package/dist-lib/spec/hyperschema/types.d.ts +3 -2
- package/package.json +6 -6
- package/.claude/skills/asana/SKILL.md +0 -86
- package/.claude/skills/gh-stack/SKILL.md +0 -181
- package/.claude/skills/gh-stack/references/commands.md +0 -177
- package/.claude/skills/gh-stack/references/stack-design.md +0 -97
- package/.claude/skills/gh-stack/references/troubleshooting.md +0 -159
- package/.claude/skills/js-house-style/SKILL.md +0 -671
- package/.claude/skills/kvcache/SKILL.md +0 -171
- package/.claude/skills/pr-create/SKILL.md +0 -180
- package/.claude/skills/pr-review/SKILL.md +0 -414
- package/CLAUDE.md +0 -39
- package/docs/decisions/2026-05-29-kvcache-tool-turns.md +0 -75
- package/docs/decisions/2026-06-02-kvcache-tool-turns-reenabled.md +0 -127
- package/docs/decisions/2026-06-23-android-cpu-llm-defaults.md +0 -126
- package/docs/decisions/2026-07-29-assistant-owned-storage-layout.md +0 -88
- package/docs/decisions/2026-08-04-sidecar-orphan-reaping.md +0 -79
- package/docs/decisions/2026-08-11-pyodide-worker-thread.md +0 -68
- package/docs/decisions/2026-08-17-harness-hook-pipeline.md +0 -84
- package/docs/decisions/2026-08-17-harness-outbound-egress-guard.md +0 -176
- package/docs/decisions/2026-08-20-chat-prompt-prefix-is-stable.md +0 -95
- package/docs/decisions/2026-08-20-kvcache-static-placement.md +0 -109
- package/docs/decisions/2026-08-21-vendored-python-wheels.md +0 -85
- package/docs/decisions/2026-08-26-exec-is-not-gated-by-skills.md +0 -64
- package/docs/decisions/2026-08-27-writable-skills-overlay.md +0 -56
- package/docs/decisions/2026-09-01-skill-operation-manifests.md +0 -53
- package/docs/decisions/2026-09-01-usage-breakdown-is-measured.md +0 -78
- package/docs/decisions/2026-09-03-usage-breakdown-reported-by-the-harness.md +0 -136
- package/docs/decisions/2026-09-10-skill-wording-is-measured-offline.md +0 -63
- package/docs/decisions/2026-09-16-a-lookup-reports-the-place-it-resolved.md +0 -70
- package/docs/decisions/INDEX.md +0 -40
- package/docs/decisions/TEMPLATE.md +0 -32
- package/docs/kvcache-reference.md +0 -655
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
---
|
|
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 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
|
-
---
|
|
5
|
-
|
|
6
|
-
# PR review
|
|
7
|
-
|
|
8
|
-
Reviews changes in `@qvac/harness`. Two modes.
|
|
9
|
-
|
|
10
|
-
- **PR mode** (a PR number or URL) — a short overview in chat, then a **pending**
|
|
11
|
-
GitHub review of per-file inline comments. The pending review is created only
|
|
12
|
-
after the user confirms the exact set of comments. **Never submit the review
|
|
13
|
-
event** (`COMMENT`, `REQUEST_CHANGES`, `APPROVE`) — that is the user's call,
|
|
14
|
-
from the GitHub UI.
|
|
15
|
-
- **Local mode** (`local`, or "review my changes") — the overview in chat only.
|
|
16
|
-
Posts nothing, anywhere.
|
|
17
|
-
|
|
18
|
-
## Reviewing is read-only
|
|
19
|
-
|
|
20
|
-
This skill never writes to the working tree. Against the user's repo these are
|
|
21
|
-
forbidden, whatever the reason:
|
|
22
|
-
|
|
23
|
-
- `git switch`, `git checkout`, `git reset`, `git restore`, `git clean`
|
|
24
|
-
- `git stash`, in any form
|
|
25
|
-
- `git pull`, `git merge`, `git rebase`, `git cherry-pick`
|
|
26
|
-
- `gh pr checkout`
|
|
27
|
-
- any edit to a file
|
|
28
|
-
|
|
29
|
-
Local mode reads the diff with `git diff`, `git status` and `git ls-files`, and
|
|
30
|
-
nothing else. If the tree is dirty in a way that makes the diff hard to read, say
|
|
31
|
-
so and let the user decide; never tidy it yourself. A review that loses
|
|
32
|
-
uncommitted work is worse than no review.
|
|
33
|
-
|
|
34
|
-
PR mode needs no checkout at all: `gh pr diff` gives the patch, and
|
|
35
|
-
`gh api repos/{owner}/{repo}/contents/{path}?ref={sha}` gives any file at the PR
|
|
36
|
-
head.
|
|
37
|
-
|
|
38
|
-
## Gather
|
|
39
|
-
|
|
40
|
-
PR mode:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
gh pr view <num> --repo tetherto/qvac-harness \
|
|
44
|
-
--json title,body,author,headRefName,headRefOid,baseRefName,files,additions,deletions,state,isDraft,mergeable,statusCheckRollup \
|
|
45
|
-
> /tmp/pr-<num>.json
|
|
46
|
-
gh pr diff <num> --repo tetherto/qvac-harness --patch > /tmp/pr-<num>.patch
|
|
47
|
-
```
|
|
48
|
-
|
|
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.
|
|
53
|
-
|
|
54
|
-
Local mode. The default scope is everything uncommitted — staged, unstaged and
|
|
55
|
-
untracked:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
git diff --staged > /tmp/local-staged.patch
|
|
59
|
-
git diff > /tmp/local-unstaged.patch
|
|
60
|
-
git status --porcelain=v1
|
|
61
|
-
git ls-files --others --exclude-standard # untracked, easy to miss
|
|
62
|
-
```
|
|
63
|
-
|
|
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.
|
|
75
|
-
|
|
76
|
-
## CI
|
|
77
|
-
|
|
78
|
-
One workflow gates this repo: **Integrate** (`.github/workflows/ci.yml`), a
|
|
79
|
-
single `Lint & Tests` job. `publish.yml` runs on release, not on PRs.
|
|
80
|
-
|
|
81
|
-
Its steps are each a separate way to fail, and the last three catch what a
|
|
82
|
-
local `bun run test` does not:
|
|
83
|
-
|
|
84
|
-
| Step | Command | Fails when |
|
|
85
|
-
| ------------------------ | ---------------------- | --------------------------------------------------------------------------------------------- |
|
|
86
|
-
| Lint | `bun run lint` | prettier, lunte or `tsc --noEmit` |
|
|
87
|
-
| Schema is regenerated | `bun run build:schema` | `spec/` is dirty after codegen — someone hand-edited it, or forgot to commit the regeneration |
|
|
88
|
-
| Tests | `bun run test` | the node, bare and addon lanes |
|
|
89
|
-
| Library surface compiles | `bun run build:lib` | the compiled export surface breaks |
|
|
90
|
-
| Sidecar bundle builds | `bun run build:bundle` | the stowed bundle no longer assembles — a new bare import, or a missing addon external |
|
|
91
|
-
|
|
92
|
-
A PR that touches `schema/` and leaves `spec/` stale is the most common red
|
|
93
|
-
here, and it looks nothing like a test failure.
|
|
94
|
-
|
|
95
|
-
Read the result from `statusCheckRollup` in `/tmp/pr-<num>.json`, and pull logs
|
|
96
|
-
only for a job that actually failed:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
gh run view --repo tetherto/qvac-harness --log-failed --job <id> > /tmp/pr-<num>-<id>.log
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
A red `Integrate` is a blocking finding on its own. Read the failing job's log
|
|
103
|
-
before writing a single line of review — half of what looks like a design
|
|
104
|
-
problem is a lint rule.
|
|
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
|
-
|
|
118
|
-
## What to check, in order
|
|
119
|
-
|
|
120
|
-
Stop at the first tier that produces findings worth the author's time; a review
|
|
121
|
-
that lists twelve nits under a broken contract has buried the real thing.
|
|
122
|
-
|
|
123
|
-
**1. Correctness.** Does it do what the title claims? Trace the changed path
|
|
124
|
-
end to end, including the error path. Look for: a promise not awaited, a
|
|
125
|
-
listener never removed, a resource opened on one path and closed on another, an
|
|
126
|
-
error swallowed into a default.
|
|
127
|
-
|
|
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
|
|
160
|
-
is a promise to a consumer.
|
|
161
|
-
|
|
162
|
-
- Anything added to or changed in the export map, `index.ts`, `types/`, or
|
|
163
|
-
`package.json#exports` — is it intentional, and is it a breaking change?
|
|
164
|
-
- `schema/` or `spec/` changes: is the wire format still compatible, and was the
|
|
165
|
-
baseline regenerated (`bun run build:schema`) rather than hand-edited?
|
|
166
|
-
- Does the change match its own type declarations, and do callers in this repo
|
|
167
|
-
still typecheck?
|
|
168
|
-
|
|
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.
|
|
173
|
-
|
|
174
|
-
**5. Tests.** A behaviour change with no test is a finding. Name the missing
|
|
175
|
-
case rather than asking for "more tests": the tests live under `test/`, run on
|
|
176
|
-
both the node and bare lanes, and a fix without a regression guard will come
|
|
177
|
-
back.
|
|
178
|
-
|
|
179
|
-
**6. Commit type and title.** Per **pr-create**: is this really a `fix` (a
|
|
180
|
-
defect a consumer of a released version could hit), or is it `chore` on code
|
|
181
|
-
that never shipped? Is the title imperative and specific? Does the branch name
|
|
182
|
-
avoid `codex` / `cursor` / `claude`?
|
|
183
|
-
|
|
184
|
-
**7. Scope.** Does the PR do one thing? A behaviour change mixed with an
|
|
185
|
-
unrelated rename is two PRs — say so, and say which commit to split out.
|
|
186
|
-
|
|
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.
|
|
208
|
-
|
|
209
|
-
### Do not over-engineer the review
|
|
210
|
-
|
|
211
|
-
The most damaging thing a review can do is talk a good PR into a worse one. AI
|
|
212
|
-
review skews defensive: a guard here, an abstraction there, a layer to absorb a
|
|
213
|
-
hypothetical, each one permanent debt bolted to a problem that never happened.
|
|
214
|
-
That runs directly against the house style, which does not program defensively.
|
|
215
|
-
|
|
216
|
-
It compounds. Successive reviews each add a layer against a different
|
|
217
|
-
speculative case, one review's fix undoes another's, and the code ends up in a
|
|
218
|
-
corner nobody chose.
|
|
219
|
-
|
|
220
|
-
- **Only raise what is wrong now**, or has a concrete path to being wrong.
|
|
221
|
-
"Someone could later..." is not a finding.
|
|
222
|
-
- **Never propose a guard, wrapper, fallback or abstraction** whose only job is
|
|
223
|
-
to make a hypothetical safe. Let caller bugs crash; that is the contract.
|
|
224
|
-
- **Prefer the fix that deletes code.** If your suggestion makes the diff
|
|
225
|
-
bigger, be sure it earns it.
|
|
226
|
-
- **Simplification is a valid finding; speculative hardening is not.**
|
|
227
|
-
- When you cannot tell whether something is real, ask instead of requesting a
|
|
228
|
-
change, and be willing to land zero findings.
|
|
229
|
-
|
|
230
|
-
## Write the findings
|
|
231
|
-
|
|
232
|
-
Each finding: the file and line, what breaks, and the concrete scenario that
|
|
233
|
-
breaks it. No finding without a failure mode you can state.
|
|
234
|
-
|
|
235
|
-
Lead with the findings, ordered by severity.
|
|
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
|
-
|
|
262
|
-
### Severity tags
|
|
263
|
-
|
|
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".
|
|
267
|
-
|
|
268
|
-
- `[P1]` — almost-certain bug, data corruption, security issue, CI breaker, or a
|
|
269
|
-
break of behaviour a consumer relies on. In the overview, and **proposed inline
|
|
270
|
-
by default**.
|
|
271
|
-
- `[P2]` — likely bug, non-obvious caveat, contract or API mismatch, subtle
|
|
272
|
-
behaviour change, missing test on a risky path. In the overview, and **proposed
|
|
273
|
-
inline by default**.
|
|
274
|
-
- `[P3]` — minor: a stale comment or doc, an ergonomic improvement, an optional
|
|
275
|
-
cleanup. In the overview when material, and **not proposed inline** unless the
|
|
276
|
-
user asks for them.
|
|
277
|
-
|
|
278
|
-
**Never silently put a `[P3]` in the inline payload.** A review that posts ten
|
|
279
|
-
nits inline teaches the author to skim the next one.
|
|
280
|
-
|
|
281
|
-
**Severity is what the code does today, not what it might do.** A finding whose
|
|
282
|
-
only support is a scenario nobody has hit is not a `[P2]`: either find the path
|
|
283
|
-
that reaches it, or drop it — do not park it at `[P3]` instead. Zero findings is
|
|
284
|
-
a legitimate result.
|
|
285
|
-
|
|
286
|
-
If the user says this is a re-review, say explicitly which earlier concerns are
|
|
287
|
-
resolved and which still stand.
|
|
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
|
-
|
|
369
|
-
## Posting (PR mode only)
|
|
370
|
-
|
|
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.
|
|
377
|
-
2. Ask the user to confirm the exact set. Wait for a clear yes.
|
|
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:
|
|
392
|
-
|
|
393
|
-
```bash
|
|
394
|
-
gh api repos/tetherto/qvac-harness/pulls/<num>/reviews \
|
|
395
|
-
--method POST --input /tmp/pr-<num>-review.json
|
|
396
|
-
```
|
|
397
|
-
|
|
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.
|
|
403
|
-
|
|
404
|
-
If the user edits the selection after the preview, rewrite the file before
|
|
405
|
-
running the command.
|
|
406
|
-
|
|
407
|
-
6. Hand the user the link and stop:
|
|
408
|
-
`https://github.com/tetherto/qvac-harness/pull/<num>#pullrequestreview-<id>`
|
|
409
|
-
|
|
410
|
-
## Sibling skills
|
|
411
|
-
|
|
412
|
-
- Writing the PR being reviewed → **pr-create**.
|
|
413
|
-
- The rules tier 4 reviews against → **js-house-style**. Load it; do not review
|
|
414
|
-
from memory of it.
|
package/CLAUDE.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# @qvac/harness
|
|
2
|
-
|
|
3
|
-
The QV.AC inference engine: `generate(context)` over the QVAC SDK, plus the tool
|
|
4
|
-
runtime skills execute in. **Inference in, events out** — it knows nothing about
|
|
5
|
-
the app, hyperdb or sync. It runs as a supervised child process (or a Bare thread
|
|
6
|
-
on mobile) that a host spawns and talks to over hrpc.
|
|
7
|
-
|
|
8
|
-
Runs on both Bare and Node. Anything that only works on one is a bug.
|
|
9
|
-
|
|
10
|
-
## Durable invariants
|
|
11
|
-
|
|
12
|
-
- **The published package spawns; it does not import.** The tarball ships the
|
|
13
|
-
prebuilt bundle and its runtime content, and its export map deliberately points
|
|
14
|
-
at files it omits. That map is not broken — do not "fix" it.
|
|
15
|
-
- **`spec/` is generated** by `bun run build:schema`. Never hand-edit it; CI fails
|
|
16
|
-
when it is dirty after regeneration.
|
|
17
|
-
- **Dependency `overrides` pin the resolutions the engine is validated against.**
|
|
18
|
-
Keep them aligned with the direct specs — npm refuses a mismatch on publish.
|
|
19
|
-
- **The cache is the SDK's.** Do not add caller-side history eviction or
|
|
20
|
-
chars-per-token estimation.
|
|
21
|
-
|
|
22
|
-
## Working here
|
|
23
|
-
|
|
24
|
-
- `bun run lint` (prettier + lunte + tsc) and `bun run test` (node, bare and
|
|
25
|
-
addon lanes) before considering a change done.
|
|
26
|
-
- Tests live in `test/` and run under brittle. A non-trivial change ships tests.
|
|
27
|
-
|
|
28
|
-
## Always
|
|
29
|
-
|
|
30
|
-
- **Comment only what the code cannot say.** Why, not what. One or two lines,
|
|
31
|
-
never more. Most code needs none — do not narrate it.
|
|
32
|
-
- **Commits read as if a human wrote them.** No `Co-Authored-By` trailer and no
|
|
33
|
-
AI or agent attribution, in a commit message or a PR.
|
|
34
|
-
- **Durable decisions are in `docs/decisions/`.** Read `INDEX.md` when touching an
|
|
35
|
-
area it lists, and open a record only on a path or keyword match — they are
|
|
36
|
-
reference, not rules. Add one when a change makes a non-obvious lasting choice.
|
|
37
|
-
- **Branch from `main`, and open the PR against `main`.** The one exception is a
|
|
38
|
-
stacked PR, which branches from the PR below it and targets that branch.
|
|
39
|
-
- **Branch names never contain `codex`, `cursor` or `claude`**, in any case.
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: ADR-2026-05-29-kvcache-tool-turns
|
|
3
|
-
status: superseded
|
|
4
|
-
introduced_by: PR-228
|
|
5
|
-
supersedes: []
|
|
6
|
-
superseded_by: [ADR-2026-06-02-kvcache-tool-turns-reenabled]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# KV-cache session is disabled for tool-using completions
|
|
10
|
-
|
|
11
|
-
> **Superseded** by
|
|
12
|
-
> [ADR-2026-06-02-kvcache-tool-turns-reenabled](./2026-06-02-kvcache-tool-turns-reenabled.md):
|
|
13
|
-
> the cache is re-enabled for tool turns now that models load with
|
|
14
|
-
> `toolsMode: "dynamic"` (the SDK's DYNAMIC path is coherent, including across
|
|
15
|
-
> reload). Kept for history.
|
|
16
|
-
|
|
17
|
-
## Decision
|
|
18
|
-
|
|
19
|
-
The `@qvac/sdk` session prompt cache (`completion({ kvCache })`) is passed
|
|
20
|
-
**only for tool-free completions** — `kvCache` is set only when
|
|
21
|
-
`tools.length === 0`. Agentic / tool-using turns always run without the session
|
|
22
|
-
cache.
|
|
23
|
-
|
|
24
|
-
## Context
|
|
25
|
-
|
|
26
|
-
The SDK session cache has two paths (see
|
|
27
|
-
`@qvac/sdk → server/bare/plugins/llamacpp-completion/ops/completion-stream.ts` in the SDK):
|
|
28
|
-
|
|
29
|
-
- **STATIC** (no `tools`): on a cache hit it sends `history.slice(savedCount)`
|
|
30
|
-
and self-heals a stale boundary by re-sending the full history. Robust against
|
|
31
|
-
our reconstructed history.
|
|
32
|
-
- **DYNAMIC** (`tools` present): on a cache hit it sends only a role-based
|
|
33
|
-
_fragment_ of the history and trusts the SDK's own tool-chain flow to have
|
|
34
|
-
anchored the rest of the cache.
|
|
35
|
-
|
|
36
|
-
Our agentic loop builds (and on reload reconstructs from a persisted transcript)
|
|
37
|
-
the full history its own way. That does not satisfy the DYNAMIC path's
|
|
38
|
-
assumptions, so on a tool-cache hit the model receives an incoherent prompt and
|
|
39
|
-
emits `<tool_call>` markup as plain text instead of a parseable tool call —
|
|
40
|
-
silently breaking tool calling (no parse, no approval, no execution). Tools are
|
|
41
|
-
text-parsed (`toolCalling: "textParse"`), so a corrupted prompt degrades to a
|
|
42
|
-
plain assistant message rather than erroring.
|
|
43
|
-
|
|
44
|
-
## Alternatives Considered
|
|
45
|
-
|
|
46
|
-
- **Always-on per-chat `kvCache`** (the state that introduced the regression):
|
|
47
|
-
breaks tool calling as above.
|
|
48
|
-
- **Auto-cache (`kvCache: true`)**: tool-call turns are never cached, and a
|
|
49
|
-
verbatim `cacheableAssistantContent` echo is required for a hit —
|
|
50
|
-
incompatible with our thinking-stripped reconstruction.
|
|
51
|
-
- **Conform to the SDK's tool-chain contract** (collapse consecutive messages +
|
|
52
|
-
`deleteCache` on divergence, like the legacy desktop worker): viable but a
|
|
53
|
-
separate project, tracked in `.ai/cursor/plans/kvcache-tool-turn-reuse.md`.
|
|
54
|
-
|
|
55
|
-
## Consequences
|
|
56
|
-
|
|
57
|
-
- Tool turns re-prefill the full history each call (no session-cache reuse).
|
|
58
|
-
Tool calling stays correct; this is the prior known-good behavior.
|
|
59
|
-
- Tool-free turns (plain chat / title / summary) still benefit from the STATIC
|
|
60
|
-
cache path.
|
|
61
|
-
- Regression guards are GPU-free and mock-based:
|
|
62
|
-
`qvac-app → core/src/services/message/agentic-completion.test.ts` (argument-contract:
|
|
63
|
-
never send `kvCache` with `tools`) and the spec-fake
|
|
64
|
-
`qvac-app → core/src/services/message/__fixtures__/fake-completion-sdk.ts` (reproduces the
|
|
65
|
-
cache-hit tool-parse failure).
|
|
66
|
-
- Revisit if the SDK adds a "send full history, you slice it" mode for tools, or
|
|
67
|
-
when the conformance project lands.
|
|
68
|
-
|
|
69
|
-
## When Agents Should Read This
|
|
70
|
-
|
|
71
|
-
Read this before changing how `kvCache` is passed to `sdk.completion`, the
|
|
72
|
-
agentic loop's history construction, or transcript reconstruction —
|
|
73
|
-
`qvac-app → core/src/services/message/agentic-completion.ts`,
|
|
74
|
-
`qvac-app → core/src/services/message/agentic-loop.ts`,
|
|
75
|
-
`qvac-app → core/src/services/common/transcript.ts`.
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: ADR-2026-06-02-kvcache-tool-turns-reenabled
|
|
3
|
-
status: superseded
|
|
4
|
-
introduced_by: PR-QVAC-19770
|
|
5
|
-
supersedes: [ADR-2026-05-29-kvcache-tool-turns]
|
|
6
|
-
superseded_by: [ADR-2026-08-20-kvcache-static-placement]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# KV-cache session is re-enabled for tool-using completions
|
|
10
|
-
|
|
11
|
-
> **Superseded** by
|
|
12
|
-
> [ADR-2026-08-20-kvcache-static-placement](./2026-08-20-kvcache-static-placement.md):
|
|
13
|
-
> the half of this record that still holds is that `kvCache: 'chat-<id>'` rides
|
|
14
|
-
> every completion. The dynamic-mode half was built on a false premise — the
|
|
15
|
-
> addon's compact-tools anchoring never ran on any shipped model (its arch gate
|
|
16
|
-
> is exact `qwen3`; ours are `qwen35`/`qwen36`), so the coherence this record
|
|
17
|
-
> credits to dynamic mode was never exercised, and the "static never surfaced
|
|
18
|
-
> the tool-call format" claim did not survive re-testing. core-v2 loads static
|
|
19
|
-
> with no sliding window since 2026-08-20; mechanics in
|
|
20
|
-
> `docs/kvcache-reference.md`. The `QVAC_PROMPT_CACHE` kill-switch and
|
|
21
|
-
> `deleteChatPromptCache` described below were core-v1 machinery and do not
|
|
22
|
-
> exist in the current runtime.
|
|
23
|
-
|
|
24
|
-
## Decision
|
|
25
|
-
|
|
26
|
-
The `@qvac/sdk` session prompt cache (`completion({ kvCache: "chat-<id>" })`) is
|
|
27
|
-
passed for **all** completions with a chatId — tool-using and tool-free alike —
|
|
28
|
-
gated only by the `QVAC_PROMPT_CACHE` kill-switch. This reverses
|
|
29
|
-
ADR-2026-05-29, which disabled the cache whenever tools were present.
|
|
30
|
-
|
|
31
|
-
## Context
|
|
32
|
-
|
|
33
|
-
The prior decision disabled tool-turn caching because the SDK's DYNAMIC cache
|
|
34
|
-
path broke tool-call parsing: it ships a role-based history fragment and trusts
|
|
35
|
-
the addon's tool-chain anchoring, which our reconstructed history did not
|
|
36
|
-
satisfy → `<tool_call>` markup leaked as text. Two things changed:
|
|
37
|
-
|
|
38
|
-
- Models loaded with `modelConfig.toolsMode: "dynamic"` (QVAC-19660). In
|
|
39
|
-
dynamic mode the addon anchors tools per-turn (compact-tools), excludes the
|
|
40
|
-
tool set from the cache config hash, and the fragment slicing keeps
|
|
41
|
-
multi-round tool turns coherent — including across a model evict/reload.
|
|
42
|
-
**No longer true as of `@qvac/sdk` 0.18 (QVAC-23936):** the key was removed
|
|
43
|
-
and the parser rejects it, so static is the only placement. Dynamic was in
|
|
44
|
-
any case a silent no-op on every arch we ship — the addon gates
|
|
45
|
-
`tools_compact` on exact-arch `qwen3` while our catalog is `qwen35`/`qwen36` —
|
|
46
|
-
and the SDK still sliced as-if-dynamic, which made the cache the chat's
|
|
47
|
-
_memory_ rather than an accelerator: after any cache reset it sent only the
|
|
48
|
-
last exchange. Under static placement the tool block sits in the addon's
|
|
49
|
-
protected prefix instead, and 0.18 keeps it out of the primed prefix
|
|
50
|
-
(QVAC-23251) so the qwen3.5 template no longer raises 'No user query found in
|
|
51
|
-
messages.'
|
|
52
|
-
- An SDK e2e guard (`kv-cache-tools-dynamic-reuse`, plus a qwen35-dialect
|
|
53
|
-
variant) verifies a custom-key multi-round tool chain reuses the on-disk cache
|
|
54
|
-
(`cacheTokens > 0`) and keeps tool calls parseable across an evict/reload.
|
|
55
|
-
|
|
56
|
-
With the DYNAMIC path proven coherent across reload, the heavyweight
|
|
57
|
-
"reconstruction invalidation / byte-identical history" work the prior ADR
|
|
58
|
-
deferred is unnecessary; divergence is handled with targeted invalidation.
|
|
59
|
-
|
|
60
|
-
## Consequences
|
|
61
|
-
|
|
62
|
-
- **Do not pass `toolsMode` at model load.** SDK 0.18 removed the key and its
|
|
63
|
-
parser rejects an unrecognised one, so passing it fails the load outright and
|
|
64
|
-
the app parks on the splash. Static placement is the default and the only mode.
|
|
65
|
-
- Dropping a key from `modelConfig` re-keys `modelId`, which is identity-by-config,
|
|
66
|
-
so expect one cold prime per chat on the first run after such a change.
|
|
67
|
-
- Tool turns reuse the session KV cache: lower TTFT/prefill on multi-round tool
|
|
68
|
-
turns and follow-ups (manually: round-2 TTFT ~1.1s → ~0.3s; cacheTokens grows
|
|
69
|
-
monotonically across a conversation).
|
|
70
|
-
- `QVAC_PROMPT_CACHE` (default on; `0`/`false`/`off`) disables all
|
|
71
|
-
prompt caching as a kill-switch.
|
|
72
|
-
- The per-chat cache is explicitly invalidated on chat delete and agent delete
|
|
73
|
-
(which cascades to its chats) via `deleteChatPromptCache`, plus a defensive
|
|
74
|
-
drop on retry (see below). Edit / branch / truncate flows do not exist yet and
|
|
75
|
-
need no hook.
|
|
76
|
-
- **Why stop / failed / new-message-after-failure need no invalidation.** The
|
|
77
|
-
cache only ever holds _successful_ turns: the SDK rolls back uncommitted turns
|
|
78
|
-
on abort (the custom-key path commits only when not aborted and tokens were
|
|
79
|
-
produced), and failed / canceled / not_delivered replies are excluded from
|
|
80
|
-
model-visible history (`isModelVisibleMessage`, `qvac-app → core/src/services/common/history.ts`).
|
|
81
|
-
So a stopped or failed turn leaves neither a committed cache entry nor a
|
|
82
|
-
visible history row — the reconstructed history and the cached prefix stay
|
|
83
|
-
aligned, and the next turn (new message or retry) does not slice against a
|
|
84
|
-
stale prefix. The SDK's own `kv-cache-cancel-then-new-prompt` e2e covers this
|
|
85
|
-
cancel-then-continue coherence. The **retry** `deleteChatPromptCache` is
|
|
86
|
-
therefore belt-and-suspenders, not load-bearing: it costs one re-prime on a
|
|
87
|
-
rare, user-initiated path and removes any dependence on the rollback timing.
|
|
88
|
-
- **Mobile scope.** Mobile now runs the same assistant and harness completion path
|
|
89
|
-
inside its supervised Bare worklets, so the per-chat cache key reaches the SDK
|
|
90
|
-
without the retired worker-to-host SDK bridge.
|
|
91
|
-
- `chatPromptCacheKey` is the single source of truth for the `chat-<id>` key
|
|
92
|
-
shared by the completion path and the invalidation callers.
|
|
93
|
-
- Known SDK limitation: on a multimodal (M-RoPE, `n_pos_per_embd > 1`) model such
|
|
94
|
-
as Qwen3.5, the SDK's KV sliding-window position shift (`seq_add`/`seq_div` via
|
|
95
|
-
`trySlidePrefill`) GGML_ASSERT-crashes the worker when the context fills. A
|
|
96
|
-
standalone SDK repro confirmed this is **independent of `toolsMode` (static and
|
|
97
|
-
dynamic both crash) and of whether tools are passed**, and is not caused by the
|
|
98
|
-
prompt cache — it is the sliding window itself, unsupported for multi-pos
|
|
99
|
-
models. Interim mitigation (separate from the kill-switch): we temporarily
|
|
100
|
-
stopped setting `n_discarded` at model load so on overflow the SDK took the
|
|
101
|
-
catchable `CONTEXT_OVERFLOW` path instead of the crashing slide.
|
|
102
|
-
**Resolved (2026-06-23):** `@qvac/sdk` 0.13.5 (llm-llamacpp 0.24.0) ships
|
|
103
|
-
M-RoPE/iM-RoPE sliding-context support, so `n_discarded` is set again in
|
|
104
|
-
`@qvac/sdk → model/model-load-config.ts` and the workbench no longer trims history. See
|
|
105
|
-
[`2026-06-23-append-only-history.md`](./2026-06-23-append-only-history.md).
|
|
106
|
-
- Regression guards updated: the contract test now asserts `kvCache` IS sent
|
|
107
|
-
with tools; the spec-fake models dynamic mode (healthy on a tools cache hit);
|
|
108
|
-
invalidation hooks are covered.
|
|
109
|
-
- **The in-repo suite cannot prove real dynamic-mode coherence.** The spec-fake
|
|
110
|
-
(`@qvac/sdk → __fixtures__/fake-completion-sdk.ts`) is parameterized to treat dynamic mode
|
|
111
|
-
as healthy, so the in-repo tests guard against regressing the model load back
|
|
112
|
-
to static mode and against dropping the `kvCache` key — but they do not prove
|
|
113
|
-
the SDK's real DYNAMIC path stays coherent against our reconstructed history.
|
|
114
|
-
That proof lives in the out-of-repo SDK e2e (`kv-cache-tools-dynamic-reuse`
|
|
115
|
-
and its qwen35 variant), which this repo's CI cannot run.
|
|
116
|
-
|
|
117
|
-
## When Agents Should Read This
|
|
118
|
-
|
|
119
|
-
Read this before changing how `kvCache` is passed to `sdk.completion`,
|
|
120
|
-
`toolsMode` at model load, the agentic loop's history construction, transcript
|
|
121
|
-
reconstruction, or the cache invalidation hooks —
|
|
122
|
-
`qvac-app → core/src/services/message/agentic-completion.ts`,
|
|
123
|
-
`qvac-app → core/src/services/model/service.ts`,
|
|
124
|
-
`qvac-app → core/src/services/execution/service.ts`,
|
|
125
|
-
`qvac-app → core/src/services/api/domain-apis.ts`,
|
|
126
|
-
`@qvac/core → lib/assistant.ts`, and
|
|
127
|
-
`lib/capabilities/completion.ts`.
|