@delegance/claude-autopilot 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/bin/autopilot.js +15 -0
- package/package.json +41 -0
- package/presets/go/autopilot.config.yaml +20 -0
- package/presets/go/rules/go-sql-injection.ts +65 -0
- package/presets/go/stack.md +20 -0
- package/presets/nextjs-supabase/autopilot.config.yaml +29 -0
- package/presets/nextjs-supabase/rules/supabase-rls-bypass.ts +39 -0
- package/presets/nextjs-supabase/stack.md +20 -0
- package/presets/python-fastapi/autopilot.config.yaml +20 -0
- package/presets/python-fastapi/rules/fastapi-missing-auth.ts +50 -0
- package/presets/python-fastapi/stack.md +20 -0
- package/presets/rails-postgres/autopilot.config.yaml +21 -0
- package/presets/rails-postgres/rules/rails-sql-injection.ts +42 -0
- package/presets/rails-postgres/stack.md +20 -0
- package/presets/t3/autopilot.config.yaml +22 -0
- package/presets/t3/rules/t3-server-only.ts +35 -0
- package/presets/t3/stack.md +20 -0
- package/scripts/test-runner.mjs +16 -0
- package/src/adapters/base.ts +19 -0
- package/src/adapters/loader.ts +101 -0
- package/src/adapters/migration-runner/supabase.ts +56 -0
- package/src/adapters/migration-runner/types.ts +36 -0
- package/src/adapters/review-bot-parser/cursor.ts +13 -0
- package/src/adapters/review-bot-parser/declarative-base.ts +64 -0
- package/src/adapters/review-bot-parser/types.ts +9 -0
- package/src/adapters/review-engine/codex.ts +108 -0
- package/src/adapters/review-engine/types.ts +19 -0
- package/src/adapters/vcs-host/github.ts +77 -0
- package/src/adapters/vcs-host/types.ts +44 -0
- package/src/cli/index.ts +110 -0
- package/src/cli/init.ts +88 -0
- package/src/cli/preflight.ts +154 -0
- package/src/cli/run.ts +152 -0
- package/src/cli/watch.ts +169 -0
- package/src/core/.gitkeep +0 -0
- package/src/core/cache/cached-engine.ts +32 -0
- package/src/core/cache/review-cache.ts +70 -0
- package/src/core/chunking/index.ts +82 -0
- package/src/core/config/loader.ts +41 -0
- package/src/core/config/preset-resolver.ts +46 -0
- package/src/core/config/schema.ts +63 -0
- package/src/core/config/types.ts +42 -0
- package/src/core/errors.ts +37 -0
- package/src/core/findings/dedup.ts +14 -0
- package/src/core/findings/types.ts +39 -0
- package/src/core/git/touched-files.ts +51 -0
- package/src/core/index.ts +1 -0
- package/src/core/logging/ndjson-writer.ts +37 -0
- package/src/core/logging/redaction.ts +19 -0
- package/src/core/phases/static-rules.ts +80 -0
- package/src/core/phases/tests.ts +51 -0
- package/src/core/pipeline/review-phase.ts +87 -0
- package/src/core/pipeline/run.ts +80 -0
- package/src/core/runtime/idempotency.ts +6 -0
- package/src/core/runtime/lock.ts +29 -0
- package/src/core/runtime/state.ts +97 -0
- package/src/core/shell.ts +48 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0-alpha.4 (2026-04-21)
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **`autopilot watch`** (`src/cli/watch.ts`) — watches cwd recursively, debounces file changes (default 300ms), re-runs `runAutopilot()` on each batch, prints phase summary per run; Ctrl+C exits cleanly
|
|
8
|
+
- **`--debounce <ms>`** flag on `watch` subcommand
|
|
9
|
+
- **`makeDebouncer`** and **`isIgnored`** exported as pure functions (testable without real watcher)
|
|
10
|
+
- **`files` field** in package.json — excludes tests, restricts publish to `bin/`, `src/`, `presets/`, `scripts/test-runner.mjs`, `CHANGELOG.md`
|
|
11
|
+
- **`private: true` removed** — package is now publishable to npm
|
|
12
|
+
- **`engines.node: >=22.0.0`**, `keywords`, `license`, `repository` added to package.json
|
|
13
|
+
- 12 new watch tests (7 isIgnored + 5 debouncer) → 74 total
|
|
14
|
+
|
|
15
|
+
## 1.0.0-alpha.3 (2026-04-21)
|
|
16
|
+
|
|
17
|
+
### New Features
|
|
18
|
+
|
|
19
|
+
- **`autopilot run`** (`src/cli/run.ts`) — runs the full pipeline from the terminal: loads config, resolves preset, auto-detects changed files via git diff, calls `runAutopilot()`, prints phase summary with inline finding details
|
|
20
|
+
- **`autopilot init`** (`src/cli/init.ts`) — interactive preset scaffold: lists 5 presets, writes `autopilot.config.yaml`, prints next steps
|
|
21
|
+
- **`autopilot preflight`** — re-routes to existing preflight checker
|
|
22
|
+
- **Git touched-files resolver** (`src/core/git/touched-files.ts`) — `resolveGitTouchedFiles()` diffs HEAD~1..HEAD, falls back to `git status` for single-commit repos; configurable `--base` ref
|
|
23
|
+
- **CLI entrypoint** (`src/cli/index.ts`) — dispatches to init/run/preflight subcommands; supports `--base`, `--config`, `--files`, `--dry-run` flags
|
|
24
|
+
- **`bin.autopilot`** restored in `package.json` pointing at the new entrypoint
|
|
25
|
+
- 10 new CLI tests (5 touched-files, 5 run-command) → 62 total
|
|
26
|
+
|
|
27
|
+
## 1.0.0-alpha.2 (2026-04-20)
|
|
28
|
+
|
|
29
|
+
### New Features
|
|
30
|
+
|
|
31
|
+
- **Run pipeline orchestrator** (`src/core/pipeline/run.ts`) — top-level `runAutopilot()` sequences static-rules → tests → review phases with fail-fast semantics and cost accumulation
|
|
32
|
+
- **2-tier chunking** (`src/core/chunking/`) — `auto` strategy selects single-pass (≤8K tokens) or file-level (≤60K); `single-pass` and `file-level` strategies configurable via `reviewStrategy`
|
|
33
|
+
- **Cost visibility** — `costUSD` accumulated across review phase, surfaced in `RunResult.totalCostUSD`; optional `cost.budgetUSD` threshold emits warning and skips remaining chunks when exceeded
|
|
34
|
+
- **Review-engine response cache** (`src/core/cache/`) — file-based SHA-256 cache with configurable TTL; `withCache()` wraps any `ReviewEngine`; atomic writes (tmp+rename)
|
|
35
|
+
- **4 new presets** — `t3` (Next.js + tRPC + Prisma), `rails-postgres`, `python-fastapi`, `go`; each ships a stack.md and at least one stack-specific static rule
|
|
36
|
+
- **20 scenario tests** (`tests/scenarios/run-pipeline.test.ts`) — covers fail-fast, autofix, budget, chunking strategies, preset loading
|
|
37
|
+
|
|
38
|
+
### Fixes
|
|
39
|
+
|
|
40
|
+
- `finalize()` now trusts per-phase status (which accounts for autofixes) instead of re-deriving from raw `allFindings` severity
|
|
41
|
+
- Test script glob changed to `find tests -name '*.test.ts' | xargs` to pick up nested scenario tests
|
|
42
|
+
|
|
43
|
+
## 1.0.0-alpha.1 (2026-04-20)
|
|
44
|
+
|
|
45
|
+
Initial release — core infrastructure: adapter interfaces, config system, preflight CLI, static-rules phase with autofix, tests phase, Codex/GitHub/Supabase/Cursor adapters, nextjs-supabase preset, 32 unit tests.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Ledbetter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# claude-autopilot
|
|
2
|
+
|
|
3
|
+
End-to-end Claude Code pipeline: approved spec → plan → worktree → implementation → migrations → validation → PR → review engine → review-bot triage.
|
|
4
|
+
|
|
5
|
+
**Status: v1.0.0-alpha.1** — core architecture in place, ported adapters (codex, github, supabase, cursor), 1 preset (nextjs-supabase), 32 passing tests. API may change through alpha.
|
|
6
|
+
|
|
7
|
+
Full design spec: `docs/superpowers/specs/2026-04-20-claude-autopilot-v1-design.md`
|
|
8
|
+
Implementation plans: `docs/superpowers/plans/`
|
|
9
|
+
|
|
10
|
+
## Changes in v1.0 (alpha.1)
|
|
11
|
+
|
|
12
|
+
- Four pluggable integration points (ReviewEngine, VcsHost, MigrationRunner, ReviewBotParser) with shared `AdapterBase`
|
|
13
|
+
- YAML config (`autopilot.config.yaml`) replaces `.autopilot/stack.md`
|
|
14
|
+
- Unified `Finding` type across validate + review-bot, with separate `TriageRecord[]` / `FixAttempt[]` history
|
|
15
|
+
- Merged static-rules phase with global re-check after autofix
|
|
16
|
+
- `AutopilotError` taxonomy with per-code retry policy
|
|
17
|
+
- `apiVersion` + `getCapabilities()` on every adapter
|
|
18
|
+
- Real tests phase — runs `testCommand` from config, emits critical finding on failure
|
|
19
|
+
- NDJSON event log with secret redaction
|
|
20
|
+
|
|
21
|
+
## Prerequisites
|
|
22
|
+
|
|
23
|
+
- Node 22+
|
|
24
|
+
- `gh` CLI authenticated
|
|
25
|
+
- `OPENAI_API_KEY` in `.env.local`
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install --save-dev @delegance/claude-autopilot@alpha
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage (alpha.1)
|
|
34
|
+
|
|
35
|
+
CLI surface is limited to `preflight` in alpha.1. `run`, `init`, `validate`, `codex-pr-review`, `bugbot` land in alpha.4.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx autopilot # runs preflight
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Preset quick-start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cp presets/nextjs-supabase/autopilot.config.yaml .
|
|
45
|
+
# Edit adapters / protectedPaths / testCommand as needed
|
|
46
|
+
npx tsx src/cli/preflight.ts
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Roadmap
|
|
50
|
+
|
|
51
|
+
- **alpha.2:** chunking, cost, cache, remaining adapters, 5 presets, 20 scenario tests
|
|
52
|
+
- **alpha.3:** idempotency wiring, concurrency, adapter trust, 60 conformance + 13 safety tests
|
|
53
|
+
- **alpha.4:** full CLI (init, install-github-action, run --resume, etc.) + programmatic API
|
|
54
|
+
- **beta → 1.0.0:** dogfood + npm publish
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT.
|
package/bin/autopilot.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Thin launcher that uses tsx to run the TypeScript CLI entry point.
|
|
3
|
+
// This is what `npx autopilot` resolves to — it hands off to tsx so TypeScript
|
|
4
|
+
// source can execute without a separate build step during alpha.
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const tsxBin = path.resolve(__dirname, '..', 'node_modules', '.bin', 'tsx');
|
|
12
|
+
const entrypoint = path.resolve(__dirname, '..', 'src', 'cli', 'index.ts');
|
|
13
|
+
|
|
14
|
+
const result = spawnSync(tsxBin, [entrypoint, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
15
|
+
process.exit(result.status ?? 1);
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@delegance/claude-autopilot",
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Claude Code automation pipeline: spec → plan → implement → validate → PR",
|
|
6
|
+
"keywords": ["claude", "autopilot", "ai", "pipeline", "code-review", "cli"],
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/axledbetter/claude-autopilot.git"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=22.0.0"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"autopilot": "bin/autopilot.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"bin/",
|
|
20
|
+
"src/",
|
|
21
|
+
"presets/",
|
|
22
|
+
"scripts/test-runner.mjs",
|
|
23
|
+
"CHANGELOG.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node scripts/test-runner.mjs",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"build": "tsc"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/js-yaml": "^4",
|
|
32
|
+
"@types/node": "^22",
|
|
33
|
+
"ajv": "^8",
|
|
34
|
+
"dotenv": ">=16",
|
|
35
|
+
"js-yaml": "^4",
|
|
36
|
+
"minimatch": ">=9",
|
|
37
|
+
"openai": ">=4",
|
|
38
|
+
"tsx": ">=4",
|
|
39
|
+
"typescript": "^5"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
configVersion: 1
|
|
2
|
+
reviewEngine: { adapter: codex }
|
|
3
|
+
vcsHost: { adapter: github }
|
|
4
|
+
reviewBot: { adapter: cursor }
|
|
5
|
+
protectedPaths:
|
|
6
|
+
- "migrations/**"
|
|
7
|
+
- "internal/auth/**"
|
|
8
|
+
- "internal/crypto/**"
|
|
9
|
+
staticRules:
|
|
10
|
+
- hardcoded-secrets
|
|
11
|
+
- go-sql-injection
|
|
12
|
+
testCommand: go test ./...
|
|
13
|
+
thresholds:
|
|
14
|
+
bugbotAutoFix: 85
|
|
15
|
+
bugbotProposePatch: 60
|
|
16
|
+
maxValidateRetries: 3
|
|
17
|
+
reviewStrategy: auto
|
|
18
|
+
chunking:
|
|
19
|
+
smallTierMaxTokens: 8000
|
|
20
|
+
perFileMaxTokens: 32000
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { StaticRule } from '../../../src/core/phases/static-rules.ts';
|
|
2
|
+
import type { Finding } from '../../../src/core/findings/types.ts';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
// fmt.Sprintf directly in a DB call (single-line)
|
|
6
|
+
const SPRINTF_IN_QUERY = /(?:Query|Exec|QueryRow)\w*\s*\([^)]*fmt\.Sprintf/;
|
|
7
|
+
// fmt.Sprintf result assigned to variable then used in DB call (multi-line)
|
|
8
|
+
const SPRINTF_ASSIGN = /\bfmt\.Sprintf\s*\(/;
|
|
9
|
+
const DB_CALL = /(?:Query|Exec|QueryRow)\w*\s*\(\s*(\w+)/;
|
|
10
|
+
|
|
11
|
+
export const goSqlInjectionRule: StaticRule = {
|
|
12
|
+
name: 'go-sql-injection',
|
|
13
|
+
severity: 'critical',
|
|
14
|
+
async check(touchedFiles: string[]): Promise<Finding[]> {
|
|
15
|
+
const findings: Finding[] = [];
|
|
16
|
+
const goFiles = touchedFiles.filter(f => f.endsWith('.go') && !f.endsWith('_test.go'));
|
|
17
|
+
for (const file of goFiles) {
|
|
18
|
+
try {
|
|
19
|
+
const content = await fs.readFile(file, 'utf8');
|
|
20
|
+
const lines = content.split('\n');
|
|
21
|
+
|
|
22
|
+
// Track variables assigned from fmt.Sprintf (multi-line detection)
|
|
23
|
+
const sprintfVars = new Set<string>();
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
const m = line.match(/^\s*(\w+)\s*(?::?=)\s*fmt\.Sprintf\s*\(/);
|
|
26
|
+
if (m) sprintfVars.add(m[1]!);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i]!;
|
|
31
|
+
let hit = false;
|
|
32
|
+
|
|
33
|
+
// Direct inline: db.Query(fmt.Sprintf(...))
|
|
34
|
+
if (SPRINTF_IN_QUERY.test(line)) hit = true;
|
|
35
|
+
|
|
36
|
+
// Indirect: variable from fmt.Sprintf passed to DB call
|
|
37
|
+
if (!hit && sprintfVars.size > 0) {
|
|
38
|
+
const dbMatch = line.match(DB_CALL);
|
|
39
|
+
if (dbMatch && sprintfVars.has(dbMatch[1]!)) hit = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (hit) {
|
|
43
|
+
findings.push({
|
|
44
|
+
id: `go-sql-injection:${file}:${i + 1}`,
|
|
45
|
+
source: 'static-rules',
|
|
46
|
+
severity: 'critical',
|
|
47
|
+
category: 'go-sql-injection',
|
|
48
|
+
file,
|
|
49
|
+
line: i + 1,
|
|
50
|
+
message: 'SQL injection risk: fmt.Sprintf used to build query string',
|
|
51
|
+
suggestion: 'Use parameterized queries: db.Query("WHERE id = $1", id)',
|
|
52
|
+
protectedPath: false,
|
|
53
|
+
createdAt: new Date().toISOString(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// unreadable — skip
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return findings;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default goSqlInjectionRule;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
A Go application with:
|
|
2
|
+
- Go 1.22+, modules (go.mod/go.sum)
|
|
3
|
+
- PostgreSQL via pgx/v5 or database/sql
|
|
4
|
+
- Standard library HTTP or Chi/Gin router
|
|
5
|
+
- testify for tests, sqlmock or pgxmock for DB mocks
|
|
6
|
+
- golang-migrate or goose for schema migrations
|
|
7
|
+
|
|
8
|
+
Conventions:
|
|
9
|
+
- Repository pattern for DB access (internal/repository/)
|
|
10
|
+
- Errors wrapped with fmt.Errorf("...: %w", err)
|
|
11
|
+
- Context propagation on all DB and HTTP calls
|
|
12
|
+
- No global state — dependency injection via constructors
|
|
13
|
+
- pgx.Pool shared across request handlers
|
|
14
|
+
|
|
15
|
+
Things that should flag CRITICAL:
|
|
16
|
+
- fmt.Sprintf in SQL queries: fmt.Sprintf("WHERE id = %d", id)
|
|
17
|
+
- Ignoring sql.ErrNoRows without handling
|
|
18
|
+
- Missing context.Context in DB query calls
|
|
19
|
+
- Goroutines spawned without WaitGroup or context cancellation
|
|
20
|
+
- Secrets in Go source (API keys, DB passwords)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
configVersion: 1
|
|
2
|
+
reviewEngine: { adapter: codex }
|
|
3
|
+
vcsHost: { adapter: github }
|
|
4
|
+
migrationRunner: { adapter: supabase }
|
|
5
|
+
reviewBot: { adapter: cursor }
|
|
6
|
+
protectedPaths:
|
|
7
|
+
- "**/auth/**"
|
|
8
|
+
- "data/deltas/*.sql"
|
|
9
|
+
- "**/payment/**"
|
|
10
|
+
- "**/stripe/**"
|
|
11
|
+
- "**/encryption/**"
|
|
12
|
+
- "lib/supabase/**"
|
|
13
|
+
- "app/api/**/route.ts"
|
|
14
|
+
- "middleware.ts"
|
|
15
|
+
- "utils/supabase/middleware.ts"
|
|
16
|
+
staticRules:
|
|
17
|
+
- hardcoded-secrets
|
|
18
|
+
- npm-audit
|
|
19
|
+
- package-lock-sync
|
|
20
|
+
- supabase-rls-bypass
|
|
21
|
+
thresholds:
|
|
22
|
+
bugbotAutoFix: 85
|
|
23
|
+
bugbotProposePatch: 60
|
|
24
|
+
maxValidateRetries: 3
|
|
25
|
+
reviewStrategy: auto
|
|
26
|
+
chunking:
|
|
27
|
+
smallTierMaxTokens: 8000
|
|
28
|
+
partialReviewTokens: 60000
|
|
29
|
+
perFileMaxTokens: 32000
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import type { Finding } from '../../../src/core/findings/types.ts';
|
|
3
|
+
import type { StaticRule } from '../../../src/core/phases/static-rules.ts';
|
|
4
|
+
|
|
5
|
+
export const supabaseRlsBypassRule: StaticRule = {
|
|
6
|
+
name: 'supabase-rls-bypass',
|
|
7
|
+
severity: 'critical',
|
|
8
|
+
|
|
9
|
+
async check(touchedFiles: string[]): Promise<Finding[]> {
|
|
10
|
+
const findings: Finding[] = [];
|
|
11
|
+
const clientSideFiles = touchedFiles.filter(f =>
|
|
12
|
+
(f.endsWith('.tsx') || f.includes('/components/')) &&
|
|
13
|
+
!f.includes('/api/') && !f.includes('.test.') && !f.includes('__tests__')
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
for (const file of clientSideFiles) {
|
|
17
|
+
let content: string;
|
|
18
|
+
try { content = await fs.readFile(file, 'utf8'); } catch { continue; }
|
|
19
|
+
if (!content.includes('createServiceRoleClient')) continue;
|
|
20
|
+
|
|
21
|
+
const lineIndex = content.split('\n').findIndex(l => l.includes('createServiceRoleClient'));
|
|
22
|
+
findings.push({
|
|
23
|
+
id: `supabase-rls-bypass-${file}-${lineIndex}`,
|
|
24
|
+
source: 'static-rules',
|
|
25
|
+
severity: 'critical',
|
|
26
|
+
category: 'supabase-rls-bypass',
|
|
27
|
+
file,
|
|
28
|
+
line: lineIndex >= 0 ? lineIndex + 1 : undefined,
|
|
29
|
+
message: 'createServiceRoleClient() in client-side code — service role key is a RLS bypass',
|
|
30
|
+
suggestion: 'Use createServerSupabase in a server component or route handler',
|
|
31
|
+
protectedPath: true,
|
|
32
|
+
createdAt: new Date().toISOString(),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return findings;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default supabaseRlsBypassRule;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
A Next.js 16 App Router application with:
|
|
2
|
+
- TypeScript, React 19, Tailwind CSS
|
|
3
|
+
- Supabase (Postgres + RLS on all tables)
|
|
4
|
+
- Jest/Vitest for unit tests, Playwright for E2E
|
|
5
|
+
- OpenAI/Anthropic for LLM calls
|
|
6
|
+
- Optional: Weaviate multi-tenant (every query must include .withTenant())
|
|
7
|
+
|
|
8
|
+
Conventions:
|
|
9
|
+
- DB mutations go through server-side service functions
|
|
10
|
+
- API routes under app/api/ return NextResponse.json
|
|
11
|
+
- Service role key is SERVER-ONLY; never imported in client components
|
|
12
|
+
- Every table has RLS; bypass via createServiceRoleClient() is server-only
|
|
13
|
+
|
|
14
|
+
Things that should flag CRITICAL:
|
|
15
|
+
- createServiceRoleClient() in client-side code
|
|
16
|
+
- Raw SQL in route handlers
|
|
17
|
+
- Missing rate limit on public POST endpoints
|
|
18
|
+
- Weaviate queries without .withTenant()
|
|
19
|
+
- Secrets committed to code
|
|
20
|
+
- RLS policy DROP without replacement
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
configVersion: 1
|
|
2
|
+
reviewEngine: { adapter: codex }
|
|
3
|
+
vcsHost: { adapter: github }
|
|
4
|
+
reviewBot: { adapter: cursor }
|
|
5
|
+
protectedPaths:
|
|
6
|
+
- "alembic/versions/**"
|
|
7
|
+
- "app/core/security.py"
|
|
8
|
+
- "app/core/config.py"
|
|
9
|
+
staticRules:
|
|
10
|
+
- hardcoded-secrets
|
|
11
|
+
- fastapi-missing-auth
|
|
12
|
+
testCommand: pytest -q
|
|
13
|
+
thresholds:
|
|
14
|
+
bugbotAutoFix: 85
|
|
15
|
+
bugbotProposePatch: 60
|
|
16
|
+
maxValidateRetries: 3
|
|
17
|
+
reviewStrategy: auto
|
|
18
|
+
chunking:
|
|
19
|
+
smallTierMaxTokens: 8000
|
|
20
|
+
perFileMaxTokens: 32000
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { StaticRule } from '../../../src/core/phases/static-rules.ts';
|
|
2
|
+
import type { Finding } from '../../../src/core/findings/types.ts';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
// Routes that are decorated with state-mutating HTTP verbs but lack a Depends() auth call
|
|
6
|
+
const MUTATION_DECORATOR = /@(app|router)\.(post|put|patch|delete)\s*\(/i;
|
|
7
|
+
const HAS_AUTH_DEP = /Depends\s*\(\s*(?:get_current_user|require_auth|authenticate|verify_token)/i;
|
|
8
|
+
|
|
9
|
+
export const fastapiMissingAuthRule: StaticRule = {
|
|
10
|
+
name: 'fastapi-missing-auth',
|
|
11
|
+
severity: 'critical',
|
|
12
|
+
async check(touchedFiles: string[]): Promise<Finding[]> {
|
|
13
|
+
const findings: Finding[] = [];
|
|
14
|
+
const pyFiles = touchedFiles.filter(f => f.endsWith('.py'));
|
|
15
|
+
for (const file of pyFiles) {
|
|
16
|
+
try {
|
|
17
|
+
const content = await fs.readFile(file, 'utf8');
|
|
18
|
+
const lines = content.split('\n');
|
|
19
|
+
// Check whether any router-level dependency covers the whole file
|
|
20
|
+
const fileHasRouterAuth = HAS_AUTH_DEP.test(content.slice(0, 2000));
|
|
21
|
+
if (fileHasRouterAuth) continue;
|
|
22
|
+
for (let i = 0; i < lines.length; i++) {
|
|
23
|
+
const line = lines[i]!;
|
|
24
|
+
if (!MUTATION_DECORATOR.test(line)) continue;
|
|
25
|
+
// Scan up to 40 lines after the decorator for per-endpoint auth
|
|
26
|
+
const block = lines.slice(i, i + 40).join('\n');
|
|
27
|
+
if (!HAS_AUTH_DEP.test(block)) {
|
|
28
|
+
findings.push({
|
|
29
|
+
id: `fastapi-missing-auth:${file}:${i + 1}`,
|
|
30
|
+
source: 'static-rules',
|
|
31
|
+
severity: 'critical',
|
|
32
|
+
category: 'fastapi-missing-auth',
|
|
33
|
+
file,
|
|
34
|
+
line: i + 1,
|
|
35
|
+
message: 'Mutation endpoint may be missing auth dependency',
|
|
36
|
+
suggestion: 'Add current_user: User = Depends(get_current_user) to the route parameters',
|
|
37
|
+
protectedPath: false,
|
|
38
|
+
createdAt: new Date().toISOString(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
// unreadable — skip
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return findings;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default fastapiMissingAuthRule;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
A Python FastAPI application with:
|
|
2
|
+
- FastAPI, Python 3.11+, SQLAlchemy 2.x (async), Alembic migrations
|
|
3
|
+
- Pydantic v2 for request/response models and settings
|
|
4
|
+
- PostgreSQL via asyncpg driver
|
|
5
|
+
- JWT authentication via python-jose or authlib
|
|
6
|
+
- pytest for tests
|
|
7
|
+
|
|
8
|
+
Conventions:
|
|
9
|
+
- Dependency injection via FastAPI Depends()
|
|
10
|
+
- DB session injected per-request (not global)
|
|
11
|
+
- Pydantic BaseSettings for all config (no os.environ direct access)
|
|
12
|
+
- Alembic for all schema changes (never raw CREATE TABLE in code)
|
|
13
|
+
- Async endpoints where possible
|
|
14
|
+
|
|
15
|
+
Things that should flag CRITICAL:
|
|
16
|
+
- f-string SQL: f"SELECT * FROM users WHERE id = {user_id}"
|
|
17
|
+
- Unauthenticated POST/PUT/DELETE endpoints on non-public paths
|
|
18
|
+
- Secrets hardcoded in Python files
|
|
19
|
+
- os.environ direct access instead of Pydantic settings
|
|
20
|
+
- Synchronous DB calls in async endpoints (blocking event loop)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
configVersion: 1
|
|
2
|
+
reviewEngine: { adapter: codex }
|
|
3
|
+
vcsHost: { adapter: github }
|
|
4
|
+
reviewBot: { adapter: cursor }
|
|
5
|
+
protectedPaths:
|
|
6
|
+
- "db/migrate/**"
|
|
7
|
+
- "config/credentials.yml.enc"
|
|
8
|
+
- "config/master.key"
|
|
9
|
+
- "app/models/user.rb"
|
|
10
|
+
staticRules:
|
|
11
|
+
- hardcoded-secrets
|
|
12
|
+
- rails-sql-injection
|
|
13
|
+
testCommand: bundle exec rspec --format progress
|
|
14
|
+
thresholds:
|
|
15
|
+
bugbotAutoFix: 85
|
|
16
|
+
bugbotProposePatch: 60
|
|
17
|
+
maxValidateRetries: 3
|
|
18
|
+
reviewStrategy: auto
|
|
19
|
+
chunking:
|
|
20
|
+
smallTierMaxTokens: 8000
|
|
21
|
+
perFileMaxTokens: 32000
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StaticRule } from '../../../src/core/phases/static-rules.ts';
|
|
2
|
+
import type { Finding } from '../../../src/core/findings/types.ts';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
const INTERPOLATED_WHERE = /\.where\s*\(\s*["'`][^"'`]*#\{/;
|
|
6
|
+
const INTERPOLATED_ORDER = /\.order\s*\(\s*["'`][^"'`]*#\{/;
|
|
7
|
+
|
|
8
|
+
export const railsSqlInjectionRule: StaticRule = {
|
|
9
|
+
name: 'rails-sql-injection',
|
|
10
|
+
severity: 'critical',
|
|
11
|
+
async check(touchedFiles: string[]): Promise<Finding[]> {
|
|
12
|
+
const findings: Finding[] = [];
|
|
13
|
+
const rubyFiles = touchedFiles.filter(f => f.endsWith('.rb'));
|
|
14
|
+
for (const file of rubyFiles) {
|
|
15
|
+
try {
|
|
16
|
+
const lines = (await fs.readFile(file, 'utf8')).split('\n');
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const line = lines[i]!;
|
|
19
|
+
if (INTERPOLATED_WHERE.test(line) || INTERPOLATED_ORDER.test(line)) {
|
|
20
|
+
findings.push({
|
|
21
|
+
id: `rails-sql-injection:${file}:${i + 1}`,
|
|
22
|
+
source: 'static-rules',
|
|
23
|
+
severity: 'critical',
|
|
24
|
+
category: 'rails-sql-injection',
|
|
25
|
+
file,
|
|
26
|
+
line: i + 1,
|
|
27
|
+
message: 'SQL injection risk: string interpolation in Active Record query',
|
|
28
|
+
suggestion: 'Use parameterized queries: .where("name = ?", params[:name])',
|
|
29
|
+
protectedPath: false,
|
|
30
|
+
createdAt: new Date().toISOString(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// unreadable — skip
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return findings;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default railsSqlInjectionRule;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
A Ruby on Rails 7 application with:
|
|
2
|
+
- Rails 7.x, Ruby 3.2+, PostgreSQL
|
|
3
|
+
- RSpec for tests (spec/), FactoryBot for fixtures
|
|
4
|
+
- Active Record ORM (migrations in db/migrate/)
|
|
5
|
+
- Devise or similar for authentication
|
|
6
|
+
- Turbo/Stimulus for frontend
|
|
7
|
+
|
|
8
|
+
Conventions:
|
|
9
|
+
- Fat models, thin controllers
|
|
10
|
+
- Service objects in app/services/ for complex business logic
|
|
11
|
+
- Migrations are irreversible destructive operations — always write down/up
|
|
12
|
+
- Strong parameters in controllers for all form inputs
|
|
13
|
+
- Background jobs in app/jobs/ via Sidekiq
|
|
14
|
+
|
|
15
|
+
Things that should flag CRITICAL:
|
|
16
|
+
- Raw SQL interpolation: User.where("name = '#{params[:name]}'")
|
|
17
|
+
- Mass assignment without strong params
|
|
18
|
+
- Secrets in application.rb or initializers (not credentials.yml.enc)
|
|
19
|
+
- Missing foreign key constraints in migrations
|
|
20
|
+
- N+1 queries (missing .includes() on associations)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
configVersion: 1
|
|
2
|
+
reviewEngine: { adapter: codex }
|
|
3
|
+
vcsHost: { adapter: github }
|
|
4
|
+
reviewBot: { adapter: cursor }
|
|
5
|
+
protectedPaths:
|
|
6
|
+
- "src/server/auth/**"
|
|
7
|
+
- "prisma/migrations/**"
|
|
8
|
+
- "src/env.js"
|
|
9
|
+
- "src/server/db.ts"
|
|
10
|
+
staticRules:
|
|
11
|
+
- hardcoded-secrets
|
|
12
|
+
- npm-audit
|
|
13
|
+
- package-lock-sync
|
|
14
|
+
- t3-server-only
|
|
15
|
+
thresholds:
|
|
16
|
+
bugbotAutoFix: 85
|
|
17
|
+
bugbotProposePatch: 60
|
|
18
|
+
maxValidateRetries: 3
|
|
19
|
+
reviewStrategy: auto
|
|
20
|
+
chunking:
|
|
21
|
+
smallTierMaxTokens: 8000
|
|
22
|
+
perFileMaxTokens: 32000
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { StaticRule } from '../../../src/core/phases/static-rules.ts';
|
|
2
|
+
import type { Finding } from '../../../src/core/findings/types.ts';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
export const t3ServerOnlyRule: StaticRule = {
|
|
6
|
+
name: 't3-server-only',
|
|
7
|
+
severity: 'critical',
|
|
8
|
+
async check(touchedFiles: string[]): Promise<Finding[]> {
|
|
9
|
+
const findings: Finding[] = [];
|
|
10
|
+
for (const file of touchedFiles) {
|
|
11
|
+
if (!file.includes('src/server/') && !file.endsWith('.server.ts')) continue;
|
|
12
|
+
try {
|
|
13
|
+
const content = await fs.readFile(file, 'utf8');
|
|
14
|
+
if (!content.includes("'server-only'") && !content.includes('"server-only"')) {
|
|
15
|
+
findings.push({
|
|
16
|
+
id: `t3-server-only:${file}`,
|
|
17
|
+
source: 'static-rules',
|
|
18
|
+
severity: 'critical',
|
|
19
|
+
category: 't3-server-only',
|
|
20
|
+
file,
|
|
21
|
+
message: 'Server utility missing `server-only` import guard',
|
|
22
|
+
suggestion: "Add `import 'server-only'` at the top of this file",
|
|
23
|
+
protectedPath: false,
|
|
24
|
+
createdAt: new Date().toISOString(),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// file unreadable — skip
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return findings;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default t3ServerOnlyRule;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
A T3 Stack application with:
|
|
2
|
+
- Next.js App Router, TypeScript, Tailwind CSS
|
|
3
|
+
- tRPC v11 for type-safe APIs (router in src/server/api/)
|
|
4
|
+
- Prisma ORM with Postgres (migrations in prisma/migrations/)
|
|
5
|
+
- NextAuth.js for authentication
|
|
6
|
+
- Zod for input validation
|
|
7
|
+
|
|
8
|
+
Conventions:
|
|
9
|
+
- tRPC procedures live in src/server/api/routers/
|
|
10
|
+
- DB access only through Prisma client in src/server/db.ts
|
|
11
|
+
- Server-only code uses `server-only` package import guard
|
|
12
|
+
- env.js validates all env vars at startup (Zod schema)
|
|
13
|
+
- Client components must not import from src/server/
|
|
14
|
+
|
|
15
|
+
Things that should flag CRITICAL:
|
|
16
|
+
- Direct Prisma client usage in client components
|
|
17
|
+
- Missing `server-only` guard on server utilities
|
|
18
|
+
- tRPC procedure without input validation (Zod)
|
|
19
|
+
- Secrets hardcoded in source
|
|
20
|
+
- Prisma $executeRaw with unsanitized user input
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Cross-platform test runner using Node 22 built-in fs.glob
|
|
2
|
+
import { glob } from 'node:fs/promises';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
const files = [];
|
|
6
|
+
for await (const f of glob('tests/**/*.test.ts')) {
|
|
7
|
+
files.push(f);
|
|
8
|
+
}
|
|
9
|
+
files.sort();
|
|
10
|
+
|
|
11
|
+
const result = spawnSync(
|
|
12
|
+
'node',
|
|
13
|
+
['--test', '--import', 'tsx', ...files],
|
|
14
|
+
{ stdio: 'inherit', shell: false },
|
|
15
|
+
);
|
|
16
|
+
process.exit(result.status ?? 1);
|