Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ Shared assets (skills, agents, hook scripts) are consumed by both hosts; host-sp
- **Agents**: `agents/pgsql-reviewer.md` (SQL review) and `agents/pgsql-plan-analyst.md` (`EXPLAIN` analysis).
- **Cursor rule**: `rules/pgsql-context.mdc` (`globs: ["**/*.sql"]`), mirroring the router.
- **Claude hooks**: `hooks/hooks.json` wires `session-start.sh` (`SessionStart`) and `lint-on-save.sh` + `skill-nudge.sh` (`PostToolUse`, `matcher: "Write|Edit"`), with `${CLAUDE_PLUGIN_ROOT}` paths.
- **Cursor hooks**: `hooks/cursor-hooks.json` (`"version": 1`) wires the same scripts to `sessionStart` and to `postToolUse` with matcher `Write`, with **workspace-relative** commands; Cursor runs a plugin hook from the plugin's install directory and sets `CURSOR_PLUGIN_ROOT` and `CURSOR_PROJECT_DIR`.
- **Cursor hooks**: `hooks/cursor-hooks.json` (`"version": 1`) wires the same scripts to `sessionStart` and to `postToolUse` with matcher `Write|StrReplace`. Commands are relative to the plugin install (`bash hooks/session-start.sh`), where Cursor runs a plugin hook; it sets `CURSOR_PLUGIN_ROOT` and `CURSOR_PROJECT_DIR`.
- **Shared hook scripts** (host-agnostic; all exit 0 always):
- `hooks/session-start.sh`: detects PostgreSQL use (pgx or lib/pq in `go.mod`, `ext-pdo_pgsql` in `composer.json`, a postgres image in a compose file, or a `*.sql` file) and prints one line naming the floor, the skills and the reviewer; under Cursor it inspects `CURSOR_PROJECT_DIR` and prints the line as JSON `additional_context`.
- `hooks/session-start.sh`: detects PostgreSQL use (pgx or lib/pq in `go.mod`, `ext-pdo_pgsql` in `composer.json`, a postgres image in a compose file, or a `*.sql` file) and prints one line naming the floor, the skills and the reviewer; under Cursor it inspects `CURSOR_PROJECT_DIR` and prints the line, with the skills' path in the plugin install, as JSON `additional_context`.
- `hooks/lint-on-save.sh`: after a `*.sql` edit, runs `squawk --reporter gcc` on that file when squawk is on PATH and a `.squawk.toml` exists in the file's directory or a parent; findings reach the model as `hookSpecificOutput.additionalContext` (Claude Code) or `additional_context` (Cursor).
- `hooks/skill-nudge.sh`: after a `.sql`, `.go` or `.php` edit, names ONE focused skill matching the text the edit adds (Claude's `new_string` or `content`; Cursor documents no `Write` input, so there it matches the file), once per skill per session (`session_id` or Cursor's `conversation_id`). Go and PHP edits nudge only on unmistakable SQL.
- `hooks/skill-nudge.sh`: after a `.sql`, `.go` or `.php` edit, names ONE focused skill matching the text the edit adds (Claude's `new_string` or `content`, or Cursor's `StrReplace` `new_string`; a Cursor `Write` with no added-text field matches the file), once per skill per session (`session_id` or Cursor's `conversation_id`). Claude is told `pgsql-coding:<skill>`; Cursor is told the skill's `SKILL.md` path in the plugin install. Go and PHP edits nudge only on unmistakable SQL.
- **References**: top-level `references/squawk.toml`, the shipped squawk config; the `/pgsql-lint-setup` scaffold block must stay byte-identical to it.
- **Evals**: `evals/<case>/` with `prompt.md` and `graders/`, run by `claude plugin eval`. Results go to `evals/results/`, which is gitignored.
- **Scripts**: `scripts/validate.sh` (wraps the stdlib-only `scripts/validate.py`), `scripts/hooks-test.sh`, `scripts/plan-summary-test.py`.
Expand All @@ -69,7 +69,7 @@ Shared assets (skills, agents, hook scripts) are consumed by both hosts; host-sp
|------|---------|
| Skills | `pgsql-coding` (auto-invoked router, with `versions.md`) + the focused, load-on-use `pgsql-query`, `pgsql-jsonb`, `pgsql-schema`, `pgsql-indexing`, `pgsql-performance`, `pgsql-migrations`, each with its `references/` |
| Slash command (user-invoked skill) | `/pgsql-lint-setup` (write the reference `.squawk.toml`) |
| Agents | `pgsql-reviewer`: context-isolated, report-only reviewer of SQL in `.sql` files, migrations, and Go or PHP query strings. `pgsql-plan-analyst`: context-isolated, report-only analyst of a long `EXPLAIN` plan or a before-and-after pair; runs the plan summariser and reads only the flagged nodes. Both load the skills with the `Skill` tool, never edit, never run DML, dispatch no sub-agents, and use `tools:` not `allowed-tools:` |
| Agents | `pgsql-reviewer`: context-isolated, report-only reviewer of SQL in `.sql` files, migrations, and Go or PHP query strings. `pgsql-plan-analyst`: context-isolated, report-only analyst of a long `EXPLAIN` plan or a before-and-after pair; runs the plan summariser and reads only the flagged nodes. Both read the skills (Claude Code: the Skill tool; Cursor: each `SKILL.md` in the plugin install), never edit, never run DML, and dispatch no sub-agents. Both declare `tools:`, never `allowed-tools:`, and `permissionMode: readonly`; what each host enforces: [docs/authoring.md](docs/authoring.md) |
| Cursor rule | `rules/pgsql-context.mdc`, scoped to `**/*.sql`, mirroring the router |
| Hooks | `session-start`, `lint-on-save`, `skill-nudge` (see Repository Layout) |

Expand Down Expand Up @@ -127,7 +127,7 @@ When adding or renaming a component, update in lockstep: the router's two tables
- **Agents use `tools:`, not `allowed-tools:`.** In an agent file `allowed-tools:` is ignored and the agent silently inherits *all* tools. The validator flags it.
- **`EXPLAIN ANALYZE` executes the statement.** Every component that suggests running SQL says so, wraps DML in `BEGIN ... ROLLBACK`, and asks before touching any database the user did not name. Keep that wording when editing.
- **Keep the two manifests in parity.** Cursor needs explicit path keys; Claude relies on default-folder discovery.
- **The Cursor hook uses a workspace-relative command** (`bash hooks/session-start.sh`), *not* `${CLAUDE_PLUGIN_ROOT}` (a Claude-Code-only variable). Don't "fix" it.
- **The Cursor hook command is relative to the plugin install** (`bash hooks/session-start.sh`), *not* `${CLAUDE_PLUGIN_ROOT}` (a Claude-Code-only variable) and *not* the user's repository. Don't "fix" it.
- **Every hook script must be wired on both hosts**, exist, and be executable; the validator checks all three.
- **`lint-on-save` is opt-in by the project.** It runs only where a `.squawk.toml` exists; squawk's rules target migrations, so linting every `.sql` file in every repository would be noise.
- **The squawk config has two copies**: `references/squawk.toml` and the block in `/pgsql-lint-setup`. Change both; the validator compares them byte for byte.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ See [docs/install.md](docs/install.md) for marketplace, local-development, updat
| Skill `pgsql-performance` | Reading `EXPLAIN` plans, planner statistics, generic vs custom plans, `pg_stat_statements`, `auto_explain`, waits and locks, and the rewrites the evidence justifies. Ships `scripts/plan-summary.py`, which summarises a TEXT or JSON plan. |
| Skill `pgsql-migrations` | Changing a live schema safely: lock levels, `lock_timeout`, `CONCURRENTLY`, `NOT VALID` then `VALIDATE`, expand and contract, batched backfills, each mapped to its squawk rule. |
| Skill `/pgsql-lint-setup` | User-invoked: writes the reference `.squawk.toml` and says how to run squawk locally and in CI. Never overwrites an existing config unprompted. |
| Agent `pgsql-reviewer` | Report-only, context-isolated SQL reviewer. Loads the skills, returns severity-ranked findings, never edits files or runs DML, and dispatches no sub-agents. |
| Agent `pgsql-plan-analyst` | Report-only, context-isolated `EXPLAIN` analyst for a long plan or a before-and-after pair. Runs the plan summariser, reads only the flagged nodes, and returns where the time goes, where an estimate error starts, and the command that confirms each fix. |
| Agent `pgsql-reviewer` | Report-only, context-isolated SQL reviewer. Reads the skills, returns severity-ranked findings, never edits files or runs DML, and dispatches no sub-agents. |
| Agent `pgsql-plan-analyst` | Report-only, context-isolated `EXPLAIN` analyst for a long plan or a before-and-after pair. Runs `scripts/plan-summary.py` from the `pgsql-performance` skill directory, reads only the flagged nodes, and returns where the time goes, where an estimate error starts, and the command that confirms each fix. |
| Session-start hook | Detects PostgreSQL use (pgx or lib/pq in `go.mod`, `ext-pdo_pgsql` in `composer.json`, a postgres image in a compose file, or a `*.sql` file) and prints one standards line; dual-host. |
| Lint-on-save hook | After a `*.sql` edit, runs squawk on that file when the project has a `.squawk.toml` and squawk is installed, and hands the findings to the model; dual-host (Cursor: [install.md](docs/install.md#hooks)); never blocks an edit. |
| Skill-nudge hook | After a `.sql`, `.go` or `.php` edit, names the one focused skill the edit calls for, once per skill per session; dual-host (Cursor: [install.md](docs/install.md#hooks)). |
Expand All @@ -78,8 +78,8 @@ SQL usually lives inside Go or PHP code, so a change often needs two plugins. Th

Subagents do not inherit the parent session's skills. A plan runner that dispatches implementers and reviewers must tell them, in every brief, to load the skills:

- **Implementer brief**: "Before writing SQL, invoke the Skill tool with `pgsql-coding:pgsql-coding`, then the focused skills matching the change (see its *Route, then load* table). Run squawk on migration files. Run `EXPLAIN` only against the database named here: <database>."
- **Reviewer brief**: "Before reading the diff, load `pgsql-coding:pgsql-coding` plus the focused skills the diff calls for; cite the rule a finding rests on. Do not dispatch `pgsql-reviewer`: you are the review seat."
- **Implementer brief**: "Before writing SQL, load the standards. On Claude Code, invoke the Skill tool with `pgsql-coding:pgsql-coding`, then the focused skills matching the change (see its *Route, then load* table). On Cursor, read `<plugin root>/skills/pgsql-coding/SKILL.md`, then the focused skills' `SKILL.md` beside it (the session-start line prints the plugin root). Run squawk on migration files. Run `EXPLAIN` only against the database named here: <database>."
- **Reviewer brief**: "Before reading the diff, read the pgsql-coding skills the diff calls for. On Claude Code, invoke the Skill tool with `pgsql-coding:pgsql-coding`. On Cursor, read each skill's `SKILL.md` under `<plugin root>/skills/` (the session-start line prints the plugin root). Cite the rule a finding rests on. Do not dispatch `pgsql-reviewer`: you are the review seat."

Use `pgsql-reviewer` directly when no such seat exists, as with an ad-hoc "review this migration" request.

Expand Down
5 changes: 3 additions & 2 deletions agents/pgsql-plan-analyst.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ description: >
(pgsql-reviewer). See "When to invoke" in the body.
model: inherit
color: cyan
permissionMode: readonly
tools:
- Read
- Grep
Expand All @@ -39,8 +40,8 @@ You are **pgsql-plan-analyst**, a report-only analyst of PostgreSQL `EXPLAIN` pl

## How to analyse

1. **Load the skills.** Invoke the Skill tool with `pgsql-coding:pgsql-performance` and read its `explain.md` reference; read `statistics.md` when an estimate is off and `rewrites.md` before proposing a rewrite. Load `pgsql-coding:pgsql-indexing` before proposing an index and `pgsql-coding:pgsql-jsonb` when a filter is on a jsonb key. Note the base directory the Skill tool prints for `pgsql-performance`.
2. **Run the summariser before reading any plan**: `python3 "<pgsql-performance base directory>/scripts/plan-summary.py" <plan file>`; without a printed base directory, the script is `skills/pgsql-performance/scripts/plan-summary.py` under the plugin root (the folder that holds `skills/`). For a plan that arrived inline, pipe it through a quoted heredoc to `plan-summary.py -`. Without Python, read the plan by hand and say so.
1. **Load the skills.** On Claude Code, invoke the Skill tool with `pgsql-coding:pgsql-performance` and read its `explain.md` reference; read `statistics.md` when an estimate is off and `rewrites.md` before proposing a rewrite. Load `pgsql-coding:pgsql-indexing` before proposing an index and `pgsql-coding:pgsql-jsonb` when a filter is on a jsonb key. Note the base directory the Skill tool prints for `pgsql-performance`. On Cursor there is no Skill tool: read `<plugin root>/skills/<name>/SKILL.md` and its `references/` for the same skills, where the plugin root is the install path your brief or the session-start line gives.
2. **Run the summariser before reading any plan**: `python3 "<pgsql-performance directory>/scripts/plan-summary.py" <plan file>`. That directory is the base directory the Skill tool printed, or else `<plugin root>/skills/pgsql-performance`; never `skills/` in the user's repository. For a plan that arrived inline, pipe it through a quoted heredoc to `plan-summary.py -`. Without Python, or if the script cannot be found, read the plan by hand and say so.
3. **Read only the flagged regions** of the plan (`Read` with an offset), and the query text and the table definitions if the caller gave them or the repository has them (`Grep` the migrations for the table and its indexes), so an index you propose does not already exist.
4. **Explain each finding** with the rule from the skill that owns it: an estimate error at its lowest node (stale or missing statistics, a jsonb or expression predicate with no statistics, correlated columns: `pgsql-performance` statistics), a node run thousands of times (the join strategy, a missing index on the inner side, a per-row subquery), a filter that discards most rows (a missing or unusable index: `pgsql-indexing`), a spill, JIT overhead on a short query.
5. **For two plans**, say what changed and why. A faster plan can be faster for a different reason than the one the caller assumes; for example, an expression index that the plan never scans can still fix an estimate, because `ANALYZE` gathers statistics on index expressions.
Expand Down
9 changes: 5 additions & 4 deletions agents/pgsql-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ description: >
migration change (the assistant's own too), a pre-PR gate, and a review of named files or
dimensions. Report-only and works alone: severity-ranked findings; never edits code, writes to a
database or dispatches agents. If a workflow already owns the review seat, have its reviewer
load pgsql-coding:pgsql-coding and the focused skills instead of dispatching this one beside it.
Not for Go or PHP style (go-reviewer, php-reviewer), a long EXPLAIN plan (pgsql-plan-analyst),
or what squawk already flags. See "When to invoke" in the body.
read the pgsql-coding skills instead of dispatching this one beside it. Not for Go or PHP style
(go-reviewer, php-reviewer), a long EXPLAIN plan (pgsql-plan-analyst), or what squawk already
flags. See "When to invoke" in the body.
model: inherit
color: blue
permissionMode: readonly
tools:
- Read
- Grep
Expand Down Expand Up @@ -41,7 +42,7 @@ You are **pgsql-reviewer**, a report-only reviewer of SQL for PostgreSQL 18 (Pos

## How to review

1. **Load the skills.** Invoke the Skill tool with `pgsql-coding:pgsql-coding`, then the focused skill for each area the change touches, per its *Route, then load* table: `pgsql-query`, `pgsql-jsonb`, `pgsql-schema`, `pgsql-indexing`, `pgsql-performance`, `pgsql-migrations`. Read the references a finding depends on before you assert it. Without the Skill tool, say so in the closing note and work from the dimensions below.
1. **Load the skills.** On Claude Code, invoke the Skill tool with `pgsql-coding:pgsql-coding`, then the focused skill for each area the change touches, per its *Route, then load* table: `pgsql-query`, `pgsql-jsonb`, `pgsql-schema`, `pgsql-indexing`, `pgsql-performance`, `pgsql-migrations`. On Cursor there is no Skill tool: read `<plugin root>/skills/<name>/SKILL.md` for the same skills, where the plugin root is the install path your brief or the session-start line gives; never `skills/` in the user's repository. Read the references a finding depends on before you assert it. If the skills cannot be loaded or opened, say so in the closing note and work from the dimensions below.
2. **Get the change.** Review a diff you are handed, or run `git diff` when a staged or branch change is implied. For every table a query touches, find its definition (migrations, a schema dump, `CREATE TABLE` files) so you know its columns, constraints and indexes; most of these bugs live in the gap between a query and the schema.
3. **Walk every dimension below.** Each line is a cue; the skill in brackets owns the rule and its source, so read it before asserting a finding, and cite it.
4. *(Optional)* run `squawk <file>` on changed migrations, and `EXPLAIN` against a named database to confirm a plan claim. Don't block on missing tools.
Expand Down
Loading
Loading