From 990dcc4c5a91c07990b801d93c50de07e7873f6e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 12 Aug 2026 02:09:17 +0000 Subject: [PATCH 1/3] fix(source-control): rule 3 no longer lists bare map(f) as element-wise-safe map(f) builds a per-page array under --paginate; only map(f) | .[] or .[] | f are element-wise-safe. Fixes the false carve-out in readiness.md rule 3 reported in #2245. Co-authored-by: Kyle Sexton --- plugins/source-control/.claude-plugin/plugin.json | 2 +- plugins/source-control/CHANGELOG.md | 9 +++++++++ .../skills/pull-request/reference/readiness.md | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/source-control/.claude-plugin/plugin.json b/plugins/source-control/.claude-plugin/plugin.json index 0a0748e7ba..1114a51995 100644 --- a/plugins/source-control/.claude-plugin/plugin.json +++ b/plugins/source-control/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "source-control", - "version": "0.51.12", + "version": "0.51.13", "description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-Authored-By trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop — safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and standing merge-rung raises binding from the team-tracked layer only — with one named exception, where an invocation line explicitly typing both the autopilot tier keyword and the dedicated raise argument --merge c3-this-run widens that single invocation's merge authority up to C3 behind a fresh independent frontier-tier resolver, while C4-structural and C5-untrusted-provenance stay unconditionally human-merge), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply — interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep — never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.", "author": { "name": "Melodic Software", diff --git a/plugins/source-control/CHANGELOG.md b/plugins/source-control/CHANGELOG.md index 9af9fa0e54..1d555f4deb 100644 --- a/plugins/source-control/CHANGELOG.md +++ b/plugins/source-control/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to the `source-control` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.51.13] + +### Fixed + +- **Rule 3 no longer lists bare `map(f)` as element-wise-safe (#2245).** `map(f)` is `[.[] | f]` — it + builds an array per page, so `--paginate` emits one array document per page unless a trailing + `| .[]` re-flattens. The carve-out now names `select` and `.[] | f` as safe and calls out + `map(f) | .[]` as the safe `map` form. + ## [0.51.12] ### Fixed diff --git a/plugins/source-control/skills/pull-request/reference/readiness.md b/plugins/source-control/skills/pull-request/reference/readiness.md index d6dad928bc..8030481365 100644 --- a/plugins/source-control/skills/pull-request/reference/readiness.md +++ b/plugins/source-control/skills/pull-request/reference/readiness.md @@ -56,7 +56,7 @@ Every gate below reads a GitHub list endpoint, and every one of those endpoints **2. Never pair a positional index with a list.** `.[-1]` on a truncated list is the 30th-oldest item, not the newest — the read returns a real item, plausibly shaped, and simply wrong. On issue #657 (33 comments) `.[-1]` unpaginated returned a comment 11.5 hours older than the actual latest. Select by the property you actually care about (an id, a SHA, an author, a timestamp) so the query states its own intent and cannot be silently satisfied by the wrong record. **Where the query is a control gate you will act on — "did my write land?" — one property is usually not enough.** Ask what else could satisfy this selector, and constrain that too: a SHA in a comment body proves the SHA was mentioned, not that *you* posted it, so a reviewer quoting it passes the gate while your failed write goes unnoticed. Pin the identity as well. -**3. Never reduce across pages inside `--jq`.** With `--paginate`, `gh` applies `--jq` to **each page separately**, so `length`, `sort_by`, `add`, `max`, `group_by` — anything that folds a whole list — silently answers per page. A count over four pages prints four numbers, none of them the total; a `sort_by` emits four separately-sorted arrays. Element-wise filters (`select`, `map` over `.[]`) are safe, because their results simply concatenate. When the operation folds, drop `--jq` and slurp the page stream with `jq -s`, indexing pages with `.[][]`. +**3. Never reduce across pages inside `--jq`.** With `--paginate`, `gh` applies `--jq` to **each page separately**, so `length`, `sort_by`, `add`, `max`, `group_by` — anything that folds a whole list — silently answers per page. A count over four pages prints four numbers, none of them the total; a `sort_by` emits four separately-sorted arrays. Element-wise filters (`select`, `.[] | f`) are safe, because their results simply concatenate. Bare `map(f)` is not — it builds an array per page; use `map(f) | .[]` or `.[] | f` instead. When the operation folds, drop `--jq` and slurp the page stream with `jq -s`, indexing pages with `.[][]`. Pagination alone only moves the cliff from 30 to 100, so where an endpoint reports a total, assert against it — slurping per rule 3: From b4fd2f45770b5549f4659d97b5b12e4dc78959d3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 12 Aug 2026 02:25:45 +0000 Subject: [PATCH 2/3] fix(source-control): qualify bare select(f) in rule 3 carve-out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bare select(f) on a paginated page errors in jq; only .[] | select(f) is element-wise-safe. Bump 0.51.13 → 0.51.14 per review feedback. Co-authored-by: Kyle Sexton --- plugins/source-control/.claude-plugin/plugin.json | 2 +- .../source-control/skills/pull-request/reference/readiness.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/source-control/.claude-plugin/plugin.json b/plugins/source-control/.claude-plugin/plugin.json index 1114a51995..789850a95b 100644 --- a/plugins/source-control/.claude-plugin/plugin.json +++ b/plugins/source-control/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "source-control", - "version": "0.51.13", + "version": "0.51.14", "description": "Git and GitHub delivery workflow: /commit (Conventional Commits + Co-Authored-By trailer via safe heredoc mechanics), /pull-request (prep, create, CI monitoring, review-comment triage, merge, CI-log fetch), /babysit-prs (self-pacing fleet loop — safe by default; opt-in worker/autopilot tiers add gate-checked merge and thread resolution behind a deterministic Python engine), /babysit-loop (the loop-lane merge lane: a standing or drain loop that invokes babysit-prs per cycle, configured through repo-scoped babysit_loop_* keys on the layered source-control.md seam, with merge authority human-only until the target repo's tracked config adopts the lane, a gate-proven C2-mechanical baseline once adopted, and standing merge-rung raises binding from the team-tracked layer only — with one named exception, where an invocation line explicitly typing both the autopilot tier keyword and the dedicated raise argument --merge c3-this-run widens that single invocation's merge authority up to C3 behind a fresh independent frontier-tier resolver, while C4-structural and C5-untrusted-provenance stay unconditionally human-merge), /worktree (create, status, cleanup, audit for parallel-session isolation), /setup (check the effective commit-subject / PR-title convention merged across its config layers and the babysit-prs config, or apply — interview the repo and write the convention config to a chosen layer), and /resolve-conflicts (intent-first merge/rebase conflict resolution with a semantic-conflict sweep — never --abort). The commit-subject / PR-title convention is configurable via a source-control.md config written by a re-runnable setup skill, layered across a ~/.claude user-global file, the tracked team file, and a gitignored .claude/source-control.local.md personal overlay merged per key; Conventional Commits is the default when no convention is declared.", "author": { "name": "Melodic Software", diff --git a/plugins/source-control/skills/pull-request/reference/readiness.md b/plugins/source-control/skills/pull-request/reference/readiness.md index 8030481365..05465d5e42 100644 --- a/plugins/source-control/skills/pull-request/reference/readiness.md +++ b/plugins/source-control/skills/pull-request/reference/readiness.md @@ -56,7 +56,7 @@ Every gate below reads a GitHub list endpoint, and every one of those endpoints **2. Never pair a positional index with a list.** `.[-1]` on a truncated list is the 30th-oldest item, not the newest — the read returns a real item, plausibly shaped, and simply wrong. On issue #657 (33 comments) `.[-1]` unpaginated returned a comment 11.5 hours older than the actual latest. Select by the property you actually care about (an id, a SHA, an author, a timestamp) so the query states its own intent and cannot be silently satisfied by the wrong record. **Where the query is a control gate you will act on — "did my write land?" — one property is usually not enough.** Ask what else could satisfy this selector, and constrain that too: a SHA in a comment body proves the SHA was mentioned, not that *you* posted it, so a reviewer quoting it passes the gate while your failed write goes unnoticed. Pin the identity as well. -**3. Never reduce across pages inside `--jq`.** With `--paginate`, `gh` applies `--jq` to **each page separately**, so `length`, `sort_by`, `add`, `max`, `group_by` — anything that folds a whole list — silently answers per page. A count over four pages prints four numbers, none of them the total; a `sort_by` emits four separately-sorted arrays. Element-wise filters (`select`, `.[] | f`) are safe, because their results simply concatenate. Bare `map(f)` is not — it builds an array per page; use `map(f) | .[]` or `.[] | f` instead. When the operation folds, drop `--jq` and slurp the page stream with `jq -s`, indexing pages with `.[][]`. +**3. Never reduce across pages inside `--jq`.** With `--paginate`, `gh` applies `--jq` to **each page separately**, so `length`, `sort_by`, `add`, `max`, `group_by` — anything that folds a whole list — silently answers per page. A count over four pages prints four numbers, none of them the total; a `sort_by` emits four separately-sorted arrays. Element-wise filters (`.[] | select(f)`, `.[] | f`) are safe, because their results simply concatenate. Bare `map(f)` is not — it builds an array per page; use `map(f) | .[]` or `.[] | f` instead. When the operation folds, drop `--jq` and slurp the page stream with `jq -s`, indexing pages with `.[][]`. Pagination alone only moves the cliff from 30 to 100, so where an endpoint reports a total, assert against it — slurping per rule 3: From 4c26cfc22f39f316325fba203b7f8ff697678998 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 12 Aug 2026 02:25:57 +0000 Subject: [PATCH 3/3] chore(source-control): add 0.51.14 changelog entry for select carve-out Co-authored-by: Kyle Sexton --- plugins/source-control/CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/source-control/CHANGELOG.md b/plugins/source-control/CHANGELOG.md index 1d555f4deb..c27ece63df 100644 --- a/plugins/source-control/CHANGELOG.md +++ b/plugins/source-control/CHANGELOG.md @@ -3,13 +3,21 @@ All notable changes to the `source-control` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.51.14] + +### Fixed + +- **Rule 3 no longer lists bare `select(f)` as element-wise-safe.** Bare `select(f)` applied to a + paginated page (an array) errors in jq rather than filtering elements; only `.[] | select(f)` is + safe. Qualified alongside the existing `map(f)` carve-out fix from 0.51.13. + ## [0.51.13] ### Fixed - **Rule 3 no longer lists bare `map(f)` as element-wise-safe (#2245).** `map(f)` is `[.[] | f]` — it builds an array per page, so `--paginate` emits one array document per page unless a trailing - `| .[]` re-flattens. The carve-out now names `select` and `.[] | f` as safe and calls out + `| .[]` re-flattens. The carve-out now names `.[] | select(f)` and `.[] | f` as safe and calls out `map(f) | .[]` as the safe `map` form. ## [0.51.12]