diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index d9dacd86e..f69d8709a 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.630.0", + "version": "1.631.0", "author": { "name": "deep-code-review contributors" }, diff --git a/.claude/skills/agentic-delivery/INDEX.md b/.claude/skills/agentic-delivery/INDEX.md index 19740a9ee..3a470950a 100644 --- a/.claude/skills/agentic-delivery/INDEX.md +++ b/.claude/skills/agentic-delivery/INDEX.md @@ -17,10 +17,10 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing | `references/host-enforcement.md` | claiming that state, permissions, or spend are *enforced* rather than merely followed, briefing a w… | 11228 | | `references/incident-response.md` | drafting an incident runbook, defining severity levels or status/comms templates, setting up a brea… | 3264 | | `references/install.md` | recommending or installing this overlay into a target repo, deciding whether it may sit next to ano… | 295 | -| `references/merge-queue-worktrees.md` | draining a ready queue or a single merge seat, landing a train (merge_train.py, train_land.sh, land… | 11204 | +| `references/merge-queue-worktrees.md` | draining a ready queue or a single merge seat, landing a train (merge_train.py, train_land.sh, land… | 12039 | | `references/multi-session-coordination.md` | two or more independent agent sessions — separate processes, machines, or accounts — coordinate on… | 18475 | | `references/operating-discipline.md` | setting up a new machine or a new fleet, or auditing whether the always-on operating layer actually… | 2902 | -| `references/pipeline-optimizer.md` | a delivery pipeline is slow (a commit or push hook over about a minute, CI over about ten minutes,… | 4964 | +| `references/pipeline-optimizer.md` | a delivery pipeline is slow (a commit or push hook over about a minute, CI over about ten minutes,… | 5446 | | `references/production-grade-product-playbook.md` | you are shaping a user-facing product surface in G3 Design, so you build to the domain-P quality ba… | 1658 | | `references/project-state.md` | beginning multi-step delivery, changing objectives, transferring ownership, checkpointing, or recov… | 1885 | | `references/red-gate-triage.md` | a gate, union, or CI check is red and you must decide whether it is real: attributing a red union (… | 7259 | diff --git a/.claude/skills/agentic-delivery/SKILL.md b/.claude/skills/agentic-delivery/SKILL.md index 02f6aeff5..1d03bc6aa 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.630.0" + version: "1.631.0" --- # Agentic delivery diff --git a/.claude/skills/agentic-delivery/VERSION b/.claude/skills/agentic-delivery/VERSION index 5d053cd9e..74e0a6c96 100644 --- a/.claude/skills/agentic-delivery/VERSION +++ b/.claude/skills/agentic-delivery/VERSION @@ -1 +1 @@ -1.630.0 +1.631.0 diff --git a/.claude/skills/deep-code-review/INDEX.md b/.claude/skills/deep-code-review/INDEX.md index 09ecef6a3..bc5acb866 100644 --- a/.claude/skills/deep-code-review/INDEX.md +++ b/.claude/skills/deep-code-review/INDEX.md @@ -138,7 +138,7 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing | `references/testing-ui.md` | the target renders a UI or ships browser / E2E specs. | 4868 | | `references/threat-modeling.md` | a change adds a trust boundary, an auth path, a data flow to a new party, an agent or tool capabili… | 2574 | | `references/time-date-correctness.md` | reviewing anything that stores, computes, schedules, or renders timestamps, durations, recurring ev… | 4809 | -| `references/tool-hub.md` | a review should lean on external scanners via the tool hub (workflows, Python, Go, SBOM/lockfiles,… | 3842 | +| `references/tool-hub.md` | a review should lean on external scanners via the tool hub (workflows, Python, Go, SBOM/lockfiles,… | 3909 | | `references/ux-components.md` | the target or diff renders one concept in more than one place: a shared component or design token,… | 7235 | | `references/ux-dataviz.md` | the target or diff renders a chart, sparkline, heat-map or calendar grid, a KPI / metric delta, a c… | 2787 | | `references/ux-gates.md` | a UI change is claimed verified on screenshot evidence, the project's auto-merge or CI policy gates… | 3756 | diff --git a/.claude/skills/deep-code-review/SKILL.md b/.claude/skills/deep-code-review/SKILL.md index e92e0eb8e..5c8ce0cf4 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.630.0" + version: "1.631.0" --- # Deep Code Review diff --git a/.claude/skills/deep-code-review/VERSION b/.claude/skills/deep-code-review/VERSION index 5d053cd9e..74e0a6c96 100644 --- a/.claude/skills/deep-code-review/VERSION +++ b/.claude/skills/deep-code-review/VERSION @@ -1 +1 @@ -1.630.0 +1.631.0 diff --git a/.claude/skills/idea-critic/SKILL.md b/.claude/skills/idea-critic/SKILL.md index 61cd89ea4..87855cf76 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.630.0" + version: "1.631.0" --- # Veles: the evil twin (idea-critic) diff --git a/.claude/skills/idea-critic/VERSION b/.claude/skills/idea-critic/VERSION index 5d053cd9e..74e0a6c96 100644 --- a/.claude/skills/idea-critic/VERSION +++ b/.claude/skills/idea-critic/VERSION @@ -1 +1 @@ -1.630.0 +1.631.0 diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 86e2b7e82..cea16fb17 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.630.0", + "version": "1.631.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 30604a221..3c181d110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,39 @@ All notable changes to this repository are documented here. Format loosely follows Keep a Changelog; versioning follows Semantic Versioning. +## [1.631.0] — 2026-10-31 + +### Fixed +- `handback_cap.py` now holds a lane's hand-back while a `git commit` from its worktree is still running, not only a `git push`. A background commit with a slow pre-commit hook was not held, so a lane could hand back before its commit existed. The process match is `git [-c k=v] [-C dir] [--opt] push|commit` on a process whose argv[0] is `git`, alone or behind `env VAR=...` or `perl -e 'alarm 30; exec @ARGV'`, so a wrapped `perl -e 'alarm 30; exec @ARGV' git commit -m x` counts while a `claude -p` lane brief, a shell string or a `grep` that only names `git commit` does not, and both words now end with `(?![\w-])`: `git commit-tree`, `git commit-graph`, `git push-x` and `git log --grep commit` do not match (the old `push\b` ending did match `push-x`). The block text reads "A git push or commit from this worktree is still running (its pre-push or pre-commit hook may take 15+ minutes)" and no longer says the lane owns the process (it may belong to another process in the same checkout), read-only agent types (review, investigat, audit, explore) are never held for a live push or commit, the hold still ends after 30 minutes (`HANDBACK_PUSH_MAX_S`), and an unreadable process table still prints `COULD_NOT_CHECK` and passes. The `BACKGROUND WORK` docstring and the `templates/lane-preamble.md` line that describes the rule say push or commit too. +- `handback_cap.py`'s live push or commit check no longer counts its own ancestors. It read `ps -axo pid=,command=` and skipped only its own pid, so a headless `claude -p ""` host (the brief is in its argv, its cwd is the lane worktree) looked like a live commit and held every hand-back until the 30-minute cap. It now reads `pid=,ppid=,command=` through `_ps_table` and skips the hook, its ancestors and its descendants with the same `_skip_pids` helper the background-work scan uses; a `git commit` that is not an ancestor still holds. A process table that parses to no rows now prints `COULD_NOT_CHECK` and passes, as an unreadable one does. +- Test: `python3 .claude/skills/agentic-delivery/scripts/handback_cap.py --selftest` (already run by CI) gains cases for a plain `git commit`, `git -C commit`, a wrapped commit (quoted and as `ps` prints it), a live commit held for its own worktree but not for the checkout that contains it, and no match for `commit-tree`, `commit-graph`, `push-x` and `git log --grep commit`. Five of them (the positive commit cases and `push-x`) fail on the previous regex, so 64 of 69 passed; all 69 pass now. The `ps` mock now emits the `pid ppid command` rows the check reads, and four more cases cover a parent and a grandparent whose argv holds `git commit` (not counted, even with its cwd in the worktree), an unrelated `git commit` (still counted) and a process table that parses to no rows (`COULD_NOT_CHECK`, not a pass as `False`); the two ancestor cases and the empty-table case fail on the previous check, so 70 of 73 passed. Ten more cases cover the argv[0] match (a sibling `claude -p` brief, a `zsh -c` string and a `grep` that name `git commit` are not held; plain, `env`-wrapped, perl-wrapped and path-qualified `git commit` and `git push` are), read-only agent types (not held) and the hold text (no ownership claim); six of them fail on the check before them, so 77 of 83 passed; all 83 pass now. + +### Added +- `merge-queue-worktrees.md` gains the section "Around the heavy slot: cheap checks before the wait, a settled base after the grant" next to "A heavy slot guards the contended stage, not the whole train", which is unchanged. Three rules: run the cheap, fast-failing preconditions (mergeability, receipts present, base fresh, lint) before queueing for a heavy slot; compose the tested union after the grant, once the previous holder's landing has settled (landed or abandoned) and with a wait bounded to one landing; end the stage early when the base moves on a path the union touches (this decides only when to abort, and landing still needs the proven base). The slot stays scoped to the contended stage, widened only by that wait and the compose. A closing paragraph says what the shipped scripts do (`train_land.sh` waits out a landing train's push lock once, at train start; `land_train.sh` only warns on a moved base for a non-split train) and what is doctrine only (recompose after the grant, a mid-stage head compare). +- `pipeline-optimizer.md` gains two bullets under "Gate cost, feeder order and ready-PR age": a rerun of only the failing tests merges into the original run's receipt (full count, each failure replaced by its rerun result, both runs' raw count strings kept beside it, same tree only) and never replaces it, because a smaller receipt makes the next full-count gate force a second full run; and a pre-merge check pool at 0 workers fails silently, so alarm on worker count 0 while ready work waits (no shipped script reads a pool's worker count, and `landing_watchdog.py` is the natural home). +- Test: five eval cases in `.claude/skills/agentic-delivery/evals/evals.json` covering the five rules: `rerun-receipt-merged-not-replaced` and `rerun-receipt-after-fix-commit-needs-full-run` (the rerun rule, on the same tree and on a different tree), `stale-union-built-before-previous-landing-settled` (compose after the grant, with the early end as one expectation), `zero-worker-check-pool-alarm` and `cheap-preconditions-before-heavy-slot`. The rules are doctrine plus evals, not new scripts: a moved base only draws a WARN at land time for non-split trains, and nothing checks it mid-stage. + +size-budget-raise: .claude/skills/agentic-delivery/references/merge-queue-worktrees.md 44819→48157 three rules around the heavy slot (cheap preconditions before the wait, a settled base after the grant, an early end on a moved base) and a note on which of them the shipped scripts enforce +size-budget-raise: .claude/skills/agentic-delivery/references/pipeline-optimizer.md 19857→21785 two bullets: a rerun merges into the receipt with both raw run strings kept, and a pool at 0 workers alarms while ready work waits + +### Added +- `route_auth_map.py` reads TypeScript type-argument route registrations (`app.post<{ Params: { id: string }; Body: Widget }>("/path/:id", h)`, also on `.all`, `.use` and `.addHook`) through a bounded balanced `<...>` scan: the angle brackets must balance within 2000 characters, no `;`, `&&` or `||` may stand outside brackets, and a `(` must follow. A file whose only registrations are typed is now scanned, and a comparison such as `x.get < y && f(z)` is not a route. +- A Fastify root hook now reads `auth=root-hook`. `x.addHook("onRequest"|"preHandler"|"preValidation"|"preParsing", fn)` on a receiver bound to a `Fastify(...)` call, at any depth (a factory function included), counts when `fn` (inline, or through the same-file declarations it names) can answer 401/403 or names an identity check; `onSend`, `onRoute` and `onListen` never count. It covers what the hook file wires onto that receiver after the hook, in source order and within the block that holds the hook: the routes of a scanned file it passes to `x.register(plugin, opts)` or calls as `fn(x, ...)` with the instance as the first argument (`fn` imported from that file by a relative path), and the non-GET routes declared on that receiver in the hook's own file. A route above the hook, a module that is imported but never registered or called, a call with another instance, and a routes file with any wiring site the hook does not cover stay HIGH. +- What a root hook covers is read from the `if` statements of the hook text up to and including its first refusal. A negated path test with a bare `return` (`if (!req.url.startsWith("/acme-api/")) return;`, also `req.routeOptions.url?.startsWith(...)`) is a scope: only routes under it are covered. A positive path test with a bare `return`, written in the condition or through a same-file declaration it names (`PUBLIC_PATHS.includes(url)`), is an allow-list entry: a route under it keeps its own evidence. A path in the condition of a refusing `if` is a scope, and a `!` before it covers nothing. A quoted path anywhere else (a role table the hook reads, a path test after the refusal) says nothing, and a route-registration literal never counts. +- A root hook's path literals are read by the operator that tests them. A `startsWith` allow-list entry lets through every route whose raw path starts with it, as JavaScript does (`if (req.url.startsWith("/acme-api/health")) return;` also lets `/acme-api/healthz` through, so that route stays HIGH); `===` and `==` against a plain name or member chain (`req.url`) name that path only, whether they allow-list it or scope it (`if (req.url === "/acme-api/admin") return reply.code(403).send();` covers `/acme-api/admin` and not `/acme-api/admin/users`); any other test (`includes`, a collection, a regex, `!==`, or `===`/`==` against a call or an index such as `req.url.slice(0, 15)` or `path.dirname(req.url)`) allow-lists by raw prefix and scopes by the exact path. Known limit: a string `includes` or `endsWith` allow-list entry also lets through a route that only contains or ends with its literal (`/acme-api/health` next to an `endsWith("/health")` entry), and that route can still read covered. A `startsWith` scope still covers the routes under it, by path segment. +- A bare return before the first refusal under a condition that is neither a path test nor an identity check (an identity call, or a comparison with a secret-looking value) is a bypass, and the hook then covers nothing; `done()` and `next()` count as bare returns. Conditions are not evaluated, so a bypass OR-ed with a path test still reads as the path test; the script docstring lists the other limits. +- The refusal test that makes a Next middleware or a Fastify hook count as a gate also reads `.code(401|403)`, `statusCode = 401|403`, `.unauthorized(` and `.forbidden(`. + +### Fixed +- `tenant_scope_check.py` no longer crashes with `IndexError` on a file whose last character is a backslash inside an open string; `route_auth_map.py` reads files through the same masker. + +### Changed +- The `review_checks.sh` tool hub now also hands zizmor the Dependabot config (`.github/dependabot.yml` or `.yaml`) and GitHub Actions action manifests (`action.yml` or `action.yaml`, in any directory the hub does not exclude: `node_modules`, `vendor`, `dist`, `build`, ...), not only workflow files, so findings such as `dependabot-cooldown` in a Dependabot config and `template-injection` or `unpinned-uses` in a composite action reach the report instead of never being scanned. Routing is by file name: other YAML such as `config/app.yml` still goes nowhere, and actionlint still reads workflow files only. A changed `action.yml` that is not a GitHub Actions manifest and is the only file zizmor is given makes it collect no input; the run reports that as `NOT RUN: zizmor (exit 3)` with a not-checked row, never as clean. The zizmor row of `references/tool-hub.md` says the same. +- The scanner JSON reader now starts at the first line that begins with `[` or `{`, not at the first bracket anywhere in the output. zizmor logs each path it audits (`INFO audit: zizmor: completed ./x/[]/action.yml`) before its JSON, and the hub merges that log into the file the reader parses, so a bracket in a changed path could hide findings: `pkgs/[id]/action.yml`, `x/{}/action.yml` or a workflow named `[id].yml` made the whole batch unparseable (not run), and `x/[]/action.yml` read as an empty list, so zizmor counted as run with no findings. Those paths now yield their findings. +- Test: `scripts/test-review-checks-zizmor-routes.sh` (registered as `scripts/ci-tests.d/review-checks-zizmor-routes.sh`) runs the hub with a zizmor shim on a hermetic PATH that logs each audited path to stderr before its JSON, as zizmor does. It checks that the Dependabot config (`.yml` and `.yaml`), a nested `.github/actions/setup/action.yml`, a root `action.yaml` and manifests under `x/[]/` and `x/[id]/` are each handed to zizmor and yield a row on that file, and that the files zizmor receives are exactly the workflows (`.yml` and `.yaml`) plus those, so a nested workflow, `sub/.github/dependabot.yml`, `docs/dependabot.yml`, `config/app.yml`, `config/reaction.yml` and `docs/action.yml.md` are not routed. Every case fails on the previous script. + +size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 15368→15637 the zizmor row now names the Dependabot and action-manifest routing, the directories the hub excludes, and the exit 3 case + ## [1.630.0] — 2026-10-31 ### Changed diff --git a/SHA256SUMS b/SHA256SUMS index bcbf56a36..453ea1f28 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -19,10 +19,10 @@ ef9e423362b12d0dd73c8e165faf32e9f53c4d4ac215b57c2e34002a88713838 .claude/skills 372f7d86157392ebfab627f6a05a892e4dd3297db9b8bfc6f404759169c7500c .claude/skills/agentic-ceo/scripts/token_report.py 9521b057ff3db92d1e60923ebd88170713914f7e235ceb45717eb39419daf811 .claude/skills/agentic-ceo/scripts/weekly_receipt.py 7e2179751e9dd2fb371b3e9fb01fca987d69605af1e8e6e609ff9b0d0abb384b .claude/skills/agentic-delivery/INDEX-scripts.md -47fff02dcb4401d5a9b42b2b3b7f7c123125eece55e7f9adee8cfe488c70a146 .claude/skills/agentic-delivery/INDEX.md -6858279bbeebfc7fb7c0df3c058378aa4505dd4b8f035567dc1bfab93bcbebd0 .claude/skills/agentic-delivery/SKILL.md -b78778d02b1ed21cf6001646fbc0fc5b212475318c3c619a05cae2d39aadf46e .claude/skills/agentic-delivery/VERSION -94144c101f82399eb6c18f688321a00e1c195dd0ab334f3a5daa2543b40ff9f5 .claude/skills/agentic-delivery/evals/evals.json +356b85280ede56adc36aea682ae0a182346892c52f8351df82c8d3225647a473 .claude/skills/agentic-delivery/INDEX.md +6629e6be179ec422847cdd67983b73ee013325e43f922d47bb91846c35d0a596 .claude/skills/agentic-delivery/SKILL.md +6de9ea98cc5c87a700b2d2834fac9153006b3fc5eda5ddb0ac1160907c32a919 .claude/skills/agentic-delivery/VERSION +b14f9f9cf466e6886fee63c6563d8a7f40c7a6a02782d44e1f4557f5ccd7fe21 .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 eed07e997b517be02c087b9d0fff769c2098eb9c3ed6528fbee3a538ce3fc250 .claude/skills/agentic-delivery/references/dev-env-ownership.md @@ -35,10 +35,10 @@ ced5452f62c7c36d7dc959ec11995005962ef630df2828df5b24688896e9ad7a .claude/skills ef53e52648126536902735d3ec9bc15cef8a422695218d73ebce41a8844ddbf9 .claude/skills/agentic-delivery/references/host-enforcement.md cf8c4e36be255f6c05e72832a062e01e6bbf213bb69433d1af89ff181b4b981c .claude/skills/agentic-delivery/references/incident-response.md e19b43f2e66aa762397a09b35fa061d6d7b5a4e6a608a2af7d216478a5804bd4 .claude/skills/agentic-delivery/references/install.md -d6bcf0e226e27a952f8843b80c08276fd124b0a2bd0a0849bfc85897954ce228 .claude/skills/agentic-delivery/references/merge-queue-worktrees.md +8cf67a67b5989efa23059deff048b0012577649755f02dd6dc30e625b462ad65 .claude/skills/agentic-delivery/references/merge-queue-worktrees.md 72b9191f7a4f459ffca3db60f21787db70f80c2a7fe7aa9b69b279a3d3c5124d .claude/skills/agentic-delivery/references/multi-session-coordination.md fc00cf483537ec847b8e3ecc1c988cd7cb9d55844525702e1cbf623a5f488ff3 .claude/skills/agentic-delivery/references/operating-discipline.md -6726bf254c7a2d53e702ab72f6793c6c140afadbf5ee50df905e2f2e56b2b3a7 .claude/skills/agentic-delivery/references/pipeline-optimizer.md +57557d7d6d13c8e25c55e565ee2f73e5049a27d4ef93a63c07e37b1d2ab27955 .claude/skills/agentic-delivery/references/pipeline-optimizer.md 0aeeac05e4eefcc5c372b56fa1fa62d6e588c34d20aa1fb93ebcefef987f9662 .claude/skills/agentic-delivery/references/production-grade-product-playbook.md 4c46afbe5c5ae4d7b544e251a950ab54cc0683e4f655ec9a29dca0693c191361 .claude/skills/agentic-delivery/references/project-state.md dd23c85b0f9f6479aadfbb13cc7e09e12d57e0e8b47f4d52f29a6e68ebf70241 .claude/skills/agentic-delivery/references/red-gate-triage.md @@ -90,7 +90,7 @@ bd29dd7f6cb2ce16402fbaa741d9abc5b081617bed739b0838054ab071d1780d .claude/skills 06824e47863d8660c084ad6ede05573ae3754029e23c41129030718a622768ea .claude/skills/agentic-delivery/scripts/freeze_hook.py e6cc0913d51d2b761c6a41938840a68425c934e39324f7afaf001cee553be0c6 .claude/skills/agentic-delivery/scripts/gh_rest.py 48a985aafe738fca5e96d30ac36a3f7f7065bd30bbb06cad8f51296be80fc975 .claude/skills/agentic-delivery/scripts/handback.py -1ca5a51e0f3c4cafd37567f8d94a177691e87f2486e66d1c3b981411008b9722 .claude/skills/agentic-delivery/scripts/handback_cap.py +0077ba64a487c3a7df4c426a76eaab0691edecbce99d3e1a4b8e8609b1a6aae8 .claude/skills/agentic-delivery/scripts/handback_cap.py 026dfd9c2d90300e54a7ec24a51391e7c8fb2eaa64dfecb61afaf8cced3bbce0 .claude/skills/agentic-delivery/scripts/handoff_inject.py 8cd0c52f1f5ef552677d523021072c8030031a36f96a3c51b9f10bde87ca8f74 .claude/skills/agentic-delivery/scripts/heavy_gate.py 6650d5257e60823c88de1a66b083681f192e316148db35c6ac3265fd11c60fb4 .claude/skills/agentic-delivery/scripts/host_accept.py @@ -166,7 +166,7 @@ b759691e776aff3bcc2c92b422179bce1f24522728c472155b8bc227ff094686 .claude/skills bbbcec171d247196c9859105239149c8d02627918b50a2b3c52775d14eed668e .claude/skills/agentic-delivery/templates/global-rules.settings.json 98ec99b865d0ebf249960be4722e16af57489a1c78af06adc4afcd5a37de9223 .claude/skills/agentic-delivery/templates/handoff.md 46a4e52954370bb53db6ebf78b7365b2c4ef790ea6e2e73f4f4c09c823c8e219 .claude/skills/agentic-delivery/templates/host-safety.tsv -ac7eb14ad5aa0add90a39d2f6d1f8ab1d53763b4912e13916f788cdd30a1d0c2 .claude/skills/agentic-delivery/templates/lane-preamble.md +ed6ed58c13a18082dbe2268bad472d218701df176e37d43a0a7a0826967315dc .claude/skills/agentic-delivery/templates/lane-preamble.md c3ef87aa65dec1f63df5f6b1aadc3a1e5c322ed7288a5dbd3b7617d56c12adbb .claude/skills/agentic-delivery/templates/loops/cleanup.md b956a4767c55179e732c5e34581abfd07b285dd73aa030ad9119005cb8e2d0a2 .claude/skills/agentic-delivery/templates/loops/coordinator.md 7f5e043836dca83534623994d97de0da88caec290e434d0c7a1a52cf2345f05b .claude/skills/agentic-delivery/templates/loops/peer.md @@ -205,9 +205,9 @@ d98f3e4380da3496061ba63be7beebe75017ab4ee1734eda9a3e798e19007b0e .claude/skills 92d22cc5d0a60ba6bdb1f4a9b597823dec5b8412cec09a5be6cf93b3973f4226 .claude/skills/contribution/scripts/prefile_check.sh 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 -38f7641cd69b145af32440d34daf29a52272bef4313da5d9483eb31a9ff7c61b .claude/skills/deep-code-review/SKILL.md -b78778d02b1ed21cf6001646fbc0fc5b212475318c3c619a05cae2d39aadf46e .claude/skills/deep-code-review/VERSION +bd033973aa245a22749cbd3e6c987813ea1be6352756b20599dc0b700bd5ab21 .claude/skills/deep-code-review/INDEX.md +bb67261ba0ce71153b771f94acc550a296c6e21cded3b41b55c66d59c21f9306 .claude/skills/deep-code-review/SKILL.md +6de9ea98cc5c87a700b2d2834fac9153006b3fc5eda5ddb0ac1160907c32a919 .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 @@ -342,7 +342,7 @@ a0ed7ad061b7e60230601ee9b38a00400841d6ddf3af386edbd4b34a981c9798 .claude/skills 8581f0023aedc9e0a4dece61cb2ea876ca9ab57f6acda63ed16c9b54494a1bc8 .claude/skills/deep-code-review/references/testing-ui.md 68df4819a34cbe23756eb19e9b513b9edcd675ad55da7d8c7624d95b124a9744 .claude/skills/deep-code-review/references/threat-modeling.md cf5218579b77d07ca49349165ab6d64c94b0145f61a4438cb09898a414793c68 .claude/skills/deep-code-review/references/time-date-correctness.md -6211a88ef18f23b7f1dc23a9b6e1246efd2fd121107f77ef4a338041a54147f9 .claude/skills/deep-code-review/references/tool-hub.md +ca4d4612485eb461744ecf5aeabe0fe9aa4fba4eea9d36682431ba8a4209e876 .claude/skills/deep-code-review/references/tool-hub.md bfda9dd8f0d1718725e0fb8c2d6890f4fc2742c9a0d7993112ecf459a3af9f9c .claude/skills/deep-code-review/references/ux-components.md 5e69c5a0c0ab8d94079f2f08e026bc0e700369c76cd7dfeee6e5c7c5c2f020bb .claude/skills/deep-code-review/references/ux-dataviz.md 68c40fe7134afac5fae125f107726136a911c67c1d4abdf0e4c627aee6751713 .claude/skills/deep-code-review/references/ux-gates.md @@ -397,15 +397,15 @@ e2903a566deee3423cc377b000475b30d6698ccebd640cb62b86b6d2001c4822 .claude/skills 5d6910164ddf6dfd4ed62dfc365a2f615f49dc07878ed3d1f881aa9518e481c2 .claude/skills/deep-code-review/scripts/post_review.sh b01cae69d80ca560cc414c1c70ef0ddfdbb910da5b68df4e9d46cf0daf3f8b16 .claude/skills/deep-code-review/scripts/provision_tools.sh f1108e2700864195a239744b319b1d04953879589f8a2b83708bc45a080e3d86 .claude/skills/deep-code-review/scripts/reaper_lint.py -eb9d92d46e92e757581a931ee63d1871664a460c3d411e9e9ed70416aed7fb0e .claude/skills/deep-code-review/scripts/review_checks.sh +9dcf6d7a46890d8f3f269e956bd38599ac92b6badfed9e18193b0a85af1469a9 .claude/skills/deep-code-review/scripts/review_checks.sh 8c87c02a050a975e453b52ecacbaf01481c7134466a9c67feaf409d22e36d9f7 .claude/skills/deep-code-review/scripts/review_feedback.py -9ccd6f708c2ee25b0ac4ba49644ccbd10a730f41b893c719cd1a5d6bb92cdb20 .claude/skills/deep-code-review/scripts/route_auth_map.py +6ca102dffcfbc5ecc4eca7c99977351154bca82ded3e499aa31f26fbd8937892 .claude/skills/deep-code-review/scripts/route_auth_map.py 859c67d6a6a781c873f47d5662d403da2321da53e6994ca3d0ccdbf74afd39ac .claude/skills/deep-code-review/scripts/score_review.py e9dfa337ad16d0022a3b7a87e0ba49acb5c2c9c0a5ee24396ff80523f2f8fa1a .claude/skills/deep-code-review/scripts/security_verify.py 90bbc7f05448c6606824423595ab1c43ee5724699c1fbe7f8f5be523be3e1b2f .claude/skills/deep-code-review/scripts/sibling_sweep.py 761ccb5730e297f41d865aeec9babd44b00b5cc029a8d4e7b2cb37b1122222be .claude/skills/deep-code-review/scripts/source_scan_tests.py b28b622d7e59fb00549141b439871f5a6c6faddcb83d2d76c7303d7efdbb2897 .claude/skills/deep-code-review/scripts/supply_chain_check.sh -ef606126a1a40934671f8fea1d5caaeaf7957070033557445692eb249a643bb7 .claude/skills/deep-code-review/scripts/tenant_scope_check.py +966d003031290c2a3e370ef7be6d2ab30dda8b8ab9e1de46ae6c56b00a333abd .claude/skills/deep-code-review/scripts/tenant_scope_check.py 01ce284157850e6f9b146023dbcf12a8ed014db578ee5f6f27ddf21a2a3a0b5e .claude/skills/deep-code-review/scripts/token_differ.py cbfd54a4fa86885f0c49b77327f034c2986170c3d74b4c39b2d9f2686ba87c40 .claude/skills/deep-code-review/scripts/validate_status_claims.py 12a01a5082b5c4466c40aab5f0114cd8442f271df1aa7ef8ddd186512681a7b8 .claude/skills/deep-code-review/templates/dcr-gates.sh @@ -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 -0d1f64cbd5c26d4ffa9fe1717835090fbd696915688e5bc596c656f6e9585b6c .claude/skills/idea-critic/SKILL.md -b78778d02b1ed21cf6001646fbc0fc5b212475318c3c619a05cae2d39aadf46e .claude/skills/idea-critic/VERSION +d92e6af4b348ea00518a5672c44ebbc6195c16828cbeaa923e524aead4fecccc .claude/skills/idea-critic/SKILL.md +6de9ea98cc5c87a700b2d2834fac9153006b3fc5eda5ddb0ac1160907c32a919 .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/handback-live-commit.md b/changelog.d/handback-live-commit.md deleted file mode 100644 index c2202ee3d..000000000 --- a/changelog.d/handback-live-commit.md +++ /dev/null @@ -1,4 +0,0 @@ -### Fixed -- `handback_cap.py` now holds a lane's hand-back while a `git commit` from its worktree is still running, not only a `git push`. A background commit with a slow pre-commit hook was not held, so a lane could hand back before its commit existed. The process match is `git [-c k=v] [-C dir] [--opt] push|commit` on a process whose argv[0] is `git`, alone or behind `env VAR=...` or `perl -e 'alarm 30; exec @ARGV'`, so a wrapped `perl -e 'alarm 30; exec @ARGV' git commit -m x` counts while a `claude -p` lane brief, a shell string or a `grep` that only names `git commit` does not, and both words now end with `(?![\w-])`: `git commit-tree`, `git commit-graph`, `git push-x` and `git log --grep commit` do not match (the old `push\b` ending did match `push-x`). The block text reads "A git push or commit from this worktree is still running (its pre-push or pre-commit hook may take 15+ minutes)" and no longer says the lane owns the process (it may belong to another process in the same checkout), read-only agent types (review, investigat, audit, explore) are never held for a live push or commit, the hold still ends after 30 minutes (`HANDBACK_PUSH_MAX_S`), and an unreadable process table still prints `COULD_NOT_CHECK` and passes. The `BACKGROUND WORK` docstring and the `templates/lane-preamble.md` line that describes the rule say push or commit too. -- `handback_cap.py`'s live push or commit check no longer counts its own ancestors. It read `ps -axo pid=,command=` and skipped only its own pid, so a headless `claude -p ""` host (the brief is in its argv, its cwd is the lane worktree) looked like a live commit and held every hand-back until the 30-minute cap. It now reads `pid=,ppid=,command=` through `_ps_table` and skips the hook, its ancestors and its descendants with the same `_skip_pids` helper the background-work scan uses; a `git commit` that is not an ancestor still holds. A process table that parses to no rows now prints `COULD_NOT_CHECK` and passes, as an unreadable one does. -- Test: `python3 .claude/skills/agentic-delivery/scripts/handback_cap.py --selftest` (already run by CI) gains cases for a plain `git commit`, `git -C commit`, a wrapped commit (quoted and as `ps` prints it), a live commit held for its own worktree but not for the checkout that contains it, and no match for `commit-tree`, `commit-graph`, `push-x` and `git log --grep commit`. Five of them (the positive commit cases and `push-x`) fail on the previous regex, so 64 of 69 passed; all 69 pass now. The `ps` mock now emits the `pid ppid command` rows the check reads, and four more cases cover a parent and a grandparent whose argv holds `git commit` (not counted, even with its cwd in the worktree), an unrelated `git commit` (still counted) and a process table that parses to no rows (`COULD_NOT_CHECK`, not a pass as `False`); the two ancestor cases and the empty-table case fail on the previous check, so 70 of 73 passed. Ten more cases cover the argv[0] match (a sibling `claude -p` brief, a `zsh -c` string and a `grep` that name `git commit` are not held; plain, `env`-wrapped, perl-wrapped and path-qualified `git commit` and `git push` are), read-only agent types (not held) and the hold text (no ownership claim); six of them fail on the check before them, so 77 of 83 passed; all 83 pass now. diff --git a/changelog.d/pipeline-patterns.md b/changelog.d/pipeline-patterns.md deleted file mode 100644 index a3e8466dd..000000000 --- a/changelog.d/pipeline-patterns.md +++ /dev/null @@ -1,7 +0,0 @@ -### Added -- `merge-queue-worktrees.md` gains the section "Around the heavy slot: cheap checks before the wait, a settled base after the grant" next to "A heavy slot guards the contended stage, not the whole train", which is unchanged. Three rules: run the cheap, fast-failing preconditions (mergeability, receipts present, base fresh, lint) before queueing for a heavy slot; compose the tested union after the grant, once the previous holder's landing has settled (landed or abandoned) and with a wait bounded to one landing; end the stage early when the base moves on a path the union touches (this decides only when to abort, and landing still needs the proven base). The slot stays scoped to the contended stage, widened only by that wait and the compose. A closing paragraph says what the shipped scripts do (`train_land.sh` waits out a landing train's push lock once, at train start; `land_train.sh` only warns on a moved base for a non-split train) and what is doctrine only (recompose after the grant, a mid-stage head compare). -- `pipeline-optimizer.md` gains two bullets under "Gate cost, feeder order and ready-PR age": a rerun of only the failing tests merges into the original run's receipt (full count, each failure replaced by its rerun result, both runs' raw count strings kept beside it, same tree only) and never replaces it, because a smaller receipt makes the next full-count gate force a second full run; and a pre-merge check pool at 0 workers fails silently, so alarm on worker count 0 while ready work waits (no shipped script reads a pool's worker count, and `landing_watchdog.py` is the natural home). -- Test: five eval cases in `.claude/skills/agentic-delivery/evals/evals.json` covering the five rules: `rerun-receipt-merged-not-replaced` and `rerun-receipt-after-fix-commit-needs-full-run` (the rerun rule, on the same tree and on a different tree), `stale-union-built-before-previous-landing-settled` (compose after the grant, with the early end as one expectation), `zero-worker-check-pool-alarm` and `cheap-preconditions-before-heavy-slot`. The rules are doctrine plus evals, not new scripts: a moved base only draws a WARN at land time for non-split trains, and nothing checks it mid-stage. - -size-budget-raise: .claude/skills/agentic-delivery/references/merge-queue-worktrees.md 44819→48157 three rules around the heavy slot (cheap preconditions before the wait, a settled base after the grant, an early end on a moved base) and a note on which of them the shipped scripts enforce -size-budget-raise: .claude/skills/agentic-delivery/references/pipeline-optimizer.md 19857→21785 two bullets: a rerun merges into the receipt with both raw run strings kept, and a pool at 0 workers alarms while ready work waits diff --git a/changelog.d/route-auth-generics.md b/changelog.d/route-auth-generics.md deleted file mode 100644 index a62e561ba..000000000 --- a/changelog.d/route-auth-generics.md +++ /dev/null @@ -1,10 +0,0 @@ -### Added -- `route_auth_map.py` reads TypeScript type-argument route registrations (`app.post<{ Params: { id: string }; Body: Widget }>("/path/:id", h)`, also on `.all`, `.use` and `.addHook`) through a bounded balanced `<...>` scan: the angle brackets must balance within 2000 characters, no `;`, `&&` or `||` may stand outside brackets, and a `(` must follow. A file whose only registrations are typed is now scanned, and a comparison such as `x.get < y && f(z)` is not a route. -- A Fastify root hook now reads `auth=root-hook`. `x.addHook("onRequest"|"preHandler"|"preValidation"|"preParsing", fn)` on a receiver bound to a `Fastify(...)` call, at any depth (a factory function included), counts when `fn` (inline, or through the same-file declarations it names) can answer 401/403 or names an identity check; `onSend`, `onRoute` and `onListen` never count. It covers what the hook file wires onto that receiver after the hook, in source order and within the block that holds the hook: the routes of a scanned file it passes to `x.register(plugin, opts)` or calls as `fn(x, ...)` with the instance as the first argument (`fn` imported from that file by a relative path), and the non-GET routes declared on that receiver in the hook's own file. A route above the hook, a module that is imported but never registered or called, a call with another instance, and a routes file with any wiring site the hook does not cover stay HIGH. -- What a root hook covers is read from the `if` statements of the hook text up to and including its first refusal. A negated path test with a bare `return` (`if (!req.url.startsWith("/acme-api/")) return;`, also `req.routeOptions.url?.startsWith(...)`) is a scope: only routes under it are covered. A positive path test with a bare `return`, written in the condition or through a same-file declaration it names (`PUBLIC_PATHS.includes(url)`), is an allow-list entry: a route under it keeps its own evidence. A path in the condition of a refusing `if` is a scope, and a `!` before it covers nothing. A quoted path anywhere else (a role table the hook reads, a path test after the refusal) says nothing, and a route-registration literal never counts. -- A root hook's path literals are read by the operator that tests them. A `startsWith` allow-list entry lets through every route whose raw path starts with it, as JavaScript does (`if (req.url.startsWith("/acme-api/health")) return;` also lets `/acme-api/healthz` through, so that route stays HIGH); `===` and `==` against a plain name or member chain (`req.url`) name that path only, whether they allow-list it or scope it (`if (req.url === "/acme-api/admin") return reply.code(403).send();` covers `/acme-api/admin` and not `/acme-api/admin/users`); any other test (`includes`, a collection, a regex, `!==`, or `===`/`==` against a call or an index such as `req.url.slice(0, 15)` or `path.dirname(req.url)`) allow-lists by raw prefix and scopes by the exact path. Known limit: a string `includes` or `endsWith` allow-list entry also lets through a route that only contains or ends with its literal (`/acme-api/health` next to an `endsWith("/health")` entry), and that route can still read covered. A `startsWith` scope still covers the routes under it, by path segment. -- A bare return before the first refusal under a condition that is neither a path test nor an identity check (an identity call, or a comparison with a secret-looking value) is a bypass, and the hook then covers nothing; `done()` and `next()` count as bare returns. Conditions are not evaluated, so a bypass OR-ed with a path test still reads as the path test; the script docstring lists the other limits. -- The refusal test that makes a Next middleware or a Fastify hook count as a gate also reads `.code(401|403)`, `statusCode = 401|403`, `.unauthorized(` and `.forbidden(`. - -### Fixed -- `tenant_scope_check.py` no longer crashes with `IndexError` on a file whose last character is a backslash inside an open string; `route_auth_map.py` reads files through the same masker. diff --git a/changelog.d/zizmor-routes.md b/changelog.d/zizmor-routes.md deleted file mode 100644 index f06b78e00..000000000 --- a/changelog.d/zizmor-routes.md +++ /dev/null @@ -1,6 +0,0 @@ -### Changed -- The `review_checks.sh` tool hub now also hands zizmor the Dependabot config (`.github/dependabot.yml` or `.yaml`) and GitHub Actions action manifests (`action.yml` or `action.yaml`, in any directory the hub does not exclude: `node_modules`, `vendor`, `dist`, `build`, ...), not only workflow files, so findings such as `dependabot-cooldown` in a Dependabot config and `template-injection` or `unpinned-uses` in a composite action reach the report instead of never being scanned. Routing is by file name: other YAML such as `config/app.yml` still goes nowhere, and actionlint still reads workflow files only. A changed `action.yml` that is not a GitHub Actions manifest and is the only file zizmor is given makes it collect no input; the run reports that as `NOT RUN: zizmor (exit 3)` with a not-checked row, never as clean. The zizmor row of `references/tool-hub.md` says the same. -- The scanner JSON reader now starts at the first line that begins with `[` or `{`, not at the first bracket anywhere in the output. zizmor logs each path it audits (`INFO audit: zizmor: completed ./x/[]/action.yml`) before its JSON, and the hub merges that log into the file the reader parses, so a bracket in a changed path could hide findings: `pkgs/[id]/action.yml`, `x/{}/action.yml` or a workflow named `[id].yml` made the whole batch unparseable (not run), and `x/[]/action.yml` read as an empty list, so zizmor counted as run with no findings. Those paths now yield their findings. -- Test: `scripts/test-review-checks-zizmor-routes.sh` (registered as `scripts/ci-tests.d/review-checks-zizmor-routes.sh`) runs the hub with a zizmor shim on a hermetic PATH that logs each audited path to stderr before its JSON, as zizmor does. It checks that the Dependabot config (`.yml` and `.yaml`), a nested `.github/actions/setup/action.yml`, a root `action.yaml` and manifests under `x/[]/` and `x/[id]/` are each handed to zizmor and yield a row on that file, and that the files zizmor receives are exactly the workflows (`.yml` and `.yaml`) plus those, so a nested workflow, `sub/.github/dependabot.yml`, `docs/dependabot.yml`, `config/app.yml`, `config/reaction.yml` and `docs/action.yml.md` are not routed. Every case fails on the previous script. - -size-budget-raise: .claude/skills/deep-code-review/references/tool-hub.md 15368→15637 the zizmor row now names the Dependabot and action-manifest routing, the directories the hub excludes, and the exit 3 case diff --git a/gemini-extension.json b/gemini-extension.json index 9e9ccd443..f4738449b 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "perun", - "version": "1.630.0", + "version": "1.631.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" } diff --git a/scripts/size-budgets.tsv b/scripts/size-budgets.tsv index 6287734cf..3a83bc425 100644 --- a/scripts/size-budgets.tsv +++ b/scripts/size-budgets.tsv @@ -42,7 +42,7 @@ .claude/skills/agentic-delivery/references/host-enforcement.md 44913 .claude/skills/agentic-delivery/references/incident-response.md 13056 .claude/skills/agentic-delivery/references/install.md 1180 -.claude/skills/agentic-delivery/references/merge-queue-worktrees.md 44819 +.claude/skills/agentic-delivery/references/merge-queue-worktrees.md 48157 .claude/skills/agentic-delivery/references/multi-session-coordination.md 73901 .claude/skills/agentic-delivery/references/operating-discipline.md 11608 .claude/skills/agentic-delivery/references/production-grade-product-playbook.md 6635 @@ -213,7 +213,7 @@ README.md 16118 .claude/skills/agentic-delivery/INDEX-scripts.md 17631 .claude/skills/deep-code-review/INDEX.md 20553 .claude/skills/deep-code-review/INDEX-scripts.md 6797 -.claude/skills/agentic-delivery/references/pipeline-optimizer.md 19857 +.claude/skills/agentic-delivery/references/pipeline-optimizer.md 21785 .claude/skills/agentic-delivery/references/focus-kit.md 5235 .claude/skills/communication-structure/references/artifact-templates.md 9443 .claude/skills/deep-code-review/references/code-structure.md 6449 @@ -234,7 +234,7 @@ README.md 16118 .claude/skills/deep-code-review/references/small-diff-read-budget.md 526 .claude/skills/deep-code-review/references/app-pentest.md 8135 .claude/skills/deep-code-review/references/supply-chain-check.md 3646 -.claude/skills/deep-code-review/references/tool-hub.md 15368 +.claude/skills/deep-code-review/references/tool-hub.md 15637 .claude/skills/deep-code-review/references/desktop-native-appsec.md 4724 .claude/skills/deep-code-review/references/dynamic-probe.md 1890 .claude/skills/agentic-delivery/references/flow-guards.md 2105