From 830b24f15fb1c1908f1304ece253b2a9a21c5873 Mon Sep 17 00:00:00 2001 From: Remi Antczak <140408771+remigiusz-antczak@users.noreply.github.com> Date: Sat, 31 Oct 2026 06:42:00 +0100 Subject: [PATCH] release: 1.630.0 (stamped at land time) Co-Authored-By: Claude Opus 5.5 --- .claude-plugin/plugin.json | 2 +- .claude/skills/agentic-delivery/SKILL.md | 2 +- .claude/skills/agentic-delivery/VERSION | 2 +- .claude/skills/deep-code-review/SKILL.md | 2 +- .claude/skills/deep-code-review/VERSION | 2 +- .claude/skills/idea-critic/SKILL.md | 2 +- .claude/skills/idea-critic/VERSION | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 13 +++++++++++++ SHA256SUMS | 14 +++++++------- changelog.d/perpr-shard-balance.md | 4 ---- changelog.d/semgrep-taint-extended-rules.md | 5 ----- gemini-extension.json | 2 +- 13 files changed, 29 insertions(+), 25 deletions(-) delete mode 100644 changelog.d/perpr-shard-balance.md delete mode 100644 changelog.d/semgrep-taint-extended-rules.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4b1d2356b..d9dacd86e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "perun", "description": "Perun: a suite of skills for building with AI agents. Deep code review, token and cost tools (spend reports, auto-compact with handoff, model-tier guidance), gated agentic delivery (merge train, lanes, destructive-command guard, auto-update), an idea critic, and product, growth, positioning, business-ops and output-safety specialists.", "skills": "./.claude/skills", - "version": "1.629.0", + "version": "1.630.0", "author": { "name": "deep-code-review contributors" }, diff --git a/.claude/skills/agentic-delivery/SKILL.md b/.claude/skills/agentic-delivery/SKILL.md index 1ceacd82f..02f6aeff5 100644 --- a/.claude/skills/agentic-delivery/SKILL.md +++ b/.claude/skills/agentic-delivery/SKILL.md @@ -8,7 +8,7 @@ description: >- license: MIT metadata: author: deep-code-review contributors - version: "1.629.0" + version: "1.630.0" --- # Agentic delivery diff --git a/.claude/skills/agentic-delivery/VERSION b/.claude/skills/agentic-delivery/VERSION index 629194afd..5d053cd9e 100644 --- a/.claude/skills/agentic-delivery/VERSION +++ b/.claude/skills/agentic-delivery/VERSION @@ -1 +1 @@ -1.629.0 +1.630.0 diff --git a/.claude/skills/deep-code-review/SKILL.md b/.claude/skills/deep-code-review/SKILL.md index 57420e92c..e92e0eb8e 100644 --- a/.claude/skills/deep-code-review/SKILL.md +++ b/.claude/skills/deep-code-review/SKILL.md @@ -8,7 +8,7 @@ description: >- license: MIT metadata: author: deep-code-review contributors - version: "1.629.0" + version: "1.630.0" --- # Deep Code Review diff --git a/.claude/skills/deep-code-review/VERSION b/.claude/skills/deep-code-review/VERSION index 629194afd..5d053cd9e 100644 --- a/.claude/skills/deep-code-review/VERSION +++ b/.claude/skills/deep-code-review/VERSION @@ -1 +1 @@ -1.629.0 +1.630.0 diff --git a/.claude/skills/idea-critic/SKILL.md b/.claude/skills/idea-critic/SKILL.md index 5e487f7a6..61cd89ea4 100644 --- a/.claude/skills/idea-critic/SKILL.md +++ b/.claude/skills/idea-critic/SKILL.md @@ -8,7 +8,7 @@ description: >- license: MIT metadata: author: deep-code-review contributors - version: "1.629.0" + version: "1.630.0" --- # Veles: the evil twin (idea-critic) diff --git a/.claude/skills/idea-critic/VERSION b/.claude/skills/idea-critic/VERSION index 629194afd..5d053cd9e 100644 --- a/.claude/skills/idea-critic/VERSION +++ b/.claude/skills/idea-critic/VERSION @@ -1 +1 @@ -1.629.0 +1.630.0 diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 8d8dff67b..86e2b7e82 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.629.0", + "version": "1.630.0", "description": "Perun: a skill suite for AI-agent teams: deep code review, token and cost tools, gated delivery, an idea critic and product specialists.", "skills": "./.claude/skills", "interface": { diff --git a/CHANGELOG.md b/CHANGELOG.md index efb6ad540..30604a221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this repository are documented here. Format loosely follows Keep a Changelog; versioning follows Semantic Versioning. +## [1.630.0] — 2026-10-31 + +### Changed +- The CI `perpr` job now runs four legs instead of three (`shard: [1, 2, 3, 4]`, `SHARD=i/4`), and its legs are balanced by recorded test time instead of by sorted position. `scripts/ci-tests-shard.sh` reads `scripts/ci-durations.tsv` (whole seconds per `scripts/ci-tests.d/*.sh` registration: the seconds between consecutive `== ` lines of the `perpr` job logs of public CI run 41148962559 (2026-10-31), rounded, at least 1), takes the serial registrations longest first (ties by name, C locale) and gives each to the least-loaded leg (ties to the lowest index). A registration with no row counts as the table's median, so adding a test never needs a table edit and two PRs never conflict on the table. `*.bg.sh` registrations keep the sorted round-robin, because they run beside the serial ones, so only serial seconds are balanced. The contract is unchanged: the `i/N` slices are disjoint and cover every registration for any N, a malformed `SHARD` exits 2, an empty slice exits 1, the listing is sorted and does not depend on the locale. On the committed table, which counts every test at least 1 s (so these are predictions from the table, not CI measurements), the old helper with three legs on the tree before this change gives legs of 283, 489 and 682 s of serial tests, and the new split over four legs gives 364, 364, 364 and 363 s. +- The `Per-PR tests` step also prints one `bg elapsed: s` line per background test (its wall time from the background start, `?` when it left no end time), because the `== ` lines show only how long the step waited for it, not how long it ran. +- Test: `scripts/test-perpr-shard-balance.sh` (registered as `scripts/ci-tests.d/perpr-shard-balance.sh`) checks the committed table's format, that the slices are disjoint and complete for N = 1 to 5, that the slowest of the legs `ci.yml` runs (the N its `perpr` legs pass in `SHARD=i/N`, read from `ci.yml`) is predicted within 1.1x of the mean (it fails on the old split), and, on a fixture, the exact longest-first assignment, the median default for a malformed or missing row, and the round-robin of background tests. `scripts/test-perpr-shard.sh` no longer asserts that the slices hold nearly equal numbers of registrations, which only the old split guaranteed, and now checks that each leg logs its `bg elapsed:` line. `CONTRIBUTING.md` describes the new split, says when to refresh the table (the slowest leg's `Per-PR tests` step more than 20% over the fastest on a PR run) and gives the awk command that rebuilds its rows from the job logs of the legs; the test runs that command on synthetic logs. + +### Added +- Bundled semgrep rule `perun.js.body-cap-after-buffer` (INFO, in `templates/semgrep-taint/js.yml`): a request body read whole with `await req.formData()`, `.json()`, `.text()` or `.arrayBuffer()` on a receiver named like `req` or `request` (`req`, `request`, `c.req`, `event.request`) and followed later in the same block by a `.size`, `.length` or `.byteLength` comparison against a cap on the result (directly, or on an entry read from it with `.get(...)`), or preceded only by a `content-length` comparison. The body is already in memory when the cap runs, and a declared length does not bound a chunked body. A comparison with the literal `0` is an emptiness check, a read on `res`, `response` or `file` is not a request body, and a counted `getReader()` read is a bounded read: none of them is reported. Test, fixture and mock paths are skipped. +- Bundled semgrep rule `perun.js.limiter-key-forwarded-header` (INFO): an `x-forwarded-for` or `x-real-ip` read (`headers.get(...)`, `headers.header(...)` or `headers[...]`) inside a function named like `clientKey`, `limiterKey`, `rateKey`, `deriveKey` or `getIp`. The message asks the reviewer to check the trusted-hop count, whether a missing header maps every caller onto one shared key, and whether it fails open. Test, fixture and mock paths are skipped. +- `scripts/test_semgrep_taint_ext.py`, registered in `scripts/ci-tests.d/semgrep-taint-ext.sh`, checks these rules with annotated fixtures: a `// ruleid:` comment marks the next line as a required finding, an `// ok:` comment marks it as one that must stay clean, and every unannotated line must stay clean. The live part needs semgrep and prints SKIP without it; it also scans a copy of each fixture under a test path and expects no finding, and loads a copy of the rules with an invalid severity and expects semgrep to reject it, so the schema check of the rule file does not depend on the network. The offline part always runs: severity, languages and message keywords, the skipped paths compared with a sibling rule's list, a `ponytail:` ceiling line in the file header for each rule, the whole pattern skeleton of each rule (every shape and the tags that join them) compared with the rule text, and replays of the rules' own regexes on positive and negative values. +- Known ceilings, also recorded as `ponytail:` lines in the `js.yml` header. `perun.js.limiter-key-forwarded-header` follows the function name, so it misses a differently named function (`getClientIp`, for one) and reports any function whose name matches. `perun.js.body-cap-after-buffer` matches the receiver by name, so it misses a request bound to another name (`r`) or read through a call (`req.clone().json()`). It recognises only `>` comparisons with the result on the left, does not tie a body bound by destructuring or read inline to a later check, does not match a `?.` or `!` on the result or on its `.get(...)` entry (`body?.length`, `body!.length`, `form.get("file")!.size`), does not count a deeper field path such as `body.items.length` or a read and a check in different blocks, and cannot see a cap enforced in middleware or a framework option. + ## [1.629.0] — 2026-10-31 ### Fixed diff --git a/SHA256SUMS b/SHA256SUMS index 75100d0c5..bcbf56a36 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -20,8 +20,8 @@ ef9e423362b12d0dd73c8e165faf32e9f53c4d4ac215b57c2e34002a88713838 .claude/skills 9521b057ff3db92d1e60923ebd88170713914f7e235ceb45717eb39419daf811 .claude/skills/agentic-ceo/scripts/weekly_receipt.py 7e2179751e9dd2fb371b3e9fb01fca987d69605af1e8e6e609ff9b0d0abb384b .claude/skills/agentic-delivery/INDEX-scripts.md 47fff02dcb4401d5a9b42b2b3b7f7c123125eece55e7f9adee8cfe488c70a146 .claude/skills/agentic-delivery/INDEX.md -0908eb84e129f09d35b869fc2f4829dae514f493c0e9a35d560be0b0d88fc3b0 .claude/skills/agentic-delivery/SKILL.md -c3928436efc2dcc3d67d251cec0e86098547dd4f135a3e1273bc4725d5499b84 .claude/skills/agentic-delivery/VERSION +6858279bbeebfc7fb7c0df3c058378aa4505dd4b8f035567dc1bfab93bcbebd0 .claude/skills/agentic-delivery/SKILL.md +b78778d02b1ed21cf6001646fbc0fc5b212475318c3c619a05cae2d39aadf46e .claude/skills/agentic-delivery/VERSION 94144c101f82399eb6c18f688321a00e1c195dd0ab334f3a5daa2543b40ff9f5 .claude/skills/agentic-delivery/evals/evals.json fba7699a2d0836b51b7c838d019b1f0cd20570522a362e8a32b8dacfb911298e .claude/skills/agentic-delivery/evals/triggers.json 39140ac0b40907dc2279bb3234ce1d8ffb4db56e8658c7f89cc370f777df0592 .claude/skills/agentic-delivery/references/cost-quality-guardrails.md @@ -206,8 +206,8 @@ d98f3e4380da3496061ba63be7beebe75017ab4ee1734eda9a3e798e19007b0e .claude/skills 18f20db537ab784460ad33cf218f7a01ac0a172fc38413f14c0e19e1ce6821cc .claude/skills/contribution/scripts/share_learning.py 57ab71eae0e846ea4e5e8287da26679a5a010cf576fed89ca37c94e0c900180f .claude/skills/deep-code-review/INDEX-scripts.md 9fcb6a002fb7e3fe28163199783968f80b920744c83280cff211a348215c9c0d .claude/skills/deep-code-review/INDEX.md -a3d823187b8bab01cc9345102057b858d7eaffa9246dfa83521e3636a0dbc152 .claude/skills/deep-code-review/SKILL.md -c3928436efc2dcc3d67d251cec0e86098547dd4f135a3e1273bc4725d5499b84 .claude/skills/deep-code-review/VERSION +38f7641cd69b145af32440d34daf29a52272bef4313da5d9483eb31a9ff7c61b .claude/skills/deep-code-review/SKILL.md +b78778d02b1ed21cf6001646fbc0fc5b212475318c3c619a05cae2d39aadf46e .claude/skills/deep-code-review/VERSION b97d95bc93cc23a8bc689e2e23db261a138367bcbc69d2f8a0452d5c2c6dfb68 .claude/skills/deep-code-review/evals/evals.json 13dbb52e4bf61b1093302e094151def167371d26e7cf67a026f3beeb09d34378 .claude/skills/deep-code-review/evals/triggers.json b84e3a2b88dc189f041784e76b97e68ad879915814e00f4b37cd27513746d6f1 .claude/skills/deep-code-review/references/a11y-aria.md @@ -416,7 +416,7 @@ f31e66a322ee172e4b9621c3b70717a6e896ee64178f0e8ea77ac2088b6d0cb6 .claude/skills d84df8bbfb7e87be95446e91151f1badcc85797c07d3cb574be00c66b4f6b7c4 .claude/skills/deep-code-review/templates/perun-review.yml 429b66e715e660931543b71e346f60df85dfa8df70d6caa409f51958f4202acd .claude/skills/deep-code-review/templates/pre-push-verify.sh a82701e4893739b8451bcfab0fe6df01e130f1659cd9b4c3780f1461c874d698 .claude/skills/deep-code-review/templates/semgrep-taint/gha.yml -b43f67489d710551171428fce62e4f129839361f5da3dda5d62d6d3523ec365c .claude/skills/deep-code-review/templates/semgrep-taint/js.yml +1470877bc6329921c1eebcd763201f9198e47b6f97670a9d42eb8dc281b99cd8 .claude/skills/deep-code-review/templates/semgrep-taint/js.yml 7bdab757847b5a5ce2323e4fb06905e4446919196f5417c25511ac279b4a1e0e .claude/skills/deep-code-review/templates/semgrep-taint/py.yml 29ee3a47eb7973011cdfe1fb4321aac89fc0fe8f98776505df70a6d6a45cbf0f .claude/skills/growth-analytics/INDEX.md 4fe9fb6176818c279ddce11bde1aa97157800190075ba79604c174a5d9a7c59c .claude/skills/growth-analytics/SKILL.md @@ -426,8 +426,8 @@ e951eaa63fd0d1870701d69c89c29ef4fe74dbad9b680ab6ef0a4e993fb8bc20 .claude/skills fd316c63bbf1cc0ea64322d7d923d66692eaf7b545c056367bfecca35146c862 .claude/skills/growth-analytics/references/full-map.md 2f1d7499e9ed7ae59f550b5c00043e465b2a4e885821ad153438e2e33fcccb45 .claude/skills/growth-analytics/scripts/readout_check.py 67e71ec40535ad0b3931c51f3decf323bce809ed4c9586c6d422b7ad5698129f .claude/skills/idea-critic/INDEX.md -47c6a04bcb63ec82fb918ed8f818b98280333077a344e5757279d65a50868072 .claude/skills/idea-critic/SKILL.md -c3928436efc2dcc3d67d251cec0e86098547dd4f135a3e1273bc4725d5499b84 .claude/skills/idea-critic/VERSION +0d1f64cbd5c26d4ffa9fe1717835090fbd696915688e5bc596c656f6e9585b6c .claude/skills/idea-critic/SKILL.md +b78778d02b1ed21cf6001646fbc0fc5b212475318c3c619a05cae2d39aadf46e .claude/skills/idea-critic/VERSION 9f77d5d6d13c99fd16cd8c398795d8be226d74bae5a5bf65369ed94a59fa56b7 .claude/skills/idea-critic/evals/evals.json 0e1c0db300661a1ab1a427a7157c75e2ed67130ab24d5c6cb02ec3ff9e793f58 .claude/skills/idea-critic/evals/heldout.json d8098c5ced63e8b8608867dd144091049563a0553cf62d27ce05b9d52ac54266 .claude/skills/idea-critic/evals/triggers.json diff --git a/changelog.d/perpr-shard-balance.md b/changelog.d/perpr-shard-balance.md deleted file mode 100644 index dcd67b42a..000000000 --- a/changelog.d/perpr-shard-balance.md +++ /dev/null @@ -1,4 +0,0 @@ -### Changed -- The CI `perpr` job now runs four legs instead of three (`shard: [1, 2, 3, 4]`, `SHARD=i/4`), and its legs are balanced by recorded test time instead of by sorted position. `scripts/ci-tests-shard.sh` reads `scripts/ci-durations.tsv` (whole seconds per `scripts/ci-tests.d/*.sh` registration: the seconds between consecutive `== ` lines of the `perpr` job logs of public CI run 41148962559 (2026-10-31), rounded, at least 1), takes the serial registrations longest first (ties by name, C locale) and gives each to the least-loaded leg (ties to the lowest index). A registration with no row counts as the table's median, so adding a test never needs a table edit and two PRs never conflict on the table. `*.bg.sh` registrations keep the sorted round-robin, because they run beside the serial ones, so only serial seconds are balanced. The contract is unchanged: the `i/N` slices are disjoint and cover every registration for any N, a malformed `SHARD` exits 2, an empty slice exits 1, the listing is sorted and does not depend on the locale. On the committed table, which counts every test at least 1 s (so these are predictions from the table, not CI measurements), the old helper with three legs on the tree before this change gives legs of 283, 489 and 682 s of serial tests, and the new split over four legs gives 364, 364, 364 and 363 s. -- The `Per-PR tests` step also prints one `bg elapsed: s` line per background test (its wall time from the background start, `?` when it left no end time), because the `== ` lines show only how long the step waited for it, not how long it ran. -- Test: `scripts/test-perpr-shard-balance.sh` (registered as `scripts/ci-tests.d/perpr-shard-balance.sh`) checks the committed table's format, that the slices are disjoint and complete for N = 1 to 5, that the slowest of the legs `ci.yml` runs (the N its `perpr` legs pass in `SHARD=i/N`, read from `ci.yml`) is predicted within 1.1x of the mean (it fails on the old split), and, on a fixture, the exact longest-first assignment, the median default for a malformed or missing row, and the round-robin of background tests. `scripts/test-perpr-shard.sh` no longer asserts that the slices hold nearly equal numbers of registrations, which only the old split guaranteed, and now checks that each leg logs its `bg elapsed:` line. `CONTRIBUTING.md` describes the new split, says when to refresh the table (the slowest leg's `Per-PR tests` step more than 20% over the fastest on a PR run) and gives the awk command that rebuilds its rows from the job logs of the legs; the test runs that command on synthetic logs. diff --git a/changelog.d/semgrep-taint-extended-rules.md b/changelog.d/semgrep-taint-extended-rules.md deleted file mode 100644 index 0565bfcdc..000000000 --- a/changelog.d/semgrep-taint-extended-rules.md +++ /dev/null @@ -1,5 +0,0 @@ -### Added -- Bundled semgrep rule `perun.js.body-cap-after-buffer` (INFO, in `templates/semgrep-taint/js.yml`): a request body read whole with `await req.formData()`, `.json()`, `.text()` or `.arrayBuffer()` on a receiver named like `req` or `request` (`req`, `request`, `c.req`, `event.request`) and followed later in the same block by a `.size`, `.length` or `.byteLength` comparison against a cap on the result (directly, or on an entry read from it with `.get(...)`), or preceded only by a `content-length` comparison. The body is already in memory when the cap runs, and a declared length does not bound a chunked body. A comparison with the literal `0` is an emptiness check, a read on `res`, `response` or `file` is not a request body, and a counted `getReader()` read is a bounded read: none of them is reported. Test, fixture and mock paths are skipped. -- Bundled semgrep rule `perun.js.limiter-key-forwarded-header` (INFO): an `x-forwarded-for` or `x-real-ip` read (`headers.get(...)`, `headers.header(...)` or `headers[...]`) inside a function named like `clientKey`, `limiterKey`, `rateKey`, `deriveKey` or `getIp`. The message asks the reviewer to check the trusted-hop count, whether a missing header maps every caller onto one shared key, and whether it fails open. Test, fixture and mock paths are skipped. -- `scripts/test_semgrep_taint_ext.py`, registered in `scripts/ci-tests.d/semgrep-taint-ext.sh`, checks these rules with annotated fixtures: a `// ruleid:` comment marks the next line as a required finding, an `// ok:` comment marks it as one that must stay clean, and every unannotated line must stay clean. The live part needs semgrep and prints SKIP without it; it also scans a copy of each fixture under a test path and expects no finding, and loads a copy of the rules with an invalid severity and expects semgrep to reject it, so the schema check of the rule file does not depend on the network. The offline part always runs: severity, languages and message keywords, the skipped paths compared with a sibling rule's list, a `ponytail:` ceiling line in the file header for each rule, the whole pattern skeleton of each rule (every shape and the tags that join them) compared with the rule text, and replays of the rules' own regexes on positive and negative values. -- Known ceilings, also recorded as `ponytail:` lines in the `js.yml` header. `perun.js.limiter-key-forwarded-header` follows the function name, so it misses a differently named function (`getClientIp`, for one) and reports any function whose name matches. `perun.js.body-cap-after-buffer` matches the receiver by name, so it misses a request bound to another name (`r`) or read through a call (`req.clone().json()`). It recognises only `>` comparisons with the result on the left, does not tie a body bound by destructuring or read inline to a later check, does not match a `?.` or `!` on the result or on its `.get(...)` entry (`body?.length`, `body!.length`, `form.get("file")!.size`), does not count a deeper field path such as `body.items.length` or a read and a check in different blocks, and cannot see a cap enforced in middleware or a framework option. diff --git a/gemini-extension.json b/gemini-extension.json index c35ba4a5a..9e9ccd443 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.629.0", + "version": "1.630.0", "description": "Perun: a skill suite for AI-agent teams: deep code review, token and cost tools, gated delivery, an idea critic and product specialists.", "contextFileName": "AGENTS.md" }