pg-feature-brainstorm — Phase 1 of the PG planner
The first stage of the two-phase PostgreSQL feature planner. Output is a
short, opinionated sketch that frames the problem and offers
candidate approaches — not an implementation plan.
The pairing:
- Phase 1 — brainstorm (this skill) → narrow the design space
- Phase 2 — plan (
pg-feature-plan skill) → make it implementable
Per the user's split: brainstorm explores, plan commits.
When to use vs not
Use when the idea is exploratory:
- "What would it take to add server-side variables?"
- "Could we add a new hook for X?"
- "I want to make EXPLAIN show Y, what are the options?"
- "Should this be a contrib extension or a core change?"
Don't use when the task is already scoped:
- "Add a
pg_buffercount() builtin" → go to pg-feature-plan directly.
- "Fix this specific bug" → no brainstorm needed; cite + patch.
Don't use for non-PG brainstorming, app architecture, infra design.
Inputs
- A natural-language description of the idea (the user's argument).
- Optional: a
slug for the planning artifacts (otherwise derived from
the idea, e.g. server-side-variables → server_side_vars).
Output
A single file at planning/<slug>/brainstorm.md with the structure
below. ~150-300 lines total. Anything longer means you're doing
Phase 2 work prematurely — stop and hand off.
Required sections (in this order)
Concrete usage surface (REQUIRED, comes FIRST). 20-30
enumerated example lines showing what the feature must let the
user write. Each example is one SQL / PL/pgSQL / DDL line.
Group by usage class. Generate this BEFORE answering DECISION
questions — every DECISION must take these examples as inputs,
not abstractions.
Why this is §0: the sesvars first calibration (2026-06-17)
produced an MVP that covered ~30% of what the user actually
wanted, because DECISION questions were phrased so narrowly
they excluded entire usage classes (array indirection, composite
access, PL/pgSQL direct writes, DDL DEFAULT, SELECT INTO,
aggregate semantics). The §0 enumeration prevents that failure
mode — if a usage class isn't on the list, the brainstorm has
admitted it's out of scope; if it IS on the list, the candidate
approaches MUST be able to support it.
How to generate the examples — parallel fan-out via subagents:
- Agent A reads
knowledge/scenarios/_index.md + every pinned
scenario; lists usage examples that show up in those scenarios.
- Agent B greps
source/src/test/regress/sql/ and
contrib/*/sql/ for similar-shaped features; extracts
idiomatic SQL surface lines.
- Agent C web-searches pgsql-hackers + CommitFest entries for
the feature keyword; extracts examples from recent threads.
- Agent D — if the user has a manual reference implementation
(see §0.7) — reads the reference's regress SQL file as the
UPPER BOUND. Every example line that file contains becomes a
candidate for §0.
- Synthesize the four agent outputs into a deduplicated 20-30
line table grouped by usage class.
Format:
## §0 Usage surface (comprehensive)
### Reader
- SELECT @x
- SELECT @arr[2], @arr[2:3]
- SELECT (@typ).field
- SELECT @js -> 'k'
...
### Utility writer (SET)
- SET @x := expr
- SET @x := expr, @y := expr, @z := expr
- SET @x TYPE DATE := expr
- SET @arr[2] := v
- SET @arr[2:3] := v
...
### Inline writer (SELECT :=)
- SELECT @x := expr
- SELECT @a := 1, @b := 2
- SELECT @x := (subquery)
- SELECT col, @cum := @cum + col FROM t
...
### Cross-feature
- PL/pgSQL DO block: BEGIN SET @pl := 3; END
- SELECT col INTO @v, @w FROM t
- CREATE TABLE t (c INT DEFAULT @v)
- EXECUTE FORMAT('SELECT @x := %L', val)
...
### Adversarial / edge
- Multi-target SET with self-referential type inference
- Chained inline := with type-shift across columns
- Quoted identifiers @"name with spaces"
- WHERE-clause assignment evaluation order
...
The DECISION questions in §7 then say things like "should the
feature support the array-indirection examples above (rows 5-8)?"
— concrete, bound to specific lines, not abstract.
0.5. Existing-PG-mechanism survey (REQUIRED, before
candidate approaches). What existing PG nodes / mechanisms /
patterns could carry this feature? The default is REUSE over
INVENT. Inventing a new Expr node, new EEOP step, new
parsetree shape is 5× more touch points than reusing an
existing one (walker coverage, EEOP_*_EXEC interpreter step,
JIT mirror, ruleutils support, copy/equal/out/read funcs
regen).
How to survey — parallel fan-out via subagents:
- Agent A: grep
source/src/include/nodes/primnodes.h +
parsenodes.h for similar-shaped Expr / Stmt nodes. Note
Param (with paramkind discriminator), SQLValueFunction
(with op discriminator), XmlExpr (with op), etc.
Discriminator-bearing existing nodes are PRIME candidates
for reuse.
- Agent B: grep
source/src/include/executor/execExpr.h for
existing EEOP_* steps that could be specialized via a
d.union.* arm without inventing a new opcode.
- Agent C: grep
source/src/include/access/ for RangeTblEntry
kinds (RTE_*) and FunctionScanState patterns. New row-source
features often fit RTE kinds without inventing a Path / Plan
node.
- Agent D: search the corpus
knowledge/idioms/*.md and
knowledge/subsystems/*.md for "existing mechanism" /
"reuse" / "PARAM_*" / "RTE_*" patterns. The corpus is built
to surface these.
Output a short matrix in §0.5:
## §0.5 Existing PG mechanisms considered for reuse
| Mechanism | Could it carry this? | Cost of reuse | Cost of invent |
|---|---|---|---|
| Param + new paramkind | YES — text-named, value-bearing, dynamically-typed | tiny — paramkind enum + paramsesvarid field | 5× more touch points |
| SQLValueFunction + new op | NO — designed for parameterless time/role funcs | n/a | n/a |
| New T_SessionVar Expr | YES but expensive | nodeFuncs.c 6 cases + parse_collate.c + clauses.c (3 walkers) + ruleutils.c + JIT mirror | baseline |
| RTE_*KIND* | NO — sessvars are scalar, not row-source | n/a | n/a |
The recommended approach in §6 MUST justify its mechanism
choice with reference to this matrix. If §6 picks invent-
new, the recommendation has to argue why the existing mechanism
doesn't fit — not just "it's cleaner".
Sesvars-calibration evidence: the user's manual implementation
chose PARAM_SESSION_VARIABLE (new paramkind on Param). My
AI-driven implementation invented T_SessionVar +
T_SessionVarAssign. Mine ended up with 5× more touch points
for ~30% less coverage. The §0.5 step exists to prevent that
repeat.
0.7. User-reference-implementation readthrough (REQUIRED IF
one exists). Ask the user explicitly: "do you have a manual
reference implementation of this feature?" Look at the
conversation history, the working directory, and any prior
session logs for hints (e.g. user mentions a thesis, a private
branch, a "my version" repo path).
If a reference exists, READ IT as the upper-bound spec. The
§2 out-of-scope lock is for features the user EXPLICITLY
EXCLUDES, not "things we haven't thought of yet". The reference
tells you what the user actually wants; the §0 usage surface
becomes the union of (the reference's surface) + (any additions
the user calls out).
This is the R15 default: comprehensive scope, not minimal MVP.
If the user has built it once already manually, the planner
suite's job is to produce something comparable to the
reference, not 30% of it.
How to readthrough:
- Read the reference's main regress SQL file in full
(
session_variables.sql for sesvars). This IS the spec.
- Read the reference's expected output to confirm semantics.
- Skim the reference's main implementation file (the
equivalent of
commands/<feature>.c) just for ARCHITECTURE
— what existing PG mechanism did they reuse? (This feeds
§0.5.)
- Note any features the reference SHIPS that look like scope
expansions worth including, AND any features that look
genuinely out-of-scope (e.g. experimental / personal-pref).
- Surface in §7 as a DECISION: "the reference ships X, Y, Z
— should we match all of these or scope down? If scope
down, which?"
Anti-pattern this rule prevents: "I won't read the reference
because it might bias me toward their design." Wrong framing.
The bias is helpful: the reference encodes the user's real
intent. The planner suite's job is to match or exceed it, not
reinvent it independently.
Problem statement (3-5 sentences). What is the user actually
asking for, in your own words? Restate the goal so misunderstandings
surface early. Name the user who would benefit (DBA, extension
author, hacker, end user).
Why this might matter (3-5 sentences). What does PG currently
force the user to do that this would replace or improve?
Relevant subsystems (bulleted). Name 1-3 knowledge/subsystems/*.md
docs that the implementation would touch. One-line summary of each.
This is the only corpus you must load in Phase 1 — don't read the
per-file docs yet.
Has this been tried? A short result of a triage pass:
- CommitFest: search
https://commitfest.postgresql.org/ for the
idea keyword. Note any matching entry by ID + status + last activity.
- git log:
git log --oneline --grep='<keyword>' source/ for the
last ~2 years. Note any related commits.
- pgsql-hackers: if a recent thread is obvious from CF or git,
reference it; otherwise skip (don't bulk-search the list).
- Corpus:
grep -r '<keyword>' knowledge/ for anything already
documented. Note any hit.
- Out-of-tree extensions on PGXN / community repos: search
pgxn.org and well-known per-area extension lists for the idea
keyword. Many feature requests already have a maintained
extension solving 80% of the problem (
pg_partman for
time-bucket retention, plpgsql_check for plpgsql static
analysis, pg_cron for in-DB scheduling, pgvector for vector
ops before native, …). When this hits, the brainstorm pivots
from "design from scratch" to upstream into core vs harden
the extension vs move to contrib — surface this as the FIRST
DECISION: in §7.
- Scenarios layer: match against
knowledge/scenarios/_index.md.
If a scenario (or a composite of scenarios) matches the
brainstormed approach, name it in this section. Phase 2
(pg-feature-plan) will PIN to it as the authoritative §3 file
checklist — knowing in advance which scenario applies lets the
user spot a scope mismatch early. Format: Scenario(s): <slug>
or Scenarios layer gap: <one-line description> if no scenario
matches and the change-class is recurring.
A brainstormed approach often spans 2-3 scenarios at once;
name all of them. The scenarios index documents the common
compositions explicitly (see knowledge/scenarios/_index.md
§"Composite features"). Phase 2 will UNION the file checklists.
Heuristic for when to flag a Scenarios layer gap: — flag it
when the same change-class would plausibly recur in a future
brainstorm. If it's truly one-off, just say "no scenario
matches" without flagging.
Candidate approaches (2-3, no more — but see the mandatory
approach E trigger below). For each:
- One-paragraph description.
- Pros (2-3 bullets).
- Cons / risks (2-3 bullets).
- Approximate scope (small / medium / large — measured in files
touched + invariants risked, not lines of code).
- Existing PG mechanism it reuses (a hook? an existing API? a
parser pattern? a catalog table? a PARAM kind? an RTE kind?).
REQUIRED: cite the §0.5 mechanism survey row this approach
came from. If "invent new", explain why §0.5's reuse candidates
don't fit.
- Storage representation (REQUIRED when the approach involves
a collection, accumulator, sequence, batch, queue, or any
other lifecycle-managed data structure). For the data structure
the approach centers on, enumerate the representation choices
explicitly:
- by-value inline (elements stored directly in struct or
palloc'd chunk — e.g.
JsonbValue items[N] in a chunked list);
- by-pointer (elements palloc'd separately, pointer array
holds references — e.g.
JsonbValue **values);
- by-reference-to-shared-pool (elements live in some
longer-lived context / arena, struct just holds pointers to
borrowed memory).
Each choice has different lifetime + ownership consequences:
by-value makes the struct's free / clear release everything
atomically; by-pointer requires per-element ownership tracking
OR a per-call MemoryContext; shared-pool requires the pool
outlive the struct. The recommended approach must state which
it picks and why. Do NOT take "by-pointer" as a given just
because the parent code does it — the brainstorm should
consider the alternative explicitly. Anchored in
planning/jsonpath_leak/comparison.md §L5: our jsonpath_leak
trilogy missed Tom Lane's 5a2043bf713 "inline chunked list"
design (by-value) because we inherited the parent's pointer-
based JsonValueList without questioning it; the inline storage
would have eliminated the ownership question Phase 2's R7
escalation had to absorb.
- Coverage of §0 usage surface (which usage classes does this
approach support? List the §0 example-row numbers explicitly.
If an approach covers only rows 1-10 out of 25, it's a 40%-
scope approach — say so and flag whether the remaining 60%
is in-scope but deferred or genuinely out-of-scope.).
- Citations (REQUIRED): at least 3
knowledge/scenarios/*.md
this approach draws on, 2 knowledge/personas/*.md whose
reviewer reflexes apply, 2 knowledge/idioms/*.md whose
patterns it follows. Inline format:
[scenarios: #11 add-new-sql-keyword, #15 add-new-expression-eval-step]
[personas: tom-lane (parse-tree durability), andres-freund (JIT mirror)]
[idioms: lexer-and-grammar, node-types].
If any cited file doesn't exist, flag as a corpus gap to fix
(don't fake it).
Mandatory approach E — "restructure control flow to match
the new invariant" (REQUIRED when the fix targets an
existing function and that function has ≥3 exit paths
AND the fix introduces a common teardown / setup / invariant
step that would need to run at every exit). Enumerate it as a
named approach even when your first instinct is to keep the
existing shape and paper the invariant on top.
The classic form: replace N early-return sites with a single
bool result = <default> (or equivalent accumulator) plus one
final exit path, then place the invariant maintenance at that
single exit. One reset call instead of N. Similar re-shapings
apply for setup steps (goto-cleanup, single-init prologue), for
inverting an early-return chain into a switch/if-else-if
ladder, or for extracting the branchy body into a helper that
the caller wraps.
Reason to enumerate it explicitly: the blind trilogy
consistently under-refactors when the fix is "add a reset /
cleanup / invariant step". Two data points to date:
planning/jsonpath_leak/comparison.md §L5 — Tom Lane
rewrote every JsonValueList caller to the "always copy on
Append + inline storage" shape; our fix kept mixed
copy/borrow semantics and needed a per-call MemoryContext to
absorb the ambiguity. Fewer lines net, cleaner API.
planning/nodesubplan_leak/comparison.md §F32 — Tom
collapsed six early-return sites in ExecHashSubPlan into a
single bool result + one exit path, then placed the
MemoryContextReset(node->hashtempcxt) at that single exit
(net −16 executable lines). Our fix kept the six branches
and placed the reset at entry (net +11 executable lines).
Both correct; Tom's cleaner.
The pattern: when a fix requires an invariant that ties to
every exit path, the parent code's exit shape was chosen
for a world without that invariant. That world no longer
exists. Treating the exit shape as fixed is inheriting an
assumption the fix has just invalidated. Enumerating approach
E forces the brainstorm to notice.
For approach E, the required sub-bullets are the same as any
other candidate — pros, cons, scope, mechanism, coverage,
citations — plus one additional bullet:
- Refactor shape: name the target function, the current
number of exit paths, the proposed collapsed shape (single
bool accumulator? goto-cleanup? extracted helper?), and
any behavioural delta the refactor risks. If the answer
is "purely mechanical, no behavioural delta", say so
explicitly — that's the low-risk case reviewers will accept.
If the refactor DOES change observable behavior beyond the
invariant (e.g. changes which of two error messages fires
when both apply), flag it as a scope escalation the user
must approve.
If the trigger condition doesn't fire (function has <3 exit
paths, or the fix is a wholly new function), say "approach E
not applicable — target function has N exit paths" instead
of omitting the bullet entirely. That makes the check
auditable.
Recommended approach (1 paragraph). Pick one. Say why. Name
what would have to be true for the other approaches to win
(so the user can flag if those conditions hold).
Decisions for the user (3-5 max). Each is a concrete question
prefixed with DECISION:. Things only the human can answer:
- Scope (MVP vs full feature)
- Backward-compat policy (break old API? add new flag?)
- Performance/UX tradeoffs
- Whether to do this as core or contrib/extension first
- Whether to target current master or wait for next CF window
Worked examples at the right level (one per category):
- "Are you aware the
<extension> extension already covers ~80%
of this? Does it not meet your need, and if so, why?"
(Prior-art reframe; ranks FIRST whenever §4 found a mature
out-of-tree extension — see Edit-2 note in §4.)
- "Should expired rows be query-invisible immediately at the
clock crossing, or is autovacuum-eventual acceptable?"
(Semantics tradeoff; shapes the user's mental model.)
- "Ship as contrib first, or aim for core in one go?"
(Path-to-release; cheap-to-revert vs harder-to-iterate.)
Anti-example (too vague): "What should the GUC name be?" — that
is a Phase-2 implementation detail, not a brainstorm DECISION:.
What this brainstorm explicitly did NOT figure out. A short list
so the boundary with Phase 2 is clear. E.g. "did not enumerate
catalog changes", "did not check WAL impact", "did not propose tests".
Forbidden in Phase 1
- File:line citations from per-file
knowledge/files/... docs.
(Phase 2 does this.)
- Phase-by-phase implementation plan.
- Catalog-bump decisions.
- WAL format decisions.
- Test surface enumeration.
- Patch-series structure.
If you find yourself writing those — stop. Save them for Phase 2.
Method
Run as a parallel-fan-out loop (NOT a sequential tight loop —
the old single-context method under-explored). The fan-out is
load-bearing.
Fallback when nested Agent tool is unavailable
If running inside a subagent (the Agent tool may not be in your
exposed tool set), fall back to PARALLEL Read + Bash tool calls
in a single message. Same fan-out logic, same coverage — the
subagent is the "container", and the parallel tool calls inside
one message are the "fan-out". Document this fallback in your
output so the orchestrator knows the harness limitation kicked in.
(Why this fallback works: a single message with N parallel tool
calls is effectively a fan-out of N concurrent reads/greps, and
the model can synthesize across them in the same context. Sesvars
F16 calibration logged this approach as effective.)
Set up: create planning/<slug>/ if it doesn't exist. Pick the
slug from the user's idea (snake_case, ≤30 chars). If a brainstorm
already exists, ask the user if they want to overwrite or revise.
Ask about user reference impl. Explicit question: "do you
have a manual reference implementation of this feature
anywhere?" Check conversation history + working dirs + STATE.md
for hints. If yes, locate the regress SQL file path before
proceeding — Agent D in step 4 needs it.
2.5. Corpus-chain keyword discovery (REQUIRED, cheap). Run
python3 scripts/corpus-chain.py --keywords "<the user's feature description>"
The output surfaces:
- candidate
knowledge/scenarios/ slugs matching the feature
- candidate
knowledge/idioms/ slugs matching the pattern class
- analogous past runs from
planning/ + sessions/ ranked by
keyword-hit + shared-file overlap
Read the top 2-3 hits BEFORE step 3. Two effects:
- If a past
planning/<slug>/ doc exists, it encodes design
decisions you'd re-litigate blindly otherwise. Read at least
the brainstorm + comparison files.
- The scenario hits become candidates for the
## Companion skills frontmatter later; the idiom hits become candidates
for the DECISION-question phrasing.
If the chain returns nothing useful, proceed anyway — this is a
discovery step, not a gate. Log "corpus-chain returned no
matches; brainstorm proceeds with fresh scope" in the doc.
Load minimal corpus: read the master knowledge/subsystems/
index to pick 1-3 subsystem docs to load. After step 2.5, prefer
the subsystems named in the top scenario/idiom hits' ## Files owned / ## Call sites sections — that's an evidence-backed pick.
Do NOT load per-file docs at this stage. Do NOT walk source/
in-context (the agents below will).
PARALLEL FAN-OUT — usage surface enumeration (§0). Spawn
4 subagents in the same message (Agent tool, subagent_type
general-purpose):
Agent A — scenario mining. Read
knowledge/scenarios/_index.md + every plausibly-relevant
scenario file. Return 10-15 example usage lines extracted
from scenario examples + every scenario slug that matches.
Agent B — source-tree mining. Grep
source/src/test/regress/sql/ + contrib/*/sql/ for
similar-shaped features (e.g. for sesvars: grep for
Param, PREPARE, SET, := patterns). Return 10-15
idiomatic SQL lines extracted from PG's own tests.
Agent C — community mining. WebFetch
https://commitfest.postgresql.org/?text=<keyword> + the
top 5 git log hits for the keyword + a WebSearch for
"pgsql-hackers proposal". Return any recent
proposal threads + the SQL surface lines they propose.
Agent D — user-reference mining (if reference impl
exists per step 2). Read the reference's regress SQL
file in full. Read its expected output. Skim its main
implementation file's TOP-OF-FILE comment for architecture
notes. Return: every distinct usage line from the regress
file + a one-paragraph architecture summary + the existing
PG mechanism the reference reuses (if discernible).
Synthesize Agents A/B/C/D outputs into the §0 usage surface
table (20-30 lines, grouped by usage class — see the §0
format in "Required sections" above).
PARALLEL FAN-OUT — existing-PG-mechanism survey (§0.5).
Spawn 4 subagents:
Agent A — primnodes/parsenodes mining. Grep
source/src/include/nodes/primnodes.h +
parsenodes.h for Expr/Stmt nodes with discriminator
fields (paramkind on Param, op on SQLValueFunction,
op on XmlExpr, etc.). Return a list of nodes that
could plausibly be specialized via a new discriminator
value.
Agent B — execExpr mining. Grep
source/src/include/executor/execExpr.h for EEOP_*
steps + their d.union.* arms. Return a list of steps
that handle similar value-domain operations (could a new
case fit into an existing step's union?).
Agent C — RTE / FunctionScan mining. Grep
source/src/include/nodes/parsenodes.h for RTE_*
kinds. Grep for FunctionScanState + similar patterns.
Return any row-source mechanisms that could carry the
feature.
Agent D — corpus pattern mining. Read
knowledge/idioms/*.md for "reuse"-pattern docs (e.g.
node-types.md, lexer-and-grammar.md). Return a short list
of "X is the canonical way to add a Y" patterns.
Synthesize into the §0.5 matrix (mechanism × can-it-carry-
this × cost-of-reuse × cost-of-invent). The recommended
approach in §6 must cite one row from this matrix.
Sketch 2-3 approaches. Keep them genuinely distinct (not three
flavors of the same approach). If you can only name one approach,
say so explicitly — it usually means the design space is narrow OR
you haven't thought hard enough.
Distinctness test: two approaches are flavors-of-the-same when
they share ALL of (a) the owning subsystem, (b) the invariant
footprint, (c) the user-visible surface (SQL vs GUC vs reloption
vs extension). If at least one of those differs meaningfully,
they're distinct. Example: "TTL via autovacuum extension" vs
"TTL via dedicated bgworker" differ on (a) but share (b) and (c)
— borderline-flavors. "TTL via autovacuum" vs "TTL via tuple-
visibility predicate" differ on all three — genuinely distinct.
Adversarial-pass for collection / accumulator / lifecycle-
managed types (L5). Before locking the approach list, run one
adversarial question: "For the data structure this approach
centers on, have I considered by-value inline storage vs
by-pointer vs by-reference-to-shared-pool?" Default failure
mode: when the parent code already uses one representation
(e.g. PG's List* of pointers), the brainstorm inherits it
without questioning, and the alternative (which may eliminate
downstream ownership / lifetime / leak questions) never enters
the candidate set. This was the jsonpath_leak trilogy's miss:
approaches A/B/C/D all assumed pointer-based JsonValueList;
Tom Lane's actual fix 5a2043bf713 redesigned to by-value
inline-chunked storage, which made Clear pfreeing the chunks
release everything atomically — no per-call MemoryContext
needed. See planning/jsonpath_leak/comparison.md §L5. The
sub-question goes in EVERY approach's "Storage representation"
field (per §5 of the output template).
Adversarial-pass for control-flow shape when adding an
every-exit invariant (L6). Before locking the approach list,
run one more adversarial question when the fix targets an
existing function: "Does the target function have ≥3 exit
paths, AND does my fix introduce a step that must run at
every one? If yes, have I enumerated the 'collapse to single
exit + place the invariant there' option as approach E?"
Default failure mode: when the parent code has 6 early-return
sites and my fix needs a cleanup call at each of them, the
easy path is to put the cleanup at function ENTRY (covers all
returns with 1 call) or ADD 6 explicit cleanup calls before
each return. Both work; neither considers "what if the 6
returns are the wrong shape given the new invariant?" This was
the nodesubplan_leak trilogy's miss: our approach put the
MemoryContextReset at entry; Tom Lane's actual fix
abdeacdb0920 collapsed the six returns into a single
bool result + one exit path with the reset there (net −16
executable lines vs our +11). See
planning/nodesubplan_leak/comparison.md §F32. Same additive-
vs-restructure pattern the jsonpath_leak trilogy already
showed — L5 and L6 are two views of the same failure mode
(blind trilogy inherits parent shape without questioning).
The sub-question fires on the FUNCTION being modified (not on
any data structure); the approach E enumeration then goes in
§5 of the output template (per the "Mandatory approach E"
trigger).
Recommend. Pick one. Default to COMPREHENSIVE scope, not
minimal MVP (per R15 in pg-implement-discipline.md). If the
user's framing or a reference impl from step 2 implies the
comprehensive approach, take it as the default. MVP framing
requires the user's EXPLICIT consent — name the tradeoff in the
recommendation and let them opt down.
The recommendation must:
- Cite the §0.5 mechanism row this approach uses (reuse-vs-
invent).
- Cite the §0 usage-class coverage (row-numbers list).
- Name 3 scenarios, 2 personas, 2 idioms the approach draws on
(per §5 Citations requirement).
- If a reference impl exists from §0.7, explicitly state how the
recommendation COMPARES to the reference: "matches reference
surface" OR "extends reference by X" OR "scopes down vs
reference by dropping Y, see DECISION 2".
Decisions. Name 3-5. Be specific — "scope (MVP vs full)" is too
vague; "Should the MVP support DEFAULT clauses or only NOT NULL?" is
right. Each DECISION must reference §0 example rows by number:
"Should we support array-indirection (§0 rows 5-8) in v1?".
Write. Single file, ~250-450 lines (raised from the old
150-300 limit — the §0 + §0.5 + §0.7 + citation requirements
make brainstorms LONGER, and that's correct: the cost of going
deep at brainstorm time is far less than the cost of
under-scoping at plan time).
Hand off. End with one short paragraph: "Run /pg-plan <slug>
when you've picked an approach and answered the DECISION: questions
inline above." Or signal that the brainstorm itself surfaced a
blocker that needs resolving before planning makes sense.
Boundaries vs other skills
pg-feature-plan (Phase 2): consumes this output. Don't do its
work here.
pg-claude: the master nav. Use to pick which subsystem docs to
load.
patch-submission: only relevant once a patch exists. Don't
pre-empt.
/implement: takes over after Phase 2 + a real plan.
Style notes
- Be opinionated. A bland brainstorm with three equivalent approaches
and no recommendation wastes the human's time.
- Be brief. The whole document should be readable in 5 minutes.
- Be honest about uncertainty. "I'm guessing the lockmgr would need a
new lock type here, but I haven't verified" is more useful than a
confident-sounding wrong claim.
- Cite the corpus when relevant:
[via knowledge/subsystems/X.md] for
any subsystem-level claim. Use [unverified] for everything else.
Anti-patterns
- Designing instead of brainstorming. If you find yourself naming
catalog columns, picking SQLSTATEs, or proposing test files — stop.
That's Phase 2. The brainstorm answers "which direction" not
"how exactly".
- Three-equivalent-approaches with no recommendation. A bland
brainstorm wastes the user's time. Pick one. If you genuinely
can't, that IS the DECISION: — surface it.
- Exhaustive prior-art search. §4 is a triage pass, not a
literature review. Top 3 git-log hits + first page of CF + a
quick PGXN check is enough. The user can ask for more later.
- Skipping the extension-already-exists reframe. If §4 hits a
mature out-of-tree extension covering most of the ask, the
candidate approaches MUST be framed against it (upstream vs
harden vs move-to-contrib), not designed from scratch. The
first DECISION: must surface this — see §Output 7 examples.
- Low-leverage DECISION: questions. "What should the GUC name
be?" or "Should we document this?" are Phase-2 implementation
details, not brainstorm DECISIONs. A DECISION: is a tradeoff
only the user can adjudicate (scope, semantics, target version,
core-vs-contrib).
- DECISION:-as-deferral. If the brainstorm offloads every
question to the user, you haven't thought hard enough.
Recommend a default; let the user override.
Where the artifact lives
planning/<slug>/brainstorm.md — in a NEW top-level directory
planning/, sibling to knowledge/ and sessions/.
The planning/ directory is for work-in-progress design docs.
Difference from knowledge/: knowledge is distilled durable
reference; planning is messy WIP that may be discarded. Difference
from sessions/: sessions are logs of what happened; planning is
forward-looking.
Cleanup policy: planning docs stay in tree until the feature lands
(then the plan is referenced by the patch's commit message and can
be archived) or until the user explicitly says drop it.
Cross-references
.claude/skills/pg-feature-plan/SKILL.md — Phase 2 consumer of this skill's output; reads planning/<slug>/brainstorm.md + the inline DECISION: answers.
.claude/skills/pg-implement/SKILL.md — Phase 3 consumer (via the plan); brainstorm is read for context only, not procedure.
.claude/skills/pg-claude/SKILL.md — master index used to pick which 1-3 knowledge/subsystems/*.md docs to load.
knowledge/scenarios/_index.md — the scenarios decision tree consulted in §4 (Has this been tried?).
.claude/skills/meta-commit-style/SKILL.md — the brainstorm.md file commits to the meta repo via this style.
planning/README.md — directory layout for planning/<slug>/.
.claude/commands/pg-brainstorm.md — slash-command wrapper that invokes this skill.
1---2name: pg-feature-brainstorm3description: pg-feature-brainstorm — Phase 1 of the PG planner4---56# pg-feature-brainstorm — Phase 1 of the PG planner78The first stage of the two-phase PostgreSQL feature planner. Output is a9**short, opinionated sketch** that frames the problem and offers10candidate approaches — not an implementation plan.1112The pairing:13- **Phase 1 — brainstorm** (this skill) → narrow the design space14- **Phase 2 — plan** (`pg-feature-plan` skill) → make it implementable1516Per the user's split: brainstorm explores, plan commits.1718## When to use vs not1920**Use** when the idea is exploratory:21- "What would it take to add server-side variables?"22- "Could we add a new hook for X?"23- "I want to make EXPLAIN show Y, what are the options?"24- "Should this be a contrib extension or a core change?"2526**Don't use** when the task is already scoped:27- "Add a `pg_buffercount()` builtin" → go to `pg-feature-plan` directly.28- "Fix this specific bug" → no brainstorm needed; cite + patch.2930**Don't use** for non-PG brainstorming, app architecture, infra design.3132## Inputs3334- A natural-language description of the idea (the user's argument).35- Optional: a `slug` for the planning artifacts (otherwise derived from36 the idea, e.g. `server-side-variables` → `server_side_vars`).3738## Output3940A single file at `planning/<slug>/brainstorm.md` with the structure41below. ~150-300 lines total. Anything longer means you're doing42Phase 2 work prematurely — stop and hand off.4344### Required sections (in this order)45460. **Concrete usage surface** (REQUIRED, comes FIRST). 20-3047 enumerated example lines showing what the feature must let the48 user *write*. Each example is one SQL / PL/pgSQL / DDL line.49 Group by usage class. Generate this BEFORE answering DECISION50 questions — every DECISION must take these examples as inputs,51 not abstractions.5253 Why this is §0: the sesvars first calibration (2026-06-17)54 produced an MVP that covered ~30% of what the user actually55 wanted, because DECISION questions were phrased so narrowly56 they excluded entire usage classes (array indirection, composite57 access, PL/pgSQL direct writes, DDL DEFAULT, SELECT INTO,58 aggregate semantics). The §0 enumeration prevents that failure59 mode — if a usage class isn't on the list, the brainstorm has60 admitted it's out of scope; if it IS on the list, the candidate61 approaches MUST be able to support it.6263 How to generate the examples — **parallel fan-out via subagents**:64 - Agent A reads `knowledge/scenarios/_index.md` + every pinned65 scenario; lists usage examples that show up in those scenarios.66 - Agent B greps `source/src/test/regress/sql/` and67 `contrib/*/sql/` for similar-shaped features; extracts68 idiomatic SQL surface lines.69 - Agent C web-searches pgsql-hackers + CommitFest entries for70 the feature keyword; extracts examples from recent threads.71 - Agent D — **if the user has a manual reference implementation**72 (see §0.7) — reads the reference's regress SQL file as the73 UPPER BOUND. Every example line that file contains becomes a74 candidate for §0.75 - Synthesize the four agent outputs into a deduplicated 20-3076 line table grouped by usage class.7778 Format:79 ```80 ## §0 Usage surface (comprehensive)8182 ### Reader83 - SELECT @x84 - SELECT @arr[2], @arr[2:3]85 - SELECT (@typ).field86 - SELECT @js -> 'k'87 ...8889 ### Utility writer (SET)90 - SET @x := expr91 - SET @x := expr, @y := expr, @z := expr92 - SET @x TYPE DATE := expr93 - SET @arr[2] := v94 - SET @arr[2:3] := v95 ...9697 ### Inline writer (SELECT :=)98 - SELECT @x := expr99 - SELECT @a := 1, @b := 2100 - SELECT @x := (subquery)101 - SELECT col, @cum := @cum + col FROM t102 ...103104 ### Cross-feature105 - PL/pgSQL DO block: BEGIN SET @pl := 3; END106 - SELECT col INTO @v, @w FROM t107 - CREATE TABLE t (c INT DEFAULT @v)108 - EXECUTE FORMAT('SELECT @x := %L', val)109 ...110111 ### Adversarial / edge112 - Multi-target SET with self-referential type inference113 - Chained inline := with type-shift across columns114 - Quoted identifiers @"name with spaces"115 - WHERE-clause assignment evaluation order116 ...117 ```118119 The DECISION questions in §7 then say things like "should the120 feature support the array-indirection examples above (rows 5-8)?"121 — concrete, bound to specific lines, not abstract.1221230.5. **Existing-PG-mechanism survey** (REQUIRED, before124 candidate approaches). What existing PG nodes / mechanisms /125 patterns could carry this feature? The default is **REUSE over126 INVENT**. Inventing a new Expr node, new EEOP step, new127 parsetree shape is 5× more touch points than reusing an128 existing one (walker coverage, EEOP_*_EXEC interpreter step,129 JIT mirror, ruleutils support, copy/equal/out/read funcs130 regen).131132 How to survey — **parallel fan-out via subagents**:133 - Agent A: grep `source/src/include/nodes/primnodes.h` +134 `parsenodes.h` for similar-shaped Expr / Stmt nodes. Note135 `Param` (with `paramkind` discriminator), `SQLValueFunction`136 (with `op` discriminator), `XmlExpr` (with `op`), etc.137 Discriminator-bearing existing nodes are PRIME candidates138 for reuse.139 - Agent B: grep `source/src/include/executor/execExpr.h` for140 existing `EEOP_*` steps that could be specialized via a141 `d.union.*` arm without inventing a new opcode.142 - Agent C: grep `source/src/include/access/` for RangeTblEntry143 kinds (`RTE_*`) and FunctionScanState patterns. New row-source144 features often fit RTE kinds without inventing a Path / Plan145 node.146 - Agent D: search the corpus `knowledge/idioms/*.md` and147 `knowledge/subsystems/*.md` for "existing mechanism" /148 "reuse" / "PARAM_*" / "RTE_*" patterns. The corpus is built149 to surface these.150151 Output a short matrix in §0.5:152 ```153 ## §0.5 Existing PG mechanisms considered for reuse154155 | Mechanism | Could it carry this? | Cost of reuse | Cost of invent |156 |---|---|---|---|157 | Param + new paramkind | YES — text-named, value-bearing, dynamically-typed | tiny — paramkind enum + paramsesvarid field | 5× more touch points |158 | SQLValueFunction + new op | NO — designed for parameterless time/role funcs | n/a | n/a |159 | New T_SessionVar Expr | YES but expensive | nodeFuncs.c 6 cases + parse_collate.c + clauses.c (3 walkers) + ruleutils.c + JIT mirror | baseline |160 | RTE_*KIND* | NO — sessvars are scalar, not row-source | n/a | n/a |161 ```162163 **The recommended approach in §6 MUST justify its mechanism164 choice with reference to this matrix.** If §6 picks invent-165 new, the recommendation has to argue why the existing mechanism166 doesn't fit — not just "it's cleaner".167168 Sesvars-calibration evidence: the user's manual implementation169 chose `PARAM_SESSION_VARIABLE` (new paramkind on Param). My170 AI-driven implementation invented `T_SessionVar` +171 `T_SessionVarAssign`. Mine ended up with 5× more touch points172 for ~30% less coverage. The §0.5 step exists to prevent that173 repeat.1741750.7. **User-reference-implementation readthrough** (REQUIRED IF176 one exists). Ask the user explicitly: "do you have a manual177 reference implementation of this feature?" Look at the178 conversation history, the working directory, and any prior179 session logs for hints (e.g. user mentions a thesis, a private180 branch, a "my version" repo path).181182 **If a reference exists, READ IT as the upper-bound spec.** The183 §2 out-of-scope lock is for features the user EXPLICITLY184 EXCLUDES, not "things we haven't thought of yet". The reference185 tells you what the user actually wants; the §0 usage surface186 becomes the union of (the reference's surface) + (any additions187 the user calls out).188189 This is the R15 default: comprehensive scope, not minimal MVP.190 If the user has built it once already manually, the planner191 suite's job is to produce something *comparable to* the192 reference, not 30% of it.193194 How to readthrough:195 - Read the reference's main regress SQL file in full196 (`session_variables.sql` for sesvars). This IS the spec.197 - Read the reference's expected output to confirm semantics.198 - Skim the reference's main implementation file (the199 equivalent of `commands/<feature>.c`) just for ARCHITECTURE200 — what existing PG mechanism did they reuse? (This feeds201 §0.5.)202 - Note any features the reference SHIPS that look like scope203 expansions worth including, AND any features that look204 genuinely out-of-scope (e.g. experimental / personal-pref).205 - Surface in §7 as a DECISION: "the reference ships X, Y, Z206 — should we match all of these or scope down? If scope207 down, which?"208209 Anti-pattern this rule prevents: "I won't read the reference210 because it might bias me toward their design." Wrong framing.211 The bias is helpful: the reference encodes the user's real212 intent. The planner suite's job is to match or exceed it, not213 reinvent it independently.2142151. **Problem statement** (3-5 sentences). What is the user actually216 asking for, in your own words? Restate the goal so misunderstandings217 surface early. Name the user who would benefit (DBA, extension218 author, hacker, end user).2192. **Why this might matter** (3-5 sentences). What does PG currently220 force the user to do that this would replace or improve?2213. **Relevant subsystems** (bulleted). Name 1-3 `knowledge/subsystems/*.md`222 docs that the implementation would touch. One-line summary of each.223 This is the **only** corpus you must load in Phase 1 — don't read the224 per-file docs yet.2254. **Has this been tried?** A short result of a triage pass:226 - CommitFest: search `https://commitfest.postgresql.org/` for the227 idea keyword. Note any matching entry by ID + status + last activity.228 - git log: `git log --oneline --grep='<keyword>' source/` for the229 last ~2 years. Note any related commits.230 - pgsql-hackers: if a recent thread is obvious from CF or git,231 reference it; otherwise skip (don't bulk-search the list).232 - Corpus: `grep -r '<keyword>' knowledge/` for anything already233 documented. Note any hit.234 - **Out-of-tree extensions on PGXN / community repos**: search235 pgxn.org and well-known per-area extension lists for the idea236 keyword. Many feature requests already have a maintained237 extension solving 80% of the problem (`pg_partman` for238 time-bucket retention, `plpgsql_check` for plpgsql static239 analysis, `pg_cron` for in-DB scheduling, `pgvector` for vector240 ops before native, …). When this hits, the brainstorm pivots241 from "design from scratch" to **upstream into core vs harden242 the extension vs move to contrib** — surface this as the FIRST243 DECISION: in §7.244 - **Scenarios layer: match against `knowledge/scenarios/_index.md`**.245 If a scenario (or a composite of scenarios) matches the246 brainstormed approach, name it in this section. Phase 2247 (`pg-feature-plan`) will PIN to it as the authoritative §3 file248 checklist — knowing in advance which scenario applies lets the249 user spot a scope mismatch early. Format: `Scenario(s): <slug>`250 or `Scenarios layer gap: <one-line description>` if no scenario251 matches and the change-class is recurring.252 A brainstormed approach often spans 2-3 scenarios at once;253 name all of them. The scenarios index documents the common254 compositions explicitly (see `knowledge/scenarios/_index.md`255 §"Composite features"). Phase 2 will UNION the file checklists.256 Heuristic for when to flag a `Scenarios layer gap:` — flag it257 when the same change-class would plausibly recur in a future258 brainstorm. If it's truly one-off, just say "no scenario259 matches" without flagging.2605. **Candidate approaches** (2-3, no more — but see the mandatory261 **approach E** trigger below). For each:262 - One-paragraph description.263 - **Pros** (2-3 bullets).264 - **Cons / risks** (2-3 bullets).265 - **Approximate scope** (small / medium / large — measured in files266 touched + invariants risked, not lines of code).267 - **Existing PG mechanism it reuses** (a hook? an existing API? a268 parser pattern? a catalog table? a PARAM kind? an RTE kind?).269 **REQUIRED**: cite the §0.5 mechanism survey row this approach270 came from. If "invent new", explain why §0.5's reuse candidates271 don't fit.272 - **Storage representation** (REQUIRED when the approach involves273 a collection, accumulator, sequence, batch, queue, or any274 other lifecycle-managed data structure). For the data structure275 the approach centers on, enumerate the representation choices276 **explicitly**:277 - **by-value inline** (elements stored directly in struct or278 palloc'd chunk — e.g. `JsonbValue items[N]` in a chunked list);279 - **by-pointer** (elements palloc'd separately, pointer array280 holds references — e.g. `JsonbValue **values`);281 - **by-reference-to-shared-pool** (elements live in some282 longer-lived context / arena, struct just holds pointers to283 borrowed memory).284 Each choice has different lifetime + ownership consequences:285 by-value makes the struct's free / clear release everything286 atomically; by-pointer requires per-element ownership tracking287 OR a per-call MemoryContext; shared-pool requires the pool288 outlive the struct. The recommended approach must state which289 it picks and why. **Do NOT take "by-pointer" as a given just290 because the parent code does it** — the brainstorm should291 consider the alternative explicitly. Anchored in292 `planning/jsonpath_leak/comparison.md` §L5: our jsonpath_leak293 trilogy missed Tom Lane's `5a2043bf713` "inline chunked list"294 design (by-value) because we inherited the parent's pointer-295 based JsonValueList without questioning it; the inline storage296 would have eliminated the ownership question Phase 2's R7297 escalation had to absorb.298 - **Coverage of §0 usage surface** (which usage classes does this299 approach support? List the §0 example-row numbers explicitly.300 If an approach covers only rows 1-10 out of 25, it's a 40%-301 scope approach — say so and flag whether the remaining 60%302 is in-scope but deferred or genuinely out-of-scope.).303 - **Citations** (REQUIRED): at least 3 `knowledge/scenarios/*.md`304 this approach draws on, 2 `knowledge/personas/*.md` whose305 reviewer reflexes apply, 2 `knowledge/idioms/*.md` whose306 patterns it follows. Inline format:307 `[scenarios: #11 add-new-sql-keyword, #15 add-new-expression-eval-step]`308 `[personas: tom-lane (parse-tree durability), andres-freund (JIT mirror)]`309 `[idioms: lexer-and-grammar, node-types]`.310 If any cited file doesn't exist, flag as a corpus gap to fix311 (don't fake it).312313 **Mandatory approach E — "restructure control flow to match314 the new invariant"** (REQUIRED when the fix targets an315 *existing function* and that function has **≥3 exit paths**316 AND the fix introduces a common teardown / setup / invariant317 step that would need to run at every exit). Enumerate it as a318 named approach even when your first instinct is to keep the319 existing shape and paper the invariant on top.320321 The classic form: replace N early-return sites with a single322 `bool result = <default>` (or equivalent accumulator) plus one323 final exit path, then place the invariant maintenance at that324 single exit. One reset call instead of N. Similar re-shapings325 apply for setup steps (goto-cleanup, single-init prologue), for326 inverting an early-return chain into a `switch/if-else-if`327 ladder, or for extracting the branchy body into a helper that328 the caller wraps.329330 Reason to enumerate it explicitly: the blind trilogy331 consistently under-refactors when the fix is "add a reset /332 cleanup / invariant step". Two data points to date:333334 - `planning/jsonpath_leak/comparison.md` §L5 — Tom Lane335 rewrote every `JsonValueList` caller to the "always copy on336 Append + inline storage" shape; our fix kept mixed337 copy/borrow semantics and needed a per-call MemoryContext to338 absorb the ambiguity. Fewer lines net, cleaner API.339 - `planning/nodesubplan_leak/comparison.md` §F32 — Tom340 collapsed six early-return sites in `ExecHashSubPlan` into a341 single `bool result` + one exit path, then placed the342 `MemoryContextReset(node->hashtempcxt)` at that single exit343 (net −16 executable lines). Our fix kept the six branches344 and placed the reset at entry (net +11 executable lines).345 Both correct; Tom's cleaner.346347 The pattern: when a fix requires an invariant that ties to348 *every exit path*, the parent code's exit shape was chosen349 for a world without that invariant. That world no longer350 exists. Treating the exit shape as fixed is inheriting an351 assumption the fix has just invalidated. Enumerating approach352 E forces the brainstorm to notice.353354 For approach E, the required sub-bullets are the same as any355 other candidate — pros, cons, scope, mechanism, coverage,356 citations — plus one additional bullet:357358 - **Refactor shape**: name the target function, the current359 number of exit paths, the proposed collapsed shape (single360 `bool` accumulator? goto-cleanup? extracted helper?), and361 any *behavioural* delta the refactor risks. If the answer362 is "purely mechanical, no behavioural delta", say so363 explicitly — that's the low-risk case reviewers will accept.364 If the refactor DOES change observable behavior beyond the365 invariant (e.g. changes which of two error messages fires366 when both apply), flag it as a scope escalation the user367 must approve.368369 If the trigger condition doesn't fire (function has <3 exit370 paths, or the fix is a wholly new function), say "approach E371 not applicable — target function has N exit paths" instead372 of omitting the bullet entirely. That makes the check373 auditable.3743756. **Recommended approach** (1 paragraph). Pick one. Say why. Name376 what would have to be true for the *other* approaches to win377 (so the user can flag if those conditions hold).3787. **Decisions for the user** (3-5 max). Each is a concrete question379 prefixed with `DECISION:`. Things only the human can answer:380 - Scope (MVP vs full feature)381 - Backward-compat policy (break old API? add new flag?)382 - Performance/UX tradeoffs383 - Whether to do this as core or contrib/extension first384 - Whether to target current master or wait for next CF window385386 Worked examples at the right level (one per category):387 - "Are you aware the `<extension>` extension already covers ~80%388 of this? Does it not meet your need, and if so, why?"389 (Prior-art reframe; ranks FIRST whenever §4 found a mature390 out-of-tree extension — see Edit-2 note in §4.)391 - "Should expired rows be query-invisible *immediately* at the392 clock crossing, or is autovacuum-eventual acceptable?"393 (Semantics tradeoff; shapes the user's mental model.)394 - "Ship as contrib first, or aim for core in one go?"395 (Path-to-release; cheap-to-revert vs harder-to-iterate.)396397 Anti-example (too vague): "What should the GUC name be?" — that398 is a Phase-2 implementation detail, not a brainstorm DECISION:.3998. **What this brainstorm explicitly did NOT figure out**. A short list400 so the boundary with Phase 2 is clear. E.g. "did not enumerate401 catalog changes", "did not check WAL impact", "did not propose tests".402403### Forbidden in Phase 1404405- File:line citations from per-file `knowledge/files/...` docs.406 (Phase 2 does this.)407- Phase-by-phase implementation plan.408- Catalog-bump decisions.409- WAL format decisions.410- Test surface enumeration.411- Patch-series structure.412413If you find yourself writing those — stop. Save them for Phase 2.414415## Method416417Run as a parallel-fan-out loop (NOT a sequential tight loop —418the old single-context method under-explored). The fan-out is419load-bearing.420421### Fallback when nested Agent tool is unavailable422423If running inside a subagent (the Agent tool may not be in your424exposed tool set), fall back to PARALLEL Read + Bash tool calls425in a single message. Same fan-out logic, same coverage — the426subagent is the "container", and the parallel tool calls inside427one message are the "fan-out". Document this fallback in your428output so the orchestrator knows the harness limitation kicked in.429430(Why this fallback works: a single message with N parallel tool431calls is effectively a fan-out of N concurrent reads/greps, and432the model can synthesize across them in the same context. Sesvars433F16 calibration logged this approach as effective.)4344351. **Set up:** create `planning/<slug>/` if it doesn't exist. Pick the436 slug from the user's idea (snake_case, ≤30 chars). If a brainstorm437 already exists, ask the user if they want to overwrite or revise.4384392. **Ask about user reference impl.** Explicit question: "do you440 have a manual reference implementation of this feature441 anywhere?" Check conversation history + working dirs + STATE.md442 for hints. If yes, locate the regress SQL file path before443 proceeding — Agent D in step 4 needs it.4444452.5. **Corpus-chain keyword discovery** (REQUIRED, cheap). Run446447 ```448 python3 scripts/corpus-chain.py --keywords "<the user's feature description>"449 ```450451 The output surfaces:452 - candidate `knowledge/scenarios/` slugs matching the feature453 - candidate `knowledge/idioms/` slugs matching the pattern class454 - analogous past runs from `planning/` + `sessions/` ranked by455 keyword-hit + shared-file overlap456457 Read the top 2-3 hits BEFORE step 3. Two effects:458 - If a past `planning/<slug>/` doc exists, it encodes design459 decisions you'd re-litigate blindly otherwise. Read at least460 the brainstorm + comparison files.461 - The scenario hits become candidates for the `## Companion462 skills` frontmatter later; the idiom hits become candidates463 for the DECISION-question phrasing.464465 If the chain returns nothing useful, proceed anyway — this is a466 discovery step, not a gate. Log "corpus-chain returned no467 matches; brainstorm proceeds with fresh scope" in the doc.4684693. **Load minimal corpus:** read the master `knowledge/subsystems/`470 index to pick 1-3 subsystem docs to load. **After step 2.5**, prefer471 the subsystems named in the top scenario/idiom hits' `## Files472 owned` / `## Call sites` sections — that's an evidence-backed pick.473 Do NOT load per-file docs at this stage. Do NOT walk source/474 in-context (the agents below will).4754764. **PARALLEL FAN-OUT — usage surface enumeration (§0).** Spawn477 4 subagents in the same message (Agent tool, subagent_type478 general-purpose):479480 - **Agent A — scenario mining.** Read481 `knowledge/scenarios/_index.md` + every plausibly-relevant482 scenario file. Return 10-15 example usage lines extracted483 from scenario examples + every scenario slug that matches.484485 - **Agent B — source-tree mining.** Grep486 `source/src/test/regress/sql/` + `contrib/*/sql/` for487 similar-shaped features (e.g. for sesvars: grep for488 `Param`, `PREPARE`, `SET`, `:=` patterns). Return 10-15489 idiomatic SQL lines extracted from PG's own tests.490491 - **Agent C — community mining.** WebFetch492 `https://commitfest.postgresql.org/?text=<keyword>` + the493 top 5 git log hits for the keyword + a WebSearch for494 "pgsql-hackers <feature> proposal". Return any recent495 proposal threads + the SQL surface lines they propose.496497 - **Agent D — user-reference mining (if reference impl498 exists per step 2).** Read the reference's regress SQL499 file in full. Read its expected output. Skim its main500 implementation file's TOP-OF-FILE comment for architecture501 notes. Return: every distinct usage line from the regress502 file + a one-paragraph architecture summary + the existing503 PG mechanism the reference reuses (if discernible).504505 Synthesize Agents A/B/C/D outputs into the §0 usage surface506 table (20-30 lines, grouped by usage class — see the §0507 format in "Required sections" above).5085095. **PARALLEL FAN-OUT — existing-PG-mechanism survey (§0.5).**510 Spawn 4 subagents:511512 - **Agent A — primnodes/parsenodes mining.** Grep513 `source/src/include/nodes/primnodes.h` +514 `parsenodes.h` for Expr/Stmt nodes with discriminator515 fields (`paramkind` on Param, `op` on SQLValueFunction,516 `op` on XmlExpr, etc.). Return a list of nodes that517 could plausibly be specialized via a new discriminator518 value.519520 - **Agent B — execExpr mining.** Grep521 `source/src/include/executor/execExpr.h` for `EEOP_*`522 steps + their `d.union.*` arms. Return a list of steps523 that handle similar value-domain operations (could a new524 case fit into an existing step's union?).525526 - **Agent C — RTE / FunctionScan mining.** Grep527 `source/src/include/nodes/parsenodes.h` for `RTE_*`528 kinds. Grep for FunctionScanState + similar patterns.529 Return any row-source mechanisms that could carry the530 feature.531532 - **Agent D — corpus pattern mining.** Read533 `knowledge/idioms/*.md` for "reuse"-pattern docs (e.g.534 node-types.md, lexer-and-grammar.md). Return a short list535 of "X is the canonical way to add a Y" patterns.536537 Synthesize into the §0.5 matrix (mechanism × can-it-carry-538 this × cost-of-reuse × cost-of-invent). The recommended539 approach in §6 must cite one row from this matrix.5405416. **Sketch 2-3 approaches.** Keep them genuinely distinct (not three542 flavors of the same approach). If you can only name one approach,543 say so explicitly — it usually means the design space is narrow OR544 you haven't thought hard enough.545546 Distinctness test: two approaches are flavors-of-the-same when547 they share ALL of (a) the owning subsystem, (b) the invariant548 footprint, (c) the user-visible surface (SQL vs GUC vs reloption549 vs extension). If at least one of those differs meaningfully,550 they're distinct. Example: "TTL via autovacuum extension" vs551 "TTL via dedicated bgworker" differ on (a) but share (b) and (c)552 — borderline-flavors. "TTL via autovacuum" vs "TTL via tuple-553 visibility predicate" differ on all three — genuinely distinct.554555 **Adversarial-pass for collection / accumulator / lifecycle-556 managed types (L5).** Before locking the approach list, run one557 adversarial question: *"For the data structure this approach558 centers on, have I considered by-value inline storage vs559 by-pointer vs by-reference-to-shared-pool?"* Default failure560 mode: when the parent code already uses one representation561 (e.g. PG's `List*` of pointers), the brainstorm inherits it562 without questioning, and the alternative (which may eliminate563 downstream ownership / lifetime / leak questions) never enters564 the candidate set. This was the jsonpath_leak trilogy's miss:565 approaches A/B/C/D all assumed pointer-based JsonValueList;566 Tom Lane's actual fix `5a2043bf713` redesigned to by-value567 inline-chunked storage, which made Clear pfreeing the chunks568 release everything atomically — no per-call MemoryContext569 needed. See `planning/jsonpath_leak/comparison.md` §L5. The570 sub-question goes in EVERY approach's "Storage representation"571 field (per §5 of the output template).572573 **Adversarial-pass for control-flow shape when adding an574 every-exit invariant (L6).** Before locking the approach list,575 run one more adversarial question when the fix targets an576 existing function: *"Does the target function have ≥3 exit577 paths, AND does my fix introduce a step that must run at578 every one? If yes, have I enumerated the 'collapse to single579 exit + place the invariant there' option as approach E?"*580 Default failure mode: when the parent code has 6 early-return581 sites and my fix needs a cleanup call at each of them, the582 easy path is to put the cleanup at function ENTRY (covers all583 returns with 1 call) or ADD 6 explicit cleanup calls before584 each return. Both work; neither considers "what if the 6585 returns are the wrong shape given the new invariant?" This was586 the nodesubplan_leak trilogy's miss: our approach put the587 `MemoryContextReset` at entry; Tom Lane's actual fix588 `abdeacdb0920` collapsed the six returns into a single589 `bool result` + one exit path with the reset there (net −16590 executable lines vs our +11). See591 `planning/nodesubplan_leak/comparison.md` §F32. Same additive-592 vs-restructure pattern the jsonpath_leak trilogy already593 showed — L5 and L6 are two views of the same failure mode594 (blind trilogy inherits parent shape without questioning).595 The sub-question fires on the FUNCTION being modified (not on596 any data structure); the approach E enumeration then goes in597 §5 of the output template (per the "Mandatory approach E"598 trigger).5996007. **Recommend.** Pick one. **Default to COMPREHENSIVE scope, not601 minimal MVP** (per R15 in pg-implement-discipline.md). If the602 user's framing or a reference impl from step 2 implies the603 comprehensive approach, take it as the default. MVP framing604 requires the user's EXPLICIT consent — name the tradeoff in the605 recommendation and let them opt down.606607 The recommendation must:608 - Cite the §0.5 mechanism row this approach uses (reuse-vs-609 invent).610 - Cite the §0 usage-class coverage (row-numbers list).611 - Name 3 scenarios, 2 personas, 2 idioms the approach draws on612 (per §5 Citations requirement).613 - If a reference impl exists from §0.7, explicitly state how the614 recommendation COMPARES to the reference: "matches reference615 surface" OR "extends reference by X" OR "scopes down vs616 reference by dropping Y, see DECISION 2".6176188. **Decisions.** Name 3-5. Be specific — "scope (MVP vs full)" is too619 vague; "Should the MVP support DEFAULT clauses or only NOT NULL?" is620 right. Each DECISION must reference §0 example rows by number:621 "Should we support array-indirection (§0 rows 5-8) in v1?".6226239. **Write.** Single file, ~250-450 lines (raised from the old624 150-300 limit — the §0 + §0.5 + §0.7 + citation requirements625 make brainstorms LONGER, and that's correct: the cost of going626 deep at brainstorm time is far less than the cost of627 under-scoping at plan time).62862910. **Hand off.** End with one short paragraph: *"Run `/pg-plan <slug>`630 when you've picked an approach and answered the DECISION: questions631 inline above."* Or signal that the brainstorm itself surfaced a632 blocker that needs resolving before planning makes sense.633634## Boundaries vs other skills635636- **`pg-feature-plan`** (Phase 2): consumes this output. Don't do its637 work here.638- **`pg-claude`**: the master nav. Use to pick which subsystem docs to639 load.640- **`patch-submission`**: only relevant once a patch exists. Don't641 pre-empt.642- **`/implement`**: takes over after Phase 2 + a real plan.643644## Style notes645646- Be opinionated. A bland brainstorm with three equivalent approaches647 and no recommendation wastes the human's time.648- Be brief. The whole document should be readable in 5 minutes.649- Be honest about uncertainty. "I'm guessing the lockmgr would need a650 new lock type here, but I haven't verified" is more useful than a651 confident-sounding wrong claim.652- Cite the corpus when relevant: `[via knowledge/subsystems/X.md]` for653 any subsystem-level claim. Use `[unverified]` for everything else.654655## Anti-patterns656657- **Designing instead of brainstorming.** If you find yourself naming658 catalog columns, picking SQLSTATEs, or proposing test files — stop.659 That's Phase 2. The brainstorm answers "*which* direction" not660 "*how* exactly".661- **Three-equivalent-approaches with no recommendation.** A bland662 brainstorm wastes the user's time. Pick one. If you genuinely663 can't, that IS the DECISION: — surface it.664- **Exhaustive prior-art search.** §4 is a triage pass, not a665 literature review. Top 3 git-log hits + first page of CF + a666 quick PGXN check is enough. The user can ask for more later.667- **Skipping the extension-already-exists reframe.** If §4 hits a668 mature out-of-tree extension covering most of the ask, the669 candidate approaches MUST be framed against it (upstream vs670 harden vs move-to-contrib), not designed from scratch. The671 first DECISION: must surface this — see §Output 7 examples.672- **Low-leverage DECISION: questions.** "What should the GUC name673 be?" or "Should we document this?" are Phase-2 implementation674 details, not brainstorm DECISIONs. A DECISION: is a tradeoff675 only the user can adjudicate (scope, semantics, target version,676 core-vs-contrib).677- **DECISION:-as-deferral.** If the brainstorm offloads every678 question to the user, you haven't thought hard enough.679 Recommend a default; let the user override.680681## Where the artifact lives682683`planning/<slug>/brainstorm.md` — in a NEW top-level directory684`planning/`, sibling to `knowledge/` and `sessions/`.685686The `planning/` directory is for **work-in-progress design docs**.687Difference from `knowledge/`: knowledge is distilled durable688reference; planning is messy WIP that may be discarded. Difference689from `sessions/`: sessions are logs of what happened; planning is690forward-looking.691692Cleanup policy: planning docs stay in tree until the feature lands693(then the plan is referenced by the patch's commit message and can694be archived) or until the user explicitly says drop it.695696## Cross-references697698- `.claude/skills/pg-feature-plan/SKILL.md` — Phase 2 consumer of this skill's output; reads `planning/<slug>/brainstorm.md` + the inline DECISION: answers.699- `.claude/skills/pg-implement/SKILL.md` — Phase 3 consumer (via the plan); brainstorm is read for context only, not procedure.700- `.claude/skills/pg-claude/SKILL.md` — master index used to pick which 1-3 `knowledge/subsystems/*.md` docs to load.701- `knowledge/scenarios/_index.md` — the scenarios decision tree consulted in §4 (Has this been tried?).702- `.claude/skills/meta-commit-style/SKILL.md` — the brainstorm.md file commits to the meta repo via this style.703- `planning/README.md` — directory layout for `planning/<slug>/`.704- `.claude/commands/pg-brainstorm.md` — slash-command wrapper that invokes this skill.