@qvac/harness 0.2.6 → 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 (174) hide show
  1. package/.claude/skills/gh-stack/SKILL.md +24 -24
  2. package/.claude/skills/gh-stack/references/commands.md +2 -2
  3. package/.claude/skills/js-house-style/SKILL.md +89 -68
  4. package/.claude/skills/kvcache/SKILL.md +7 -7
  5. package/.claude/skills/pr-create/SKILL.md +58 -15
  6. package/.claude/skills/pr-review/SKILL.md +307 -41
  7. package/CLAUDE.md +3 -0
  8. package/README.md +26 -4
  9. package/dist/harness.bundle +12 -10
  10. package/dist-lib/index.js +58 -58
  11. package/dist-lib/lib/audio-transcode-bare.js +2 -2
  12. package/dist-lib/lib/capabilities/completion.js +30 -30
  13. package/dist-lib/lib/capabilities/download.js +2 -2
  14. package/dist-lib/lib/capabilities/speech-text.js +1 -1
  15. package/dist-lib/lib/capabilities/tool-prompt-log.js +1 -1
  16. package/dist-lib/lib/capabilities/tool-selection.js +2 -2
  17. package/dist-lib/lib/capabilities/tool-turns.js +1 -1
  18. package/dist-lib/lib/capabilities/usage-breakdown.js +1 -1
  19. package/dist-lib/lib/capabilities/voice.js +3 -3
  20. package/dist-lib/lib/connect.js +11 -11
  21. package/dist-lib/lib/context-window.js +1 -1
  22. package/dist-lib/lib/docx/index.d.ts +5 -0
  23. package/dist-lib/lib/docx/index.js +49 -0
  24. package/dist-lib/lib/hardware/capability-probe.js +3 -3
  25. package/dist-lib/lib/hardware/gpu-probe-addon.js +2 -2
  26. package/dist-lib/lib/hardware/gpu-probe-entry.js +1 -1
  27. package/dist-lib/lib/helpers/markup-stream-filter.js +1 -1
  28. package/dist-lib/lib/helpers/run-outcome.js +1 -1
  29. package/dist-lib/lib/helpers/spawn-path.d.ts +1 -1
  30. package/dist-lib/lib/helpers/thinking.js +2 -2
  31. package/dist-lib/lib/helpers/tool-intent.js +1 -1
  32. package/dist-lib/lib/helpers/tool-label.js +3 -3
  33. package/dist-lib/lib/hooks/built-in/arg-repair.d.ts +1 -1
  34. package/dist-lib/lib/hooks/built-in/arg-repair.js +1 -1
  35. package/dist-lib/lib/hooks/built-in/egress.d.ts +1 -1
  36. package/dist-lib/lib/hooks/built-in/egress.js +2 -2
  37. package/dist-lib/lib/hooks/built-in/index.js +3 -3
  38. package/dist-lib/lib/hooks/built-in/skill-catalog.d.ts +1 -1
  39. package/dist-lib/lib/hooks/pipeline.js +4 -4
  40. package/dist-lib/lib/hooks/registry.js +1 -1
  41. package/dist-lib/lib/hooks/run.js +1 -1
  42. package/dist-lib/lib/models/model-catalog.js +4 -4
  43. package/dist-lib/lib/models/model-delete.js +1 -1
  44. package/dist-lib/lib/models/model-handle.js +1 -1
  45. package/dist-lib/lib/models/model-list.js +1 -1
  46. package/dist-lib/lib/models/model-pool.js +8 -8
  47. package/dist-lib/lib/models/model-source.js +1 -1
  48. package/dist-lib/lib/models/music-catalog.d.ts +1 -1
  49. package/dist-lib/lib/no-redirect-fetch-bare.js +3 -3
  50. package/dist-lib/lib/no-redirect-fetch-impl.js +1 -1
  51. package/dist-lib/lib/no-redirect-fetch-node.js +2 -2
  52. package/dist-lib/lib/resolution-detector.js +1 -1
  53. package/dist-lib/lib/sdk-loader.js +2 -2
  54. package/dist-lib/lib/sdk-storage.js +1 -1
  55. package/dist-lib/lib/serve.js +10 -10
  56. package/dist-lib/lib/spawn.js +3 -3
  57. package/dist-lib/lib/staging.js +1 -1
  58. package/dist-lib/lib/tools/catalog.js +1 -1
  59. package/dist-lib/lib/tools/create-documents.js +2 -2
  60. package/dist-lib/lib/tools/edit-documents.js +2 -2
  61. package/dist-lib/lib/tools/edit-image.js +2 -2
  62. package/dist-lib/lib/tools/exec/adapters/python/attachments.js +2 -2
  63. package/dist-lib/lib/tools/exec/adapters/python/create-effect.js +1 -1
  64. package/dist-lib/lib/tools/exec/adapters/python/loader.js +1 -1
  65. package/dist-lib/lib/tools/exec/adapters/python/memfs.js +2 -2
  66. package/dist-lib/lib/tools/exec/adapters/python/pyodide.js +8 -6
  67. package/dist-lib/lib/tools/exec/adapters/python/python-adapter.js +3 -3
  68. package/dist-lib/lib/tools/exec/adapters/python/run-session.js +5 -5
  69. package/dist-lib/lib/tools/exec/adapters/python/wheels.js +3 -2
  70. package/dist-lib/lib/tools/exec/adapters/python/worker/client.js +4 -4
  71. package/dist-lib/lib/tools/exec/adapters/python/worker/entry.js +4 -4
  72. package/dist-lib/lib/tools/exec/adapters/shell/shell-adapter.js +1 -1
  73. package/dist-lib/lib/tools/exec/obsidian/cli-process.js +2 -2
  74. package/dist-lib/lib/tools/exec/obsidian/cli.js +4 -4
  75. package/dist-lib/lib/tools/exec/obsidian/command.js +1 -1
  76. package/dist-lib/lib/tools/exec/obsidian/route.js +7 -7
  77. package/dist-lib/lib/tools/exec/obsidian/vault-create.js +2 -2
  78. package/dist-lib/lib/tools/exec/obsidian/vault-fs.js +6 -6
  79. package/dist-lib/lib/tools/exec/obsidian/vault-paths.js +2 -2
  80. package/dist-lib/lib/tools/exec/obsidian/vault-read.js +1 -1
  81. package/dist-lib/lib/tools/exec/obsidian/vault-write.js +2 -2
  82. package/dist-lib/lib/tools/exec/runner.js +1 -1
  83. package/dist-lib/lib/tools/exec.js +13 -13
  84. package/dist-lib/lib/tools/file-glob.js +3 -3
  85. package/dist-lib/lib/tools/fs/local.js +1 -1
  86. package/dist-lib/lib/tools/fs/remote.js +1 -1
  87. package/dist-lib/lib/tools/fs/walk.js +2 -2
  88. package/dist-lib/lib/tools/generate-image.js +4 -4
  89. package/dist-lib/lib/tools/generate-music.js +6 -6
  90. package/dist-lib/lib/tools/grep.js +6 -6
  91. package/dist-lib/lib/tools/http-request/truncate-response.js +1 -1
  92. package/dist-lib/lib/tools/http-request.js +9 -9
  93. package/dist-lib/lib/tools/mcp/utils.js +1 -1
  94. package/dist-lib/lib/tools/mcp-call.js +8 -8
  95. package/dist-lib/lib/tools/operations/builders.d.ts +1 -1
  96. package/dist-lib/lib/tools/operations/builders.js +1 -1
  97. package/dist-lib/lib/tools/operations/compile.js +1 -1
  98. package/dist-lib/lib/tools/operations/manifest.d.ts +16 -16
  99. package/dist-lib/lib/tools/operations/manifest.js +1 -1
  100. package/dist-lib/lib/tools/outbound-guard.js +1 -1
  101. package/dist-lib/lib/tools/rag-search.js +1 -1
  102. package/dist-lib/lib/tools/read-files.js +3 -3
  103. package/dist-lib/lib/tools/skill.js +8 -8
  104. package/dist-lib/lib/tools/skills/auth-resolver.js +2 -2
  105. package/dist-lib/lib/tools/skills/bin-check.js +1 -1
  106. package/dist-lib/lib/tools/skills/bundled-dir.js +2 -2
  107. package/dist-lib/lib/tools/skills/catalog.js +10 -10
  108. package/dist-lib/lib/tools/skills/cli-schema-check.js +1 -1
  109. package/dist-lib/lib/tools/skills/cli-schema-load.js +1 -1
  110. package/dist-lib/lib/tools/skills/cli-schema.js +4 -4
  111. package/dist-lib/lib/tools/skills/credential-check.js +3 -3
  112. package/dist-lib/lib/tools/skills/credential-grants.js +1 -1
  113. package/dist-lib/lib/tools/skills/credential-health.js +2 -2
  114. package/dist-lib/lib/tools/skills/frontmatter-parser.js +1 -1
  115. package/dist-lib/lib/tools/skills/index.js +5 -5
  116. package/dist-lib/lib/tools/skills/mention.js +1 -1
  117. package/dist-lib/lib/tools/skills/overlay-crud.js +1 -1
  118. package/dist-lib/lib/tools/skills/setup-routes.js +1 -1
  119. package/dist-lib/lib/tools/skills/skill-settings.js +1 -1
  120. package/dist-lib/lib/tools/skills/skill-state-constants.d.ts +8 -8
  121. package/dist-lib/lib/tools/skills/skill-state.js +9 -9
  122. package/dist-lib/lib/tools/toolsets.d.ts +1 -1
  123. package/dist-lib/lib/tools/url-guard.js +2 -2
  124. package/dist-lib/lib/tools/weather-lookup/place.d.ts +11 -0
  125. package/dist-lib/lib/tools/weather-lookup/place.js +152 -0
  126. package/dist-lib/lib/tools/weather-lookup/report.d.ts +37 -0
  127. package/dist-lib/lib/tools/weather-lookup/report.js +56 -0
  128. package/dist-lib/lib/tools/weather-lookup.d.ts +8 -0
  129. package/dist-lib/lib/tools/weather-lookup.js +110 -0
  130. package/dist-lib/lib/tools/web-fetch.js +10 -10
  131. package/dist-lib/lib/tools/web-search.js +2 -2
  132. package/dist-lib/lib/transport.js +1 -1
  133. package/dist-lib/lib/validating-lookup.js +1 -1
  134. package/dist-lib/lib/web-fetch/embedded-state.js +1 -1
  135. package/dist-lib/lib/web-fetch/extract.js +1 -1
  136. package/dist-lib/lib/web-fetch/images.js +1 -1
  137. package/dist-lib/lib/web-fetch/video.js +1 -1
  138. package/dist-lib/lib/web-search/engine-registry.js +7 -7
  139. package/dist-lib/lib/web-search/engines/aol.js +3 -3
  140. package/dist-lib/lib/web-search/engines/bing.js +4 -4
  141. package/dist-lib/lib/web-search/engines/brave.js +2 -2
  142. package/dist-lib/lib/web-search/engines/duckduckgo.js +3 -3
  143. package/dist-lib/lib/web-search/engines/google.js +4 -4
  144. package/dist-lib/lib/web-search/engines/wikipedia.js +2 -2
  145. package/dist-lib/lib/web-search/engines/yahoo.js +3 -3
  146. package/dist-lib/lib/web-search/fetch-html.js +1 -1
  147. package/dist-lib/lib/web-search/merge-results.js +1 -1
  148. package/dist-lib/lib/web-search/orchestrator.js +3 -3
  149. package/dist-lib/schema/baseline.js +1 -1
  150. package/dist-lib/schema/build.js +3 -3
  151. package/dist-lib/schema/release-tags.d.ts +2 -1
  152. package/dist-lib/schema/release-tags.js +9 -7
  153. package/docs/decisions/2026-05-29-kvcache-tool-turns.md +75 -0
  154. package/docs/decisions/2026-06-02-kvcache-tool-turns-reenabled.md +127 -0
  155. package/docs/decisions/2026-06-23-android-cpu-llm-defaults.md +126 -0
  156. package/docs/decisions/2026-07-29-assistant-owned-storage-layout.md +88 -0
  157. package/docs/decisions/2026-08-04-sidecar-orphan-reaping.md +79 -0
  158. package/docs/decisions/2026-08-11-pyodide-worker-thread.md +68 -0
  159. package/docs/decisions/2026-08-17-harness-hook-pipeline.md +84 -0
  160. package/docs/decisions/2026-08-17-harness-outbound-egress-guard.md +176 -0
  161. package/docs/decisions/2026-08-20-chat-prompt-prefix-is-stable.md +95 -0
  162. package/docs/decisions/2026-08-20-kvcache-static-placement.md +109 -0
  163. package/docs/decisions/2026-08-21-vendored-python-wheels.md +85 -0
  164. package/docs/decisions/2026-08-26-exec-is-not-gated-by-skills.md +64 -0
  165. package/docs/decisions/2026-08-27-writable-skills-overlay.md +56 -0
  166. package/docs/decisions/2026-09-01-skill-operation-manifests.md +53 -0
  167. package/docs/decisions/2026-09-01-usage-breakdown-is-measured.md +78 -0
  168. package/docs/decisions/2026-09-03-usage-breakdown-reported-by-the-harness.md +136 -0
  169. package/docs/decisions/2026-09-10-skill-wording-is-measured-offline.md +63 -0
  170. package/docs/decisions/2026-09-16-a-lookup-reports-the-place-it-resolved.md +70 -0
  171. package/docs/decisions/INDEX.md +40 -0
  172. package/docs/decisions/TEMPLATE.md +32 -0
  173. package/package.json +21 -4
  174. package/worker/harness-sidecar.mjs +2 -0
@@ -7,7 +7,7 @@ description: >
7
7
  branch layers, dependent PRs, or gh stack; or when a stack is checked out.
8
8
  metadata:
9
9
  author: github
10
- version: "0.1.0"
10
+ version: '0.1.0'
11
11
  ---
12
12
 
13
13
  # gh-stack
@@ -45,16 +45,16 @@ Agent harnesses differ, so always pass the flags below instead of relying on tha
45
45
  `--remote <name>` unless `remote.pushDefault` is configured. `checkout` and `trunk` have no
46
46
  `--remote` flag and require the config.
47
47
 
48
- | Always run | Never run bare | Why |
49
- |---|---|---|
50
- | `gh stack view --json` | `gh stack view` | opens a TUI under a PTY |
51
- | `gh stack submit --auto` | `gh stack submit` | prompts for a title per new PR |
52
- | `gh stack merge <target> --yes` | `gh pr merge` | `gh pr merge` cannot merge a stack |
53
- | `gh stack init <branch>...` | `gh stack init` | prompts for branch names |
54
- | `gh stack add <branch>` | `gh stack add` | prompts for a name, and fails even when piped |
55
- | `gh stack checkout <target>` | `gh stack checkout` | opens a selection menu |
56
- | `gh stack up` / `down` / `top` / `bottom` | `gh stack switch` | `switch` is menu-only |
57
- | — | `gh stack modify` | TUI-only, no non-interactive path |
48
+ | Always run | Never run bare | Why |
49
+ | ----------------------------------------- | ------------------- | --------------------------------------------- |
50
+ | `gh stack view --json` | `gh stack view` | opens a TUI under a PTY |
51
+ | `gh stack submit --auto` | `gh stack submit` | prompts for a title per new PR |
52
+ | `gh stack merge <target> --yes` | `gh pr merge` | `gh pr merge` cannot merge a stack |
53
+ | `gh stack init <branch>...` | `gh stack init` | prompts for branch names |
54
+ | `gh stack add <branch>` | `gh stack add` | prompts for a name, and fails even when piped |
55
+ | `gh stack checkout <target>` | `gh stack checkout` | opens a selection menu |
56
+ | `gh stack up` / `down` / `top` / `bottom` | `gh stack switch` | `switch` is menu-only |
57
+ | — | `gh stack modify` | TUI-only, no non-interactive path |
58
58
 
59
59
  - `view --short` is safe in both modes, but it is formatted for humans. Use `--json` to parse.
60
60
  - **`checkout <pr>` when a different local stack already covers those branches** cannot be forced.
@@ -138,19 +138,19 @@ an ancestor of the branch.
138
138
 
139
139
  ## Exit codes
140
140
 
141
- | Code | Meaning | Recovery |
142
- |---|---|---|
143
- | 0 | Success | — |
144
- | 1 | Generic error | Read stderr |
145
- | 2 | Not in a stack | `gh stack init`, or `gh stack checkout <target>` |
146
- | 3 | Rebase conflict | Follow the Exit 3 recovery below |
147
- | 4 | GitHub API failure | Check `gh auth status`, retry |
148
- | 5 | Invalid arguments | Fix the invocation; see `<command> --help` |
149
- | 6 | Disambiguation required | Branch is in several stacks; check out a non-shared branch |
150
- | 7 | Rebase already in progress | `gh stack rebase --continue` or `--abort` |
151
- | 8 | Stack file locked | Another `gh stack` process is writing; retry after ~5s |
152
- | 9 | Stacked PRs unavailable | Not enabled on the repository; tell the user |
153
- | 10 | Modify recovery required | `gh stack modify --abort` |
141
+ | Code | Meaning | Recovery |
142
+ | ---- | -------------------------- | ---------------------------------------------------------- |
143
+ | 0 | Success | — |
144
+ | 1 | Generic error | Read stderr |
145
+ | 2 | Not in a stack | `gh stack init`, or `gh stack checkout <target>` |
146
+ | 3 | Rebase conflict | Follow the Exit 3 recovery below |
147
+ | 4 | GitHub API failure | Check `gh auth status`, retry |
148
+ | 5 | Invalid arguments | Fix the invocation; see `<command> --help` |
149
+ | 6 | Disambiguation required | Branch is in several stacks; check out a non-shared branch |
150
+ | 7 | Rebase already in progress | `gh stack rebase --continue` or `--abort` |
151
+ | 8 | Stack file locked | Another `gh stack` process is writing; retry after ~5s |
152
+ | 9 | Stacked PRs unavailable | Not enabled on the repository; tell the user |
153
+ | 10 | Modify recovery required | `gh stack modify --abort` |
154
154
 
155
155
  **Exit 3 recovery:**
156
156
 
@@ -67,7 +67,7 @@ first non-merged ancestor, then links them into a Stack on GitHub.
67
67
  the title and its body as the PR body. A branch with multiple commits humanizes the branch name
68
68
  (hyphens and underscores become spaces). There is no flag for a custom title or body; use
69
69
  `gh pr edit` afterwards.
70
- - `--open` marks new *and existing* PRs ready for review; without it new PRs are drafts.
70
+ - `--open` marks new _and existing_ PRs ready for review; without it new PRs are drafts.
71
71
  - Requires stacked PRs to be enabled on the repository. If not, `submit` exits **9** when
72
72
  non-interactive (under a TTY it offers to create ordinary unstacked PRs instead).
73
73
 
@@ -172,6 +172,6 @@ Removes the stack **grouping** only. It never deletes pull requests or branches.
172
172
 
173
173
  `up`, `down`, `top`, `bottom`, and `trunk` are always non-interactive. `up` and `down` accept a
174
174
  count (`gh stack up 3`). Movement clamps at the stack bounds, and merged branches are skipped when
175
- navigating from an active branch, so `bottom` lands on the lowest *unmerged* branch.
175
+ navigating from an active branch, so `bottom` lands on the lowest _unmerged_ branch.
176
176
 
177
177
  `gh stack switch` is a selection menu with no non-interactive path. Use the commands above instead.
@@ -12,7 +12,7 @@ The most important rule comes first because it governs all the others:
12
12
 
13
13
  **Don't follow rules without thinking. Make the best code you can, always.**
14
14
 
15
- Everything below is a default with a reason attached. Defaults exist so you don't re-derive them every time, and so a codebase reads consistently. But a default you can't justify in the moment is a default you should question. Important things are made well and thoughtfully — the judgment about *when a rule earns its place* is the actual skill. The rest is scaffolding for that judgment.
15
+ Everything below is a default with a reason attached. Defaults exist so you don't re-derive them every time, and so a codebase reads consistently. But a default you can't justify in the moment is a default you should question. Important things are made well and thoughtfully — the judgment about _when a rule earns its place_ is the actual skill. The rest is scaffolding for that judgment.
16
16
 
17
17
  If you internalize five things:
18
18
 
@@ -34,7 +34,7 @@ It is **not** the place for workflow. Commit, branch-naming, and push/PR-deliver
34
34
 
35
35
  For anything Bare loads, set **`"erasableSyntaxOnly": true`** in the tsconfig (already the case in this repo's `tsconfig.json`). Bare strips types rather than compiling them, so only erasable syntax survives: no `enum`, no `namespace`, no parameter properties (`constructor (private x)`), no `declare` class fields. `erasableSyntaxOnly` makes `tsc` reject those at build time instead of letting Bare fail to load the module at runtime — which presents as an opaque load failure you have to bisect file-by-file. Use plain `const` objects (`as const`) instead of `enum`, and assign fields in the constructor body.
36
36
 
37
- Exports follow what the code *is*, not which runtime it targets:
37
+ Exports follow what the code _is_, not which runtime it targets:
38
38
 
39
39
  - **Product source** — `lib/`, `worker/`, `schema/` — **named exports**, one concept per file.
40
40
  - **Standalone publishable modules** — a holepunch-style module that stands on its own and could be `npm i`-ed — a **single default export**, per the module anatomy below.
@@ -46,16 +46,16 @@ When in doubt you are writing product source; the default export is for the modu
46
46
  Before structure comes direction. The way we approach a problem (mafintosh's framing, which the whole way of working is built on):
47
47
 
48
48
  - Start from the **great ambition** — the real thing you want to exist.
49
- - Plan *backwards* into small, self-contained pieces. Sort them topologically.
49
+ - Plan _backwards_ into small, self-contained pieces. Sort them topologically.
50
50
  - Build the **smallest piece that most efficiently validates the rest** first. Scope it down until the first iteration is a couple of days. Then execute immediately — never stall, never wait for a grand plan to be complete.
51
51
  - Lineage example: "unstoppable P2P file sharing" → Hypercore → "address Merkle-tree nodes efficiently" → `flat-tree`, built day 0. Each layer is a small publishable module that stands on its own.
52
52
 
53
53
  Two failure modes this guards against, both worth catching in review:
54
54
 
55
- - **Working hard in the wrong direction.** Effort mimics progress — tasks closed, bugs fixed — while the direction is wrong. Run the complexity check continuously: *is this the right way, and is there a simpler way to execute the ambition?* Sunk cost is real. Cut early.
55
+ - **Working hard in the wrong direction.** Effort mimics progress — tasks closed, bugs fixed — while the direction is wrong. Run the complexity check continuously: _is this the right way, and is there a simpler way to execute the ambition?_ Sunk cost is real. Cut early.
56
56
  - **Scope creep.** It kills projects. Small modules are the antidote — each is independently finishable, testable, replaceable.
57
57
 
58
- On using AI to do this work (heed it, since this skill is often *for* an AI): AI with human comprehension is powerful; without it, quickly destructive. **Don't generate a subsystem you can't evaluate.** If you don't know what *good* looks like for the thing (the canonical cautionary tale: a BBR congestion-control implementation that looked fine and wasn't), generating it costs more than it saves. Build the comprehension first, or get it from someone who has it, then generate.
58
+ On using AI to do this work (heed it, since this skill is often _for_ an AI): AI with human comprehension is powerful; without it, quickly destructive. **Don't generate a subsystem you can't evaluate.** If you don't know what _good_ looks like for the thing (the canonical cautionary tale: a BBR congestion-control implementation that looked fine and wasn't), generating it costs more than it saves. Build the comprehension first, or get it from someone who has it, then generate.
59
59
 
60
60
  Practical upshot: bias to the smallest correct thing, make it composable, move on.
61
61
 
@@ -80,17 +80,17 @@ hypercore → hyperbee → hyperdb → autobee
80
80
  on the bee)
81
81
  ```
82
82
 
83
- Read those left to right: each arrow is "built *on*, not instead of." `hyperschema` doesn't invent its own serialization — it generates `compact-encoding` codecs. `hrpc` doesn't invent a message format — it speaks `hyperschema`. `hyperbee` doesn't invent storage — it's a B-tree over a `hypercore`. `hyperdb` doesn't invent a tree — it's collections and indexes over a `hyperbee`. `autobee` doesn't invent consensus from scratch — it's a `hyperbee` made multiwriter through `autobase`.
83
+ Read those left to right: each arrow is "built _on_, not instead of." `hyperschema` doesn't invent its own serialization — it generates `compact-encoding` codecs. `hrpc` doesn't invent a message format — it speaks `hyperschema`. `hyperbee` doesn't invent storage — it's a B-tree over a `hypercore`. `hyperdb` doesn't invent a tree — it's collections and indexes over a `hyperbee`. `autobee` doesn't invent consensus from scratch — it's a `hyperbee` made multiwriter through `autobase`.
84
84
 
85
85
  What this means when you write code:
86
86
 
87
87
  - **Find the lowest layer that already solves your sub-problem and build the thin next layer on it.** Before writing storage, ask which of hypercore/hyperbee/hyperdb already gives you 90% of it. You are almost never starting from zero — you're adding one sharp capability to an existing primitive.
88
88
  - **Each layer is independently perfect and independently usable.** Someone who needs just the wire format uses `compact-encoding` and never sees `hrpc`. Don't bundle concerns upward — a lower layer must never depend on a higher one, and must be useful on its own.
89
- - **Don't reach around a layer.** If you're on `hyperdb` and find yourself poking at the underlying `hypercore` directly to do something the bee/db layer should express, that's a smell: either the layer is missing a capability (add it *there*, in its own module) or you're at the wrong layer.
90
- - **Don't collapse layers "for convenience."** A module that does encoding *and* storage *and* RPC is the thing this style exists to prevent. Three modules, each perfect, composed. The seams are the feature — they're where reuse, testing, and replacement happen.
89
+ - **Don't reach around a layer.** If you're on `hyperdb` and find yourself poking at the underlying `hypercore` directly to do something the bee/db layer should express, that's a smell: either the layer is missing a capability (add it _there_, in its own module) or you're at the wrong layer.
90
+ - **Don't collapse layers "for convenience."** A module that does encoding _and_ storage _and_ RPC is the thing this style exists to prevent. Three modules, each perfect, composed. The seams are the feature — they're where reuse, testing, and replacement happen.
91
91
  - **Composition is by passing the lower thing in.** A higher module takes the primitive it builds on as a constructor argument (the `store`, the `core`, the `bee`), it doesn't construct it secretly or hard-wire it. That's what keeps layers swappable and testable.
92
92
 
93
- When you reach for a new capability, the instinct is: *which primitive is this one honest step above, and what is the smallest module that adds exactly that step?*
93
+ When you reach for a new capability, the instinct is: _which primitive is this one honest step above, and what is the smallest module that adds exactly that step?_
94
94
 
95
95
  ---
96
96
 
@@ -102,17 +102,17 @@ A well-shaped module is a single default export, an options bag, cheap construct
102
102
  const b4a = require('b4a')
103
103
 
104
104
  module.exports = class Thing {
105
- constructor (store, opts = {}) {
105
+ constructor(store, opts = {}) {
106
106
  this.store = store
107
107
  this.foo = opts.foo ?? defaultFoo
108
108
  this._internal = null // declare every field here, even if null
109
109
  }
110
110
 
111
- async open () {
111
+ async open() {
112
112
  this._internal = await this.store.get()
113
113
  }
114
114
 
115
- async doWork (arg) {
115
+ async doWork(arg) {
116
116
  // ...
117
117
  }
118
118
  }
@@ -120,13 +120,13 @@ module.exports = class Thing {
120
120
 
121
121
  The conventions, each with its reason:
122
122
 
123
- - **One thing per module, exported once.** A standalone publishable module is a single default export; product source uses named exports, one concept per file. Either way, a second unrelated export is usually a sign you have two modules. *Reason: composition is by importing the thing, and one-concept modules slot together predictably.*
124
- - **`(required, opts = {})` constructor.** Positional for the one or two things the module fundamentally *is* (the store, the key, the core); everything else in the options bag with `??` defaults. *Reason: the signature documents what's essential vs. configurable.*
125
- - **Declare all instance fields in the constructor**, even `null` ones. *Reason: a reader sees the whole shape in one place, and the engine keeps a stable hidden class.*
126
- - **No async work in the constructor.** Construction is synchronous and cheap; async setup happens in an `open`/`ready` step, lazily. *Reason: constructors can't await, and an object that's secretly half-built is a footgun.*
123
+ - **One thing per module, exported once.** A standalone publishable module is a single default export; product source uses named exports, one concept per file. Either way, a second unrelated export is usually a sign you have two modules. _Reason: composition is by importing the thing, and one-concept modules slot together predictably._
124
+ - **`(required, opts = {})` constructor.** Positional for the one or two things the module fundamentally _is_ (the store, the key, the core); everything else in the options bag with `??` defaults. _Reason: the signature documents what's essential vs. configurable._
125
+ - **Declare all instance fields in the constructor**, even `null` ones. _Reason: a reader sees the whole shape in one place, and the engine keeps a stable hidden class._
126
+ - **No async work in the constructor.** Construction is synchronous and cheap; async setup happens in an `open`/`ready` step, lazily. _Reason: constructors can't await, and an object that's secretly half-built is a footgun._
127
127
  - **Opts pass down.** An options bag lets a caller configure without touching the signature, and lets a parent hand its opts to the child it constructs.
128
128
  ```js
129
- function aThing (somethingSuperImportantLikeACorestore, opts = {}) {
129
+ function aThing(somethingSuperImportantLikeACorestore, opts = {}) {
130
130
  const { signal } = opts
131
131
  this._otherThing = new OtherThing(opts)
132
132
  }
@@ -134,12 +134,12 @@ The conventions, each with its reason:
134
134
  - **Functions under ~20 lines**, one responsibility, early returns over nesting. Flat over nested; group by feature, not by type. No `utils.ts` dumping grounds.
135
135
  - **Split on domains, never on line count.** Splitting before there's a real seam buys import graphs, indirection, and shared-state plumbing in exchange for nothing. But a long file is only legitimate once everything separable has actually been taken out of it.
136
136
 
137
- `@qvac/core`'s `lib/core/mesh.ts` is the worked example. It's ~680 lines, and it should stay one file — but only because each domain it touches already lives somewhere else:
137
+ `@qvac/core → lib/core/mesh.ts` is the worked example. It's ~680 lines, and it should stay one file — but only because each domain it touches already lives somewhere else:
138
138
 
139
139
  ```ts
140
- import { syncChanges } from './sync/index.ts' // per-domain sync: knowledge, chunks, devices, chats
141
- import { registerRoutes } from './contract/index.ts' // the RPC surface
142
- import { createSyncActivity } from './sync-status.ts' // activity tracking
140
+ import { syncChanges } from './sync/index.ts' // per-domain sync: knowledge, chunks, devices, chats
141
+ import { registerRoutes } from './contract/index.ts' // the RPC surface
142
+ import { createSyncActivity } from './sync-status.ts' // activity tracking
143
143
  ```
144
144
 
145
145
  `sync/` in turn is five files of 8–122 lines, one per domain, because keeping them together would blur independent implementations into one.
@@ -148,16 +148,17 @@ The conventions, each with its reason:
148
148
 
149
149
  So the 443 lines are **residual, not undivided** — what's left is mesh lifecycle itself, which has no seam that isn't arbitrary. That's the test in both directions: a long file is fine if you can't name a domain still trapped inside it, and a split is right the moment you can name one.
150
150
 
151
- **Split by domain, not by operation.** The same file's `blob-sweep.ts` is the counter-example — it carves out one *verb* on blobs, so blob logic ends up spread across files that each own a fraction of it. Blobs are one domain: `blobs.ts`, holding sweep alongside the rest of blob handling. If the filename is a verb phrase, you probably split an operation off its domain rather than splitting two domains apart.
151
+ **Split by domain, not by operation.** The same file's `blob-sweep.ts` is the counter-example — it carves out one _verb_ on blobs, so blob logic ends up spread across files that each own a fraction of it. Blobs are one domain: `blobs.ts`, holding sweep alongside the rest of blob handling. If the filename is a verb phrase, you probably split an operation off its domain rather than splitting two domains apart.
152
152
 
153
153
  Corollary: a file growing past what you can hold in your head is a prompt to ask whether a second domain crept in — not a mandate to cut it at a line number.
154
+
154
155
  - **Match the surrounding project on CommonJS vs ESM.** Read the package and be consistent within it. Don't mix in one package without a reason.
155
156
 
156
157
  ### Parameters
157
158
 
158
159
  The constructor rule above — one or two positional things, everything else in an options bag — is not special to constructors. It is how every function takes arguments.
159
160
 
160
- Positional is for the one or two things the function fundamentally acts *on*. Everything else goes in an object, because at the call site an object labels itself and a position doesn't.
161
+ Positional is for the one or two things the function fundamentally acts _on_. Everything else goes in an object, because at the call site an object labels itself and a position doesn't.
161
162
 
162
163
  ```ts
163
164
  // no — eight positional args, and three adjacent numbers that all accept each other
@@ -175,7 +176,7 @@ frameProgress(update, {
175
176
  })
176
177
  ```
177
178
 
178
- **The trigger is ambiguity, not arity.** Three parameters of three distinct types read fine. *Two* adjacent parameters of the same type are already a hazard — swap them and it compiles, runs, and quietly produces a wrong answer that no test you thought to write will catch. Count makes it worse; sameness makes it dangerous.
179
+ **The trigger is ambiguity, not arity.** Three parameters of three distinct types read fine. _Two_ adjacent parameters of the same type are already a hazard — swap them and it compiles, runs, and quietly produces a wrong answer that no test you thought to write will catch. Count makes it worse; sameness makes it dangerous.
179
180
 
180
181
  - **Same-typed neighbours go in an object.** Especially numbers, and especially sizes/offsets/indexes, which are indistinguishable to the compiler and to the reader.
181
182
  - **Never take a positional boolean.** `render(node, true, false)` is unreadable at the call site. Use named options — and prefer that the flag not exist at all.
@@ -200,26 +201,32 @@ Types are the cheapest contract you get. Keep them honest at the boundary and th
200
201
  - **Explicit return types on exported functions.** `readonly` on object properties, `ReadonlyArray<T>` over `T[]`.
201
202
  - Test files may use narrow casts for mock wiring or intentionally invalid fixtures. Keep them local; never copy into production code.
202
203
 
203
- This is the typed counterpart of *don't program defensively* below — the type checker enforces the contract statically, so you don't re-check it at runtime.
204
+ This is the typed counterpart of _don't program defensively_ below — the type checker enforces the contract statically, so you don't re-check it at runtime.
204
205
 
205
206
  ### Comments and whitespace
206
207
 
207
208
  Well-structured code in small modules with honest names does not need narration. Comments are the exception, not the habit.
208
209
 
209
- - **Prefer no comment.** If a comment explains *what* the code does, delete it and fix the name. The API, the README, and the tests are the documentation.
210
+ - **Prefer no comment.** If a comment explains _what_ the code does, delete it and fix the name. The API, the README, and the tests are the documentation.
210
211
  - **One line, maximum.** A comment that needs a second line is a sign the code needs a seam, not a paragraph. Never write a comment block.
211
- - **Only ever explain *why*** — a non-obvious constraint, a workaround with a reason, why an operational error is safe to swallow.
212
+ - **Only ever explain _why_** — a non-obvious constraint, a workaround with a reason, why an operational error is safe to swallow.
212
213
  - **Long AI-generated comment blocks are an anti-pattern.** They restate the obvious, drift out of date silently, and mislead the next reader. Don't emit them.
214
+
213
215
  ### Whitespace groups the code
214
216
 
215
- Blank lines are the structure a reader navigates by. They are not padding, and removing them is not tidiness — an unbroken wall of statements is *harder* to read than the same code grouped, because nothing signals where one thought ends and the next begins.
217
+ Blank lines are the structure a reader navigates by. They are not padding, and removing them is not tidiness — an unbroken wall of statements is _harder_ to read than the same code grouped, because nothing signals where one thought ends and the next begins.
216
218
 
217
219
  Group by what the code is doing: setup, then action, then the assertions or effects that follow from it. A new group starts wherever the reader's question changes.
218
220
 
219
221
  ```js
220
222
  // no — one wall, no seam between setup, the four scenarios, or their assertions
221
223
  const { ragSearch } = await toolModeApi({ ragEnabled: false })
222
- const tool = await ragSearch({ chatId: 'chat-1', query: 'alpha fuel pressure', mode: 'tool', limit: 5 })
224
+ const tool = await ragSearch({
225
+ chatId: 'chat-1',
226
+ query: 'alpha fuel pressure',
227
+ mode: 'tool',
228
+ limit: 5
229
+ })
223
230
  t.ok(tool.enabled, 'the toggle does not gate tool mode')
224
231
  t.ok((tool.hits?.length ?? 0) > 0, 'hits found on a toggle-off chat')
225
232
  const injection = await ragSearch({ chatId: 'chat-1', query: 'alpha fuel pressure' })
@@ -230,7 +237,12 @@ t.absent(injection.enabled, 'injection mode still honors the toggle')
230
237
  // yes — setup, then one group per scenario
231
238
  const { ragSearch } = await toolModeApi({ ragEnabled: false })
232
239
 
233
- const tool = await ragSearch({ chatId: 'chat-1', query: 'alpha fuel pressure', mode: 'tool', limit: 5 })
240
+ const tool = await ragSearch({
241
+ chatId: 'chat-1',
242
+ query: 'alpha fuel pressure',
243
+ mode: 'tool',
244
+ limit: 5
245
+ })
234
246
  t.ok(tool.enabled, 'the toggle does not gate tool mode')
235
247
  t.ok((tool.hits?.length ?? 0) > 0, 'hits found on a toggle-off chat')
236
248
  t.absent(tool.targetDocumentId, 'tool mode never resolves a target document')
@@ -242,7 +254,7 @@ const missing = await ragSearch({ chatId: 'chat-404', query: 'alpha', mode: 'too
242
254
  t.absent(missing.enabled, 'an unknown chat never searches, even in tool mode')
243
255
  ```
244
256
 
245
- Split a group further when it grows its own phases — *set up this case*, *wait for the state*, *check it* — rather than letting one group run long. The grouping is what a comment would otherwise have to say, which is most of why the comment isn't needed.
257
+ Split a group further when it grows its own phases — _set up this case_, _wait for the state_, _check it_ — rather than letting one group run long. The grouping is what a comment would otherwise have to say, which is most of why the comment isn't needed.
246
258
 
247
259
  What isn't grouping, and shouldn't appear: banner or section-divider comments, columns aligned with padding spaces, a blank line immediately after `{` or before `}`, and two blank lines where one does the job.
248
260
 
@@ -270,16 +282,16 @@ Every value that exists twice will eventually disagree. Most "impossible" bugs a
270
282
 
271
283
  **One source of truth per value.** If a number is already available, don't carry a second copy of it beside the first. A consumer then has to know which to read, and the day they diverge nobody will know which was right.
272
284
 
273
- **But check the contract before you call it duplication.** Two fields holding the same value are sometimes two *channels* that are documented to mean different things, and which happen to agree right now. Then the defect isn't the copy — it's that a value was written into the wrong channel, and deleting a field would be the wrong fix.
285
+ **But check the contract before you call it duplication.** Two fields holding the same value are sometimes two _channels_ that are documented to mean different things, and which happen to agree right now. Then the defect isn't the copy — it's that a value was written into the wrong channel, and deleting a field would be the wrong fix.
274
286
 
275
- Find the contract first: the type declaration and its comments, and the order a consumer reads the fields in. A reader that prefers one field over another *is* the contract, even where nothing is written down.
287
+ Find the contract first: the type declaration and its comments, and the order a consumer reads the fields in. A reader that prefers one field over another _is_ the contract, even where nothing is written down.
276
288
 
277
289
  ```ts
278
290
  // looks like duplication — the same number in four places
279
291
  { downloaded, total, percentage, fileSetInfo: { overallDownloaded, overallTotal, overallPercentage } }
280
292
  ```
281
293
 
282
- Here the type said top-level is *this asset* and `fileSetInfo` is *the whole transfer*, and the consumer read `fileSetInfo` in preference. So the real defect was writing the aggregate over the per-asset fields — a contract violation, fixed by putting each value in its own channel, not by deleting either.
294
+ Here the type said top-level is _this asset_ and `fileSetInfo` is _the whole transfer_, and the consumer read `fileSetInfo` in preference. So the real defect was writing the aggregate over the per-asset fields — a contract violation, fixed by putting each value in its own channel, not by deleting either.
283
295
 
284
296
  The two diagnoses lead to opposite fixes, so get the diagnosis right:
285
297
 
@@ -301,7 +313,7 @@ for (const src of srcs) { offsets.push(running); running += sizes[src] }
301
313
 
302
314
  The second version is the same arithmetic with the mutation confined to where it's obviously right, and the loop body doesn't participate in keeping it true.
303
315
 
304
- **Don't launder a bad value into a plausible one.** `Math.max(x, 0)`, `?? 0`, and `|| 0` on something that should never have been negative, null, or absent don't fix anything — they convert a loud failure into a wrong number that flows onward and gets debugged three layers away. Clamping for a *presentational* guarantee (a progress bar that must not go backwards) is legitimate; clamping because you aren't sure what the producer sends is not. Know which one you're writing.
316
+ **Don't launder a bad value into a plausible one.** `Math.max(x, 0)`, `?? 0`, and `|| 0` on something that should never have been negative, null, or absent don't fix anything — they convert a loud failure into a wrong number that flows onward and gets debugged three layers away. Clamping for a _presentational_ guarantee (a progress bar that must not go backwards) is legitimate; clamping because you aren't sure what the producer sends is not. Know which one you're writing.
305
317
 
306
318
  Note the repo's own trap here: an optional `uint` decodes to `0` rather than `null` when absent, so `??` won't catch a missing timestamp and `||` is the correct operator.
307
319
 
@@ -342,7 +354,7 @@ The rules when you use it:
342
354
  if (!this.opened) await this.ready()
343
355
  ```
344
356
 
345
- Not a bare `await this.ready()`. `ready()` is idempotent and returns an already-resolved promise once open, but awaiting it still costs a microtask tick on *every* call — on a hot path that's a tick per operation for no work. The guard makes the common case synchronous and only awaits the first time. This is the laziness contract: construct, then call, and the first call opens.
357
+ Not a bare `await this.ready()`. `ready()` is idempotent and returns an already-resolved promise once open, but awaiting it still costs a microtask tick on _every_ call — on a hot path that's a tick per operation for no work. The guard makes the common case synchronous and only awaits the first time. This is the laziness contract: construct, then call, and the first call opens.
346
358
 
347
359
  - **Make every async step interruptible.** Check `closing` before and after each suspension point, in `_open` and in any long-running method:
348
360
 
@@ -386,32 +398,32 @@ Everything in [Buffers and wire formats](#buffers-and-wire-formats-bare--node) a
386
398
 
387
399
  ### Flows → streamx
388
400
 
389
- Use `streamx`, not Node core `stream`. *Reason: lighter, predictable backpressure, runs on Bare, and it's what the ecosystem speaks.* `Readable`/`Writable`/`Duplex`/`Transform` plus `pipeline`.
401
+ Use `streamx`, not Node core `stream`. _Reason: lighter, predictable backpressure, runs on Bare, and it's what the ecosystem speaks._ `Readable`/`Writable`/`Duplex`/`Transform` plus `pipeline`.
390
402
 
391
403
  ```js
392
404
  const { Readable } = require('streamx')
393
405
 
394
406
  class Query extends Readable {
395
- constructor (db, range) {
407
+ constructor(db, range) {
396
408
  super()
397
409
  this.db = db
398
410
  this.range = range
399
411
  this._it = null
400
412
  }
401
413
 
402
- _open (cb) {
414
+ _open(cb) {
403
415
  this._it = this.db.iterator(this.range)
404
416
  cb(null)
405
417
  }
406
418
 
407
- async _read (cb) {
419
+ async _read(cb) {
408
420
  const { done, value } = await this._it.next()
409
421
  if (done) this.push(null)
410
422
  else this.push(value)
411
423
  cb(null)
412
424
  }
413
425
 
414
- _destroy (cb) {
426
+ _destroy(cb) {
415
427
  this._it?.close().then(() => cb(null), cb)
416
428
  }
417
429
  }
@@ -425,17 +437,17 @@ EventEmitter (or streamx's emitter) for "something happened": `'close'`, `'updat
425
437
 
426
438
  ### Errors: don't program defensively
427
439
 
428
- We do **not** program defensively. We don't litter methods with arg-type checks, null guards, or "did you call this in the right order" assertions. *Reason: defensive code is complexity you write, read, and pay for at runtime forever — to handle a case that is a caller bug, not a real condition.* The contract is the README and the tests; semver protects it. A dev who uses the API totally wrong gets a native crash, and that is the correct outcome — fail fast, fail loud, fix the call site.
440
+ We do **not** program defensively. We don't litter methods with arg-type checks, null guards, or "did you call this in the right order" assertions. _Reason: defensive code is complexity you write, read, and pay for at runtime forever — to handle a case that is a caller bug, not a real condition._ The contract is the README and the tests; semver protects it. A dev who uses the API totally wrong gets a native crash, and that is the correct outcome — fail fast, fail loud, fix the call site.
429
441
 
430
442
  - **Let misuse fail.** Pass a string where a buffer goes? It throws from deep in the stack. Fine. That's a bug in their code, surfaced immediately, not a state your module should anticipate or absorb.
431
- - **Don't validate inputs you've documented.** The documented type *is* the validation. Re-checking it in code is redundant with the README and the tests.
443
+ - **Don't validate inputs you've documented.** The documented type _is_ the validation. Re-checking it in code is redundant with the README and the tests.
432
444
  - **Operational failure is different — that travels with the resource.** A dropped peer, a replication error, a disk hiccup are real runtime conditions, not caller bugs → emit `'error'`, reject the in-flight promise, or expose state. Don't take the process down for a network blip.
433
445
  - **`safety-catch`** rethrows real bugs while swallowing benign teardown rejections, so a genuine programmer error still crashes loudly rather than getting silently eaten:
434
446
  ```js
435
447
  const safetyCatch = require('safety-catch')
436
448
  somePromise().catch(safetyCatch)
437
449
  ```
438
- - **Never swallow silently.** If you catch and ignore an *operational* error, leave a comment on why it's safe.
450
+ - **Never swallow silently.** If you catch and ignore an _operational_ error, leave a comment on why it's safe.
439
451
 
440
452
  The line to hold: **caller bug → let it crash. Runtime condition → handle it on the resource.** Don't blur the two by defending against the first.
441
453
 
@@ -450,7 +462,7 @@ const ScopeLock = require('scope-lock')
450
462
 
451
463
  const l = new ScopeLock()
452
464
 
453
- async function append (data) {
465
+ async function append(data) {
454
466
  await l.lock()
455
467
  try {
456
468
  await reallyAppend(data)
@@ -467,8 +479,9 @@ It also has a **debounced** mode, for when you don't need every queued entry to
467
479
  ```js
468
480
  const d = new ScopeLock({ debounce: true })
469
481
 
470
- async function resync () {
471
- if (await d.lock()) { // only the entries that actually get the scope return true
482
+ async function resync() {
483
+ if (await d.lock()) {
484
+ // only the entries that actually get the scope return true
472
485
  try {
473
486
  await doResync()
474
487
  } finally {
@@ -484,18 +497,18 @@ Use a plain lock when every call must run; use debounce when intermediate calls
484
497
 
485
498
  ### Closures capture the variable, not its value
486
499
 
487
- A callback that outlives the statement that created it reads whatever the variable holds *when it fires*, not when it was passed. Combine that with a mutation after an `await` and you have a bug that only appears under real timing:
500
+ A callback that outlives the statement that created it reads whatever the variable holds _when it fires_, not when it was passed. Combine that with a mutation after an `await` and you have a bug that only appears under real timing:
488
501
 
489
502
  ```ts
490
503
  let completedBytes = 0
491
504
  for (let i = 0; i < srcs.length; i++) {
492
- const onProgress = (u) => report(completedBytes + u.downloaded) // reads it later
505
+ const onProgress = (u) => report(completedBytes + u.downloaded) // reads it later
493
506
  await download(srcs[i], { onProgress })
494
- completedBytes += sizes[srcs[i]] // moved by then
507
+ completedBytes += sizes[srcs[i]] // moved by then
495
508
  }
496
509
  ```
497
510
 
498
- A progress event that lands after its download resolves — a straggler over IPC, a queued microtask, an event flushed on close — sees the *next* offset and double-counts. It won't reproduce locally, and clamping the output only hides it.
511
+ A progress event that lands after its download resolves — a straggler over IPC, a queued microtask, an event flushed on close — sees the _next_ offset and double-counts. It won't reproduce locally, and clamping the output only hides it.
499
512
 
500
513
  - **Don't let a callback close over state that a later step mutates.** Pass the value the callback needs, or compute the whole table up front (see [Derived state](#derived-state)).
501
514
  - **`for (let i …)` is per-iteration; `let x` outside the loop is not.** The loop index is safe to capture, the accumulator beside it isn't.
@@ -513,15 +526,15 @@ If nothing exposes readiness, that's the bug: add the event or the promise to th
513
526
 
514
527
  ### b4a everywhere
515
528
 
516
- **Don't use the `Buffer` global.** *Reason: it may not exist on Bare. `b4a` is identical behavior, runs on both.* This is the default precisely because we target Bare and Node from the same source.
529
+ **Don't use the `Buffer` global.** _Reason: it may not exist on Bare. `b4a` is identical behavior, runs on both._ This is the default precisely because we target Bare and Node from the same source.
517
530
 
518
531
  ```js
519
532
  const b4a = require('b4a')
520
533
 
521
- b4a.from('hello') // not Buffer.from
522
- b4a.alloc(32) // not Buffer.alloc
523
- b4a.equals(a, b) // not a.equals(b)
524
- b4a.toString(buf, 'hex') // not buf.toString('hex')
534
+ b4a.from('hello') // not Buffer.from
535
+ b4a.alloc(32) // not Buffer.alloc
536
+ b4a.equals(a, b) // not a.equals(b)
537
+ b4a.toString(buf, 'hex') // not buf.toString('hex')
525
538
  b4a.concat([a, b])
526
539
  ```
527
540
 
@@ -535,9 +548,17 @@ Wire and on-disk formats use `compact-encoding` — positional, length-prefixed,
535
548
  const c = require('compact-encoding')
536
549
 
537
550
  const message = {
538
- preencode (state, m) { c.uint.preencode(state, m.seq); c.string.preencode(state, m.name) },
539
- encode (state, m) { c.uint.encode(state, m.seq); c.string.encode(state, m.name) },
540
- decode (state) { return { seq: c.uint.decode(state), name: c.string.decode(state) } }
551
+ preencode(state, m) {
552
+ c.uint.preencode(state, m.seq)
553
+ c.string.preencode(state, m.name)
554
+ },
555
+ encode(state, m) {
556
+ c.uint.encode(state, m.seq)
557
+ c.string.encode(state, m.name)
558
+ },
559
+ decode(state) {
560
+ return { seq: c.uint.decode(state), name: c.string.decode(state) }
561
+ }
541
562
  }
542
563
  ```
543
564
 
@@ -555,7 +576,7 @@ Each dependency is maintained forever — its bugs, breaking changes, security s
555
576
  - **Justify every `package.json` entry in one sentence.** If you can't, it shouldn't be there.
556
577
  - **Don't import to avoid an obvious function.** `isOdd` is the cautionary tale.
557
578
 
558
- The governing question is the complexity check again: *is there a simpler way to execute the ambition?* Often it's no dependency at all.
579
+ The governing question is the complexity check again: _is there a simpler way to execute the ambition?_ Often it's no dependency at all.
559
580
 
560
581
  ---
561
582
 
@@ -585,9 +606,9 @@ test('basic append and read', async function (t) {
585
606
  What "test the API" means here:
586
607
 
587
608
  - **Tests exercise the public surface as a consumer would call it** — construct, `ready()`, call the documented methods, assert the documented behavior. They're the executable version of the README's Usage section. If a method is documented, there's a test that uses it the documented way.
588
- - **Test how the thing is *meant* to be used, not how it's built.** Assert behavior through the public API (`t.is`, `t.alike`, `t.ok`), never reach into internals. A refactor that keeps the API stable must keep the tests green — that's the whole point.
609
+ - **Test how the thing is _meant_ to be used, not how it's built.** Assert behavior through the public API (`t.is`, `t.alike`, `t.ok`), never reach into internals. A refactor that keeps the API stable must keep the tests green — that's the whole point.
589
610
  - **You do not test misuse.** We don't program defensively, so there's nothing to assert about passing garbage in — that path is a native crash by design. Don't write tests that pin down the crash behavior of unsupported input; it isn't part of the contract and pinning it makes the contract accidentally wider.
590
- - **Test the real failure paths that *are* part of the contract** — close-during-open, the race you just fixed, the documented bounds that legitimately return nothing, operational errors surfacing on the resource. These are behaviors you promise; prove them.
611
+ - **Test the real failure paths that _are_ part of the contract** — close-during-open, the race you just fixed, the documented bounds that legitimately return nothing, operational errors surfacing on the resource. These are behaviors you promise; prove them.
591
612
  - **`t.teardown()` for cleanup** — closes resources even when assertions fail. Use it for everything you open.
592
613
  - **Flat tests, one `test()` per behavior.** The name states the assertion. No deep describe/it nesting.
593
614
  - **Semver follows the tests.** A change that breaks an existing API test is a breaking change — major bump. Tests are how you know. Even small throwaway modules get CI, lint, and basic API tests; same checklist every time.
@@ -596,9 +617,9 @@ What "test the API" means here:
596
617
  For a suite that builds real multi-peer structure, put the setup behind one factory that owns the testnet and the teardown:
597
618
 
598
619
  ```js
599
- const { things } = await create(t) // testnet + teardown registered
600
- const thing1 = await things.start() // takes opts, awaits ready, wires the testnet in
601
- const t1 = await things.restart(0) // close and re-open thing1
620
+ const { things } = await create(t) // testnet + teardown registered
621
+ const thing1 = await things.start() // takes opts, awaits ready, wires the testnet in
622
+ const t1 = await things.restart(0) // close and re-open thing1
602
623
  ```
603
624
 
604
625
  The test then reads as the scenario it describes, and lifecycle correctness lives in one place instead of being re-derived per test.
@@ -607,7 +628,7 @@ The test then reads as the scenario it describes, and lifecycle correctness live
607
628
 
608
629
  ## Review checklist
609
630
 
610
- Apply to any module, regardless of size or visibility. When reviewing, walk it and report violations tersely with the fix, in dependency order — don't rewrite wholesale unless asked. And remember the first rule: a flagged item is a prompt to think, not an automatic defect. If the code broke a default *for a good reason*, that's good code.
631
+ Apply to any module, regardless of size or visibility. When reviewing, walk it and report violations tersely with the fix, in dependency order — don't rewrite wholesale unless asked. And remember the first rule: a flagged item is a prompt to think, not an automatic defect. If the code broke a default _for a good reason_, that's good code.
611
632
 
612
633
  - [ ] One responsibility, describable without "and"; built on the layer below, not reimplementing or reaching around it.
613
634
  - [ ] Composed by passing the lower primitive in (store/core/bee), not constructing it secretly; no layer depends upward.
@@ -615,7 +636,7 @@ Apply to any module, regardless of size or visibility. When reviewing, walk it a
615
636
  - [ ] Exports match the kind of code (named for product source, one default for a publishable module); `(required, opts = {})` constructor; all fields declared.
616
637
  - [ ] Functions take one or two positional subjects and an object for the rest — no same-typed neighbours, no positional booleans, no loop-invariants re-passed per call.
617
638
  - [ ] TypeScript with no `as`, no `any`, `unknown` only at boundaries; `erasableSyntaxOnly` respected for anything Bare loads.
618
- - [ ] Comments: none unless they explain *why*, one line max, no comment blocks, no banner dividers.
639
+ - [ ] Comments: none unless they explain _why_, one line max, no comment blocks, no banner dividers.
619
640
  - [ ] Blank lines group the code into setup / action / assertion — neither an unbroken wall nor padding that groups nothing.
620
641
  - [ ] No conditional spreads — optional values assigned plainly and allowed to be `undefined`; no commented-out code or unused scaffolding.
621
642
  - [ ] One source of truth per value — nothing carried twice or computed twice in the same payload, and where two fields do hold the same value, each is in the channel its type documents.
@@ -647,4 +668,4 @@ This skill governs the shape of the code. The others cover surfaces and workflow
647
668
  - Reviewing a PR or a local diff against these rules → **pr-review**.
648
669
  - The KV cache, the context window and the overflow ladder → **kvcache**.
649
670
 
650
- Commit, branch, and PR-delivery workflow lives in [`CLAUDE.md`](../../../CLAUDE.md).
671
+ Commit and branch rules live in [`CLAUDE.md`](../../../CLAUDE.md); how a PR itself is shaped and delivered is **pr-create**.
@@ -11,19 +11,19 @@ common wrong turns.
11
11
 
12
12
  This repo owns the cache: `lib/capabilities/completion.ts` (the `kvCache` key,
13
13
  the round loop, the overflow ladder), `lib/context-window.ts`,
14
- `lib/models/kv-cache.ts` and `lib/models/model-pool.ts`. The *prompt* side —
15
- folding a chat's history so it fits — is `@qvac/core`'s `lib/compaction.ts`;
14
+ `lib/models/kv-cache.ts` and `lib/models/model-pool.ts`. The _prompt_ side —
15
+ folding a chat's history so it fits — is `@qvac/core → lib/compaction.ts`;
16
16
  see its **compaction** skill before proposing anything that shrinks history.
17
17
 
18
18
  ## The one thing to internalise
19
19
 
20
20
  Three state sets exist and are **never reconciled**:
21
21
 
22
- | Layer | State | Keyed by |
23
- | --- | --- | --- |
24
- | caller (`@qvac/core`) | message **list** (shrinks only on an overflow retry) | chat |
25
- | SDK | message **count** (`savedCount`) | cache file path |
26
- | addon | token **positions** (`nPast`) + the on-disk `.bin` | cache key |
22
+ | Layer | State | Keyed by |
23
+ | --------------------- | ---------------------------------------------------- | --------------- |
24
+ | caller (`@qvac/core`) | message **list** (shrinks only on an overflow retry) | chat |
25
+ | SDK | message **count** (`savedCount`) | cache file path |
26
+ | addon | token **positions** (`nPast`) + the on-disk `.bin` | cache key |
27
27
 
28
28
  Almost every bug here is drift between those three, not context pressure.
29
29
  `savedCount` means "already sent", never "still resident": nothing below the