diff --git a/plugins/claude-ops/.claude-plugin/plugin.json b/plugins/claude-ops/.claude-plugin/plugin.json index 8fddef5778..6dd6acbb16 100644 --- a/plugins/claude-ops/.claude-plugin/plugin.json +++ b/plugins/claude-ops/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-ops", - "version": "0.29.1", + "version": "0.29.2", "description": "Claude Code operations toolkit. Eight skills: audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json \u2014 full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), observability (read locally captured telemetry \u2014 OTEL store, collector, hook-event JSONL, ccusage \u2014 with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand \u2014 marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view \u2014 queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action \u2014 an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.", "author": { "name": "Melodic Software", diff --git a/plugins/claude-ops/CHANGELOG.md b/plugins/claude-ops/CHANGELOG.md index bf14b2119f..a2a106d089 100644 --- a/plugins/claude-ops/CHANGELOG.md +++ b/plugins/claude-ops/CHANGELOG.md @@ -3,6 +3,29 @@ All notable changes to the `claude-ops` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.29.2] + +### Changed + +- **`skills/lanes/scripts` `--paginate` reads now carry `per_page=100`.** + `restart-consumer.sh`'s telemetry-comment read and `telemetry-upsert.sh`'s comment listing + paginated without a page size — complete, but non-conformant with the published pagination rule + and 3.3x the requests at the 30-item default. No behavior change: both folds are page-shape + agnostic. `telemetry-upsert.test.sh`'s `gh` stub matched the list endpoint with an exact `*/comments` + suffix, which the query string would have fallen through silently; it now matches the query form + explicitly. + +### Fixed + +- **`telemetry-upsert.sh`'s slurp rationale no longer misdescribes `gh --paginate`.** The comment + above the comment listing claimed `--paginate` "concatenates one JSON array per page". It does + not: with no `--jq`, `gh` merges array-shaped pages into ONE array, so `jq -s 'add'` unwraps a + one-element slurp rather than concatenating. `--paginate` is still load-bearing (it is what makes + a page-2 comment visible at all) and `add` is still correct — but for a different reason than the + comment gave, and a reader trusting it would mispredict the next endpoint's shape. Same correction + applied to the pagination fixture's header comment in `telemetry-upsert.test.sh`. Measured against + `gh` 2.95.0. + ## [0.29.1] ### Changed diff --git a/plugins/claude-ops/skills/lanes/scripts/restart-consumer.sh b/plugins/claude-ops/skills/lanes/scripts/restart-consumer.sh index cb8eea2ee2..8f6e25be69 100755 --- a/plugins/claude-ops/skills/lanes/scripts/restart-consumer.sh +++ b/plugins/claude-ops/skills/lanes/scripts/restart-consumer.sh @@ -771,7 +771,7 @@ lane_comment_bodies() { jq -c --arg l "$lane" '[ (.[$l] // [])[] | .body // "" ]' "$TELEMETRY_JSON_FILE" 2>/dev/null || return 1 return 0 fi - raw="$(gh api --paginate "repos/$repo/issues/$issue/comments" -q '.[] | {body}' 2>/dev/null)" || return 1 + raw="$(gh api --paginate "repos/$repo/issues/$issue/comments?per_page=100" -q '.[] | {body}' 2>/dev/null)" || return 1 printf '%s' "$raw" | jq -s -c '[ .[].body // "" ]' 2>/dev/null || return 1 } diff --git a/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.sh b/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.sh index 4e715428b6..0492b155a2 100755 --- a/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.sh +++ b/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.sh @@ -337,11 +337,14 @@ fi # --- List existing comments (paginated, raw) --------------------------------- # Fetch raw JSON and select in-script rather than via `gh --jq`, so the whole -# two-tier detection is one auditable jq program. --paginate concatenates one -# JSON array per page; `jq -s 'add'` slurps them into a single array — without -# it, an existing comment on page 2 of a busy tracking issue is invisible and we +# two-tier detection is one auditable jq program. With no `--jq`, `gh` merges +# array-shaped pages into ONE array, so `jq -s 'add'` unwraps a one-element +# slurp rather than concatenating pages — but it is not optional: supplying +# `--jq` would suppress that merge and restore per-page emission, and `add` +# keeps this correct under either shape. `--paginate` itself is what makes an +# existing comment on page 2 of a busy tracking issue visible; without it we # would POST a duplicate, the exact failure this script exists to prevent. -raw_pages="$(gh api --paginate "repos/$REPO/issues/$ISSUE/comments" 2>/dev/null)" || { +raw_pages="$(gh api --paginate "repos/$REPO/issues/$ISSUE/comments?per_page=100" 2>/dev/null)" || { err "failed to list comments on $REPO#$ISSUE (gh api)" exit 5 } diff --git a/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.test.sh b/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.test.sh index ed867060e4..c4370c845f 100755 --- a/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.test.sh +++ b/plugins/claude-ops/skills/lanes/scripts/telemetry-upsert.test.sh @@ -142,8 +142,9 @@ if [[ "$url" == */issues/comments/* ]]; then fi # List comments (GET, --paginate): serve the fixture verbatim. Logged like every # other call so a pre-write rejection can assert an EMPTY log — proving zero API -# calls, not merely zero mutations. -if [[ "$url" == */comments ]]; then +# calls, not merely zero mutations. The query string is matched explicitly rather +# than with a bare `?` glob, which would also swallow `/commentsX`. +if [[ "$url" == */comments || "$url" == */comments'?'* ]]; then printf 'CALL method=GET url=%s\n' "$url" >>"$STUB_LOG" cat "${STUB_COMMENTS_FILE:-/dev/null}" exit 0 @@ -293,7 +294,9 @@ assert_eq "instance-suffixed marker is a valid --marker" 0 "$rc" # ============================================================================ # pagination — sentinel comment on a SECOND page is still found (no duplicate) -# --paginate concatenates one array per page; the script slurps them. +# Real `gh --paginate` merges array pages into one array, but the script's +# `jq -s 'add'` is correct under either shape, so the fixture feeds it the +# harder one: a page-per-document stream. # ============================================================================ cat >"$TMP/paged.json" <//commits//check-runs?per_page=100" \ | jq -s -r '"total_count=\(.[0].total_count) returned=\([.[].check_runs[]] | length)"' ``` -`/annotations` is shaped differently — a bare JSON array with no envelope and no `total_count` — so the assertion above is not available there and `--paginate` is the only guard. Concatenated pages are arrays, so they are combined with `add`, not by reaching through a wrapper: +`/annotations` is shaped differently — a bare JSON array with no envelope and no `total_count` — so the assertion above is not available there and `--paginate` is the only guard. With no `--jq`, `gh` merges array-shaped pages into **one** JSON array, emitting a document per page only for object envelopes like `check-runs` — so `jq -s` here yields a one-element slurp and `add` unwraps it rather than concatenating pages. Supplying `--jq` suppresses that merge and restores per-page emission, which is why the per-page caveat above still governs any reduction pushed into the filter: ```bash gh api --paginate "repos///check-runs//annotations?per_page=100" \ diff --git a/plugins/work-items/.claude-plugin/plugin.json b/plugins/work-items/.claude-plugin/plugin.json index 73ae2923f3..888f4abe7e 100644 --- a/plugins/work-items/.claude-plugin/plugin.json +++ b/plugins/work-items/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "work-items", - "version": "0.35.2", + "version": "0.35.3", "description": "Manages development work items through a provider-neutral tracker seam that ships with the plugin (bundled dispatcher plus github and local-markdown adapters; seam plugin-dir canonical, adapters consumer-local-first): dashboard, taxonomy-labeled creation, a race-safe assignee-plus-lease claim protocol, recurring-schedule checks, TODO scanning, stale-lease auditing, plan decomposition into vertical-slice items, raw-intake triage (issues and unsolicited PRs through raw, verified, briefed, autonomous-eligible states), plus the two work-items loop lanes of the loop-lane convention: a self-paced autonomous work-loop drain (work-class admission gate, adaptive item cap, PR-only) and an attended attend-queue escalation lane. The re-runnable setup skill binds the provider (.work-item-tracker.json), seeds the recurring-schedule seam (.github/recurring-schedule.json), and remaps canonical role labels.", "author": { "name": "Melodic Software", diff --git a/plugins/work-items/CHANGELOG.md b/plugins/work-items/CHANGELOG.md index 6cb3c6ffd9..a5f648b089 100644 --- a/plugins/work-items/CHANGELOG.md +++ b/plugins/work-items/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to the `work-items` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.35.3] + +### Changed + +- **Every `--paginate` list read now carries `per_page=100`.** `skills/attend-queue/SKILL.md`, + `skills/work-loop/reference/telemetry-upsert.md`, + `tools/work-item-tracker/adapters/github/common.sh` (`wit_list_lease_comments`), and + `tools/work-item-tracker/adapters/github/reclaim.sh` (comment activity and timeline + cross-references) paginated without a page size. These were not truncation defects — `--paginate` + fetches every page regardless — but they were non-conformant with the pagination rule + `source-control:pull-request`'s readiness reference publishes, and at the 30-item default they + cost 3.3x the requests. No behavior change: each site's downstream fold (`jq -s 'add // []'` / + `'add // 0'`) sums or concatenates per-page results, and `gh` applies `--jq` per page under either + page size, so the same value is produced from fewer pages. + ## [0.35.2] ### Fixed diff --git a/plugins/work-items/skills/attend-queue/SKILL.md b/plugins/work-items/skills/attend-queue/SKILL.md index 01a8209d3c..4353aa5a66 100644 --- a/plugins/work-items/skills/attend-queue/SKILL.md +++ b/plugins/work-items/skills/attend-queue/SKILL.md @@ -133,7 +133,7 @@ esac } MARKER="work-items:attend-queue@$INSTANCE" SENT="" # $BODY_FILE MUST open with this line -LOOKUP() { gh api --paginate "repos/$REPO/issues/$ISSUE/comments" \ +LOOKUP() { gh api --paginate "repos/$REPO/issues/$ISSUE/comments?per_page=100" \ --jq ".[] | select(.body | startswith(\"$SENT\")) | .id"; } SENTINEL_OK() { # $1 = text; true iff line 1 is exactly $SENT and >=16 payload bytes follow [ "$(printf '%s' "$1" | head -c ${#SENT})" = "$SENT" ] && diff --git a/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md b/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md index 832c315cf5..b32a2aa0bf 100644 --- a/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md +++ b/plugins/work-items/skills/work-loop/reference/telemetry-upsert.md @@ -40,7 +40,7 @@ that cannot produce a conforming id stops the lane rather than yielding a marker ```bash MARKER="work-items:work-loop@$INSTANCE" SENT="" # $BODY_FILE MUST open with this line -LOOKUP() { gh api --paginate "repos/$REPO/issues/$ISSUE/comments" \ +LOOKUP() { gh api --paginate "repos/$REPO/issues/$ISSUE/comments?per_page=100" \ --jq ".[] | select(.body | startswith(\"$SENT\")) | .id"; } SENTINEL_OK() { # $1 = text; true iff line 1 is exactly $SENT and >=16 payload bytes follow [ "$(printf '%s' "$1" | head -c ${#SENT})" = "$SENT" ] && diff --git a/plugins/work-items/tools/work-item-tracker/adapters/github/common.sh b/plugins/work-items/tools/work-item-tracker/adapters/github/common.sh index e65be8a997..b73a34aa5d 100644 --- a/plugins/work-items/tools/work-item-tracker/adapters/github/common.sh +++ b/plugins/work-items/tools/work-item-tracker/adapters/github/common.sh @@ -186,7 +186,7 @@ wit_emit_item() { # {id, node_id, body, created_at} for lease-marker comments, ascending id. wit_list_lease_comments() { local owner="$1" repo="$2" number="$3" - wit_run_gh read api --paginate "repos/$owner/$repo/issues/$number/comments" \ + wit_run_gh read api --paginate "repos/$owner/$repo/issues/$number/comments?per_page=100" \ --jq '[.[] | select(.body | startswith("