vouchington-tooling 0.43.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/agent-blackboard/client.d.mts +6 -0
- package/dist/cli/commands/fresh-base.d.mts +6 -0
- package/dist/cli/commands/fresh-base.mjs +14 -0
- package/dist/cli/index.mjs +3 -0
- package/dist/cli/parse-fresh-base.d.mts +2 -0
- package/dist/cli/parse-fresh-base.mjs +15 -0
- package/dist/cli/parse.d.mts +5 -0
- package/dist/cli/parse.mjs +3 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +3 -1
- package/dist/fresh-base/classify.d.mts +21 -0
- package/dist/fresh-base/classify.mjs +29 -0
- package/dist/fresh-base/index.d.mts +4 -0
- package/dist/fresh-base/index.mjs +2 -0
- package/dist/fresh-base/run.d.mts +25 -0
- package/dist/fresh-base/run.mjs +66 -0
- package/dist/gh-cli/closing-refs.d.mts +24 -0
- package/dist/gh-cli/closing-refs.mjs +37 -0
- package/dist/gh-cli/index.d.mts +3 -0
- package/dist/gh-cli/index.mjs +2 -0
- package/dist/gh-cli/non-closing-refs.d.mts +11 -0
- package/dist/gh-cli/non-closing-refs.mjs +24 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +3 -0
- package/dist/mcp-server/context.d.mts +3 -0
- package/dist/mcp-server/dispatch.d.mts +3 -0
- package/dist/mcp-server/dispatch.mjs +7 -1
- package/dist/mcp-server/fake-blackboard.test-helpers.d.mts +2 -0
- package/dist/mcp-server/fake-blackboard.test-helpers.mjs +3 -0
- package/dist/mcp-server/handlers-pr.d.mts +1 -1
- package/dist/mcp-server/handlers-pr.mjs +6 -97
- package/dist/mcp-server/handlers-retrospective-save.d.mts +10 -0
- package/dist/mcp-server/handlers-retrospective-save.mjs +138 -0
- package/dist/mcp-server/handlers-retrospective.d.mts +1 -12
- package/dist/mcp-server/handlers-retrospective.mjs +1 -154
- package/dist/mcp-server/handlers-session.mjs +25 -6
- package/dist/mcp-server/handlers-snapshot-cleanup.d.mts +7 -0
- package/dist/mcp-server/handlers-snapshot-cleanup.mjs +36 -0
- package/dist/mcp-server/harness.test-helpers.d.mts +2 -0
- package/dist/mcp-server/harness.test-helpers.mjs +1 -0
- package/dist/mcp-server/policy-run.d.mts +1 -0
- package/dist/mcp-server/policy-run.mjs +1 -0
- package/dist/mcp-server/pr-body.d.mts +4 -2
- package/dist/mcp-server/pr-body.mjs +15 -3
- package/dist/mcp-server/pr-defaults.d.mts +3 -0
- package/dist/mcp-server/pr-defaults.mjs +7 -2
- package/dist/mcp-server/pr-input.d.mts +12 -5
- package/dist/mcp-server/pr-input.mjs +21 -4
- package/dist/mcp-server/pr-live.d.mts +3 -1
- package/dist/mcp-server/pr-live.mjs +7 -2
- package/dist/mcp-server/pr-mode.d.mts +7 -0
- package/dist/mcp-server/pr-mode.mjs +34 -0
- package/dist/mcp-server/pr-no-source.d.mts +9 -0
- package/dist/mcp-server/pr-no-source.mjs +22 -0
- package/dist/mcp-server/pr-prepare.d.mts +25 -0
- package/dist/mcp-server/pr-prepare.mjs +103 -0
- package/dist/mcp-server/preview-registry.d.mts +25 -0
- package/dist/mcp-server/preview-registry.mjs +38 -0
- package/dist/mcp-server/retrospective-check.d.mts +3 -0
- package/dist/mcp-server/retrospective-check.mjs +38 -0
- package/dist/mcp-server/retrospective-input.mjs +2 -2
- package/dist/mcp-server/retrospective-preview.d.mts +17 -0
- package/dist/mcp-server/retrospective-preview.mjs +60 -0
- package/dist/mcp-server/snapshot-registry.d.mts +25 -0
- package/dist/mcp-server/snapshot-registry.mjs +32 -0
- package/dist/mcp-server/tools-pr.mjs +20 -4
- package/dist/mcp-server/tools-retrospective.mjs +25 -5
- package/dist/mcp-server/tools.mjs +6 -0
- package/dist/pull-request-diff/diff-size.d.mts +29 -0
- package/dist/pull-request-diff/diff-size.mjs +35 -0
- package/dist/pull-request-diff/index.d.mts +2 -0
- package/dist/pull-request-diff/index.mjs +1 -0
- package/dist/retrospective-distill/args.d.mts +13 -0
- package/dist/retrospective-distill/args.mjs +27 -0
- package/dist/retrospective-distill/checkpoint-entry.d.mts +2 -0
- package/dist/retrospective-distill/checkpoint-entry.mjs +11 -0
- package/dist/retrospective-distill/fixtures.test-helpers.d.mts +3 -0
- package/dist/retrospective-distill/fixtures.test-helpers.mjs +21 -0
- package/dist/retrospective-distill/flag-args.d.mts +5 -0
- package/dist/retrospective-distill/flag-args.mjs +31 -0
- package/dist/retrospective-distill/index.d.mts +10 -0
- package/dist/retrospective-distill/index.mjs +6 -0
- package/dist/retrospective-distill/partition-records.d.mts +6 -0
- package/dist/retrospective-distill/partition-records.mjs +49 -0
- package/dist/retrospective-distill/retrospective-entry.d.mts +3 -0
- package/dist/retrospective-distill/retrospective-entry.mjs +4 -0
- package/dist/retrospective-distill/run.d.mts +5 -0
- package/dist/retrospective-distill/run.mjs +28 -0
- package/dist/retrospective-distill/session-types.d.mts +17 -0
- package/dist/retrospective-distill/session-types.mjs +1 -0
- package/dist/retrospective-distill/shape.d.mts +29 -0
- package/dist/retrospective-distill/shape.mjs +111 -0
- package/docs/mcp-server.md +95 -23
- package/package.json +20 -5
- package/skills/agent-workflow/SKILL.md +20 -0
- package/skills/agent-workflow/references/four-refs.md +74 -0
- package/skills/blackboard/SKILL.md +21 -1
- package/skills/github-issue/SKILL.md +6 -0
- package/skills/manifest.json +11 -5
- package/skills/postgres-node-performance-tuning/SKILL.md +2 -0
- package/skills/postgres-node-performance-tuning/references/performance-patterns.md +76 -0
- package/skills/postgres-node-performance-tuning/references/reading-plans.md +111 -0
- package/skills/postgres-schema-design/references/relations.md +18 -0
- package/skills/retrospective/SKILL.md +3 -1
- package/skills/retrospective-distill/SKILL.md +72 -4
- package/skills/revisit-followups/SKILL.md +17 -0
- package/skills/stacked-prs/SKILL.md +47 -16
- package/skills/stacked-prs/references/drain.md +168 -0
- package/skills/stacked-prs/references/gh-stack.md +143 -0
package/docs/mcp-server.md
CHANGED
|
@@ -82,17 +82,18 @@ Every tool takes an explicit `sessionId` (`^[A-Za-z0-9._:-]+$`, at most 256 char
|
|
|
82
82
|
server never infers, and an optional `worktree`. Unknown arguments are rejected. Failures come back
|
|
83
83
|
as MCP tool errors (`isError: true`) with an actionable message rather than crashing the server.
|
|
84
84
|
|
|
85
|
-
| Tool | Arguments besides `sessionId` and `worktree`
|
|
86
|
-
| --------------------- |
|
|
87
|
-
| `journal_append` | `parentSessionId`, `agent`, `version`, `mode` (`interactive` or `autonomous`), `markdown`, `sourceEventId`, `workOutcome`, `repositories`, `feedbackCoverage`, optional `category`
|
|
88
|
-
| `journal_entries` | none
|
|
89
|
-
| `outbox_status` | none
|
|
90
|
-
| `outbox_flush` | none
|
|
91
|
-
| `session_ensure` | `parentSessionId`, `agent`, `version`
|
|
92
|
-
| `retrospective_check` | optional `sourceEventId`
|
|
93
|
-
| `retrospective_save` | `parentSessionId`, `agent`, `version`, `mode`, `workOutcome`, `feedbackCoverage`, exactly one of `markdown` or `compose`, optional `repositories`, `sourceEventId`, `category`, `dryRun` | as `journal_append` plus `policy.warnings`; with `dryRun`, `markdown` and `policy.warnings`
|
|
94
|
-
| `snapshot_export` | optional `agent`, `version`, `parentSessionId`, `data`, `dataArrayContains`, `inactiveForHours`
|
|
95
|
-
| `
|
|
85
|
+
| Tool | Arguments besides `sessionId` and `worktree` | Returns |
|
|
86
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
87
|
+
| `journal_append` | `parentSessionId`, `agent`, `version`, `mode` (`interactive` or `autonomous`), `markdown`, `sourceEventId`, `workOutcome`, `repositories`, `feedbackCoverage`, optional `category` | `sessionId`, `timestamp`, delivery `status` (`delivered` with a verified receipt, or `pending`), `pendingCount`, `worktreePendingCount` |
|
|
88
|
+
| `journal_entries` | none | `sessionId` and `entries`, every entry oldest first |
|
|
89
|
+
| `outbox_status` | none | `sessionId`, `status`, `pendingCount`, `worktreePendingCount`, `rejectedCount`, `worktreeRejectedCount` |
|
|
90
|
+
| `outbox_flush` | none | `sessionId`, `status`, `pendingCount`, `worktreePendingCount`, `rejectedCount`, `worktreeRejectedCount`, `deliveredCount`, `rejected` (when any) |
|
|
91
|
+
| `session_ensure` | `parentSessionId`, `agent`, `version` | `sessionId`, `status` (`created` or `exists`), `archived` |
|
|
92
|
+
| `retrospective_check` | optional `sourceEventId` | `sessionId`, `saved`, `entries` (`sourceEventId`, `timestamp`, `issues`, `prs` of each retrospective, oldest first) |
|
|
93
|
+
| `retrospective_save` | `parentSessionId`, `agent`, `version`, `mode`, `workOutcome`, `feedbackCoverage`, exactly one of `markdown` or `compose`, optional `repositories`, `sourceEventId`, `category`, `dryRun`, `knownSensitiveValues` (markdown only); or `previewToken` with identity, `mode` and `sourceEventId` | as `journal_append` plus `policy.warnings`; with `dryRun`, `markdown`, `previewToken` and `policy.warnings` |
|
|
94
|
+
| `snapshot_export` | optional `agent`, `version`, `parentSessionId`, `data`, `dataArrayContains`, `inactiveForHours` | `sessionId`, `path`, `counts`, `checksum`, `manifest`, `cleanupToken` |
|
|
95
|
+
| `snapshot_cleanup` | `cleanupToken` | `sessionId`, `cleaned: true` |
|
|
96
|
+
| `session_archive` | none | `sessionId`, `archived: true` |
|
|
96
97
|
|
|
97
98
|
`journal_append` validates the whole feedback envelope with `validateFeedbackEnvelope` before
|
|
98
99
|
anything is written, so a missing required field writes nothing. It returns the verified read-back
|
|
@@ -146,6 +147,35 @@ outbox does not create the directory. `snapshot_export` never accepts a destinat
|
|
|
146
147
|
`agent-blackboard` client chooses a private file and returns its path. `session_archive` is the one
|
|
147
148
|
destructive tool; archived metadata is immutable, while entries stay appendable.
|
|
148
149
|
|
|
150
|
+
### Snapshot cleanup
|
|
151
|
+
|
|
152
|
+
`snapshot_export` writes a private file in the OS temp directory, and `snapshot_cleanup` is the only
|
|
153
|
+
way to delete it through the server. It was chosen over cleanup inside `session_archive` because
|
|
154
|
+
the exporting session and the archived sessions differ during distillation, and cleanup must run
|
|
155
|
+
whether or not archiving happened. Call it as a finally step after processing, on success and on
|
|
156
|
+
failure.
|
|
157
|
+
|
|
158
|
+
- **No caller-chosen path.** The tool takes only `cleanupToken` (`path`, `directory`, and `receipt`
|
|
159
|
+
are unsupported arguments). The server records the export's file path itself, plus a partition
|
|
160
|
+
directory and its signed receipt when the client reports both, and cleans exactly that record.
|
|
161
|
+
When the client's export carried its own cleanup capability, the server keeps it (never
|
|
162
|
+
returning it) and cleans through the client's `cleanupSnapshotPartitions`, so the client's
|
|
163
|
+
ownership marker goes with the file; otherwise it uses the in-tree `cleanupSnapshotPartitions`.
|
|
164
|
+
- **Server-minted, bound token.** `snapshot_export` replaces the client's `cleanupToken` with 32
|
|
165
|
+
random bytes the server generates, recorded with the caller's `sessionId` and resolved `worktree`.
|
|
166
|
+
The binding is checked before any filesystem call.
|
|
167
|
+
- **Single use.** A successful cleanup spends the token; a repeat fails as unknown and deletes
|
|
168
|
+
nothing. The record is taken synchronously, so concurrent calls cannot both clean.
|
|
169
|
+
- **Failure modes.** An unknown, forged (including a path used as a token), spent, other-session, or
|
|
170
|
+
other-worktree token fails with one message and deletes nothing; a mismatched caller does not spend
|
|
171
|
+
the owner's token. If the cleanup itself fails, the token stays valid and the error carries the
|
|
172
|
+
reason, including the retained tombstone path when a partition directory was half removed, so the
|
|
173
|
+
same call can retry. A file that is already gone counts as cleaned.
|
|
174
|
+
- **Server restart.** Bindings are in memory only. After a restart every earlier token is unknown, so
|
|
175
|
+
the file is left for the operating system to reap from its temp directory; run the distill pass
|
|
176
|
+
again to export a fresh snapshot. The registry also remembers at most 256 exports and forgets the
|
|
177
|
+
oldest first.
|
|
178
|
+
|
|
149
179
|
The outbox lives at `<worktree>/.local/blackboard-outbox` and is used only in interactive mode.
|
|
150
180
|
Autonomous mode forbids a filesystem outbox, so a failed delivery is an error.
|
|
151
181
|
|
|
@@ -168,7 +198,9 @@ instructions shows it to the agent.
|
|
|
168
198
|
## Retrospective tools and the policy contract
|
|
169
199
|
|
|
170
200
|
`retrospective_check` reads the session through the same client as `journal_entries` and reports the
|
|
171
|
-
entries whose envelope `type` is `retrospective`. A
|
|
201
|
+
entries whose envelope `type` is `retrospective`. A schema-versioned entry counts only when its
|
|
202
|
+
envelope passes `validateFeedbackEnvelope`; a malformed one is not saved, while an unversioned legacy
|
|
203
|
+
entry is still accepted. A session that was never created (the blackboard
|
|
172
204
|
answers 404) reports `saved: false`.
|
|
173
205
|
|
|
174
206
|
`retrospective_save` builds a `retrospective` envelope and writes it exactly as `journal_append`
|
|
@@ -189,7 +221,20 @@ are killed after 120 seconds. `compose.facts` takes `pr`, `branch`, `noPr`, or `
|
|
|
189
221
|
worktree's. Facts run `git` and `gh` in the worktree as the server's user, honoring its
|
|
190
222
|
repository configuration (which can select helpers that run code), so the worktree, including
|
|
191
223
|
`.git/config`, must be as trusted as the policy module. `dryRun`
|
|
192
|
-
runs every check and returns the document without writing
|
|
224
|
+
runs every check and returns the document without writing, plus a `previewToken`.
|
|
225
|
+
|
|
226
|
+
`knownSensitiveValues` (each at least 4 characters) is also accepted beside `markdown`, and
|
|
227
|
+
redacts the markdown, `category` and `feedbackCoverage` just as `compose.knownSensitiveValues`
|
|
228
|
+
does; with `compose`, pass it inside `compose`. The `previewToken` is 32 random bytes (base64url),
|
|
229
|
+
opaque, single use, and held only in server memory: a restart drops every token, and the registry
|
|
230
|
+
keeps the 32 most recent. It is bound to the `sessionId`, `parentSessionId`, `mode`, the resolved worktree, the `sourceEventId`
|
|
231
|
+
and the exact redacted content. Save it by calling `retrospective_save` again with the identity,
|
|
232
|
+
the same `parentSessionId`, `mode`, `sourceEventId` and `previewToken`, and none of `markdown`, `compose`,
|
|
233
|
+
`workOutcome`, `feedbackCoverage`, `repositories`, `category`, `knownSensitiveValues` or `dryRun`.
|
|
234
|
+
The server stores exactly the previewed content (so a sensitive value never has to be sent
|
|
235
|
+
again), mints a new timestamp, and does not run the policy again. An unknown, spent, restarted or
|
|
236
|
+
mismatched token is one error and the dry run must be repeated; a mismatch does not spend the
|
|
237
|
+
token, and neither does a failed write.
|
|
193
238
|
|
|
194
239
|
The document must open with the front matter `composeRetrospective` writes: lines of
|
|
195
240
|
`key: <JSON value>` between two `---` lines. Recognized keys are `date`, `issues`, `prs` (required),
|
|
@@ -229,7 +274,7 @@ whole process group is killed, so nothing it started outlives the call. The runn
|
|
|
229
274
|
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------- |
|
|
230
275
|
| `validateRetrospective` | `sessionId`, `parentSessionId`, `mode`, `workOutcome`, `feedbackCoverage`, `repositories`, `markdown`, `sourceEventId`, `metadata` (`date`, `issues`, `prs`, `description`) | `{ errors: string[], warnings?: string[] }` | 30 s |
|
|
231
276
|
| `pullRequestDefaults` | `{}` from `retrospective_save`; `{ title, base, relatedIssues }` from `pr_create` | `{ repositories?, labels?, milestone?, project? }` (an object) | 120 s |
|
|
232
|
-
| `validatePullRequest` | `title`, `body`, `base`, `head`, `mode`, and `pr`
|
|
277
|
+
| `validatePullRequest` | `title`, `body`, `base`, `head`, the trusted `mode`, `noSourceIssue` when given, and `pr` when set | `{ errors: string[], warnings?: string[] }` | 120 s |
|
|
233
278
|
|
|
234
279
|
Any `errors` reject the save and write nothing. A non-zero exit, a timeout (reported as
|
|
235
280
|
`policy <export> timed out after <N>s`), output over 1 MB, malformed JSON, or a result of the wrong
|
|
@@ -241,9 +286,33 @@ shape is an error, never a pass.
|
|
|
241
286
|
retrospective tools. All three take `sessionId` (required) and `worktree` (optional), need a server
|
|
242
287
|
launched inside a repository worktree (they run the checkout policy), and share this structured
|
|
243
288
|
input: `title` (single line, at most 256 characters), `summary`, `impact`, `relatedIssues`
|
|
244
|
-
(`[{ number, relation: "closes" | "refs", reason }]`, may be empty), `
|
|
245
|
-
`
|
|
246
|
-
`
|
|
289
|
+
(`[{ number, relation: "closes" | "refs", reason }]`, may be empty), optional `noSourceIssue`
|
|
290
|
+
(`{ reason }`, see below), `details` (string array), `base` (default `main`), `agent`, optional
|
|
291
|
+
`model`, optional `mode` (`interactive` | `autonomous`) and `workspaceSetup`. `pr_update` also
|
|
292
|
+
requires `pr`; `pr_validate` accepts an optional `pr`.
|
|
293
|
+
|
|
294
|
+
### Trusted mode
|
|
295
|
+
|
|
296
|
+
The mode the policy sees is derived by the server from its own launch environment, never from the
|
|
297
|
+
caller. It fails closed: when any automation marker is set to something other than an empty
|
|
298
|
+
string, `0` or `false` (`CI`, `GITHUB_ACTIONS`, `GITLAB_CI`, `BUILDKITE`, `CIRCLECI`,
|
|
299
|
+
`JENKINS_URL`, `TF_BUILD`, `CODEBUILD_BUILD_ID`, `VOUCHINGTON_AUTOMATION`), the mode is
|
|
300
|
+
`autonomous` whatever the caller passes. `CLAUDE_CODE_SESSION_ATTENDED` set to exactly `0` (Claude
|
|
301
|
+
Code's marker for an unattended `claude -p` run, inherited by MCP servers) also means `autonomous`;
|
|
302
|
+
a missing value or `1` never does. This list is a superset of the downstream check it replaces,
|
|
303
|
+
which was exactly `GITHUB_ACTIONS === 'true' || CI === 'true'`. Otherwise the mode is `interactive`. The optional `mode`
|
|
304
|
+
argument can only narrow `interactive` to `autonomous` (it never widens), so an autonomous run
|
|
305
|
+
cannot unlock an interactive-only policy exception. The policy child receives no env markers, so it
|
|
306
|
+
relies on this value.
|
|
307
|
+
|
|
308
|
+
### Pull requests with no source issue
|
|
309
|
+
|
|
310
|
+
Pass `noSourceIssue: { reason }` (a single line) with an empty `relatedIssues`; a non-empty
|
|
311
|
+
`relatedIssues` beside it is rejected. The Related issues section then holds the generic statement
|
|
312
|
+
`- No source issue — <reason>` instead of `- None`. A policy may replace the statement by
|
|
313
|
+
exporting `renderNoSourceIssue({ reason })`, returning a non-empty string (30 s). The field also
|
|
314
|
+
reaches `validatePullRequest` (with the trusted `mode`, so the policy can apply an interactive-only
|
|
315
|
+
rule) and `pullRequestDefaults`.
|
|
247
316
|
|
|
248
317
|
### Rendering
|
|
249
318
|
|
|
@@ -258,7 +327,8 @@ input: `title` (single line, at most 256 characters), `summary`, `impact`, `rela
|
|
|
258
327
|
|
|
259
328
|
## Related issues
|
|
260
329
|
|
|
261
|
-
- Closes #N — <reason> (or "Refs #N"; "- None" when empty
|
|
330
|
+
- Closes #N — <reason> (or "Refs #N"; "- None" when empty;
|
|
331
|
+
"- No source issue — <reason>" for noSourceIssue)
|
|
262
332
|
|
|
263
333
|
## Details
|
|
264
334
|
|
|
@@ -270,18 +340,20 @@ Device: <os user>@<lowercased hostname>
|
|
|
270
340
|
Worktree: <path after "/worktrees/", "main" for the main worktree, else the directory name>
|
|
271
341
|
```
|
|
272
342
|
|
|
273
|
-
The head branch is the worktree's current branch
|
|
274
|
-
rejected. The body must stay within GitHub's 65536 characters. `pr_create` sees no stack membership,
|
|
343
|
+
The head branch is the worktree's current branch (for `pr_validate` with `pr`, the live pull
|
|
344
|
+
request's head, which may equal `base` for a fork); otherwise a detached HEAD, or a head equal to `base`, is rejected. The body must stay within GitHub's 65536 characters. `pr_create` sees no stack membership,
|
|
275
345
|
so a consumer policy may reject any `base` other than the default branch; create stacked layers
|
|
276
346
|
with the repository's stack tooling instead.
|
|
277
347
|
|
|
278
348
|
### Tools
|
|
279
349
|
|
|
280
350
|
- `pr_validate` (read-only): renders the body, runs the policy `validatePullRequest({ title, body,
|
|
281
|
-
base, head, mode, pr? })` export and returns `{ body, errors, warnings }`. With
|
|
282
|
-
body is read (never written) so its Shepherd Journal is preserved as in
|
|
351
|
+
base, head, mode, noSourceIssue?, pr? })` export and returns `{ body, errors, warnings }`. With
|
|
352
|
+
`pr`, the live body is read (never written) so its Shepherd Journal is preserved as in
|
|
353
|
+
`pr_update`. The liveness checks of `pr_update` do not apply: any state (merged or closed
|
|
354
|
+
included) and any head are accepted, and the policy sees the pull request's own head and base.
|
|
283
355
|
- `pr_create` (additive): renders and validates (errors block), then calls the policy
|
|
284
|
-
`pullRequestDefaults({ title, base, relatedIssues })` export for optional `labels`, `milestone`
|
|
356
|
+
`pullRequestDefaults({ title, base, relatedIssues, noSourceIssue? })` export for optional `labels`, `milestone`
|
|
285
357
|
and `project`. It checks the head is pushed and current, writes the body to a mode 0600 temp file
|
|
286
358
|
(removed afterwards) and runs `gh pr create --draft` with the labels, `--milestone` and
|
|
287
359
|
`--project`. Returns `{ number, url, warnings }`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -101,6 +101,11 @@
|
|
|
101
101
|
"import": "./dist/require-up-to-date/index.mjs",
|
|
102
102
|
"default": "./dist/require-up-to-date/index.mjs"
|
|
103
103
|
},
|
|
104
|
+
"./fresh-base": {
|
|
105
|
+
"types": "./dist/fresh-base/index.d.mts",
|
|
106
|
+
"import": "./dist/fresh-base/index.mjs",
|
|
107
|
+
"default": "./dist/fresh-base/index.mjs"
|
|
108
|
+
},
|
|
104
109
|
"./gitleaks-directory-scan": {
|
|
105
110
|
"types": "./dist/gitleaks-directory-scan/index.d.mts",
|
|
106
111
|
"import": "./dist/gitleaks-directory-scan/index.mjs",
|
|
@@ -136,6 +141,11 @@
|
|
|
136
141
|
"import": "./dist/shared-context/index.mjs",
|
|
137
142
|
"default": "./dist/shared-context/index.mjs"
|
|
138
143
|
},
|
|
144
|
+
"./retrospective-distill": {
|
|
145
|
+
"types": "./dist/retrospective-distill/index.d.mts",
|
|
146
|
+
"import": "./dist/retrospective-distill/index.mjs",
|
|
147
|
+
"default": "./dist/retrospective-distill/index.mjs"
|
|
148
|
+
},
|
|
139
149
|
"./source-module-graph": {
|
|
140
150
|
"types": "./dist/source-module-graph/index.d.mts",
|
|
141
151
|
"import": "./dist/source-module-graph/index.mjs",
|
|
@@ -321,6 +331,11 @@
|
|
|
321
331
|
"import": "./dist/compiler-build/index.mjs",
|
|
322
332
|
"default": "./dist/compiler-build/index.mjs"
|
|
323
333
|
},
|
|
334
|
+
"./pull-request-diff": {
|
|
335
|
+
"types": "./dist/pull-request-diff/index.d.mts",
|
|
336
|
+
"import": "./dist/pull-request-diff/index.mjs",
|
|
337
|
+
"default": "./dist/pull-request-diff/index.mjs"
|
|
338
|
+
},
|
|
324
339
|
"./github-projects": {
|
|
325
340
|
"types": "./dist/github-projects/index.d.mts",
|
|
326
341
|
"import": "./dist/github-projects/index.mjs",
|
|
@@ -364,7 +379,7 @@
|
|
|
364
379
|
"dependencies": {
|
|
365
380
|
"@types/mdast": "4.0.4",
|
|
366
381
|
"@types/unist": "3.0.3",
|
|
367
|
-
"@typescript-eslint/parser": "^8.71.
|
|
382
|
+
"@typescript-eslint/parser": "^8.71.1",
|
|
368
383
|
"csv-parse": "7.0.3",
|
|
369
384
|
"csv-stringify": "6.9.0",
|
|
370
385
|
"dockerfile-ast": "0.7.1",
|
|
@@ -376,13 +391,13 @@
|
|
|
376
391
|
"yaml": "2.9.1"
|
|
377
392
|
},
|
|
378
393
|
"devDependencies": {
|
|
379
|
-
"@ast-grep/cli": "0.
|
|
380
|
-
"@modelcontextprotocol/sdk": "1.32.
|
|
394
|
+
"@ast-grep/cli": "0.50.0",
|
|
395
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
381
396
|
"@types/picomatch": "^4.0.3",
|
|
382
397
|
"@types/spdx-expression-parse": "4.0.0",
|
|
383
398
|
"agent-blackboard": "^0.6.1",
|
|
384
399
|
"no-mistakes": "0.83.0",
|
|
385
|
-
"smol-toml": "1.9.
|
|
400
|
+
"smol-toml": "1.9.1",
|
|
386
401
|
"typescript5": "npm:typescript@5.9.3"
|
|
387
402
|
},
|
|
388
403
|
"peerDependencies": {
|
|
@@ -22,10 +22,30 @@ evidence record.
|
|
|
22
22
|
- Before handoff, use [evidence sweep](references/evidence-sweep.md).
|
|
23
23
|
- For review, use [review](references/review.md).
|
|
24
24
|
- For surfaced feedback, use [review response](references/review-response.md).
|
|
25
|
+
- Before any rebase or lease push, use [four refs](references/four-refs.md).
|
|
25
26
|
|
|
26
27
|
Load only the references required by the current phase. Keep scope changes explicit; a discovered
|
|
27
28
|
blocker or follow-up does not authorize widening the task.
|
|
28
29
|
|
|
30
|
+
## Close-out
|
|
31
|
+
|
|
32
|
+
This section is the single home for the hand-off retrospective and merge-note rules.
|
|
33
|
+
|
|
34
|
+
- Report actual validation, open pull requests, and remaining blockers.
|
|
35
|
+
- **Hand-off retrospective.** When you hand a pull request to the human as ready, write one
|
|
36
|
+
[retrospective](../retrospective/SKILL.md) from the session that did the work, which reads that
|
|
37
|
+
session's transcripts and journal. A native stack gets one retrospective when the stack is handed
|
|
38
|
+
off, covering every layer; never one per layer or for an intermediate commit.
|
|
39
|
+
- Give each hand-off its own `sourceEventId`, such as `handoff-pr<number>` (a stack uses its top
|
|
40
|
+
pull request's number), on both `retrospective_check` and `retrospective_save`. The default ID
|
|
41
|
+
fits only the first hand-off in a session; a second would be mistaken for it.
|
|
42
|
+
- **Merge note.** If you are still running when the merge happens and it changes the outcome, such
|
|
43
|
+
as a merge-queue ejection and its fix, a follow-up push, or review comments addressed after
|
|
44
|
+
hand-off, append one short note with the [blackboard skill](../blackboard/SKILL.md). Name the
|
|
45
|
+
pull request and the retrospective's source event, and give the note its own `sourceEventId` (for
|
|
46
|
+
example `merge-pr<number>`): the writer rejects the retrospective's ID reused for different
|
|
47
|
+
content. Never write a second retrospective.
|
|
48
|
+
|
|
29
49
|
## Parallel by default
|
|
30
50
|
|
|
31
51
|
Split work into independent units and run them concurrently, one worker and worktree each, within
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Reading the four refs
|
|
2
|
+
|
|
3
|
+
Use before any rebase or lease push of a branch other agents or humans may also write. Publish this
|
|
4
|
+
branch; do not open a replacement pull request.
|
|
5
|
+
|
|
6
|
+
`origin` and `main` below stand for the consumer's remote and default branch; substitute the names
|
|
7
|
+
local policy declares.
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
- Run `git fetch origin main <branch>` as its own command. Never chain the fetch with a rebase,
|
|
12
|
+
reset, or merge.
|
|
13
|
+
- HEAD must be the branch being updated. A stack checkout can leave the top layer checked out, so
|
|
14
|
+
check out the layer first. A missing stack field means membership is unknown, not unstacked.
|
|
15
|
+
- A dirty worktree stops you before a fast-forward, reset, or rebase. Do not switch branches.
|
|
16
|
+
- Continue a conflicted rebase with `GIT_EDITOR=true git rebase --continue`.
|
|
17
|
+
|
|
18
|
+
## Read the refs
|
|
19
|
+
|
|
20
|
+
- Print `git rev-parse` of HEAD, `origin/<branch>`, `origin/main`, and local `main` when it exists.
|
|
21
|
+
Local `main` is never the rebase target; when it exists, `git rev-list --left-right --count
|
|
22
|
+
main...origin/main` with a nonzero right count means local `main` is behind.
|
|
23
|
+
- Print `git rev-list --left-right --count HEAD...origin/<branch>` (left: commits only on the
|
|
24
|
+
local branch; right: commits only on `origin/<branch>`) and
|
|
25
|
+
`git log --oneline --left-right HEAD...origin/<branch>`.
|
|
26
|
+
- Print `git cherry -v origin/<branch> HEAD` and `git cherry -v HEAD origin/<branch>`. A leading `-`
|
|
27
|
+
is the same patch under another SHA; a leading `+` is a patch the other side lacks.
|
|
28
|
+
- Treat `git range-diff` as a reading aid, not the rebase cut, and read it from the base those
|
|
29
|
+
commits were built on. Never run it from a new stack parent: after a stack rebase, old parent
|
|
30
|
+
commits look like unique local work.
|
|
31
|
+
- Unmatched commits on `origin/<branch>` are new commits someone pushed.
|
|
32
|
+
|
|
33
|
+
## Lease push
|
|
34
|
+
|
|
35
|
+
- Capture `<sha>` with `git rev-parse origin/<branch>` after the fetch and before any rewrite. Push
|
|
36
|
+
a non-fast-forward with `git push --force-with-lease=<branch>:<sha> origin <branch>`, always
|
|
37
|
+
naming the remote and branch so a configured push remote cannot change the destination. With concurrent writers,
|
|
38
|
+
capture right after the setup fetch and before any later fetch, so a later fetch cannot silently
|
|
39
|
+
widen the lease.
|
|
40
|
+
- If HEAD equals that SHA, do not push.
|
|
41
|
+
- If a lease or non-force push is rejected, fetch again and re-read. It is still this branch.
|
|
42
|
+
|
|
43
|
+
## Unstacked
|
|
44
|
+
|
|
45
|
+
Before any move and after every move onto `origin/<branch>`, including when HEAD already equals it
|
|
46
|
+
(`0 0`) but `origin/main` has advanced, run `git merge-base --is-ancestor origin/main HEAD`. Exit 0
|
|
47
|
+
means the branch already contains `origin/main`; nonzero means rebase onto `origin/main`, then lease
|
|
48
|
+
push.
|
|
49
|
+
|
|
50
|
+
| Left/right | Action |
|
|
51
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `0 0`, `origin/main` already an ancestor | Stop. |
|
|
53
|
+
| `0 0`, `origin/main` not an ancestor | Rebase onto `origin/main`, then lease push. |
|
|
54
|
+
| `0 N` | `git merge --ff-only origin/<branch>`, then the ancestor check. |
|
|
55
|
+
| `N 0` | Read `git log --oneline origin/<branch>..HEAD`: those commits are what a push adds, including a remote rewind to an ancestor. Own unpushed work with `origin/main` an ancestor: `git push origin <branch>`. Otherwise rebase onto `origin/main`, then lease push. A rewind you did not make: stop and ask. |
|
|
56
|
+
| Diverged, local-only commits are rewritten copies (every local-only commit has cherry `-`; matching subjects alone do not prove equivalence), no unique local work | Clean tree, `git reset --hard origin/<branch>`, then the ancestor check. |
|
|
57
|
+
| Diverged, origin has new patches to keep and HEAD has unique local commits | `git rebase origin/<branch>` as its own command, then the ancestor check, then lease push whichever way the check exits (after another rebase onto `origin/main` if it is nonzero). This is the one added rebase target, not a license to rebase onto an arbitrary ref. If the rewrite changed patches and unique local work is mixed in, stop and ask. |
|
|
58
|
+
|
|
59
|
+
## Stacked
|
|
60
|
+
|
|
61
|
+
A stack rebase is the usual reason `origin/<branch>` was rewritten, but another agent or a human can
|
|
62
|
+
rewrite an unstacked branch the same way.
|
|
63
|
+
|
|
64
|
+
- On a diverged layer, adopt `origin/<branch>` with `git reset --hard` when the layer is checked
|
|
65
|
+
out, or `git branch -f` when it is not, if the left-right log shows pre-rebase copies: the same
|
|
66
|
+
layer subjects on both sides and old parent subjects only on the left. Require `git cherry`
|
|
67
|
+
patch equivalence (`-`) for every local layer commit first; matching subjects alone do not
|
|
68
|
+
prove it, and a `+` commit is unique local work.
|
|
69
|
+
- Never `git rebase origin/<branch>` on a stack layer: it replays parent commits onto the layer.
|
|
70
|
+
- Never run the plain rebase-onto-main procedure on a mid-stack branch; use its stack-aware form.
|
|
71
|
+
- If the layer has unpushed commits that are not the old parent and not already on origin under a
|
|
72
|
+
new SHA, stop and ask.
|
|
73
|
+
- After adopting the rewritten tip, run the stack-aware rebase only when the stack still needs to
|
|
74
|
+
move onto `origin/main`.
|
|
@@ -50,7 +50,10 @@ human which case applies before continuing:
|
|
|
50
50
|
registration and diagnosis steps.
|
|
51
51
|
- Registered but disconnected: reconnect it through the harness's MCP controls, or restart the
|
|
52
52
|
harness.
|
|
53
|
-
- Automated runs with no human to ask:
|
|
53
|
+
- Automated runs with no human to ask: an unattended `interactive` run continues its primary work,
|
|
54
|
+
while an `autonomous` run keeps its admission gate below. The final report states why journaling
|
|
55
|
+
was unavailable, the `pendingCount` and `worktreePendingCount` (or that none was written), and
|
|
56
|
+
each unjournaled finding.
|
|
54
57
|
|
|
55
58
|
Prefer restoring MCP over the CLI fallback below, and use the fallback only when the human asks or
|
|
56
59
|
MCP cannot be restored for this session. Harness sandboxes commonly withhold
|
|
@@ -176,5 +179,22 @@ bounded structured facts or sanitized summaries, never raw command output, trans
|
|
|
176
179
|
environment dumps, provider payloads, or secrets. Known-secret redaction supplements the author's
|
|
177
180
|
minimization responsibility; it cannot prove arbitrary prose contains no undisclosed secret.
|
|
178
181
|
|
|
182
|
+
## Credential failures
|
|
183
|
+
|
|
184
|
+
- Never search shell profiles, `env`, or `.env*` files for a credential. Check presence only with
|
|
185
|
+
`[ -n "${VAR+x}" ] && echo SET || echo UNSET`; never copy the token around a sandbox denial.
|
|
186
|
+
- A missing or stale credential blocks hosted delivery. In interactive mode the outbox keeps the
|
|
187
|
+
record visibly pending: do not drop it or invent a fallback. Ask the human to refresh the
|
|
188
|
+
connection, then restart the MCP client or call `outbox_flush`; an automated run reports.
|
|
189
|
+
|
|
190
|
+
## Child identity for spawned agents
|
|
191
|
+
|
|
192
|
+
- Pass the parent's own session id in the assignment prompt. The child resolves its own id from its
|
|
193
|
+
runtime through the consumer's identity policy, then calls `session_ensure` with it, the given
|
|
194
|
+
`parentSessionId`, its `agent`, and its version before other work. With no id or a failed ensure,
|
|
195
|
+
it stops and reports the blocker; never guess an id or pass a path, URL, or synthesized value.
|
|
196
|
+
- A subagent sharing its parent's id must not `session_ensure` it as a child. It asks the parent
|
|
197
|
+
to append, or skips this protocol.
|
|
198
|
+
|
|
179
199
|
Consumer wrappers own service configuration, server registration and approval rules, trusted mode
|
|
180
200
|
selection, issue routing, and archival authorization. The server fixes the outbox location.
|
|
@@ -99,5 +99,11 @@ For a batch, preflight every entry before writing any issue. One invalid entry f
|
|
|
99
99
|
batch. A partial transport failure stops further creation and reports every issue already created.
|
|
100
100
|
Do not retry an entry that succeeded.
|
|
101
101
|
|
|
102
|
+
## Bounded output
|
|
103
|
+
|
|
104
|
+
- A search returns at most five `#N: title -- url` lines.
|
|
105
|
+
- A near-duplicate returns `Duplicate of #N: <url>`.
|
|
106
|
+
- A mutation returns the action, URL, labels, and verification, plus milestone and project when set.
|
|
107
|
+
|
|
102
108
|
Consumer wrapper owns: repository allowlist, taxonomy, issue template, credential, and CLI wrapper.
|
|
103
109
|
It may tighten this gate and may not weaken it.
|
package/skills/manifest.json
CHANGED
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
"plugin": "vouchington-workflow",
|
|
7
7
|
"pluginVersion": "0.12.0",
|
|
8
8
|
"path": "agent-workflow/SKILL.md",
|
|
9
|
-
"prerequisites": [
|
|
9
|
+
"prerequisites": [
|
|
10
|
+
"blackboard",
|
|
11
|
+
"github-issue",
|
|
12
|
+
"pr-description",
|
|
13
|
+
"retrospective",
|
|
14
|
+
"review-ci-logs"
|
|
15
|
+
]
|
|
10
16
|
},
|
|
11
17
|
{
|
|
12
18
|
"name": "backend-vitest-test-authoring",
|
|
@@ -24,7 +30,7 @@
|
|
|
24
30
|
{
|
|
25
31
|
"name": "bounded-iteration",
|
|
26
32
|
"plugin": "vouchington-database",
|
|
27
|
-
"pluginVersion": "0.
|
|
33
|
+
"pluginVersion": "0.7.0",
|
|
28
34
|
"path": "bounded-iteration/SKILL.md"
|
|
29
35
|
},
|
|
30
36
|
{
|
|
@@ -109,19 +115,19 @@
|
|
|
109
115
|
{
|
|
110
116
|
"name": "postgres-node-performance-tuning",
|
|
111
117
|
"plugin": "vouchington-database",
|
|
112
|
-
"pluginVersion": "0.
|
|
118
|
+
"pluginVersion": "0.7.0",
|
|
113
119
|
"path": "postgres-node-performance-tuning/SKILL.md"
|
|
114
120
|
},
|
|
115
121
|
{
|
|
116
122
|
"name": "postgres-partitioning-uuid-v7",
|
|
117
123
|
"plugin": "vouchington-database",
|
|
118
|
-
"pluginVersion": "0.
|
|
124
|
+
"pluginVersion": "0.7.0",
|
|
119
125
|
"path": "postgres-partitioning-uuid-v7/SKILL.md"
|
|
120
126
|
},
|
|
121
127
|
{
|
|
122
128
|
"name": "postgres-schema-design",
|
|
123
129
|
"plugin": "vouchington-database",
|
|
124
|
-
"pluginVersion": "0.
|
|
130
|
+
"pluginVersion": "0.7.0",
|
|
125
131
|
"path": "postgres-schema-design/SKILL.md"
|
|
126
132
|
},
|
|
127
133
|
{
|
|
@@ -20,6 +20,8 @@ Measure the query plan and the workload shape before changing code.
|
|
|
20
20
|
connection pressure together.
|
|
21
21
|
- Cut round trips: fold dependent reads, batch per-item lookups, and overlap independent reads; see
|
|
22
22
|
[round trips](references/performance-patterns.md#round-trips).
|
|
23
|
+
- Read the plan for hidden per-row work before trusting a change; see
|
|
24
|
+
[reading plans](references/reading-plans.md).
|
|
23
25
|
- Read [performance patterns](references/performance-patterns.md) before changing a high-volume
|
|
24
26
|
path.
|
|
25
27
|
|
|
@@ -38,6 +38,82 @@ recipient-keyed fan-out rows. Verify actual pruning with EXPLAIN.
|
|
|
38
38
|
Generic `jonathanong/no-mistakes` checks include `postgres-no-offset`; inspect available rules
|
|
39
39
|
and configure reviewed cross-partition exceptions rather than treating a rule as plan evidence.
|
|
40
40
|
|
|
41
|
+
## Volatile functions in predicates
|
|
42
|
+
|
|
43
|
+
`uuidv7(interval)` and `random()` are volatile: PostgreSQL evaluates them for every row, so
|
|
44
|
+
`WHERE id >= uuidv7(INTERVAL '-30 days')` is a `Filter`, cannot be an `Index Cond`, and blocks
|
|
45
|
+
partition pruning. Compute the bound once per statement and compare against that. A bind
|
|
46
|
+
parameter works, as does a stable expression such as `now() - INTERVAL '30 days'` fed to a stable
|
|
47
|
+
or immutable bound helper. A stable bound can be an index condition, and a partitioned table
|
|
48
|
+
prunes at executor start. Confirm `Index Cond` and `Subplans Removed` in the plan.
|
|
49
|
+
|
|
50
|
+
```sql
|
|
51
|
+
-- evaluated per row
|
|
52
|
+
SELECT id FROM events WHERE id >= uuidv7(INTERVAL '-30 days');
|
|
53
|
+
-- bound computed once by the caller
|
|
54
|
+
SELECT id FROM events WHERE id >= $1;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Function inlining
|
|
58
|
+
|
|
59
|
+
PostgreSQL inlines a simple scalar SQL function into the calling query, so its body is planned
|
|
60
|
+
with the outer query and its predicates can become index conditions. A SQL function whose body
|
|
61
|
+
contains a subquery (for example `EXISTS`) is never inlined as a scalar call; a set-returning SQL
|
|
62
|
+
function in `FROM` follows a separate inlining path. Functions with `SECURITY DEFINER` or
|
|
63
|
+
a `SET` clause, and PL/pgSQL functions, are not inlined either, nor is a body that is not a single
|
|
64
|
+
simple expression. When the function is not inlined, the plan shows only `fn_x(col)` in a `Filter`
|
|
65
|
+
or output, and none of the queries it runs per row.
|
|
66
|
+
|
|
67
|
+
```sql
|
|
68
|
+
-- not inlined: the EXISTS runs once per orders row
|
|
69
|
+
CREATE FUNCTION fn_has_events(order_id uuid) RETURNS boolean
|
|
70
|
+
LANGUAGE sql STABLE AS $$ SELECT EXISTS (SELECT 1 FROM events e WHERE e.order_id = $1) $$;
|
|
71
|
+
SELECT id FROM orders WHERE fn_has_events(id);
|
|
72
|
+
-- plannable: write the semi-join in the query
|
|
73
|
+
SELECT o.id FROM orders o WHERE EXISTS (SELECT 1 FROM events e WHERE e.order_id = o.id);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Tell from the plan: a function name in `Filter` or output with no matching scan nodes. Count calls
|
|
77
|
+
with `SET LOCAL track_functions = 'all'` and `pg_stat_xact_user_functions` in the same transaction;
|
|
78
|
+
inlined functions are not counted. See [reading plans](reading-plans.md#opaque-function-calls).
|
|
79
|
+
Put per-row logic in the query as a join or `EXISTS`, or keep the function a plain expression.
|
|
80
|
+
|
|
81
|
+
## Reading views by key
|
|
82
|
+
|
|
83
|
+
PostgreSQL expands a view into the calling query, and a simple view lets the outer filter push
|
|
84
|
+
down into its base tables. A view that cannot be flattened (aggregation, `DISTINCT`, `LIMIT`,
|
|
85
|
+
set operations, window functions, volatile target-list expressions, a security barrier) is built
|
|
86
|
+
before a filter on a value it derives (such as an aggregate) applies, and before a sort, page, or
|
|
87
|
+
count over its output. A restriction on a grouping column can still be pushed below an aggregate,
|
|
88
|
+
so let the plan decide. Confirm from the
|
|
89
|
+
plan (large `Actual Rows` and `Actual Loops` inside the view's nodes), then choose the ids from the
|
|
90
|
+
base tables first and read the view by its key, as a correlated `LATERAL` lookup so the page
|
|
91
|
+
restriction can reach inside the view. This works for an aggregate or `GROUP BY` view whose key is
|
|
92
|
+
a grouping column; a `LIMIT` in the view blocks pushdown of any qual, so rewrite that view's query
|
|
93
|
+
against the base tables instead. A plain join to the page is a join clause, not a restriction
|
|
94
|
+
inside the view, so the planner can still build the whole aggregate first.
|
|
95
|
+
|
|
96
|
+
```sql
|
|
97
|
+
-- order_summaries aggregates events, so event_count is built for every order, then filtered
|
|
98
|
+
SELECT * FROM order_summaries WHERE event_count > 3 ORDER BY id LIMIT 50;
|
|
99
|
+
-- choose ids from base tables with an equivalent predicate, then read the view by key
|
|
100
|
+
WITH page AS (
|
|
101
|
+
SELECT o.id FROM orders o
|
|
102
|
+
WHERE (SELECT count(*) FROM events e WHERE e.order_id = o.id) > 3
|
|
103
|
+
ORDER BY o.id LIMIT 50
|
|
104
|
+
)
|
|
105
|
+
SELECT s.id, s.total_cents, s.event_count
|
|
106
|
+
FROM page
|
|
107
|
+
CROSS JOIN LATERAL (
|
|
108
|
+
SELECT * FROM order_summaries WHERE id = page.id
|
|
109
|
+
) s
|
|
110
|
+
ORDER BY s.id;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Filter and sort on indexed base-table columns, and read only the view columns the page needs.
|
|
114
|
+
Confirm in the plan that the view's inner nodes run with `Actual Loops` equal to the page size and
|
|
115
|
+
read only that page's rows.
|
|
116
|
+
|
|
41
117
|
## Round trips
|
|
42
118
|
|
|
43
119
|
Sequential queries can add network round trips when the client waits between statements, and planning
|