Review client documentation PR
Usage: /review-client <PR-number> — for example, /review-client 451.
The PR number is required. If the user invokes the skill with no
argument (e.g. just /review-client), do not proceed: ask them which PR to
review and stop. Do not infer the PR from the current branch, recent
commits, gh pr list, or any other source — the answer must come from the
user. Acceptable forms are a bare number (451), a #-prefixed form
(#451), or a full PR URL; reject anything else and ask again.
Reviews a pull request that touches QWP client documentation against a
fixed checklist of questions an application developer (human or agent) would
need answered before they can ship code. The output is a structured review
the docs author can act on directly.
Scope: QWP only
There are two parallel families of client documentation in this repo:
- QWP (in scope). The new WebSocket transport. Pages render under
/docs/connect/clients/<lang>/ (slug slug: /connect/clients/<lang>) and
document the ws:: / wss:: connect-string schemas, the QWP ingress and
egress wire protocols, store-and-forward, durable ACK, multi-host
failover, etc. These are the only pages this skill reviews.
- Legacy ILP (out of scope). The older HTTP/TCP transport. Pages document
only
http:: / https:: / tcp:: / tcps:: schemas and ILP-specific
buffering. These pages may still live on disk under
documentation/ingestion/clients/ (and some have been given a
/connect/clients/... slug as part of a routing reshuffle without their
content being rewritten). Skip them. If the user wants those reviewed,
recommend /review instead.
How to tell them apart on a per-file basis: open the file and look for
QWP signals — ws:: / wss:: in code blocks, references to
QwpQueryClient / QwpWs* / qwp_ws_* APIs, sections on store-and-forward,
durable ACK, or FSN watermarks, a link to
/docs/connect/clients/connect-string/. A page whose only schemas are
http:: / https:: / tcp:: / tcps:: and whose content centres on ILP
buffering is legacy — exclude it from the review even if it appears in the
PR's file list.
When to use
Trigger when the user runs /review-client <PR> or asks to review a QWP
client documentation PR. Examples:
/review-client 451
- "review the Java QWP client doc PR #451"
- "run review-client on PR 451"
For generic doc review use /review instead. For legacy ILP client pages,
also use /review.
Inputs
- PR number (required). Resolve via
gh pr view <number> to get the
branch, head SHA, and changed files.
- If the user omits the PR number, ask for it. Do not guess from the current
branch.
Workflow
Step 1: Fetch PR metadata and changed files
gh pr view <PR> --json number,title,headRefName,baseRefName,headRefOid,files
Then narrow the file list to QWP client documentation only. Candidate
paths to consider:
documentation/ingestion/clients/*.md — but include only files whose
content documents QWP (see the Scope section above for
the QWP-vs-legacy signals). Files that still document http:: / tcp::
ILP only are out of scope, even when their slug now resolves to
/docs/connect/clients/....
documentation/connect/**/*.md — the new Connect section. In scope.
documentation/client-configuration/connect-string.md (and any sibling
connect-string* file wherever it lives) — the shared QWP connect-string
reference. Always in scope when any QWP client page is changed; read
it even if the PR did not modify it.
To classify a candidate file fast: read its first ~80 lines. If you see
ws:: / wss:: in code blocks, references to QwpQueryClient or
qwp_ws_* APIs, or a link to the connect-string reference under
/docs/connect/clients/connect-string/, treat it as QWP. If the only
schemas it shows are http:: / tcp::, treat it as legacy and skip.
Be explicit in your output about which files you considered and why each
was included or skipped — the docs author needs to know whether the absence
of a file from the review means "reviewed clean" or "skipped as legacy."
If the PR changes no QWP client docs, stop and tell the user — recommend
/review instead.
Step 2: Read each changed client doc in full
Use the Read tool on each file at the PR's head SHA (check it out, or read
from the working tree if the branch is already checked out). Do not rely on
the diff alone — context outside the diff matters for "is this question
answered anywhere on the page" checks.
If a page links to a sibling reference (e.g., connect-string.md), read
that too. The connect-string page is shared across all client docs; treat it
as in-scope whenever any client page is changed.
For multi-file PRs, the parent does not need to read each in-scope file
end-to-end — the deep reads are delegated to per-file subagents in Step 3.
The parent only needs the first ~80 lines of each candidate to classify it
as QWP vs legacy (Step 1). The shared connect-string reference is the one
exception: the parent should read it once, because every per-file subagent
will need to cite it.
Step 3: Run the checklist
Parallelization
The per-file checklist work is independent across files. For any PR
touching more than one in-scope QWP file, fan out using the Agent tool:
spawn one subagent per in-scope file, each running the full checklist
against its assigned file, and have the parent consolidate the per-file
reports into the final review.
Send the subagents in a single message with multiple Agent tool uses so
they run concurrently. Each subagent's prompt must be self-contained — it
will not see this skill's text or the conversation history. Include:
- The exact file path to review (absolute path).
- The PR number and head SHA, for citation context.
- The full checklist (items 1–30 from this section, including the
Agent one-shot suitability block) inlined into the prompt. Do not
just reference "the skill's checklist" — the subagent cannot resolve
that. Items 23–30 are the agent-fit bar and must be applied with
equal weight to the original 22; do not let the subagent silently
treat them as "nice to have."
- The expected output format: one finding per checklist item, severity
ordered (❌ → ⚠️ → ✅), with the section tag and exact line citations
(per Step 4). The subagent returns the file's section of the review,
ready to drop into the consolidated output.
- The doublecheck requirement from the Doublecheck
subsection at the end of Step 3.
- The list of other in-scope QWP client files in the PR, so cross-file
checks (item 21 schema drift, item 18 sibling-link verification) can
reference them. The subagent should read those siblings as needed for
comparison, but not produce findings on them — the agent reviewing each
sibling will cover its own.
The shared connect-string.md reference (always in scope when any QWP
client page changes) gets its own subagent, evaluated only against the
checklist items that apply to a reference page (typically 12–14, 18, 19,
the parts of 22 that surface in the error-handling section, and the
agent-fit items 23, 24, 26, 28 — section self-containedness and
inference-trap refutations matter for a reference doc that agents grep
for individual keys, possibly more than for a client doc).
If the PR only touches one QWP file, run the checklist inline in the
parent — fan-out has no benefit and adds latency.
After the subagents return, the parent's job is to (a) consolidate
findings into the final output structure per Step 4, (b) write the
end-of-review summary that compares across files (top-three gaps,
cross-cutting themes, actively misleading items), and (c) doublecheck any
cross-file claim it added that no single subagent could have verified
alone.
Verdict definitions
For each changed client page, evaluate every item below against two
bars, and take the lower verdict:
- Human-skim bar. Can a developer reading the page top-down answer
the question?
- Agent one-shot bar. If an agent retrieves only the most relevant
section (a single
##/### chunk) into its context, can it generate
working code without hallucinating, without requiring earlier-section
context, and without chasing more than one link hop? This bar is
strictly harder than the human-skim bar.
Each item gets one of three verdicts (the lower of the two bars
dominates):
- ✅ Covered — passes both bars. Cite the section/line.
- ⚠️ Partial — passes one bar but not the other; or touched on but
unclear, buried, or missing an example. Quote the relevant text and
say which bar fails and how — "right answer, wrong section" (agent),
"buried under unrelated heading" (human), "indirection chain" (agent),
"phrasing requires earlier prose to disambiguate" (agent).
- ❌ Missing — fails both bars / not addressed. Say so plainly.
Be specific. "Section X doesn't mention Y" beats "could be clearer."
Reference exact line numbers and quote short snippets when calling out a
gap. When the failure is agent-specific (e.g. an explanation is correct
but lives one section away from where an agent retrieves the code
example), say so explicitly — that is the signal the docs author needs
to choose between moving the explanation or duplicating it.
Ingestion checklist
- Inserting NULL values during ingestion. Can the reader figure out how
to write a null for a given column without trial and error? Is there an
example? Does it explain whether "omit the column" is equivalent to
"explicit null," and whether that interacts with schema inference?
- Multiple concurrent publishers. Is it clear whether
Sender (or its
per-language equivalent) is thread-safe? If not, what is the
recommended pattern — one sender per thread, pool, queue+single-writer?
Is there guidance on whether parallel senders writing to the same table
need distinct identities (sender_id, store-and-forward slots)?
- Easy to execute DDL. Is there a concrete copy-paste example for
CREATE TABLE, ALTER, DROP, TRUNCATE via the query client? Does
the page distinguish DDL response (onExecDone, EXEC_DONE,
rowsAffected = 0) from SELECT response?
- Easy to execute DML and stream rows. Is
SELECT with a row-by-row
callback shown? Is bind-parameter usage shown with the syntax
($1/?/named) the client actually accepts? Is the
"columnar batch vs row view" tradeoff explained, with an example?
- Sync vs async acks — are acks optional? Does the page say plainly
whether the application must await acknowledgements before
considering data durable, or whether
flush() / close() is enough?
For async clients (WebSocket), is the error-handler callback shown?
Does it explain what happens to in-flight data if the app exits without
awaiting the ack?
- Durable ack vs WAL ack. Is the distinction between "committed to
local WAL" and "uploaded to object storage" (Enterprise) clear? When
would an app care about
request_durable_ack?
Failover and resilience checklist
- Ingress failover is bounded. Does the page say that ingress reconnect
has a budget (
reconnect_max_duration_millis) and will eventually give
up? Is it clear what the application sees when the budget is exhausted
(terminal exception, callback, etc.)?
- Backpressure on the application side. If the server is unreachable
for a long time, where does buffered data go? Is store-and-forward
explained as the durability story, and the RAM buffer cap explained for
the non-SF case? Does the page tell the app how to detect "I am being
backpressured" so it can stop producing?
- Connection-state notifications. Can the app wire a callback that
fires on
CONNECTED, DISCONNECTED, RECONNECTED, FAILED_OVER,
AUTH_FAILED, RECONNECT_BUDGET_EXHAUSTED? Is there a code example?
Does the example show what an app would actually do (log, alert,
redirect traffic)?
- Mid-stream query failover — duplicate-data hazard. Does the page
explain that if a query fails over mid-result, the server replays from
the start of the result set? Does it show the
onFailoverReset
callback and warn explicitly that without wiring this callback the
application will see duplicate rows? This is the single most common
footgun — it must be impossible to miss. Repetition required: the
warning (at least a one-line note + link to the dedicated subsection)
must also appear in every other section that mentions failover,
mid-stream behaviour, or query restart — the per-query failover knob
table, the failover event-fields table, the connection-state
observability section. An agent retrieving the knob table alone
without the warning attached will generate buggy code.
- Per-query failover bounds. Are the failover knobs
(
failover_max_attempts, failover_backoff_*, failover_max_duration_ms)
listed with defaults? What does the app see if all attempts are
exhausted?
Connect string and config checklist
- Reference to connect-string docs. Is there at least one link from
the client page to the connect-string reference? Is the link placed
where a reader needing it would actually look (near the first connect
string example, not just in a footer)?
- Connect string is easy to assemble. Can a reader build a working
QWP connect string from scratch? Schema (
ws:: / wss::), address
syntax, where to put auth, where to put TLS, separator/terminator
rules. Are common pitfalls called out (trailing ;, escaping ; or
= in values, multi-address syntax)? Legacy http:: / tcp:: need
only a "for legacy ILP transports, see [link]" pointer — do not
require coverage on the QWP page itself.
- Environment variable path. Is
QDB_CLIENT_CONF (or per-language
equivalent) documented as the credentials-out-of-code path?
Cross-cutting
Thread safety statement. Stated in every section whose first
example creates a handle (sender, reader, query, cursor) — at
minimum a one-line restatement linking to the dedicated Concurrency
section. The naive "stated once" policy works for a human scanning
top-down but fails agent retrieval, which loads one section at a
time: an agent that fetches "DDL execution" but not "Concurrency"
will generate code that shares a single handle across threads. The
placement test ("a reader looking for can I share this instance?
would find it") still applies to the dedicated section.
Error-handling story is end-to-end. For each error class (auth,
schema, parse, transport, mid-stream), the page should answer: how is
the error surfaced (throw vs callback), what state is the client in
afterward (usable vs must-reset vs must-close), and what should the
app do.
Migration / "what changed from before" notes if applicable. If this
PR introduces a new transport (e.g., QWP) alongside legacy (e.g., ILP),
is there a side-by-side that a maintainer of existing code can scan?
No content dependencies on legacy ILP pages. Legacy ILP client
documentation (documentation/ingestion/clients/{c-and-cpp,dotnet,nodejs,python}.md,
documentation/connect/compatibility/ilp/**, documentation/ingestion/clients/date-to-timestamp-conversion.md,
and similar ILP-era support material) is on a deprecation path and
will be removed. Outbound links from a QWP client page to legacy ILP
content are acceptable only when framed as a "for legacy ILP,
see X" escape hatch — typically inside a :::tip Legacy transports
admonition near the top of the page. Flag as Missing any link
that depends on a legacy page to explain a concept the QWP reader
needs (e.g., "see the ILP overview for exactly-once delivery
semantics"). The concept must live somewhere that survives ILP
deprecation: the QWP page itself, the connect-string reference, a
transport-agnostic concepts page, or a new QWP-native page. Look
especially for sneaky cases: anchor links into legacy pages
(/docs/connect/compatibility/ilp/overview/#some-section) and
references to timestamp-conversion / date-handling support pages
that were authored for ILP. Fix shape suggestion: "move this
explanation onto the QWP page, or root it in a shared concepts page
under /docs/concepts/."
Enterprise connection patterns and OIDC. The page shows at least
one worked example combining TLS (wss::), credentials, and
multi-host addr=... — the realistic production shape — not just
three separate one-liners. For each Enterprise auth path the client
supports (HTTP basic, bearer token, OIDC, mTLS), there is either
(a) a concrete example showing how an application obtains and
passes the credential, or (b) an explicit one-line statement that
the path is not supported by this client, with a pointer to the
closest alternative. Silence is not acceptable — a reader must
not have to grep the page to discover that OIDC token refresh, mTLS
client certificates, or token rotation is unsupported. Special
attention to OIDC: the OpenID Connect page
documents the server-side flow; the client page must answer "how
does the application acquire a token to pass to the client" and
"what happens when the token expires mid-session — does the client
refresh, does it fail, does it expect the app to register a
callback?" A bare "for OIDC, see the security page" is not
coverage — flag as Partial at best.
Bind-parameter type coverage and limitations. Where the page
documents bind parameters (or the per-language equivalent), it
enumerates all supported bind types — not a sample ending in
"and more" or "…". For every QuestDB column type a reader might
expect to bind (BOOLEAN, BYTE, SHORT, CHAR, INT, IPv4, LONG, FLOAT,
DOUBLE, TIMESTAMP, timestamp_ns, DATE, SYMBOL, VARCHAR, BINARY,
UUID, LONG256, DECIMAL64/128/256, GEOHASH, DOUBLE[]/ARRAY), the
page either (a) shows the setter / API and the type code, or
(b) lists the type explicitly under "unsupported as bind parameter"
with a one-line rationale (e.g., "ARRAY: bind ARGS frames don't
carry array shape; use SQL array literals instead"). Verdict
ladder: complete enumeration → Covered; sample-and-handwave ("and
more", "…", "see source") → ⚠️ Partial; no list at all → ❌
Missing. The same principle — enumerate or call out as unsupported
— applies wherever the page documents a type-keyed surface
(ingestion column setters, result-batch accessors). The
bind-parameter table is the most common place coverage drifts
because the API is younger than the type system.
Consistent capital-markets data model across clients. Every code
example uses a capital-markets domain (trades, quotes, order books,
FX, market data). Reject generic placeholders — foo, bar,
baz, my_table, t1, Example, Test. The placeholder pattern
is a tell that the example was written in isolation and was never
cross-read against sibling client pages. Beyond the per-page check,
examples must be consistent across the full set of QWP client
pages: same table names, same column names, same column types,
same symbol values. When the PR ships one client page and the other
QWP client pages already exist, compare schemas — flag every
inconsistency the reader would hit when porting between languages:
| Class of drift |
Examples |
| Table name |
trades vs Trades vs market_trades |
| Column name |
qty vs quantity vs amount; symbol vs sym vs instrument |
| Column type |
LONG vs DOUBLE for size; SYMBOL vs VARCHAR for ticker |
| Symbol value |
EURUSD vs EUR/USD vs EUR-USD; ETH-USD vs ETHUSD |
| Timestamp precision |
microseconds vs nanoseconds for the same notional event |
Verdict ladder: domain-correct, placeholder-free, schema matches
every other QWP client page → ✅ Covered; domain-correct but
schema drifts from siblings → ⚠️ Partial (cite the specific
drift); generic placeholders or non-capital-markets domain
(sensors, IoT, logs) → ❌ Missing. Fix shape: pick the schema
used by the page with the most polished example and align the
others, or call out one canonical schema in this skill / a README
under documentation/ingestion/clients/ so future client docs land
on it without negotiation.
Diagnostic information on the error object/event. Item 16
enumerates the error categories and the surfacing / recovery
model. This item demands the next level of detail: what
structured information is on the error and how user code reads
it, so a real production handler can log, alert, debug, and
correlate with server-side state.
For every error path the client exposes, the page documents:
- Server message text — which field or parameter carries it
(
SenderError.getServerMessage(), the message parameter on
onError, QwpWsSenderError.message, etc.), whether it is
stable enough to pattern-match on, localized vs English, and
whether it is capped in length.
- Status code — both numeric (e.g.
0x05) and named (e.g.
PARSE_ERROR), and how user code reads each.
- Affected scope — table name on ingest errors, FSN range
(
from_fsn/to_fsn) or batch identifier on async ingest
rejections, failing SQL / bind index on query parse errors,
query ID on mid-stream query failures.
- Server correlation / request ID for support tickets, if the
protocol carries one; otherwise an explicit statement that no
such ID is surfaced.
- PII / secret safety — whether the message text is safe to
forward to end-user UIs or third-party error trackers, or
whether the application must sanitise first.
Verdict ladder: every bullet covered on every error path → ✅
Covered; primary fields named but stability / PII / correlation
silent → ⚠️ Partial; only "the message is human-readable text"
with no field-by-field guidance → ❌ Missing. The fix shape is
almost always a small table next to the error-handling code
example listing the fields, their types, and one-line guidance per
field — much more readable than burying these properties in
prose.
Co-location requirement (agent one-shot bar). Every error path's
diagnostic table must live in the same ##/### section as
that error path's code example. A correct table located one section
away from its example fails this item under the agent bar: an agent
retrieving the code-example section gets no field-level guidance
and will fabricate field names; an agent retrieving the table
section gets no concrete invocation and will fabricate the
surrounding code. Duplicate the table (or the example) into both
sections when one error surface is referenced from multiple places.
Agent one-shot suitability checklist
The previous 22 items test what the page says. The next seven items
test whether the page is retrievable in one shot — whether an agent
that loads a single section into context can ship working code from it.
The bar is strictly harder than the human-skim bar: a page where every
fact is true but every fact requires three hops to assemble is human-
adequate and agent-broken.
Section self-containedness. Each ## and ### section must
name (or link to a single ≤ 1-hop reference for) the concrete API
call, the connect string used in its example, the language-specific
import / #include, and the next step the reader takes. An agent
that retrieves only that section into context must be able to
compile the example without inheriting setup from an earlier
paragraph. Test: read each section in isolation — if the first code
block references a reader variable that was constructed three
sections earlier and nowhere referenced in the current section, the
section fails the bar. Verdict ladder: every section closes its own
setup → ✅; one or two sections borrow setup from a labelled "Quick
start" but link to it explicitly → ⚠️ Partial; most sections assume
inherited state → ❌ Missing. Fix shape: prepend a one-line "you
have a reader constructed as in Quick start"
pointer, or duplicate a 2-line setup snippet.
Inference-trap explicitness. The high-confidence wrong guesses
an LLM makes about this API must be explicitly refuted somewhere
on the page — ideally in the section where the wrong guess would be
written. Enumerate the plausible non-existent API calls and check
each:
setNull(name) / set_null(col) when nulls are actually written
by omitting the setter.
bind_array(...) / setArray(...) when arrays are not supported
as bind parameters.
setInterval(...) when INTERVAL has no bind setter.
request_durable_ack on the reader when it is sender-only.
connectionListener / onConnect / onDisconnect callbacks
when no structured connection-state callback exists.
setUuid(string) when only the 16-byte form exists.
auto_flush_rows / auto_flush_bytes for the WebSocket sender
when auto-flush is rejected.
- Any sibling-language API name that doesn't exist in this
language's client (
flushAndKeep vs flush_and_keep,
await_acked_fsn vs awaitAckedFsn, etc.).
Verdict ladder: every plausible wrong guess refuted in-section (or
in a dedicated "Unsupported" table that the in-section text links
to) → ✅; some refuted → ⚠️ Partial; only positive knowledge with
no negative knowledge → ❌ Missing. Fix shape: an "Unsupported"
row in the relevant setter / column-getter / config-key table, or
a one-line refutation in the section that mentions the closest
supported alternative.
Information flow simple → complex. Walk the code examples in
document order. The first runnable example uses the simplest
connect string (ws::addr=localhost:9000;), no auth, no TLS, no
failover, and the smallest possible payload. Each subsequent
example introduces at most one new concept (auth, then TLS, then
multi-host, then failover, then SF, etc.) — never two at once.
Production-shape examples (multi-host + token + TLS + failover +
durable ACK) appear at the end, not the beginning. Verdict
ladder: monotonic complexity from top to bottom → ✅; one
section inverts the order (e.g. the first example shows TLS but
a later section shows the bare connect string) → ⚠️ Partial;
the page opens with the production example → ❌ Missing. Fix
shape: reorder examples, or split a too-complex first example
into a minimal version followed by a "production shape" version.
Vocabulary consistency within the page. The same operation must
be referred to by the same name everywhere. The same identifier
must be spelled identically in code and prose. If the page says
"the cursor" in section X, "the result handle" in section Y, and
"the row stream" in a table caption, an agent will treat them as
three distinct objects. Extends item 21 (cross-page schema
consistency) inward: this is the intra-page equivalent. Verdict
ladder: identifiers and operation names match across the page → ✅;
one or two drifts the agent could plausibly disambiguate from
context (e.g. "callback" vs "handler" used interchangeably) → ⚠️
Partial; multiple drifts at the same surface (different setter
names referenced in code vs prose, different connect-string-key
capitalisation) → ❌ Missing. Fix shape: pick one name per concept
and grep-replace across the page.
Working-code minimum (no ellipses in the first example). The
first code example in each section must be complete: explicit
setup, explicit error handling (see item 29), explicit cleanup. No
// ... placeholders, no // process row..., no // handle error.... Subsequent examples in the same section may abbreviate
once the pattern is established. The bar is "an agent pasting this
snippet verbatim gets a compiling program" — not "an agent
paraphrasing this snippet gets the gist." Verdict ladder: every
first-in-section example is complete → ✅; some have ellipses but
the missing pieces are trivially inferable (// imports omitted)
→ ⚠️ Partial; load-bearing logic is replaced by // ... → ❌
Missing. Fix shape: inline the omitted lines.
Indirection-chain cap (≤ 1 hop). From the section where a
question is naturally asked, the answer must be reachable in at
most one link click. A "see X" pointer to a section that itself
says "see Y" is a two-hop chain and fails. Item 12 requires a
link to the connect-string reference; this item caps how often the
page resolves a question by indirection at all. Note that the
"links to a sibling page count as Covered" allowance in Style
guidance applies only to the human-skim bar; under the agent bar,
every indirection is consumed against this cap, and a section
answering a question by sending the agent on a two-hop chase fails.
Verdict ladder: every concept reachable in ≤ 1 hop → ✅; one or
two two-hop chains for non-load-bearing detail → ⚠️ Partial;
load-bearing concepts (null handling, thread safety, failover
callback) require two or more hops → ❌ Missing. Fix shape: inline
the destination content, or restructure so the question is asked
in the section that already contains the answer.
Failure mode in the golden path, not bolted on later. The first
runnable example in each section must include the error path — not
a happy-path try { ... } catch { print } placeholder, but the
real shape of error handling the section's API requires (the C
err_out parameter check + goto on_error cleanup; the C++
catch (const line_reader_error&) with e.code() dispatch; the
sender error-handler callback registration; the async error poll).
Agents prompted "write the function" overwhelmingly copy the
first example's shape — if the first example skips error
handling, the generated app will skip it too. Item 16 covers
whether error handling is documented; this item covers whether
it is demonstrated in the example agents will copy. Verdict
ladder: every first-in-section example demonstrates the real
error path → ✅; some examples bolt error handling on as a later
snippet → ⚠️ Partial; the page's error-handling section is the
only place error handling appears → ❌ Missing. Fix shape: rewrite
the first example to include the error path, or move the
error-handling section ahead of the API examples and reference it
from each.
Capability surface visible above the fold. Pages that document a
client with more than one capability class — ingestion and
querying, read and write, sync and async, blocking and
streaming — must surface every capability in all three of these
places:
- the frontmatter
description: field (this is what search,
sidebar tooltips, and AI retrieval index for landing decisions);
- the page's opening paragraph (the first text a reader sees after
the title);
- a first-level
## heading on the page (not buried under ###).
A reader landing on the page from a "how do I query QuestDB from
<language>" query must learn within the first screen of the page
— without scrolling past hundreds of lines of ingestion content —
that the client does queries at all. An agent that retrieves only
the page's opening chunk into context must learn what the client
can do from that chunk alone; if the second capability surfaces
only midway down the page, the retrieval misses it and the agent
will either fabricate a non-existent separate client or send the
user to the wrong page entirely (PGWire / REST). The most common
failure pattern is a page that was originally ingestion-only and
gained query support in a later PR — frontmatter, opening
paragraph, and any top-of-page :::info / :::tip admonition all
need to be rewritten when the new capability lands, not just a new
## section appended at the bottom.
Verdict ladder: every capability mentioned in frontmatter
description AND opening paragraph AND a first-level heading → ✅
Covered; one of those three surfaces omits a capability → ⚠️
Partial (cite which surface and which capability); a capability
appears only deep in the page → ❌ Missing. Fix shape: rewrite the
frontmatter description to enumerate every capability class
explicitly (e.g. "client for high-throughput ingestion and SQL
query execution"), rewrite the opening paragraph as "two
complementary APIs live in the same library: ... and ...", promote
the new capability to its own ## heading at the same depth as
the existing ones, and update any top-of-page admonitions that
redirect readers elsewhere ("for querying see PGWire") to reflect
the new capability.
Doublecheck before reporting a finding
Every ❌ Missing and ⚠️ Partial verdict must be verified by re-reading
the cited lines before it goes into the output. False findings damage
the review more than missed ones — they make the docs author waste
time chasing a non-issue and erode trust in the rest of the report. The
checklist is long and the same words ("flush", "error", "thread") recur
across sections; it is easy to write a finding from memory and miss the
paragraph two sections down that already covers it.
Before finalizing each ❌ / ⚠️ finding, do the following — and only then
write it into the output:
- Re-read the cited line range. Read the exact range you plan to
cite, plus enough surrounding lines (5–10 above and below) to confirm
the gap is real and the cited text is what you think it is.
- Search the whole page for the missing concept. A finding of the
form "the page never mentions X" is invalid if X appears under a
different heading. Grep / scan for the relevant identifier (
setNull,
onFailoverReset, sender_id, OIDC, backpressure, etc.) across
the file before claiming absence.
- Confirm section / header attributions. A finding like "the
thread-safety statement is buried under 'Parallel queries'" is wrong
if the statement is actually under "Concurrency"; verify the section
heading that contains the cited line.
- Verify both sides of cross-file claims. For findings comparing
across files (schema drift in item 21, link-target validity in item
18, "the Rust page does this correctly" comparisons), re-read both
files at the cited locations before reporting. A misattributed
comparison undermines the whole cross-file argument.
- Confirm exact quotes. If the finding quotes the page (e.g., "ends
literally with 'and more'"), re-confirm the quote is verbatim and
appears at the cited line — paraphrased "quotes" are a common
failure mode.
- Simulate one-section retrieval for agent-fit findings (items 23-29
and the agent-bar half of any other item). Before flagging an
agent-fit gap, mentally load just the cited
##/### section into a
fresh context window and ask: "Can I generate the code from this
alone?" If the answer is yes, the section is self-contained; the
finding is invalid. If the answer requires earlier-section state, an
identifier introduced elsewhere, or a two-hop link chase, the gap is
real — and the finding should name which of those three causes it.
✅ Covered findings can be lighter-touch: a citation that points to the
right region is sufficient verification. The skew toward verifying
negative findings is intentional — a false ❌ wastes more author
attention than a false ✅.
When the per-file work is fanned out to subagents (see
Parallelization), each subagent is responsible for
doublechecking its own findings before returning. The parent must
additionally doublecheck any cross-file claim it adds to the end-of-review
summary — those claims didn't exist in any single subagent's output and
therefore weren't verified upstream.
Step 4: Produce the review
Format the output as one section per changed file. Within each file,
order findings by severity, worst first — ❌ Missing, then ⚠️ Partial,
then ✅ Covered at the bottom. This is the load-bearing rule of the output
format: human readers scan top-down looking for action items, and a doc
author should be able to stop reading as soon as the ❌/⚠️ blocks end.
Do not group by checklist section (Ingestion / Failover / Connect
string / Cross-cutting / Agent one-shot). Instead, tag each finding
with its section in parentheses after the title — (Ingestion),
(Failover), (Connect string), (Cross-cutting), (Agent one-shot)
— so the author still knows which category an item belongs to without
losing the severity ordering. For items where the verdict is the lower
of two bars (e.g. human-Covered but agent-Partial), tag the failing
bar in the finding body: "Partial under the agent bar: the table at
lines X-Y answers the question but lives in a separate section from
the code example at lines A-B that an agent would retrieve first."
Within a severity bucket, order by impact (the gap a reader would hit
first or hardest comes first). When in doubt, follow the checklist's own
ordering as a tiebreaker.
The ✅ Covered block at the bottom may be terser than the ❌/⚠️ blocks
above it — one-line confirmations with citation are fine. The point of
keeping Covered findings in the output at all is to let the author see
that the item was checked and reassure them no follow-up is needed; it
is not to re-justify the verdict.
Use this structure:
## documentation/ingestion/clients/<lang>.md
- ❌ **Missing — inserting NULL values (Ingestion).** The column-method
list (lines 245-256) shows typed setters but never says how to write
null. No example. Recommend adding either an explicit `setNull(name)`
example or a one-liner stating that omitted columns are stored as null.
- ❌ **Missing — duplicate-data hazard on mid-stream failover (Failover).**
The `onFailoverReset` callback is mentioned (lines 784-790) but the page
does not say *what happens if you don't wire it*. Add an explicit
warning: "Without an onFailoverReset handler that clears accumulated
results, the application will observe duplicate rows after a mid-stream
reconnect."
- ⚠️ **Partial — multiple publishers (Ingestion).** Line 845 states
`Sender` is not thread-safe, but the statement is under "Parallel
queries" where a reader looking for ingestion guidance would not look.
Move or duplicate under "Data ingestion."
- ⚠️ **Partial — OIDC (Cross-cutting).** Line 172 is a bare "see the
security page" pointer; the client page must answer how the app
acquires the token and what happens on expiry.
- ⚠️ **Partial under agent bar — thread safety in DDL section
(Cross-cutting, item 15).** The CREATE TABLE example at lines
559-582 never restates the thread-safety contract. An agent
retrieving only the DDL section will not learn that `Sender` is
single-threaded. Add a one-line note linking to the Concurrency
section at the top of the DDL section.
- ⚠️ **Partial under agent bar — inference trap (Agent one-shot,
item 24).** Th
…(truncated)
1---2name: review-client3description: Review client documentation PR4---56# Review client documentation PR78**Usage:** `/review-client <PR-number>` — for example, `/review-client 451`.910The PR number is **required**. If the user invokes the skill with no11argument (e.g. just `/review-client`), do not proceed: ask them which PR to12review and stop. Do **not** infer the PR from the current branch, recent13commits, `gh pr list`, or any other source — the answer must come from the14user. Acceptable forms are a bare number (`451`), a `#`-prefixed form15(`#451`), or a full PR URL; reject anything else and ask again.1617Reviews a pull request that touches **QWP** client documentation against a18fixed checklist of questions an application developer (human or agent) would19need answered before they can ship code. The output is a structured review20the docs author can act on directly.2122## Scope: QWP only2324There are two parallel families of client documentation in this repo:2526- **QWP (in scope).** The new WebSocket transport. Pages render under27 `/docs/connect/clients/<lang>/` (slug `slug: /connect/clients/<lang>`) and28 document the `ws::` / `wss::` connect-string schemas, the QWP ingress and29 egress wire protocols, store-and-forward, durable ACK, multi-host30 failover, etc. **These are the only pages this skill reviews.**31- **Legacy ILP (out of scope).** The older HTTP/TCP transport. Pages document32 only `http::` / `https::` / `tcp::` / `tcps::` schemas and ILP-specific33 buffering. These pages may still live on disk under34 `documentation/ingestion/clients/` (and some have been given a35 `/connect/clients/...` slug as part of a routing reshuffle without their36 content being rewritten). **Skip them.** If the user wants those reviewed,37 recommend `/review` instead.3839**How to tell them apart on a per-file basis:** open the file and look for40QWP signals — `ws::` / `wss::` in code blocks, references to41`QwpQueryClient` / `QwpWs*` / `qwp_ws_*` APIs, sections on store-and-forward,42durable ACK, or FSN watermarks, a link to43`/docs/connect/clients/connect-string/`. A page whose only schemas are44`http::` / `https::` / `tcp::` / `tcps::` and whose content centres on ILP45buffering is legacy — exclude it from the review even if it appears in the46PR's file list.4748## When to use4950Trigger when the user runs `/review-client <PR>` or asks to review a QWP51client documentation PR. Examples:5253- `/review-client 451`54- "review the Java QWP client doc PR #451"55- "run review-client on PR 451"5657For generic doc review use `/review` instead. For legacy ILP client pages,58also use `/review`.5960## Inputs6162- **PR number** (required). Resolve via `gh pr view <number>` to get the63 branch, head SHA, and changed files.64- If the user omits the PR number, ask for it. Do not guess from the current65 branch.6667## Workflow6869### Step 1: Fetch PR metadata and changed files7071```bash72gh pr view <PR> --json number,title,headRefName,baseRefName,headRefOid,files73```7475Then narrow the file list to **QWP client documentation only**. Candidate76paths to consider:7778- `documentation/ingestion/clients/*.md` — **but** include only files whose79 content documents QWP (see the [Scope](#scope-qwp-only) section above for80 the QWP-vs-legacy signals). Files that still document `http::` / `tcp::`81 ILP only are out of scope, even when their slug now resolves to82 `/docs/connect/clients/...`.83- `documentation/connect/**/*.md` — the new Connect section. In scope.84- `documentation/client-configuration/connect-string.md` (and any sibling85 `connect-string*` file wherever it lives) — the shared QWP connect-string86 reference. **Always in scope** when any QWP client page is changed; read87 it even if the PR did not modify it.8889To classify a candidate file fast: read its first ~80 lines. If you see90`ws::` / `wss::` in code blocks, references to `QwpQueryClient` or91`qwp_ws_*` APIs, or a link to the connect-string reference under92`/docs/connect/clients/connect-string/`, treat it as QWP. If the only93schemas it shows are `http::` / `tcp::`, treat it as legacy and skip.9495Be explicit in your output about which files you considered and why each96was included or skipped — the docs author needs to know whether the absence97of a file from the review means "reviewed clean" or "skipped as legacy."9899If the PR changes no QWP client docs, stop and tell the user — recommend100`/review` instead.101102### Step 2: Read each changed client doc in full103104Use the Read tool on each file at the PR's head SHA (check it out, or read105from the working tree if the branch is already checked out). Do not rely on106the diff alone — context outside the diff matters for "is this question107answered anywhere on the page" checks.108109If a page links to a sibling reference (e.g., `connect-string.md`), read110that too. The connect-string page is shared across all client docs; treat it111as in-scope whenever any client page is changed.112113For multi-file PRs, the parent does **not** need to read each in-scope file114end-to-end — the deep reads are delegated to per-file subagents in Step 3.115The parent only needs the first ~80 lines of each candidate to classify it116as QWP vs legacy (Step 1). The shared connect-string reference is the one117exception: the parent should read it once, because every per-file subagent118will need to cite it.119120### Step 3: Run the checklist121122#### Parallelization123124The per-file checklist work is independent across files. **For any PR125touching more than one in-scope QWP file, fan out using the Agent tool**:126spawn one subagent per in-scope file, each running the full checklist127against its assigned file, and have the parent consolidate the per-file128reports into the final review.129130Send the subagents in a single message with multiple Agent tool uses so131they run concurrently. Each subagent's prompt must be self-contained — it132will not see this skill's text or the conversation history. Include:133134- The exact file path to review (absolute path).135- The PR number and head SHA, for citation context.136- The full checklist (items 1–30 from this section, including the137 Agent one-shot suitability block) inlined into the prompt. Do **not**138 just reference "the skill's checklist" — the subagent cannot resolve139 that. Items 23–30 are the agent-fit bar and must be applied with140 equal weight to the original 22; do not let the subagent silently141 treat them as "nice to have."142- The expected output format: one finding per checklist item, severity143 ordered (❌ → ⚠️ → ✅), with the section tag and exact line citations144 (per Step 4). The subagent returns the file's section of the review,145 ready to drop into the consolidated output.146- The doublecheck requirement from the [Doublecheck](#doublecheck-before-reporting-a-finding)147 subsection at the end of Step 3.148- The list of *other* in-scope QWP client files in the PR, so cross-file149 checks (item 21 schema drift, item 18 sibling-link verification) can150 reference them. The subagent should read those siblings as needed for151 comparison, but not produce findings on them — the agent reviewing each152 sibling will cover its own.153154The shared `connect-string.md` reference (always in scope when any QWP155client page changes) gets its own subagent, evaluated only against the156checklist items that apply to a reference page (typically 12–14, 18, 19,157the parts of 22 that surface in the error-handling section, and the158agent-fit items 23, 24, 26, 28 — section self-containedness and159inference-trap refutations matter for a reference doc that agents grep160for individual keys, possibly more than for a client doc).161162If the PR only touches one QWP file, run the checklist inline in the163parent — fan-out has no benefit and adds latency.164165After the subagents return, the parent's job is to (a) consolidate166findings into the final output structure per Step 4, (b) write the167end-of-review summary that compares across files (top-three gaps,168cross-cutting themes, actively misleading items), and (c) doublecheck any169cross-file claim it added that no single subagent could have verified170alone.171172#### Verdict definitions173174For each changed client page, evaluate every item below against **two175bars**, and take the lower verdict:176177- **Human-skim bar.** Can a developer reading the page top-down answer178 the question?179- **Agent one-shot bar.** If an agent retrieves only the most relevant180 section (a single `##`/`###` chunk) into its context, can it generate181 working code without hallucinating, without requiring earlier-section182 context, and without chasing more than one link hop? This bar is183 strictly harder than the human-skim bar.184185Each item gets one of three verdicts (the lower of the two bars186dominates):187188- ✅ **Covered** — passes both bars. Cite the section/line.189- ⚠️ **Partial** — passes one bar but not the other; or touched on but190 unclear, buried, or missing an example. Quote the relevant text and191 say which bar fails and how — "right answer, wrong section" (agent),192 "buried under unrelated heading" (human), "indirection chain" (agent),193 "phrasing requires earlier prose to disambiguate" (agent).194- ❌ **Missing** — fails both bars / not addressed. Say so plainly.195196Be specific. "Section X doesn't mention Y" beats "could be clearer."197Reference exact line numbers and quote short snippets when calling out a198gap. When the failure is agent-specific (e.g. an explanation is correct199but lives one section away from where an agent retrieves the code200example), say so explicitly — that is the signal the docs author needs201to choose between moving the explanation or duplicating it.202203#### Ingestion checklist2042051. **Inserting NULL values during ingestion.** Can the reader figure out how206 to write a null for a given column without trial and error? Is there an207 example? Does it explain whether "omit the column" is equivalent to208 "explicit null," and whether that interacts with schema inference?2092. **Multiple concurrent publishers.** Is it clear whether `Sender` (or its210 per-language equivalent) is thread-safe? If not, what is the211 recommended pattern — one sender per thread, pool, queue+single-writer?212 Is there guidance on whether parallel senders writing to the same table213 need distinct identities (`sender_id`, store-and-forward slots)?2143. **Easy to execute DDL.** Is there a concrete copy-paste example for215 `CREATE TABLE`, `ALTER`, `DROP`, `TRUNCATE` via the query client? Does216 the page distinguish DDL response (`onExecDone`, `EXEC_DONE`,217 `rowsAffected = 0`) from SELECT response?2184. **Easy to execute DML and stream rows.** Is `SELECT` with a row-by-row219 callback shown? Is bind-parameter usage shown with the syntax220 (`$1`/`?`/named) the client actually accepts? Is the221 "columnar batch vs row view" tradeoff explained, with an example?2225. **Sync vs async acks — are acks optional?** Does the page say plainly223 whether the application **must** await acknowledgements before224 considering data durable, or whether `flush()` / `close()` is enough?225 For async clients (WebSocket), is the error-handler callback shown?226 Does it explain what happens to in-flight data if the app exits without227 awaiting the ack?2286. **Durable ack vs WAL ack.** Is the distinction between "committed to229 local WAL" and "uploaded to object storage" (Enterprise) clear? When230 would an app care about `request_durable_ack`?231232#### Failover and resilience checklist2332347. **Ingress failover is bounded.** Does the page say that ingress reconnect235 has a budget (`reconnect_max_duration_millis`) and will eventually give236 up? Is it clear what the application sees when the budget is exhausted237 (terminal exception, callback, etc.)?2388. **Backpressure on the application side.** If the server is unreachable239 for a long time, where does buffered data go? Is store-and-forward240 explained as the durability story, and the RAM buffer cap explained for241 the non-SF case? Does the page tell the app how to detect "I am being242 backpressured" so it can stop producing?2439. **Connection-state notifications.** Can the app wire a callback that244 fires on `CONNECTED`, `DISCONNECTED`, `RECONNECTED`, `FAILED_OVER`,245 `AUTH_FAILED`, `RECONNECT_BUDGET_EXHAUSTED`? Is there a code example?246 Does the example show what an app would actually do (log, alert,247 redirect traffic)?24810. **Mid-stream query failover — duplicate-data hazard.** Does the page249 explain that if a query fails over mid-result, the server replays from250 the start of the result set? Does it show the `onFailoverReset`251 callback and **warn explicitly** that without wiring this callback the252 application will see duplicate rows? This is the single most common253 footgun — it must be impossible to miss. **Repetition required**: the254 warning (at least a one-line note + link to the dedicated subsection)255 must also appear in every other section that mentions failover,256 mid-stream behaviour, or query restart — the per-query failover knob257 table, the failover event-fields table, the connection-state258 observability section. An agent retrieving the knob table alone259 without the warning attached will generate buggy code.26011. **Per-query failover bounds.** Are the failover knobs261 (`failover_max_attempts`, `failover_backoff_*`, `failover_max_duration_ms`)262 listed with defaults? What does the app see if all attempts are263 exhausted?264265#### Connect string and config checklist26626712. **Reference to connect-string docs.** Is there at least one link from268 the client page to the connect-string reference? Is the link placed269 where a reader needing it would actually look (near the first connect270 string example, not just in a footer)?27113. **Connect string is easy to assemble.** Can a reader build a working272 QWP connect string from scratch? Schema (`ws::` / `wss::`), address273 syntax, where to put auth, where to put TLS, separator/terminator274 rules. Are common pitfalls called out (trailing `;`, escaping `;` or275 `=` in values, multi-address syntax)? Legacy `http::` / `tcp::` need276 only a "for legacy ILP transports, see [link]" pointer — do not277 require coverage on the QWP page itself.27814. **Environment variable path.** Is `QDB_CLIENT_CONF` (or per-language279 equivalent) documented as the credentials-out-of-code path?280281#### Cross-cutting28228315. **Thread safety statement.** Stated in **every section whose first284 example creates a handle** (sender, reader, query, cursor) — at285 minimum a one-line restatement linking to the dedicated Concurrency286 section. The naive "stated once" policy works for a human scanning287 top-down but fails agent retrieval, which loads one section at a288 time: an agent that fetches "DDL execution" but not "Concurrency"289 will generate code that shares a single handle across threads. The290 placement test ("a reader looking for *can I share this instance?*291 would find it") still applies to the dedicated section.29216. **Error-handling story is end-to-end.** For each error class (auth,293 schema, parse, transport, mid-stream), the page should answer: how is294 the error surfaced (throw vs callback), what state is the client in295 afterward (usable vs must-reset vs must-close), and what should the296 app do.29717. **Migration / "what changed from before" notes** if applicable. If this298 PR introduces a new transport (e.g., QWP) alongside legacy (e.g., ILP),299 is there a side-by-side that a maintainer of existing code can scan?30018. **No content dependencies on legacy ILP pages.** Legacy ILP client301 documentation (`documentation/ingestion/clients/{c-and-cpp,dotnet,nodejs,python}.md`,302 `documentation/connect/compatibility/ilp/**`, `documentation/ingestion/clients/date-to-timestamp-conversion.md`,303 and similar ILP-era support material) is on a deprecation path and304 will be removed. Outbound links from a QWP client page to legacy ILP305 content are acceptable **only** when framed as a "for legacy ILP,306 see X" escape hatch — typically inside a `:::tip Legacy transports`307 admonition near the top of the page. Flag as **Missing** any link308 that *depends* on a legacy page to explain a concept the QWP reader309 needs (e.g., "see the ILP overview for exactly-once delivery310 semantics"). The concept must live somewhere that survives ILP311 deprecation: the QWP page itself, the connect-string reference, a312 transport-agnostic concepts page, or a new QWP-native page. Look313 especially for sneaky cases: anchor links into legacy pages314 (`/docs/connect/compatibility/ilp/overview/#some-section`) and315 references to timestamp-conversion / date-handling support pages316 that were authored for ILP. Fix shape suggestion: "move this317 explanation onto the QWP page, or root it in a shared concepts page318 under `/docs/concepts/`."31919. **Enterprise connection patterns and OIDC.** The page shows at least320 one worked example combining TLS (`wss::`), credentials, and321 multi-host `addr=...` — the realistic production shape — not just322 three separate one-liners. For each Enterprise auth path the client323 supports (HTTP basic, bearer token, OIDC, mTLS), there is either324 (a) a concrete example showing how an application obtains and325 passes the credential, or (b) an explicit one-line statement that326 the path is not supported by this client, with a pointer to the327 closest alternative. **Silence is not acceptable** — a reader must328 not have to grep the page to discover that OIDC token refresh, mTLS329 client certificates, or token rotation is unsupported. Special330 attention to OIDC: the [OpenID Connect](/docs/security/oidc/) page331 documents the server-side flow; the client page must answer "how332 does the application acquire a token to pass to the client" and333 "what happens when the token expires mid-session — does the client334 refresh, does it fail, does it expect the app to register a335 callback?" A bare "for OIDC, see the security page" is **not**336 coverage — flag as Partial at best.33720. **Bind-parameter type coverage and limitations.** Where the page338 documents bind parameters (or the per-language equivalent), it339 enumerates **all** supported bind types — not a sample ending in340 "and more" or "…". For every QuestDB column type a reader might341 expect to bind (BOOLEAN, BYTE, SHORT, CHAR, INT, IPv4, LONG, FLOAT,342 DOUBLE, TIMESTAMP, timestamp_ns, DATE, SYMBOL, VARCHAR, BINARY,343 UUID, LONG256, DECIMAL64/128/256, GEOHASH, DOUBLE[]/ARRAY), the344 page either (a) shows the setter / API and the type code, or345 (b) lists the type explicitly under "unsupported as bind parameter"346 with a one-line rationale (e.g., "ARRAY: bind ARGS frames don't347 carry array shape; use SQL array literals instead"). Verdict348 ladder: complete enumeration → Covered; sample-and-handwave ("and349 more", "…", "see source") → ⚠️ Partial; no list at all → ❌350 Missing. The same principle — enumerate or call out as unsupported351 — applies wherever the page documents a type-keyed surface352 (ingestion column setters, result-batch accessors). The353 bind-parameter table is the most common place coverage drifts354 because the API is younger than the type system.35521. **Consistent capital-markets data model across clients.** Every code356 example uses a capital-markets domain (trades, quotes, order books,357 FX, market data). **Reject** generic placeholders — `foo`, `bar`,358 `baz`, `my_table`, `t1`, `Example`, `Test`. The placeholder pattern359 is a tell that the example was written in isolation and was never360 cross-read against sibling client pages. Beyond the per-page check,361 examples must be **consistent across the full set of QWP client362 pages**: same table names, same column names, same column types,363 same symbol values. When the PR ships one client page and the other364 QWP client pages already exist, compare schemas — flag every365 inconsistency the reader would hit when porting between languages:366367 | Class of drift | Examples |368 |---|---|369 | Table name | `trades` vs `Trades` vs `market_trades` |370 | Column name | `qty` vs `quantity` vs `amount`; `symbol` vs `sym` vs `instrument` |371 | Column type | `LONG` vs `DOUBLE` for size; `SYMBOL` vs `VARCHAR` for ticker |372 | Symbol value | `EURUSD` vs `EUR/USD` vs `EUR-USD`; `ETH-USD` vs `ETHUSD` |373 | Timestamp precision | microseconds vs nanoseconds for the same notional event |374375 Verdict ladder: domain-correct, placeholder-free, schema matches376 every other QWP client page → ✅ Covered; domain-correct but377 schema drifts from siblings → ⚠️ Partial (cite the specific378 drift); generic placeholders or non-capital-markets domain379 (sensors, IoT, logs) → ❌ Missing. Fix shape: pick the schema380 used by the page with the most polished example and align the381 others, or call out one canonical schema in this skill / a README382 under `documentation/ingestion/clients/` so future client docs land383 on it without negotiation.38422. **Diagnostic information on the error object/event.** Item 16385 enumerates the error categories and the surfacing / recovery386 model. This item demands the next level of detail: **what387 structured information is on the error and how user code reads388 it**, so a real production handler can log, alert, debug, and389 correlate with server-side state.390391 For every error path the client exposes, the page documents:392 - **Server message text** — which field or parameter carries it393 (`SenderError.getServerMessage()`, the `message` parameter on394 `onError`, `QwpWsSenderError.message`, etc.), whether it is395 stable enough to pattern-match on, localized vs English, and396 whether it is capped in length.397 - **Status code** — both numeric (e.g. `0x05`) and named (e.g.398 `PARSE_ERROR`), and how user code reads each.399 - **Affected scope** — table name on ingest errors, FSN range400 (`from_fsn`/`to_fsn`) or batch identifier on async ingest401 rejections, failing SQL / bind index on query parse errors,402 query ID on mid-stream query failures.403 - **Server correlation / request ID** for support tickets, if the404 protocol carries one; otherwise an explicit statement that no405 such ID is surfaced.406 - **PII / secret safety** — whether the message text is safe to407 forward to end-user UIs or third-party error trackers, or408 whether the application must sanitise first.409410 Verdict ladder: every bullet covered on every error path → ✅411 Covered; primary fields named but stability / PII / correlation412 silent → ⚠️ Partial; only "the message is human-readable text"413 with no field-by-field guidance → ❌ Missing. The fix shape is414 almost always a small table next to the error-handling code415 example listing the fields, their types, and one-line guidance per416 field — much more readable than burying these properties in417 prose.418419 **Co-location requirement (agent one-shot bar).** Every error path's420 diagnostic table must live in the **same** `##`/`###` section as421 that error path's code example. A correct table located one section422 away from its example fails this item under the agent bar: an agent423 retrieving the code-example section gets no field-level guidance424 and will fabricate field names; an agent retrieving the table425 section gets no concrete invocation and will fabricate the426 surrounding code. Duplicate the table (or the example) into both427 sections when one error surface is referenced from multiple places.428429#### Agent one-shot suitability checklist430431The previous 22 items test what the page *says*. The next seven items432test whether the page is *retrievable in one shot* — whether an agent433that loads a single section into context can ship working code from it.434The bar is strictly harder than the human-skim bar: a page where every435fact is true but every fact requires three hops to assemble is human-436adequate and agent-broken.43743823. **Section self-containedness.** Each `##` and `###` section must439 name (or link to a single ≤ 1-hop reference for) the concrete API440 call, the connect string used in its example, the language-specific441 import / `#include`, and the next step the reader takes. An agent442 that retrieves only that section into context must be able to443 compile the example without inheriting setup from an earlier444 paragraph. Test: read each section in isolation — if the first code445 block references a `reader` variable that was constructed three446 sections earlier and nowhere referenced in the current section, the447 section fails the bar. Verdict ladder: every section closes its own448 setup → ✅; one or two sections borrow setup from a labelled "Quick449 start" but link to it explicitly → ⚠️ Partial; most sections assume450 inherited state → ❌ Missing. Fix shape: prepend a one-line "you451 have a `reader` constructed as in [Quick start](#quick-start)"452 pointer, or duplicate a 2-line setup snippet.45345424. **Inference-trap explicitness.** The high-confidence wrong guesses455 an LLM makes about *this* API must be explicitly refuted somewhere456 on the page — ideally in the section where the wrong guess would be457 written. Enumerate the plausible non-existent API calls and check458 each:459 - `setNull(name)` / `set_null(col)` when nulls are actually written460 by omitting the setter.461 - `bind_array(...)` / `setArray(...)` when arrays are not supported462 as bind parameters.463 - `setInterval(...)` when `INTERVAL` has no bind setter.464 - `request_durable_ack` on the reader when it is sender-only.465 - `connectionListener` / `onConnect` / `onDisconnect` callbacks466 when no structured connection-state callback exists.467 - `setUuid(string)` when only the 16-byte form exists.468 - `auto_flush_rows` / `auto_flush_bytes` for the WebSocket sender469 when auto-flush is rejected.470 - Any sibling-language API name that doesn't exist in this471 language's client (`flushAndKeep` vs `flush_and_keep`,472 `await_acked_fsn` vs `awaitAckedFsn`, etc.).473474 Verdict ladder: every plausible wrong guess refuted in-section (or475 in a dedicated "Unsupported" table that the in-section text links476 to) → ✅; some refuted → ⚠️ Partial; only positive knowledge with477 no negative knowledge → ❌ Missing. Fix shape: an "Unsupported"478 row in the relevant setter / column-getter / config-key table, or479 a one-line refutation in the section that mentions the closest480 supported alternative.48148225. **Information flow simple → complex.** Walk the code examples in483 document order. The first runnable example uses the simplest484 connect string (`ws::addr=localhost:9000;`), no auth, no TLS, no485 failover, and the smallest possible payload. Each subsequent486 example introduces at most one new concept (auth, then TLS, then487 multi-host, then failover, then SF, etc.) — never two at once.488 Production-shape examples (multi-host + token + TLS + failover +489 durable ACK) appear at the **end**, not the beginning. Verdict490 ladder: monotonic complexity from top to bottom → ✅; one491 section inverts the order (e.g. the first example shows TLS but492 a later section shows the bare connect string) → ⚠️ Partial;493 the page opens with the production example → ❌ Missing. Fix494 shape: reorder examples, or split a too-complex first example495 into a minimal version followed by a "production shape" version.49649726. **Vocabulary consistency within the page.** The same operation must498 be referred to by the same name everywhere. The same identifier499 must be spelled identically in code and prose. If the page says500 "the cursor" in section X, "the result handle" in section Y, and501 "the row stream" in a table caption, an agent will treat them as502 three distinct objects. Extends item 21 (cross-page schema503 consistency) inward: this is the intra-page equivalent. Verdict504 ladder: identifiers and operation names match across the page → ✅;505 one or two drifts the agent could plausibly disambiguate from506 context (e.g. "callback" vs "handler" used interchangeably) → ⚠️507 Partial; multiple drifts at the same surface (different setter508 names referenced in code vs prose, different connect-string-key509 capitalisation) → ❌ Missing. Fix shape: pick one name per concept510 and grep-replace across the page.51151227. **Working-code minimum (no ellipses in the first example).** The513 first code example in each section must be complete: explicit514 setup, explicit error handling (see item 29), explicit cleanup. No515 `// ...` placeholders, no `// process row...`, no `// handle516 error...`. Subsequent examples in the same section may abbreviate517 once the pattern is established. The bar is "an agent pasting this518 snippet verbatim gets a compiling program" — not "an agent519 paraphrasing this snippet gets the gist." Verdict ladder: every520 first-in-section example is complete → ✅; some have ellipses but521 the missing pieces are trivially inferable (`// imports omitted`)522 → ⚠️ Partial; load-bearing logic is replaced by `// ...` → ❌523 Missing. Fix shape: inline the omitted lines.52452528. **Indirection-chain cap (≤ 1 hop).** From the section where a526 question is naturally asked, the answer must be reachable in at527 most one link click. A "see X" pointer to a section that itself528 says "see Y" is a two-hop chain and fails. Item 12 requires *a*529 link to the connect-string reference; this item caps how often the530 page resolves a question by indirection at all. Note that the531 "links to a sibling page count as Covered" allowance in Style532 guidance applies only to the human-skim bar; under the agent bar,533 every indirection is consumed against this cap, and a section534 answering a question by sending the agent on a two-hop chase fails.535 Verdict ladder: every concept reachable in ≤ 1 hop → ✅; one or536 two two-hop chains for non-load-bearing detail → ⚠️ Partial;537 load-bearing concepts (null handling, thread safety, failover538 callback) require two or more hops → ❌ Missing. Fix shape: inline539 the destination content, or restructure so the question is asked540 in the section that already contains the answer.54154229. **Failure mode in the golden path, not bolted on later.** The first543 runnable example in each section must include the error path — not544 a happy-path `try { ... } catch { print }` placeholder, but the545 real shape of error handling the section's API requires (the C546 `err_out` parameter check + `goto on_error` cleanup; the C++547 `catch (const line_reader_error&)` with `e.code()` dispatch; the548 sender error-handler callback registration; the async error poll).549 Agents prompted "write the function" overwhelmingly copy the550 *first* example's shape — if the first example skips error551 handling, the generated app will skip it too. Item 16 covers552 whether error handling is *documented*; this item covers whether553 it is *demonstrated in the example agents will copy*. Verdict554 ladder: every first-in-section example demonstrates the real555 error path → ✅; some examples bolt error handling on as a later556 snippet → ⚠️ Partial; the page's error-handling section is the557 only place error handling appears → ❌ Missing. Fix shape: rewrite558 the first example to include the error path, or move the559 error-handling section ahead of the API examples and reference it560 from each.56156230. **Capability surface visible above the fold.** Pages that document a563 client with more than one capability class — ingestion **and**564 querying, read **and** write, sync **and** async, blocking **and**565 streaming — must surface every capability in all three of these566 places:567568 - the frontmatter `description:` field (this is what search,569 sidebar tooltips, and AI retrieval index for landing decisions);570 - the page's opening paragraph (the first text a reader sees after571 the title);572 - a first-level `##` heading on the page (not buried under `###`).573574 A reader landing on the page from a "how do I query QuestDB from575 `<language>`" query must learn within the first screen of the page576 — without scrolling past hundreds of lines of ingestion content —577 that the client does queries at all. An agent that retrieves only578 the page's opening chunk into context must learn what the client579 can do from that chunk alone; if the second capability surfaces580 only midway down the page, the retrieval misses it and the agent581 will either fabricate a non-existent separate client or send the582 user to the wrong page entirely (PGWire / REST). The most common583 failure pattern is a page that was originally ingestion-only and584 gained query support in a later PR — frontmatter, opening585 paragraph, and any top-of-page `:::info` / `:::tip` admonition all586 need to be rewritten when the new capability lands, not just a new587 `##` section appended at the bottom.588589 Verdict ladder: every capability mentioned in frontmatter590 description AND opening paragraph AND a first-level heading → ✅591 Covered; one of those three surfaces omits a capability → ⚠️592 Partial (cite which surface and which capability); a capability593 appears only deep in the page → ❌ Missing. Fix shape: rewrite the594 frontmatter description to enumerate every capability class595 explicitly (e.g. "client for high-throughput **ingestion** and SQL596 **query execution**"), rewrite the opening paragraph as "two597 complementary APIs live in the same library: ... and ...", promote598 the new capability to its own `##` heading at the same depth as599 the existing ones, and update any top-of-page admonitions that600 redirect readers elsewhere ("for querying see PGWire") to reflect601 the new capability.602603#### Doublecheck before reporting a finding604605Every ❌ Missing and ⚠️ Partial verdict must be verified by re-reading606the cited lines before it goes into the output. **False findings damage607the review more than missed ones** — they make the docs author waste608time chasing a non-issue and erode trust in the rest of the report. The609checklist is long and the same words ("flush", "error", "thread") recur610across sections; it is easy to write a finding from memory and miss the611paragraph two sections down that already covers it.612613Before finalizing each ❌ / ⚠️ finding, do the following — and only then614write it into the output:615616- **Re-read the cited line range.** Read the exact range you plan to617 cite, plus enough surrounding lines (5–10 above and below) to confirm618 the gap is real and the cited text is what you think it is.619- **Search the whole page for the missing concept.** A finding of the620 form "the page never mentions X" is invalid if X appears under a621 different heading. Grep / scan for the relevant identifier (`setNull`,622 `onFailoverReset`, `sender_id`, `OIDC`, `backpressure`, etc.) across623 the file before claiming absence.624- **Confirm section / header attributions.** A finding like "the625 thread-safety statement is buried under 'Parallel queries'" is wrong626 if the statement is actually under "Concurrency"; verify the section627 heading that contains the cited line.628- **Verify both sides of cross-file claims.** For findings comparing629 across files (schema drift in item 21, link-target validity in item630 18, "the Rust page does this correctly" comparisons), re-read both631 files at the cited locations before reporting. A misattributed632 comparison undermines the whole cross-file argument.633- **Confirm exact quotes.** If the finding quotes the page (e.g., "ends634 literally with 'and more'"), re-confirm the quote is verbatim and635 appears at the cited line — paraphrased "quotes" are a common636 failure mode.637- **Simulate one-section retrieval for agent-fit findings (items 23-29638 and the agent-bar half of any other item).** Before flagging an639 agent-fit gap, mentally load just the cited `##`/`###` section into a640 fresh context window and ask: "Can I generate the code from this641 alone?" If the answer is yes, the section is self-contained; the642 finding is invalid. If the answer requires earlier-section state, an643 identifier introduced elsewhere, or a two-hop link chase, the gap is644 real — and the finding should name which of those three causes it.645646✅ Covered findings can be lighter-touch: a citation that points to the647right region is sufficient verification. The skew toward verifying648negative findings is intentional — a false ❌ wastes more author649attention than a false ✅.650651When the per-file work is fanned out to subagents (see652[Parallelization](#parallelization)), each subagent is responsible for653doublechecking its own findings before returning. The parent must654additionally doublecheck any cross-file claim it adds to the end-of-review655summary — those claims didn't exist in any single subagent's output and656therefore weren't verified upstream.657658### Step 4: Produce the review659660Format the output as one section per changed file. **Within each file,661order findings by severity, worst first** — ❌ Missing, then ⚠️ Partial,662then ✅ Covered at the bottom. This is the load-bearing rule of the output663format: human readers scan top-down looking for action items, and a doc664author should be able to stop reading as soon as the ❌/⚠️ blocks end.665666Do **not** group by checklist section (Ingestion / Failover / Connect667string / Cross-cutting / Agent one-shot). Instead, tag each finding668with its section in parentheses after the title — `(Ingestion)`,669`(Failover)`, `(Connect string)`, `(Cross-cutting)`, `(Agent one-shot)`670— so the author still knows which category an item belongs to without671losing the severity ordering. For items where the verdict is the lower672of two bars (e.g. human-Covered but agent-Partial), tag the failing673bar in the finding body: *"Partial under the agent bar: the table at674lines X-Y answers the question but lives in a separate section from675the code example at lines A-B that an agent would retrieve first."*676677Within a severity bucket, order by impact (the gap a reader would hit678first or hardest comes first). When in doubt, follow the checklist's own679ordering as a tiebreaker.680681The ✅ Covered block at the bottom may be terser than the ❌/⚠️ blocks682above it — one-line confirmations with citation are fine. The point of683keeping Covered findings in the output at all is to let the author see684that the item *was* checked and reassure them no follow-up is needed; it685is not to re-justify the verdict.686687Use this structure:688689```markdown690## documentation/ingestion/clients/<lang>.md691692- ❌ **Missing — inserting NULL values (Ingestion).** The column-method693 list (lines 245-256) shows typed setters but never says how to write694 null. No example. Recommend adding either an explicit `setNull(name)`695 example or a one-liner stating that omitted columns are stored as null.696- ❌ **Missing — duplicate-data hazard on mid-stream failover (Failover).**697 The `onFailoverReset` callback is mentioned (lines 784-790) but the page698 does not say *what happens if you don't wire it*. Add an explicit699 warning: "Without an onFailoverReset handler that clears accumulated700 results, the application will observe duplicate rows after a mid-stream701 reconnect."702- ⚠️ **Partial — multiple publishers (Ingestion).** Line 845 states703 `Sender` is not thread-safe, but the statement is under "Parallel704 queries" where a reader looking for ingestion guidance would not look.705 Move or duplicate under "Data ingestion."706- ⚠️ **Partial — OIDC (Cross-cutting).** Line 172 is a bare "see the707 security page" pointer; the client page must answer how the app708 acquires the token and what happens on expiry.709- ⚠️ **Partial under agent bar — thread safety in DDL section710 (Cross-cutting, item 15).** The CREATE TABLE example at lines711 559-582 never restates the thread-safety contract. An agent712 retrieving only the DDL section will not learn that `Sender` is713 single-threaded. Add a one-line note linking to the Concurrency714 section at the top of the DDL section.715- ⚠️ **Partial under agent bar — inference trap (Agent one-shot,716 item 24).** Th717718…(truncated)