Skip to content

Commit 669a5ac

Browse files
grichaclaude
andcommitted
test: multi-pass pipeline with linter-rule-judge
- Point warden at feat/multi-pass-pipeline branch - Add phase-2 linter-rule-judge skill - Add bait code with eval(), new Function(), execSync interpolation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent eb6a83c commit 669a5ac

File tree

4 files changed

+99
-7
lines changed

4 files changed

+99
-7
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
name: linter-rule-judge
3+
description: Generate lint rules that replace AI findings with deterministic checks
4+
allowed-tools: Read Grep Glob
5+
---
6+
7+
# Linter Rule Judge
8+
9+
You are a second-pass skill. Your job: turn AI findings into deterministic lint rules.
10+
11+
The bar is high. Only propose a rule when you can guarantee it catches the exact pattern through AST structure, not heuristics. A rule that fires on `eval(anything)` is deterministic. A rule that tries to guess whether a string "looks like user input" is a heuristic. Only the first kind belongs here.
12+
13+
## Step 1: Detect the linter
14+
15+
Before evaluating any findings, determine what linter system the project uses. Use `Glob` and `Read` to check for:
16+
17+
- `.oxlintrc.json` / `oxlint.json` (oxlint)
18+
- `.eslintrc.*` / `eslint.config.*` / `"eslintConfig"` in package.json (eslint)
19+
- `clippy.toml` / `.clippy.toml` (Rust clippy)
20+
- `.pylintrc` / `pyproject.toml` with `[tool.pylint]` (pylint)
21+
- `.flake8` / `setup.cfg` with `[flake8]` (flake8)
22+
- `biome.json` / `biome.jsonc` (biome)
23+
24+
Also check whether the linter supports custom/plugin rules:
25+
- oxlint: check for `jsPlugins` in config and an existing plugins directory
26+
- eslint: check for local plugins or `eslint-plugin-*` deps
27+
- biome: no custom rule support, existing rules only
28+
29+
If the project has no linter, return an empty findings array. You cannot propose rules for a tool that doesn't exist.
30+
31+
## Step 2: Evaluate prior findings
32+
33+
For each prior finding that has a `suggestedFix`, ask: can this exact pattern be caught by a deterministic AST check in the linter we found?
34+
35+
**Deterministic means:**
36+
- The rule matches a specific syntactic pattern in the AST (node type, property name, call signature)
37+
- Zero or near-zero false positives -- if the AST matches, the code is wrong
38+
- No guessing about intent, data flow, variable contents, or runtime behavior
39+
- Examples: banning `eval()`, requiring `===` over `==`, disallowing `execSync` with template literal arguments, flagging `new Function()` calls
40+
41+
**Not deterministic (skip these):**
42+
- "This variable might contain user input" (data flow analysis)
43+
- "This function name suggests it handles sensitive data" (naming heuristic)
44+
- "This pattern is usually a bug" (probabilistic)
45+
- Anything that requires understanding what a variable contains at runtime
46+
47+
**Only report if ALL of these are true:**
48+
1. You can identify a specific existing rule by name, OR you can write a complete working custom rule
49+
2. The rule is deterministic: it matches AST structure, not heuristics
50+
3. The project's linter actually supports this
51+
52+
## What to skip silently
53+
54+
- Findings without `suggestedFix`
55+
- Patterns that need type information the linter can't access, cross-file context, or runtime knowledge
56+
- Patterns where the rule would need to guess or use heuristics
57+
- Cases where you're not confident the rule is correct and complete
58+
59+
Return an empty findings array when nothing qualifies. That's the expected common case.
60+
61+
## Output format
62+
63+
For existing rules:
64+
- **title**: The rule name (e.g., `no-eval`)
65+
- **severity**: `low`
66+
- **description**: One sentence: what AST pattern it matches
67+
- **suggestedFix**: A diff enabling the rule in the project's linter config
68+
- **location**: Same as the original finding
69+
70+
For custom rules:
71+
- **title**: `custom: <rule-name>` (e.g., `custom: no-execsync-interpolation`)
72+
- **severity**: `low`
73+
- **description**: One sentence: what AST pattern it matches
74+
- **suggestedFix**: The complete rule implementation file AND the config diff to wire it up. Match the conventions of existing custom rules in the project.
75+
- **location**: Same as the original finding

.github/workflows/warden.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ jobs:
2020
fetch-depth: 0
2121
- name: Strip newlines from OAuth token
2222
run: echo "CLAUDE_CODE_OAUTH_TOKEN=$(printf '%s' "$CLAUDE_CODE_OAUTH_TOKEN" | tr -d '\n\r\t ')" >> "$GITHUB_ENV"
23-
- uses: getsentry/warden@v0
23+
- uses: getsentry/warden@feat/multi-pass-pipeline

src/config/loader.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,24 @@ export async function loadAgentConfig(configDir?: string): Promise<AgentConfig>
120120
}
121121
}
122122

123+
export async function runConfigScript(scriptName: string, configDir?: string): Promise<string> {
124+
const { execSync } = await import('child_process');
125+
const scriptDir = path.join(getConfigDir(configDir), 'scripts');
126+
const result = execSync(`${scriptDir}/${scriptName}`, {
127+
encoding: 'utf-8',
128+
timeout: 30000,
129+
});
130+
return result;
131+
}
132+
133+
export function evaluateConfigExpression(expr: string): unknown {
134+
return eval(expr);
135+
}
136+
137+
export function createDynamicHandler(code: string): Function {
138+
return new Function('config', code);
139+
}
140+
123141
export async function saveAgentConfig(config: AgentConfig, configDir?: string): Promise<void> {
124142
const dir = getConfigDir(configDir);
125143
await ensureConfigDir(dir);

warden.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ actions = ["opened", "synchronize", "reopened"]
1616
type = "local"
1717

1818
[[skills]]
19-
name = "react-best-practices"
20-
paths = ["**/*.tsx", "**/*.jsx"]
21-
remote = "vercel-labs/agent-skills"
19+
name = "code-simplifier"
20+
paths = ["src/**", "web/**", "mobile/**"]
21+
remote = "getsentry/skills"
2222

2323
[[skills.triggers]]
2424
type = "pull_request"
@@ -28,9 +28,8 @@ actions = ["opened", "synchronize", "reopened"]
2828
type = "local"
2929

3030
[[skills]]
31-
name = "code-simplifier"
32-
paths = ["src/**", "web/**", "mobile/**"]
33-
remote = "getsentry/skills"
31+
name = "linter-rule-judge"
32+
phase = 2
3433

3534
[[skills.triggers]]
3635
type = "pull_request"

0 commit comments

Comments
 (0)