From fe008ce39a7a277d71f202789995fe25328a484e Mon Sep 17 00:00:00 2001 From: Remi Antczak <140408771+remigiusz-antczak@users.noreply.github.com> Date: Fri, 30 Oct 2026 16:07:56 +0100 Subject: [PATCH] release: 1.626.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 | 40 +++++++++++++++++++ SHA256SUMS | 12 +++--- changelog.d/perf-tools-hyperfine-k6.md | 7 ---- .../pipeline-hub-duplicate-precision.md | 2 - changelog.d/pipeline-hub-skip-failed-run.md | 2 - changelog.d/review-checks-guarddog.md | 9 ----- changelog.d/semgrep-gha-actor-allowlist.md | 5 --- changelog.d/sibling-sweep.md | 7 ---- gemini-extension.json | 2 +- 17 files changed, 55 insertions(+), 47 deletions(-) delete mode 100644 changelog.d/perf-tools-hyperfine-k6.md delete mode 100644 changelog.d/pipeline-hub-duplicate-precision.md delete mode 100644 changelog.d/pipeline-hub-skip-failed-run.md delete mode 100644 changelog.d/review-checks-guarddog.md delete mode 100644 changelog.d/semgrep-gha-actor-allowlist.md delete mode 100644 changelog.d/sibling-sweep.md diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 79a790ff4..5cd377674 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.625.0", + "version": "1.626.0", "author": { "name": "deep-code-review contributors" }, diff --git a/.claude/skills/agentic-delivery/SKILL.md b/.claude/skills/agentic-delivery/SKILL.md index 8b321836f..9c3ac6964 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.625.0" + version: "1.626.0" --- # Agentic delivery diff --git a/.claude/skills/agentic-delivery/VERSION b/.claude/skills/agentic-delivery/VERSION index 1bb512367..575f722ed 100644 --- a/.claude/skills/agentic-delivery/VERSION +++ b/.claude/skills/agentic-delivery/VERSION @@ -1 +1 @@ -1.625.0 +1.626.0 diff --git a/.claude/skills/deep-code-review/SKILL.md b/.claude/skills/deep-code-review/SKILL.md index 02c145f9f..64c71c167 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.625.0" + version: "1.626.0" --- # Deep Code Review diff --git a/.claude/skills/deep-code-review/VERSION b/.claude/skills/deep-code-review/VERSION index 1bb512367..575f722ed 100644 --- a/.claude/skills/deep-code-review/VERSION +++ b/.claude/skills/deep-code-review/VERSION @@ -1 +1 @@ -1.625.0 +1.626.0 diff --git a/.claude/skills/idea-critic/SKILL.md b/.claude/skills/idea-critic/SKILL.md index bebe09e62..bed36cf4e 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.625.0" + version: "1.626.0" --- # Veles: the evil twin (idea-critic) diff --git a/.claude/skills/idea-critic/VERSION b/.claude/skills/idea-critic/VERSION index 1bb512367..575f722ed 100644 --- a/.claude/skills/idea-critic/VERSION +++ b/.claude/skills/idea-critic/VERSION @@ -1 +1 @@ -1.625.0 +1.626.0 diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 0b42584d5..c4f8a959c 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.625.0", + "version": "1.626.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 8fed968e7..8d06f4444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,46 @@ All notable changes to this repository are documented here. Format loosely follows Keep a Changelog; versioning follows Semantic Versioning. +## [1.626.0] — 2026-10-30 + +### Added +- `scripts/perf_compare.sh [--runs N] [--base REF] [--json FILE] -- CMD`: a hyperfine A/B of CMD in a detached worktree of the base (default: the merge-base with `origin/main`) against the current tree. It prints one verdict line (means, sd, ratio; `within noise (ranges overlap)` when the mean plus or minus sd ranges overlap) and keeps the raw export in `/perf-compare-last.json`. A missing hyperfine fails visibly (`NOT CHECKED: hyperfine`, exit 1), and the temporary worktree is removed on exit and on signals. `heavy_gate.py` now holds it like the other heavy entrypoints (`scripts/test_heavy_gate_perf.py`); `scripts/test-perf-compare.sh` covers refusals, the verdict math, cleanup and one real hyperfine run (skipped visibly when hyperfine is absent). +- `scripts/app_pentest.sh --load [--vus N] [--secs N] URL`: a k6 smoke run (default 5 VUs for 15 s, hard caps 20 VUs and 60 s, refused rather than clamped) behind the same target rules as the scanners, alone (not with `--active` or `--openapi`). The JSON carries `load` with p50, p95 and error rate; every request failing is status `error`, and a missing k6 is `skipped` with `NOT CHECKED: k6`. `scripts/test-app-pentest-load.sh` uses a fake k6 plus real k6 runs against a local `python3 -m http.server` on 127.0.0.1 (skipped visibly when k6 is absent). All three tests are wired in `scripts/ci-tests.d/perf-tools.sh`. +- `references/tool-hub.md` Perf tools rows (hyperfine and k6, flags verified against `--help` on 2026-10-30), a Load smoke section in `references/app-pentest.md`, and one routed line in `references/performance-latency-depth.md`. +size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 10152→14455 wave total: the guarddog row with its egress and dead-proxy notes and the pinact line, Perf tools rows for hyperfine and k6 with the perf_compare.sh verdict rules, and one row for the bundled GitHub Actions semgrep rule +size-budget-raise: .claude/skills/deep-code-review/references/app-pentest.md 6393→8135 Load smoke section: caps, statuses and what the numbers do not prove +size-budget-raise: .claude/skills/deep-code-review/references/performance-latency-depth.md 13236→13456 one routed line to perf_compare.sh and app_pentest.sh --load + +### Fixed +- `pipeline_hub.py` duplicate checks no longer call two different gates a duplicate and no longer print a saving they did not measure. A duplicate is now the same normalized command (script and arguments) in two enforced CI steps. The same script run with different arguments in each step, such as one gate run with `--changed` in one step and `--all` in another, is listed as an advisory (`same suite, different arguments: check one covers the other before dropping`) with no drop advice and no saving; before, both steps were reported as the same suite and the recommendation said to drop one. The hub also stopped printing `saves about X per run (even split over its step's suites)`: the API times whole steps, never one suite, so that figure was the step's median divided by the number of suites in it. A saving is printed only for a step that runs that one command, and then as an upper bound (`saves at most about X per run (its step's median)`); for any other step the recommendation says the suite's own time is unmeasured and to time it locally. The JSON `duplicates` entries gain `identical` (false for an advisory), and `step_p50_s` is null for a step that runs several commands, so `saved_s` sums only the measured copies. Tests: `scripts/test_pipeline_hub.py`. + +### Fixed +- `pipeline_hub.py` stopped collecting at the first failed call, so one run whose jobs endpoint always answers HTTP 502 left most sampled runs and every PR uncollected, and the note read `collection stopped: }.`. A failed jobs or attempts call for one run is now named once in the notes (run id, API message and HTTP status) and collection goes on with the next run and the merged-PR pages; a failed runs-list or pulls call still stops, and resuming with the same `--data` file works as before. The error text now takes the API's JSON `message` or gh's ` (HTTP nnn)` line instead of the last line of the reply. + +### Added +- `review_checks.sh` now routes guarddog 3.2.0 as an offline supply-chain heuristic step. With a base, it reads every changed `package-lock.json`, finds the npm packages the lock newly adds (a name@version pair absent from the merge-base lock, so a version bump counts as new) and runs `guarddog npm scan --output-format json DIR` on each one that is installed under `node_modules` inside the repo at the locked version. The installed `package.json` must say so: a lock-only bump leaves the old code on disk, and scanning it would read as clean, so that package is counted as not installed at the locked version. Packages whose lock entry has `hasInstallScript` go first. At most 25 are scanned, within a total budget of about 300 s (`REVIEW_GUARDDOG_BUDGET`, in seconds; each scan also keeps the usual 60 s timebox), and every package left out is named in a `not-checked` row. The step runs only when `guarddog --version` says 3.x (the scan JSON was checked on 3.2.0); any other answer is one named `not-checked` row. +- Leads and gaps. Each `threat-*` rule that hits becomes one lead per package and rule (first location and hit count), tagged `[heuristic]`. `capability-*` rules fire on benign packages and stay quiet, and code or match snippets are never read. A rule that errors (guarddog lists it under `errors`) costs only that rule: the other hits stay and a `not-checked` row names the failed rule. Output with no `threat-*` rule at all is not-run. A lock that adds nothing is silent; nothing installed at the locked version, a lockfileVersion 1 lock, an unparseable lock and `--full` (no base) print one `SKIPPED` line each. It never runs `verify` and never fetches a package: guarddog treats a non-directory argument as a registry name, so only existing in-repo directories are passed. +- Control characters never drop a row. A `\r` in a package name, lock key or hit location used to split the row, and the closing summary dropped the pieces silently. Those characters now show as `?`, and a lock entry that cannot be scanned safely (no version, or control characters in its name, version or key) is a named `not-checked` row, never skipped quietly. +- Install hooks. guarddog's install-hook rules (`threat-npm-preinstall-script`, `threat-process-hooks`) match `*/package.json`, a nested path, so they never flag a package's own root `package.json`, where install hooks live (checked on 3.2.0 with fictional fixtures). A newly added lock entry with `hasInstallScript` is therefore a lead of its own (tool `package-lock`, rule `has-install-script`, tagged `[heuristic]`), emitted whether or not guarddog is installed. +- guarddog has no offline flag, and at startup it refreshes its top-package lists from github.com, hugovk.github.io and packages.ecosyste.ms (seen under a dead proxy). The supported opt-out is the environment variable `GUARDDOG_TOP_PACKAGES_CACHE_LOCATION` pointing at a directory of fresh stub lists, which a local scan never reads. The step sets it. When guarddog's own kernel sandbox cannot start (a nested sandbox: its stderr says `Sandbox initialization failed`, matched case-insensitively), the step retries once with `--no-sandbox` and keeps a dead proxy (`HTTP_PROXY` and friends pointing at 127.0.0.1:9) on every later scan, because that sandbox is what otherwise blocks guarddog's network. +- New test `scripts/test-review-checks-guarddog.sh` (68 checks with guarddog installed; 60 checks plus one SKIP line where it is not, as in CI; registered in `scripts/ci-tests.d/review-checks-guarddog.sh`). It runs git with `GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null` and clears the proxy variables. A shim covers found, clean, crash, bad JSON, no threat rule, rule errors, a wrong or failing `--version`, the nested sandbox with its real capitalised message, `--full`, a missing tool, the cap, the time budget and its order, a removal-only lock, nothing installed, a stale install, no lock at base, a v1 lock, a moved base tip, control characters and the `hasInstallScript` leads. The real-binary section is host-only: CI has no guarddog, so it prints one SKIP line there. It uses fictional packages (placeholder names, `example.invalid` hosts) and includes a control proving the no-egress check can fail: an empty-dir scan with an empty cache dir under a dead proxy must print a download error. Against the script from before the guarddog step it fails 47 of 68. +- `tool-hub.md`: a CLI row for guarddog, the egress and dead-proxy lines, and one line saying pinact (`pinact run`) is the fixer for unpinned-action findings; it needs the GitHub API, so never in offline runs. +- Measured on the installed `node_modules` of a real app (2026-10-30, guarddog 3.2.0, `--no-sandbox` because the host sandbox nests; taken before the 3.x version probe, about 0.3 s, was added): 25 newly-added-style packages (evenly spaced by name from the 382 top-level packages without nested dependencies, 6.4 MB) took 15.7 s for the whole `review_checks.sh` run (0.3 s without the step) and produced 0 leads. The largest package alone (next 16.4.3, 192 MB of compiled bundles) took 20.1 s and produced 10 leads, all presumed false positives (heuristic matches in bundled framework code, not audited by hand). Expect leads on large bundled packages; small packages stayed quiet. + +### Added +- One bundled GitHub Actions semgrep rule in `templates/semgrep-taint/gha.yml`, for a workflow that authorizes `github.actor` against an allow-list read from a repository variable: anyone with write access can edit repository variables, so any writer could add themselves. `review_checks.sh` already loads the whole template directory, so it runs in the default review with no wiring change, like the other bundled rules (and, like them, only when the repo has no semgrep rules of its own). +- `perun.gha.actor-allowlist-from-vars` (WARNING; a heuristic lead, so the message starts with `[heuristic]`), scanned only in `.github/workflows/*.yml|yaml` and `action.yml|yaml` (language `regex`, not `yaml`, so a file that fails YAML parsing is still scanned): an actor check (`github.actor`, `github.triggering_actor`, `GITHUB_ACTOR`) fed by `vars.NAME`. Three regexes, comment lines skipped: the variable and the actor are the two operands of one comparison or `contains()` (any variable name), or a variable named like an allow-list (allow, releaser, deployer, approver, admin, maintainer, trusted) and an actor reference sit in one step, in either order. The message says who can edit what (anyone with write access can create, update and delete repository variables, and a repository variable overrides an organization variable of the same name) and names the fix: an environment variable read in a step of a job that declares that environment (creating one needs admin access in an organization repository, and environment variables are only available once the job starts, so not in a job-level `if`), or a code-owned file. Known ceilings: the rule cannot see where a variable is defined, so a job that already reads the list from its environment still flags (the message says to drop the lead); a hint-named variable and an unrelated actor reference in one step flag; `context.actor`, `GITHUB_ACTOR_ID` and a job- or workflow-level `env` compared in a later step are not matched. +- New test `scripts/test_semgrep_gha_actor_allowlist.py` (registered in `scripts/ci-tests.d/semgrep-gha-actor-allowlist.sh`): fictional workflow and `action.yml` fixtures annotated `# ruleid:` and `# ok:` (14 expected hits: 9 in one release workflow, 1 in a `.yaml` deploy workflow, 1 in each of three composite-action copies, 1 in a workflow that is not valid YAML), with a literal list, a bot check, an unrelated variable, a hint-named variable with no actor, adjacent steps and jobs, a code-owned file and a commented-out check as the clean cases, plus a copy under `docs/` that must stay clean. The live part runs semgrep offline with one job and prints SKIP without it; CI installs none, so the offline part replays the rule's own regexes with Python's `re` over every fixture and pins the severity, the `[heuristic]` message, the name hints and the path scope. Against the previous release's `templates/semgrep-taint` the live check misses all 14 expected hits, and a copy of the rule with `languages: [yaml]` fails on the invalid-YAML workflow. +- `references/tool-hub.md` gains one table row for the rule (no reference listed the bundled semgrep rules by id, so the CLI tools table is the nearest home), and `docs/standards-index.md` logs the five GitHub Docs pages fetched 2026-10-30 that ground the message, including that the REST page says "collaborator access" for environment variables where the how-to page says `admin`. + +### Added +- Sibling-call sweep for incomplete fixes. New `.claude/skills/deep-code-review/scripts/sibling_sweep.py --repo DIR --base REF [--head REF]` finds the functions whose call arguments a diff changes at some call sites and lists the same function's unchanged call sites in non-test code as review leads. This is the shape of a fix that covers one route and misses its sibling: the guard call is edited in the route the report named and left alone in the route next to it. It is stdlib only and read-only: it reads `git diff` and `git grep` against commits, so it needs no checkout and never touches the working tree. A row is the rule `unchanged-sibling-call`, the sibling's file and line, and a message that names the call, the changed site and the old and new arguments. A lead is a place to look, never a finding. +- What it skips and how it caps. Definitions, test paths (also `tests.py`, `test.ts`, `*.mock.ts` and `__fixtures__/`), calls already in the new form, same-named calls in another language and the call whose callback body (not its own arguments) was edited are not leads. A name with more than 25 call sites is not swept, because a common name's sites are noise; the first five such names are listed in the `not-checked` row (the hub keeps rows and drops the script's stderr summary line) and on stderr. Sites whose arguments equal an old form come first. At most 5 leads are listed per function and 30 in all; the rest are counted in one `not-checked` row. At most 40 functions and 300 changed files are read. Known ceilings are written in the script's docstring: the match is by name with no scope or type resolution, so same-named functions in different modules collide, and a changed definition signature is `impact_map.py`'s job (it lists at most 8 references per symbol by default). +- `review_checks.sh --base` now runs it as the `sibling-sweep` step, right after the committed-data step. Rows carry the usual shape and the tool `sibling-sweep`, and the step is listed under `analyzers ran`. It does not run under `--full`, which has no diff, and a missing script gives one `not-checked` row. `method-situational.md` names the step in the Phase-1 analyzer list. +- Measured, read-only, on the first version (before the test-path, keyword-receiver and colour fixes). Replayed on a real fix whose sibling route kept the old arguments, it flagged that route: 1 true miss among its 2 leads. On a sample of 30 recent fix commits across 10 private repositories it ran without error and listed 28 leads (75 before the per-function cap), and none was a missed sibling: the changes were variable renames, a wrapper's own change, same-named helpers in two files and a moved call. Expect most leads to be dismissed in a minute each; the sweep is there for the one that is not. +- New test `scripts/test-review-checks-sibling-sweep.sh` (23 checks, registered in `scripts/ci-tests.d/sibling-sweep.sh`): a fictional repo with a base and a fix commit covers the exact lead rows and their order, every exclusion above, a multi-line insertion and an empty old argument list, a commit-only sweep with the fix not checked out, the refused and unresolvable refs, the hub step (rows, `analyzers ran`, no `--full` run, the missing-script row), both caps, a call far into a long line (still a lead) next to a long comment line (still no call), the over-cap name as a `not-checked` row from the script and through the hub, test paths named by file alone and `__fixtures__/`, a receiver named like a keyword (`proc.wait()`, `impl.run()`: a call, not a definition), git run with `color.ui=always` (the same leads, not none), and the test's own pin of the host's git config and tool dir. 20 of the 23 checks fail on the previous release; the other 3 are negative guards and the pin check, which hold when the step is absent. +- size-budget-raise: .claude/skills/deep-code-review/references/method-situational.md 44231→44396 the analyzer list names the sibling-sweep step and what its rows are + ## [1.625.0] — 2026-10-30 ### Fixed diff --git a/SHA256SUMS b/SHA256SUMS index 701764b10..33a22ac5e 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -20,8 +20,8 @@ ef9e423362b12d0dd73c8e165faf32e9f53c4d4ac215b57c2e34002a88713838 .claude/skills 9521b057ff3db92d1e60923ebd88170713914f7e235ceb45717eb39419daf811 .claude/skills/agentic-ceo/scripts/weekly_receipt.py bb2f134ab9b07e2cf15c4c8a0fad4708bc398144195178960d63b8ea6fd4a2d4 .claude/skills/agentic-delivery/INDEX-scripts.md 5d66d96d22947e0528fd3f337f789d3a8445dfeeb8240b41fe28303aa5a5c8ad .claude/skills/agentic-delivery/INDEX.md -2298706eaed5b386a927204e2dcd841ba551a97a1d7e1206690d7aeb557e7c41 .claude/skills/agentic-delivery/SKILL.md -4731d34a6bcc1e86310d93ce3e94853be847645fbd12278cda9d76dcf5ab5cd0 .claude/skills/agentic-delivery/VERSION +4d303d0acad9e15f50bedebc2dbae988b3ac1e47a32c168813ab43e8203085bd .claude/skills/agentic-delivery/SKILL.md +b2dcfd5d3e07a7f9fd383376ed4d95e26fe3af6a279f147f48aa6a2e4c7106b3 .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 @@ -204,8 +204,8 @@ d98f3e4380da3496061ba63be7beebe75017ab4ee1734eda9a3e798e19007b0e .claude/skills 18f20db537ab784460ad33cf218f7a01ac0a172fc38413f14c0e19e1ce6821cc .claude/skills/contribution/scripts/share_learning.py 63e7d07ffa54b224a83abee280456f6a24dc8222b9b55f6e249b10ea0c7f5afc .claude/skills/deep-code-review/INDEX-scripts.md 251fb5029ebb1c672fdf66e56888e514c85ef6bddc675411d3dd8cb4edf2802b .claude/skills/deep-code-review/INDEX.md -4c60387330f75f5f8ad3396b339c3093879d562ded072a1a79a9b790ccfe5a25 .claude/skills/deep-code-review/SKILL.md -4731d34a6bcc1e86310d93ce3e94853be847645fbd12278cda9d76dcf5ab5cd0 .claude/skills/deep-code-review/VERSION +a5bd5b6dc031e2d10a635c05b265c5766aab4ceb3cbd99c4b6e5215b17df4709 .claude/skills/deep-code-review/SKILL.md +b2dcfd5d3e07a7f9fd383376ed4d95e26fe3af6a279f147f48aa6a2e4c7106b3 .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 @@ -423,8 +423,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 -bf9d2625aa35c3942352f2c1c9d8a6e1a8217e4a074795ecc195765816fe0152 .claude/skills/idea-critic/SKILL.md -4731d34a6bcc1e86310d93ce3e94853be847645fbd12278cda9d76dcf5ab5cd0 .claude/skills/idea-critic/VERSION +d45fd9cc927928e26724ae8f69dcca4c6420fd052899efa04197bc8a6d9dbbe9 .claude/skills/idea-critic/SKILL.md +b2dcfd5d3e07a7f9fd383376ed4d95e26fe3af6a279f147f48aa6a2e4c7106b3 .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/perf-tools-hyperfine-k6.md b/changelog.d/perf-tools-hyperfine-k6.md deleted file mode 100644 index 93adc1a49..000000000 --- a/changelog.d/perf-tools-hyperfine-k6.md +++ /dev/null @@ -1,7 +0,0 @@ -### Added -- `scripts/perf_compare.sh [--runs N] [--base REF] [--json FILE] -- CMD`: a hyperfine A/B of CMD in a detached worktree of the base (default: the merge-base with `origin/main`) against the current tree. It prints one verdict line (means, sd, ratio; `within noise (ranges overlap)` when the mean plus or minus sd ranges overlap) and keeps the raw export in `/perf-compare-last.json`. A missing hyperfine fails visibly (`NOT CHECKED: hyperfine`, exit 1), and the temporary worktree is removed on exit and on signals. `heavy_gate.py` now holds it like the other heavy entrypoints (`scripts/test_heavy_gate_perf.py`); `scripts/test-perf-compare.sh` covers refusals, the verdict math, cleanup and one real hyperfine run (skipped visibly when hyperfine is absent). -- `scripts/app_pentest.sh --load [--vus N] [--secs N] URL`: a k6 smoke run (default 5 VUs for 15 s, hard caps 20 VUs and 60 s, refused rather than clamped) behind the same target rules as the scanners, alone (not with `--active` or `--openapi`). The JSON carries `load` with p50, p95 and error rate; every request failing is status `error`, and a missing k6 is `skipped` with `NOT CHECKED: k6`. `scripts/test-app-pentest-load.sh` uses a fake k6 plus real k6 runs against a local `python3 -m http.server` on 127.0.0.1 (skipped visibly when k6 is absent). All three tests are wired in `scripts/ci-tests.d/perf-tools.sh`. -- `references/tool-hub.md` Perf tools rows (hyperfine and k6, flags verified against `--help` on 2026-10-30), a Load smoke section in `references/app-pentest.md`, and one routed line in `references/performance-latency-depth.md`. -size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 10152→14455 wave total: the guarddog row with its egress and dead-proxy notes and the pinact line, Perf tools rows for hyperfine and k6 with the perf_compare.sh verdict rules, and one row for the bundled GitHub Actions semgrep rule -size-budget-raise: .claude/skills/deep-code-review/references/app-pentest.md 6393→8135 Load smoke section: caps, statuses and what the numbers do not prove -size-budget-raise: .claude/skills/deep-code-review/references/performance-latency-depth.md 13236→13456 one routed line to perf_compare.sh and app_pentest.sh --load diff --git a/changelog.d/pipeline-hub-duplicate-precision.md b/changelog.d/pipeline-hub-duplicate-precision.md deleted file mode 100644 index dd963210c..000000000 --- a/changelog.d/pipeline-hub-duplicate-precision.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- `pipeline_hub.py` duplicate checks no longer call two different gates a duplicate and no longer print a saving they did not measure. A duplicate is now the same normalized command (script and arguments) in two enforced CI steps. The same script run with different arguments in each step, such as one gate run with `--changed` in one step and `--all` in another, is listed as an advisory (`same suite, different arguments: check one covers the other before dropping`) with no drop advice and no saving; before, both steps were reported as the same suite and the recommendation said to drop one. The hub also stopped printing `saves about X per run (even split over its step's suites)`: the API times whole steps, never one suite, so that figure was the step's median divided by the number of suites in it. A saving is printed only for a step that runs that one command, and then as an upper bound (`saves at most about X per run (its step's median)`); for any other step the recommendation says the suite's own time is unmeasured and to time it locally. The JSON `duplicates` entries gain `identical` (false for an advisory), and `step_p50_s` is null for a step that runs several commands, so `saved_s` sums only the measured copies. Tests: `scripts/test_pipeline_hub.py`. diff --git a/changelog.d/pipeline-hub-skip-failed-run.md b/changelog.d/pipeline-hub-skip-failed-run.md deleted file mode 100644 index 0255727bc..000000000 --- a/changelog.d/pipeline-hub-skip-failed-run.md +++ /dev/null @@ -1,2 +0,0 @@ -### Fixed -- `pipeline_hub.py` stopped collecting at the first failed call, so one run whose jobs endpoint always answers HTTP 502 left most sampled runs and every PR uncollected, and the note read `collection stopped: }.`. A failed jobs or attempts call for one run is now named once in the notes (run id, API message and HTTP status) and collection goes on with the next run and the merged-PR pages; a failed runs-list or pulls call still stops, and resuming with the same `--data` file works as before. The error text now takes the API's JSON `message` or gh's ` (HTTP nnn)` line instead of the last line of the reply. diff --git a/changelog.d/review-checks-guarddog.md b/changelog.d/review-checks-guarddog.md deleted file mode 100644 index 5c10f96ec..000000000 --- a/changelog.d/review-checks-guarddog.md +++ /dev/null @@ -1,9 +0,0 @@ -### Added -- `review_checks.sh` now routes guarddog 3.2.0 as an offline supply-chain heuristic step. With a base, it reads every changed `package-lock.json`, finds the npm packages the lock newly adds (a name@version pair absent from the merge-base lock, so a version bump counts as new) and runs `guarddog npm scan --output-format json DIR` on each one that is installed under `node_modules` inside the repo at the locked version. The installed `package.json` must say so: a lock-only bump leaves the old code on disk, and scanning it would read as clean, so that package is counted as not installed at the locked version. Packages whose lock entry has `hasInstallScript` go first. At most 25 are scanned, within a total budget of about 300 s (`REVIEW_GUARDDOG_BUDGET`, in seconds; each scan also keeps the usual 60 s timebox), and every package left out is named in a `not-checked` row. The step runs only when `guarddog --version` says 3.x (the scan JSON was checked on 3.2.0); any other answer is one named `not-checked` row. -- Leads and gaps. Each `threat-*` rule that hits becomes one lead per package and rule (first location and hit count), tagged `[heuristic]`. `capability-*` rules fire on benign packages and stay quiet, and code or match snippets are never read. A rule that errors (guarddog lists it under `errors`) costs only that rule: the other hits stay and a `not-checked` row names the failed rule. Output with no `threat-*` rule at all is not-run. A lock that adds nothing is silent; nothing installed at the locked version, a lockfileVersion 1 lock, an unparseable lock and `--full` (no base) print one `SKIPPED` line each. It never runs `verify` and never fetches a package: guarddog treats a non-directory argument as a registry name, so only existing in-repo directories are passed. -- Control characters never drop a row. A `\r` in a package name, lock key or hit location used to split the row, and the closing summary dropped the pieces silently. Those characters now show as `?`, and a lock entry that cannot be scanned safely (no version, or control characters in its name, version or key) is a named `not-checked` row, never skipped quietly. -- Install hooks. guarddog's install-hook rules (`threat-npm-preinstall-script`, `threat-process-hooks`) match `*/package.json`, a nested path, so they never flag a package's own root `package.json`, where install hooks live (checked on 3.2.0 with fictional fixtures). A newly added lock entry with `hasInstallScript` is therefore a lead of its own (tool `package-lock`, rule `has-install-script`, tagged `[heuristic]`), emitted whether or not guarddog is installed. -- guarddog has no offline flag, and at startup it refreshes its top-package lists from github.com, hugovk.github.io and packages.ecosyste.ms (seen under a dead proxy). The supported opt-out is the environment variable `GUARDDOG_TOP_PACKAGES_CACHE_LOCATION` pointing at a directory of fresh stub lists, which a local scan never reads. The step sets it. When guarddog's own kernel sandbox cannot start (a nested sandbox: its stderr says `Sandbox initialization failed`, matched case-insensitively), the step retries once with `--no-sandbox` and keeps a dead proxy (`HTTP_PROXY` and friends pointing at 127.0.0.1:9) on every later scan, because that sandbox is what otherwise blocks guarddog's network. -- New test `scripts/test-review-checks-guarddog.sh` (68 checks with guarddog installed; 60 checks plus one SKIP line where it is not, as in CI; registered in `scripts/ci-tests.d/review-checks-guarddog.sh`). It runs git with `GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null` and clears the proxy variables. A shim covers found, clean, crash, bad JSON, no threat rule, rule errors, a wrong or failing `--version`, the nested sandbox with its real capitalised message, `--full`, a missing tool, the cap, the time budget and its order, a removal-only lock, nothing installed, a stale install, no lock at base, a v1 lock, a moved base tip, control characters and the `hasInstallScript` leads. The real-binary section is host-only: CI has no guarddog, so it prints one SKIP line there. It uses fictional packages (placeholder names, `example.invalid` hosts) and includes a control proving the no-egress check can fail: an empty-dir scan with an empty cache dir under a dead proxy must print a download error. Against the script from before the guarddog step it fails 47 of 68. -- `tool-hub.md`: a CLI row for guarddog, the egress and dead-proxy lines, and one line saying pinact (`pinact run`) is the fixer for unpinned-action findings; it needs the GitHub API, so never in offline runs. -- Measured on the installed `node_modules` of a real app (2026-10-30, guarddog 3.2.0, `--no-sandbox` because the host sandbox nests; taken before the 3.x version probe, about 0.3 s, was added): 25 newly-added-style packages (evenly spaced by name from the 382 top-level packages without nested dependencies, 6.4 MB) took 15.7 s for the whole `review_checks.sh` run (0.3 s without the step) and produced 0 leads. The largest package alone (next 16.4.3, 192 MB of compiled bundles) took 20.1 s and produced 10 leads, all presumed false positives (heuristic matches in bundled framework code, not audited by hand). Expect leads on large bundled packages; small packages stayed quiet. diff --git a/changelog.d/semgrep-gha-actor-allowlist.md b/changelog.d/semgrep-gha-actor-allowlist.md deleted file mode 100644 index 98d5baae8..000000000 --- a/changelog.d/semgrep-gha-actor-allowlist.md +++ /dev/null @@ -1,5 +0,0 @@ -### Added -- One bundled GitHub Actions semgrep rule in `templates/semgrep-taint/gha.yml`, for a workflow that authorizes `github.actor` against an allow-list read from a repository variable: anyone with write access can edit repository variables, so any writer could add themselves. `review_checks.sh` already loads the whole template directory, so it runs in the default review with no wiring change, like the other bundled rules (and, like them, only when the repo has no semgrep rules of its own). -- `perun.gha.actor-allowlist-from-vars` (WARNING; a heuristic lead, so the message starts with `[heuristic]`), scanned only in `.github/workflows/*.yml|yaml` and `action.yml|yaml` (language `regex`, not `yaml`, so a file that fails YAML parsing is still scanned): an actor check (`github.actor`, `github.triggering_actor`, `GITHUB_ACTOR`) fed by `vars.NAME`. Three regexes, comment lines skipped: the variable and the actor are the two operands of one comparison or `contains()` (any variable name), or a variable named like an allow-list (allow, releaser, deployer, approver, admin, maintainer, trusted) and an actor reference sit in one step, in either order. The message says who can edit what (anyone with write access can create, update and delete repository variables, and a repository variable overrides an organization variable of the same name) and names the fix: an environment variable read in a step of a job that declares that environment (creating one needs admin access in an organization repository, and environment variables are only available once the job starts, so not in a job-level `if`), or a code-owned file. Known ceilings: the rule cannot see where a variable is defined, so a job that already reads the list from its environment still flags (the message says to drop the lead); a hint-named variable and an unrelated actor reference in one step flag; `context.actor`, `GITHUB_ACTOR_ID` and a job- or workflow-level `env` compared in a later step are not matched. -- New test `scripts/test_semgrep_gha_actor_allowlist.py` (registered in `scripts/ci-tests.d/semgrep-gha-actor-allowlist.sh`): fictional workflow and `action.yml` fixtures annotated `# ruleid:` and `# ok:` (14 expected hits: 9 in one release workflow, 1 in a `.yaml` deploy workflow, 1 in each of three composite-action copies, 1 in a workflow that is not valid YAML), with a literal list, a bot check, an unrelated variable, a hint-named variable with no actor, adjacent steps and jobs, a code-owned file and a commented-out check as the clean cases, plus a copy under `docs/` that must stay clean. The live part runs semgrep offline with one job and prints SKIP without it; CI installs none, so the offline part replays the rule's own regexes with Python's `re` over every fixture and pins the severity, the `[heuristic]` message, the name hints and the path scope. Against the previous release's `templates/semgrep-taint` the live check misses all 14 expected hits, and a copy of the rule with `languages: [yaml]` fails on the invalid-YAML workflow. -- `references/tool-hub.md` gains one table row for the rule (no reference listed the bundled semgrep rules by id, so the CLI tools table is the nearest home), and `docs/standards-index.md` logs the five GitHub Docs pages fetched 2026-10-30 that ground the message, including that the REST page says "collaborator access" for environment variables where the how-to page says `admin`. diff --git a/changelog.d/sibling-sweep.md b/changelog.d/sibling-sweep.md deleted file mode 100644 index 4ff84e380..000000000 --- a/changelog.d/sibling-sweep.md +++ /dev/null @@ -1,7 +0,0 @@ -### Added -- Sibling-call sweep for incomplete fixes. New `.claude/skills/deep-code-review/scripts/sibling_sweep.py --repo DIR --base REF [--head REF]` finds the functions whose call arguments a diff changes at some call sites and lists the same function's unchanged call sites in non-test code as review leads. This is the shape of a fix that covers one route and misses its sibling: the guard call is edited in the route the report named and left alone in the route next to it. It is stdlib only and read-only: it reads `git diff` and `git grep` against commits, so it needs no checkout and never touches the working tree. A row is the rule `unchanged-sibling-call`, the sibling's file and line, and a message that names the call, the changed site and the old and new arguments. A lead is a place to look, never a finding. -- What it skips and how it caps. Definitions, test paths (also `tests.py`, `test.ts`, `*.mock.ts` and `__fixtures__/`), calls already in the new form, same-named calls in another language and the call whose callback body (not its own arguments) was edited are not leads. A name with more than 25 call sites is not swept, because a common name's sites are noise; the first five such names are listed in the `not-checked` row (the hub keeps rows and drops the script's stderr summary line) and on stderr. Sites whose arguments equal an old form come first. At most 5 leads are listed per function and 30 in all; the rest are counted in one `not-checked` row. At most 40 functions and 300 changed files are read. Known ceilings are written in the script's docstring: the match is by name with no scope or type resolution, so same-named functions in different modules collide, and a changed definition signature is `impact_map.py`'s job (it lists at most 8 references per symbol by default). -- `review_checks.sh --base` now runs it as the `sibling-sweep` step, right after the committed-data step. Rows carry the usual shape and the tool `sibling-sweep`, and the step is listed under `analyzers ran`. It does not run under `--full`, which has no diff, and a missing script gives one `not-checked` row. `method-situational.md` names the step in the Phase-1 analyzer list. -- Measured, read-only, on the first version (before the test-path, keyword-receiver and colour fixes). Replayed on a real fix whose sibling route kept the old arguments, it flagged that route: 1 true miss among its 2 leads. On a sample of 30 recent fix commits across 10 private repositories it ran without error and listed 28 leads (75 before the per-function cap), and none was a missed sibling: the changes were variable renames, a wrapper's own change, same-named helpers in two files and a moved call. Expect most leads to be dismissed in a minute each; the sweep is there for the one that is not. -- New test `scripts/test-review-checks-sibling-sweep.sh` (23 checks, registered in `scripts/ci-tests.d/sibling-sweep.sh`): a fictional repo with a base and a fix commit covers the exact lead rows and their order, every exclusion above, a multi-line insertion and an empty old argument list, a commit-only sweep with the fix not checked out, the refused and unresolvable refs, the hub step (rows, `analyzers ran`, no `--full` run, the missing-script row), both caps, a call far into a long line (still a lead) next to a long comment line (still no call), the over-cap name as a `not-checked` row from the script and through the hub, test paths named by file alone and `__fixtures__/`, a receiver named like a keyword (`proc.wait()`, `impl.run()`: a call, not a definition), git run with `color.ui=always` (the same leads, not none), and the test's own pin of the host's git config and tool dir. 20 of the 23 checks fail on the previous release; the other 3 are negative guards and the pin check, which hold when the step is absent. -- size-budget-raise: .claude/skills/deep-code-review/references/method-situational.md 44231→44396 the analyzer list names the sibling-sweep step and what its rows are diff --git a/gemini-extension.json b/gemini-extension.json index 1598d9aa5..af2232125 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.625.0", + "version": "1.626.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" }