Skip to content

Commit 4800661

Browse files
authored
Merge pull request #2 from Casa/feat/rule-filter-command
Add optional `filter` command for second-stage rule applicability
2 parents bf2f294 + 277f7bc commit 4800661

18 files changed

Lines changed: 662 additions & 50 deletions

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- Optional `filter` front-matter field: a second-stage applicability command run
12+
after a rule's globs match, with the matched paths appended as arguments.
13+
grep-style exit codes (`0` applies, `1` skips, anything else / timeout / missing
14+
command fails open and applies). Disable with `--no-filters` / `runFilters:
15+
false`; bound with `--filter-timeout` / `filterTimeoutMs` (default 10000 ms).
16+
Filter errors surface in the new `ReviewResult.warnings`. New exports:
17+
`makeFilterExecutor`, `FilterResult`, `FilterExecutor`, `DiscoverOptions`.
18+
919
## [0.1.0] - 2026-06-26
1020

1121
### Added

Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ COPY tsconfig.json tsconfig.build.json ./
2727
COPY src ./src
2828
RUN yarn build
2929

30-
# Scripts, sample rules, and entrypoint.
30+
# Scripts, sample rules, unit tests, and entrypoint. The `test/` dir is needed
31+
# for the `test` command (vitest); vitest is a devDependency already installed
32+
# above by `yarn install --frozen-lockfile`.
3133
COPY scripts ./scripts
3234
COPY examples ./examples
35+
COPY test ./test
3336
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
3437
RUN chmod +x /usr/local/bin/entrypoint.sh scripts/*.sh
3538

README.md

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,47 @@ globs:
5353
Use the project logger instead of `console.log` in non-test source files.
5454
```
5555

56-
| Field | Description |
57-
| ------------- | ----------------------------------------------------------------------------- |
58-
| `description` | Display name (falls back to the filename) |
59-
| `globs` | Inline list or YAML list; `!` negates. A rule with no globs is never applied. |
60-
| `reviewSkip` | If `true`, the rule is parsed but excluded from review |
56+
| Field | Description |
57+
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
58+
| `description` | Display name (falls back to the filename) |
59+
| `globs` | Inline list or YAML list; `!` negates. A rule with no globs is never applied. |
60+
| `reviewSkip` | If `true`, the rule is parsed but excluded from review |
61+
| `filter` | Optional command run after a glob match to decide if the rule applies. Matched paths are appended as args. See below. Absent ⇒ no extra check. |
62+
63+
### Filtering beyond globs
64+
65+
Globs match file _paths_. A `filter` command lets a rule also depend on file
66+
_content_ or relationships between changes. After a rule's globs select the
67+
changed files, its `filter` runs once with those paths appended as arguments,
68+
and decides applicability by exit code:
69+
70+
| Exit code | Meaning |
71+
| ---------------------------------------------- | --------------------------------------------------- |
72+
| `0` | Filter passed — the rule applies |
73+
| `1` | Clean rejection — the rule is skipped |
74+
| anything else, a missing command, or a timeout | Error — fail-open: the rule applies (and is warned) |
75+
76+
```markdown
77+
---
78+
description: No raw SQL in repositories
79+
globs:
80+
- 'src/repositories/**/*.ts'
81+
filter: "grep -ilq 'select \\|insert \\|update '"
82+
---
83+
84+
Use the query builder, not raw SQL strings, in repository classes.
85+
```
86+
87+
Here `grep` exits `0` if any matched file contains a SQL keyword (rule applies),
88+
`1` if none do (skipped). The command can be inline (with flags) or a script;
89+
see [`examples/filters/`](./examples/filters/) and [`examples/rules/no-raw-sql.md`](./examples/rules/no-raw-sql.md).
90+
91+
> **Filter commands execute with your privileges.** Treat the rules directory as
92+
> trusted code, like a git hook. When reviewing an untrusted diff (e.g. a fork PR
93+
> that could edit a `filter`), pass `--no-filters`. Note also that a filter reads
94+
> files from the working tree — for `--diff <range>` reviews those may differ from
95+
> the diffed revision, so content filters should query git (`git grep <range>`)
96+
> rather than read the tree.
6197
6298
## CLI
6399

@@ -78,12 +114,18 @@ agent-rules --working-tree --transport codex
78114

79115
# Force an explicit transport command (any stdin->stdout program)
80116
agent-rules --working-tree --exec "claude -p --output-format json"
117+
118+
# Review an untrusted diff without executing any rule `filter` commands
119+
agent-rules --diff origin/main...HEAD --no-filters
81120
```
82121

83122
Diff sources (exactly one): `--working-tree`, `--staged`, `--diff <range>`.
84123
Run `agent-rules --help` for all options. Exit codes: `0` clean, `1` blocking
85124
findings, `2` error.
86125

126+
Rule `filter` commands run by default (including under `--list`); `--no-filters`
127+
disables them and `--filter-timeout <ms>` bounds each one (default `10000`).
128+
87129
## Library
88130

89131
```ts

docker/README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,20 @@ docker build -t agent-rules .
1515
# or: yarn docker:build
1616
```
1717

18-
## Hermetic smoke test (no credentials)
18+
## Hermetic tests (no credentials)
1919

20-
The default command runs `scripts/smoke.sh` — a fake `--exec` transport, so it
21-
needs no agent login and is safe anywhere (also what CI runs).
20+
These need no agent login and are safe to run anywhere (also what CI runs).
2221

2322
```sh
23+
# End-to-end smoke test (fake --exec transport) — the default command
2424
docker run --rm agent-rules
2525
# or: yarn docker:smoke
26+
27+
# Unit test suite (vitest)
28+
docker run --rm agent-rules test
29+
30+
# Both: unit tests + smoke
31+
docker run --rm agent-rules check
2632
```
2733

2834
## Live review against a real agent
@@ -65,6 +71,8 @@ docker run --rm \
6571

6672
## Commands
6773

68-
The entrypoint accepts: `smoke` (default), `verify [args]`, `demo [args]`,
69-
`cli [args]`, or any other command to exec directly. `verify`/`demo` forward
70-
extra args to the CLI, e.g. `--transport codex` or `--output json`.
74+
The entrypoint accepts: `smoke` (default), `test`, `check`, `verify [args]`,
75+
`demo [args]`, `cli [args]`, or any other command to exec directly. `smoke`,
76+
`test`, and `check` are hermetic (no credentials); `verify`/`demo` need a real
77+
agent and forward extra args to the CLI, e.g. `--transport codex` or
78+
`--output json`.

docker/entrypoint.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# Container entrypoint. Dispatches to the project scripts / CLI.
44
#
55
# smoke hermetic end-to-end test (fake transport, no creds) [default]
6+
# test unit test suite (vitest), no creds
7+
# check unit tests + smoke (full hermetic test pass), no creds
68
# verify [args] live review against a real agent (needs creds);
79
# extra args pass through, e.g. `verify --transport codex`
810
# demo [args] review the bundled examples/ sample against a real agent
@@ -15,6 +17,13 @@ case "$cmd" in
1517
smoke)
1618
exec bash scripts/smoke.sh
1719
;;
20+
test)
21+
exec yarn test
22+
;;
23+
check)
24+
yarn test
25+
exec bash scripts/smoke.sh
26+
;;
1827
verify)
1928
shift
2029
exec bash scripts/verify-transport.sh "$@"

0 commit comments

Comments
 (0)