@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/LICENSE +21 -0
  3. package/README.md +58 -0
  4. package/bin/autopilot.js +15 -0
  5. package/package.json +41 -0
  6. package/presets/go/autopilot.config.yaml +20 -0
  7. package/presets/go/rules/go-sql-injection.ts +65 -0
  8. package/presets/go/stack.md +20 -0
  9. package/presets/nextjs-supabase/autopilot.config.yaml +29 -0
  10. package/presets/nextjs-supabase/rules/supabase-rls-bypass.ts +39 -0
  11. package/presets/nextjs-supabase/stack.md +20 -0
  12. package/presets/python-fastapi/autopilot.config.yaml +20 -0
  13. package/presets/python-fastapi/rules/fastapi-missing-auth.ts +50 -0
  14. package/presets/python-fastapi/stack.md +20 -0
  15. package/presets/rails-postgres/autopilot.config.yaml +21 -0
  16. package/presets/rails-postgres/rules/rails-sql-injection.ts +42 -0
  17. package/presets/rails-postgres/stack.md +20 -0
  18. package/presets/t3/autopilot.config.yaml +22 -0
  19. package/presets/t3/rules/t3-server-only.ts +35 -0
  20. package/presets/t3/stack.md +20 -0
  21. package/scripts/test-runner.mjs +16 -0
  22. package/src/adapters/base.ts +19 -0
  23. package/src/adapters/loader.ts +101 -0
  24. package/src/adapters/migration-runner/supabase.ts +56 -0
  25. package/src/adapters/migration-runner/types.ts +36 -0
  26. package/src/adapters/review-bot-parser/cursor.ts +13 -0
  27. package/src/adapters/review-bot-parser/declarative-base.ts +64 -0
  28. package/src/adapters/review-bot-parser/types.ts +9 -0
  29. package/src/adapters/review-engine/codex.ts +108 -0
  30. package/src/adapters/review-engine/types.ts +19 -0
  31. package/src/adapters/vcs-host/github.ts +77 -0
  32. package/src/adapters/vcs-host/types.ts +44 -0
  33. package/src/cli/index.ts +110 -0
  34. package/src/cli/init.ts +88 -0
  35. package/src/cli/preflight.ts +154 -0
  36. package/src/cli/run.ts +152 -0
  37. package/src/cli/watch.ts +169 -0
  38. package/src/core/.gitkeep +0 -0
  39. package/src/core/cache/cached-engine.ts +32 -0
  40. package/src/core/cache/review-cache.ts +70 -0
  41. package/src/core/chunking/index.ts +82 -0
  42. package/src/core/config/loader.ts +41 -0
  43. package/src/core/config/preset-resolver.ts +46 -0
  44. package/src/core/config/schema.ts +63 -0
  45. package/src/core/config/types.ts +42 -0
  46. package/src/core/errors.ts +37 -0
  47. package/src/core/findings/dedup.ts +14 -0
  48. package/src/core/findings/types.ts +39 -0
  49. package/src/core/git/touched-files.ts +51 -0
  50. package/src/core/index.ts +1 -0
  51. package/src/core/logging/ndjson-writer.ts +37 -0
  52. package/src/core/logging/redaction.ts +19 -0
  53. package/src/core/phases/static-rules.ts +80 -0
  54. package/src/core/phases/tests.ts +51 -0
  55. package/src/core/pipeline/review-phase.ts +87 -0
  56. package/src/core/pipeline/run.ts +80 -0
  57. package/src/core/runtime/idempotency.ts +6 -0
  58. package/src/core/runtime/lock.ts +29 -0
  59. package/src/core/runtime/state.ts +97 -0
  60. 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.
@@ -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);