Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.634.0",
"version": "1.635.0",
"author": {
"name": "deep-code-review contributors"
},
Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/agentic-delivery/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing

| File | Read this when... | Tok |
|---|---|---|
| `references/cost-quality-guardrails.md` | a change cuts cost — a cheaper model tier, a lane or tool-call cap, a must-load section moved behin… | 4057 |
| `references/cost-quality-guardrails.md` | a change cuts cost — a cheaper model tier, a lane or tool-call cap, a must-load section moved behin… | 4074 |
| `references/dev-env-ownership.md` | booting a dev server in a lane, separating the serving tree from the committing tree, re-running a… | 8423 |
| `references/fanout-host-sizing.md` | sizing a fan-out or capping in-flight write lanes, cutting per-lane cycle-time or handback verbosit… | 12456 |
| `references/fast-agentic-delivery.md` | sizing a heavy-lane fan-out on a shared machine (the full environment-probe procedure and the fan-o… | 1703 |
Expand All @@ -25,14 +25,14 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing
| `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 |
| `references/retrospective.md` | running G10, or when a target's own incident/postmortem practice is in scope for a review. | 1273 |
| `references/roles.md` | standing up (or running) the full delivery team on a repo: who does what, when a hat fires, which g… | 6830 |
| `references/roles.md` | standing up (or running) the full delivery team on a repo: who does what, when a hat fires, which g… | 6889 |
| `references/support-ops.md` | the product has users who file tickets, bug reports, or feature requests, and you need a support sy… | 1113 |
| `references/template-adr.md` | G3 Design must record a decision — an interface, NFR budget, or data/security choice — as an ADR (c… | 660 |
| `references/template-onepager.md` | G0 Intake turns a vague idea into a brief the owner can approve in one reply, or when /idea drafts… | 416 |
| `references/template-postmortem.md` | G10 or an incident meets retrospective.md's mandatory-trigger criteria and you must write the blame… | 879 |
| `references/unattended-operating-mode.md` | an agent or a Conductor is granted a block of unattended time to work a backlog — an overnight or m… | 6709 |
| `references/unattended-trackers.md` | deciding whether an open issue's fix is absent, placing an auto-close keyword or a gate-parsed PR-b… | 11803 |
| `references/verification-handback.md` | delegating visual/parity work by measured number, running or finalizing verification, writing a lan… | 15292 |
| `references/verification-handback.md` | delegating visual/parity work by measured number, running or finalizing verification, writing a lan… | 15313 |
| `references/work-tracking.md` | a repo uses a work tracker (Linear first-class | 1051 |
| `references/worktree-isolation.md` | isolating a lane's worktree: stale refs, git stash, out-of-tree scratch, an assignment path, a work… | 4120 |

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/agentic-delivery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >-
license: MIT
metadata:
author: deep-code-review contributors
version: "1.634.0"
version: "1.635.0"
---

# Agentic delivery
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/agentic-delivery/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.634.0
1.635.0
2 changes: 1 addition & 1 deletion .claude/skills/deep-code-review/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing
| `references/lang-jvm.md` | the target contains Java or Kotlin. | 427 |
| `references/lang-python.md` | the target contains Python. | 389 |
| `references/lang-ruby-php.md` | the target contains Ruby or PHP. | 187 |
| `references/lang-shell.md` | the target runs any shell — CI run: steps, hooks, Dockerfile RUN lines, Makefile recipes, or shell… | 2312 |
| `references/lang-shell.md` | the target runs any shell — CI run: steps, hooks, Dockerfile RUN lines, Makefile recipes, or shell… | 2471 |
| `references/lang-sql.md` | the target contains any SQL string, ORM query, or schema/data migration. | 188 |
| `references/language-stack-redflags.md` | Read this to turn the abstract checks in SKILL.md into concrete patterns you can grep for in the ta… | 2520 |
| `references/machine-report.md` | writing Phase 5, when the run is a re-verification of an earlier review (PRIOR <path>), or when ano… | 3266 |
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/deep-code-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >-
license: MIT
metadata:
author: deep-code-review contributors
version: "1.634.0"
version: "1.635.0"
---

# Deep Code Review
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/deep-code-review/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.634.0
1.635.0
2 changes: 1 addition & 1 deletion .claude/skills/idea-critic/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: >-
license: MIT
metadata:
author: deep-code-review contributors
version: "1.634.0"
version: "1.635.0"
---

# Veles: the evil twin (idea-critic)
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/idea-critic/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.634.0
1.635.0
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "perun",
"version": "1.634.0",
"version": "1.635.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": {
Expand Down
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,43 @@
All notable changes to this repository are documented here. Format loosely
follows Keep a Changelog; versioning follows Semantic Versioning.

## [1.635.0] — 2026-11-01

### Fixed
- The stop-starting hook in `.claude/skills/agentic-delivery/scripts/flow_alarm.py` denied any Bash command whose raw text matched `gh pr create`, so at the cap a `grep`, an `echo`, a commit message or an issue body that only named the command was blocked, while a quoted or escaped form such as `gh pr "create"` or `g\h pr create` was not matched at all. A command now counts as naming it when its text, in any case and also read with quotes and backslashes removed and backslash-newline joined, contains `gh pr create`, wherever it sits (in code, in a comment or in a heredoc body). A command that names it is denied (exit 2 at the cap) unless every command in it is a known text tool (echo, printf, cat, grep, rg, head, tail, git and similar, and `gh` outside `pr create`, `pr new` and `api`). A failed `gh`, a timeout and unparseable hook JSON still allow, and `# priority: <reason>` still overrides.
- Every step that drops text before parsing now keeps the text when the input is ambiguous (keep means deny). A `#` is dropped as a comment, and a heredoc body as data, only when the text before it is provably shell code: no open quote, `$'..'`, `${..}`, `$[..]`, `((..))`, backtick or earlier comment, and the one open quote accepted is the `"$(` of a heredoc commit message. A backslash-newline is joined only where a shell joins it, not at the end of a comment. A heredoc needs one plain or fully quoted delimiter word without a backslash, and a delimiter line; a command with no delimiter line is denied, and so is one whose body holds `$(` or a backtick under an unquoted delimiter, whose operator line opens a `(`, a backtick or a quote after the marker (the shell reads it across the body lines), or whose body or operator line holds `)` inside a `$(` (an older shell can end the substitution there). A heredoc beside a comment is not stripped at all: the command is read again with every body in place, so each body line and its delimiter line must pass as commands (a `#` inside a body is data and does not count as the comment).
- A command that holds the text may use `git` only for the log, show, diff, grep, status, blame, commit and add subcommands, in every `git` segment of it, so `git rebase --exec`, `git bisect run`, `git submodule foreach`, `git apply` (a patch can write a config that a later `git` runs), `-c alias.*`, `git grep -O` (also bundled, as in `-nOcmd`), `--output` (it writes a file), `-p`, `--config-env` and any option before the subcommand other than `-C DIR` deny, and so does a `git` word that holds `$` or `{` (an expansion can build an option: `o=--output=gc` then `git log ... $o` writes a config that a later `git status` runs), except the message after `commit -m` or `-am`. These rules, and the file-write rule below, read the whole command, so a mention that sits only in a comment or a heredoc body counts as the text: `git rebase --exec '...' # gh pr create` is denied.
- `gh` is a text tool only outside `pr create`: a `pr` followed anywhere by `create`, its alias `new` or an expansion (`gh pr -R o/r create`, `gh pr --repo=o/r create`, `gh pr new --fill`, `gh -R o/r pr new`), and `gh` with an `api` word (it can open a pull request), an `alias`, `config` or `extension` word, or a `$`, `*`, `?`, `[` or `{` among its first two words, deny. So do `rg --pre` and `rg --hostname-bin`, a `gh` word that spells `create` through an expansion (`create$x`, `create{,}`, `create*`), an unquoted backtick and `$'..'` quoting. The command word must be a listed name exactly, so `./echo` and `/bin/echo` are other programs and deny.
- A variable set before a command passes only when its name is inert: `GIT_AUTHOR_NAME`, `GIT_AUTHOR_EMAIL`, `GIT_AUTHOR_DATE`, `GIT_COMMITTER_NAME`, `GIT_COMMITTER_EMAIL`, `GIT_COMMITTER_DATE`, `LANG`, `LC_*`, `TZ` and `NO_COLOR`. Any other name, such as `GIT_EXTERNAL_DIFF`, `GIT_EDITOR`, `GIT_CONFIG_*` or `PAGER`, is denied, bare or before a command, because an editor, a pager, a config entry or an external diff makes a tool run its value. `printf -v` (any `printf` whose first word is an option or expands) and an expansion that assigns (`${NAME:=..}`) deny for the same reason.
- Operators are read one at a time, so a redirect that follows a newline or `;` skips its own target and no longer hands the next word to the allowlist as a command. A command that names the text and writes a file (an output redirect to anything but `/dev/null` or a file descriptor, including `| cat > file`) is denied, and so is a file written beside a heredoc body that holds the text. `tee` and `uniq` are not on the allowlist.
- Still passing, and named in the hook docstring: `$(which gh) pr create`, `${GH} pr create`, `gh${IFS}pr create`, `gh $'pr' create` and `gh pr cre{a,}te` (the text is not in the command), a script written by an editor tool and run later, a `gh` alias or extension that is already installed (it runs under its own name), and another allowed tool's own exec flag that the hook does not list. Over-denied, the safe direction: a `#` that cannot be proven to start a comment (for example a quoted `#` before a heredoc), a single-quoted backtick or `$(`, a `(`, backtick or open quote after a heredoc marker, a backslash in a delimiter word, a `)` in a heredoc body inside `$(`, a `gh` word with `$`, `*`, `?`, `[` or `{` among its first two (its own `{owner}` placeholder), a mention-only pipeline through a command outside the allowlist, a non-read `git` subcommand such as `push` beside a mention (also one only in a comment), a heredoc written to a file beside a mention, a comment beside a heredoc, an assignment of a variable that is not inert, `printf -v`, `${x:=..}`, a `$` or `{` in a `git` word other than a `commit -m` message (`git log "$base"..HEAD`, `git -C "$dir" log`), and a command word that is a path. This is a flow alarm, not a security boundary.

### Changed
- Tests: `python3 scripts/test_flow_alarm.py` goes from 10 to 22 tests. The new `Gate` class checks 35 commands that only name the command (exit 0 at the cap), 48 shapes that run it and that the old substring test denied on the raw text (exit 2), 125 deny probes (exit 2) that run it, write it where a later command runs it, hide it from a step that drops text before parsing, or spell it in another case, and 26 commands that only name it and are denied anyway (the safe direction). Further tests pin the git subcommand rule, the inert-variable rule, the file-write rule, the residuals above that still pass (5 shapes) and the unparseable-text case.

### Added
- `reaper_lint.py` gains the rule `LINUX_ONLY_NO_PREFLIGHT`: a command line whose command word is `setsid`, `flock` or `timeout` with at least one argument (the first word after shell keywords, `sudo`, `nohup`, `env`, `time`, `nice`, `exec`, `xargs`, `command`, `stdbuf`, `ionice`, `chrt`, `taskset`, `NAME=value` and a Makefile recipe's leading `@`, `-` or `+`), or that has a bare `/proc/<path>` word or redirect source, fires when the file has no platform preflight. A stock macOS install has none of those tools and no procfs, so a gate that calls one exits 127 or reads nothing there and posts FAIL for a check that never ran. A preflight anywhere in the file clears the line: `command -v`, `type`, `hash` or `which` naming that tool; any `uname`; `$OSTYPE` or `${OSTYPE`; a `-e`, `-d`, `-r`, `-f`, `-s` or `-x` test on a path (a `/proc` path clears `/proc`, a path to the tool clears the tool); the tool run as its own probe (`--version` or `--help` as its only argument, or `true` or `:` as its whole payload, as in `timeout 1 true`). Only commands are read: heredoc bodies, multi-line quoted strings, `package.json` text outside `scripts`, text after a `$(...)` inside double quotes, arithmetic, Makefile and justfile assignments, and `/proc` as a `find` operand, a `grep`, `sed` or `awk` pattern, or a word `echo` or `printf` prints do not fire. YAML files, hook configs included, are skipped (a CI step runs on its runner's OS) and the usual `# reaper-lint: allow <reason>` marker suppresses a line. The docstring names the residuals, among them `sh -c` strings, `find -exec` tails, option-argument wrappers such as `nice -n 10`, a preflight in a sourced file or a wrapper function, and preflight clearing that is file-wide and blind to mentions.
- `lang-shell.md` gains one bullet, "A gate that calls a Linux-only tool turns red on macOS for a check that never ran", after the `env` bullet; it ends by routing to the rule.

### Changed
- A repo that opted in to the gate (`DCR_REAPER_LINT=1`) and has an unguarded Linux-only call goes red on upgrade, until it adds a preflight or a `# reaper-lint: allow <reason>` marker.
- Tests: `python3 .claude/skills/deep-code-review/scripts/reaper_lint.py --selftest` (already run by CI) goes from 90 to 185 cases: each Linux-only tool and `/proc` shape that fires, each preflight form that clears it (a tool self-probe included), the command-word handling (wrappers, `NAME=value`, a Makefile recipe's `@`, `-` and `+`, a `for` word list, a `grep` argument, a quote split at `$(`), and each non-command the rule leaves quiet (heredoc and multi-line string bodies, JSON prose, text after a quoted `$(...)`, arithmetic, Makefile and justfile assignments, `/proc` as a pattern, `find` operand or printed word). Every earlier case keeps its result.

size-budget-raise: .claude/skills/deep-code-review/references/lang-shell.md 9249→9885 one bullet on a gate that calls a Linux-only tool

### Fixed
- Nothing bounded a test's run time, so a test that never returned spun a core until someone killed it, and parallel runs stacked up. `scripts/run-ci-tests.sh` now runs each test under a wall-clock bound, `PERUN_TEST_TIMEOUT_S` seconds (default 900; a missing, non-numeric or non-positive value, or one longer than 9 digits, means 900). A test that outlives it is killed and fails like any other failing test, printing `run-ci-tests: FAIL <test> (timed out after <secs>s)`. The alarm kills the test's own process, not child processes it started, so a caller that pipes the runner's output waits for those; a test that arms its own alarm replaces the runner's.
- `scripts/test_security_gates_corpus.py` now bounds each guard check to 5 seconds, and so do the in-process Python blocks of `scripts/test-destructive-guard.sh`. A check that never returns (catastrophic regex backtracking, say) is a `FAIL` line naming the case or block instead of a hang: the corpus test moves on to the next case, and a destructive-guard block skips the rest of its checks. The destructive-guard test's selftest and hook calls are bounded by the runner only. The privacy-gate call in the Python test has a 120 second timeout, and a timeout is a `FAIL` line, not a traceback (the gate's own child processes are not stopped). Every existing case keeps its result.

### Changed
- Tests: `scripts/test_security_gates_corpus.py` checks its own bound against a catastrophic regex, and the new `scripts/ci-tests.d/run-ci-tests-timeout.sh` covers the runner bound: a test that sleeps far past `PERUN_TEST_TIMEOUT_S=2` fails promptly with the timed-out line and its own process is gone, and a bad value falls back to 900.

### Changed
- The commit-tier gate row in agentic-delivery's `references/roles.md` now asks for a `Tests: X/X` count only when a suite printed it on the staged tree. When the suites run later, in a merge train or in CI, the line reads `Tests: none (<where they run>)`: a count copied from an older tree, another branch or the suite's size is an over-claim. The hand-back check (`handback.py`) reads such a line, a `Tests: none (<place>)` with a non-empty parenthetical, as a stated no-run, so that one line is enough; a bare `Tests: none` still fails, and digits inside the parenthetical are no count. A new eval, `tests-line-only-from-a-run`, pins the rule.
size-budget-raise: .claude/skills/agentic-delivery/references/roles.md 27320→27559 the unit-tests row says a count must come from a run on the staged tree and names the `Tests: none (<where they run>)` line for suites that run later
size-budget-raise: .claude/skills/agentic-delivery/references/cost-quality-guardrails.md 16230→16298 the required-trailers sentence names both forms of the Tests: line
size-budget-raise: .claude/skills/agentic-delivery/references/verification-handback.md 61170→61252 the hand-back count sentence names both forms of the Tests: line

## [1.634.0] — 2026-11-01

### Fixed
Expand Down
Loading
Loading