@bradtaylorsf/alpha-loop 1.1.2 → 1.2.0

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.
@@ -1,124 +0,0 @@
1
- # Playwright E2E Testing Skill
2
-
3
- ## Overview
4
-
5
- This skill teaches agents how to write and maintain Playwright end-to-end tests for the Alpha Loop dashboard. E2E tests validate that the app works visually in a browser, catching issues that pass unit tests but break the UI.
6
-
7
- ## Test Configuration
8
-
9
- - **Test directory**: `tests/e2e/`
10
- - **Config file**: `playwright.config.ts`
11
- - **E2E port**: `4002` (isolated from dev 4001 and prod 4000)
12
- - **Database**: In-memory SQLite (`DATABASE_PATH=:memory:`)
13
- - **Mock API**: `MOCK_CLAUDE_API=true` to avoid real AI calls
14
- - **Browser**: Chromium only
15
- - **Run command**: `pnpm test:e2e`
16
-
17
- ## Writing E2E Tests
18
-
19
- ### File naming
20
-
21
- - Place tests in `tests/e2e/`
22
- - Use `.spec.ts` suffix (e.g., `dashboard.spec.ts`)
23
-
24
- ### Test structure
25
-
26
- ```typescript
27
- import { test, expect } from "@playwright/test";
28
-
29
- test.describe("Feature Name", () => {
30
- test("describes what it validates", async ({ page }) => {
31
- await page.goto("/");
32
- // Use waitForSelector or expect with auto-waiting
33
- await expect(page.locator("h1")).toHaveText("Alpha Loop");
34
- });
35
- });
36
- ```
37
-
38
- ### Best practices for reliability
39
-
40
- 1. **Use Playwright auto-waiting** — `expect(locator).toBeVisible()` auto-waits up to the configured timeout
41
- 2. **Avoid fixed timeouts** — use `waitForSelector`, `waitForResponse`, or `expect` assertions instead of `page.waitForTimeout()`
42
- 3. **Use data-testid attributes** — prefer `[data-testid='config-view']` over fragile CSS selectors
43
- 4. **Clean up state** — each test should work in isolation, don't depend on test execution order
44
- 5. **Seed test data via API** — create test data by hitting API endpoints, not by manipulating the DB directly
45
-
46
- ### Seeding test data
47
-
48
- ```typescript
49
- test("shows runs created via API", async ({ request, page }) => {
50
- // Create test data via API
51
- await request.post("/api/runs", {
52
- data: {
53
- issue_number: 42,
54
- issue_title: "Test issue",
55
- agent: "claude",
56
- model: "sonnet",
57
- },
58
- });
59
-
60
- // Navigate and verify
61
- await page.goto("/");
62
- await page.click("button:has-text('Run History')");
63
- await expect(page.locator("text=Test issue")).toBeVisible();
64
- });
65
- ```
66
-
67
- ### Testing SSE / Live View
68
-
69
- ```typescript
70
- test("live view connects to SSE stream", async ({ page }) => {
71
- await page.goto("/");
72
- // The EventSource connection happens automatically
73
- await expect(page.locator("text=Connected")).toBeVisible({ timeout: 5000 });
74
- });
75
- ```
76
-
77
- ### Common selectors
78
-
79
- | Element | Selector |
80
- |---------|----------|
81
- | App title | `h1` (text: "Alpha Loop") |
82
- | Tab buttons | `button:has-text('Live View')`, `button:has-text('Run History')`, `button:has-text('Config')` |
83
- | Live log | `[data-testid='live-log']` |
84
- | Config view | `[data-testid='config-view']` |
85
- | Config editor | `[data-testid='config-editor']` |
86
- | Status badge | `[data-testid='status-badge']` |
87
- | History table | `table` within Run History tab |
88
-
89
- ## Running E2E Tests
90
-
91
- ```bash
92
- # Run all E2E tests
93
- pnpm test:e2e
94
-
95
- # Run with headed browser (for debugging)
96
- npx playwright test --headed
97
-
98
- # Run a specific test file
99
- npx playwright test tests/e2e/dashboard.spec.ts
100
-
101
- # Show test report after failure
102
- npx playwright show-report
103
- ```
104
-
105
- ## Environment Variables
106
-
107
- | Variable | Default | Description |
108
- |----------|---------|-------------|
109
- | `SKIP_E2E` | `false` | Skip Playwright in the loop pipeline |
110
- | `DATABASE_PATH` | file path | Set to `:memory:` for in-memory SQLite |
111
- | `MOCK_CLAUDE_API` | `false` | Mock Claude API calls |
112
- | `PORT` | `4000` | Server port (E2E uses `4002`) |
113
-
114
- ## Integration with Loop Pipeline
115
-
116
- E2E tests run after unit/API tests in the loop pipeline:
117
-
118
- 1. Implement
119
- 2. Run unit/API tests (with retry)
120
- 3. **Run Playwright E2E tests** (with same retry loop)
121
- 4. Code review
122
- 5. Create PR
123
-
124
- If `SKIP_E2E=true`, the E2E step is skipped. E2E test failures trigger the same retry loop as unit tests — the agent receives the error output and attempts to fix the issue.
@@ -1,229 +0,0 @@
1
- ---
2
- name: sqlite-patterns
3
- description: SQLite database patterns using better-sqlite3. Synchronous API, file-based storage, great for single-user apps and development.
4
- ---
5
-
6
- # SQLite Patterns Skill
7
-
8
- Patterns for SQLite database operations using better-sqlite3.
9
-
10
- ## When to Use SQLite
11
-
12
- - Single-user applications
13
- - Development/prototyping
14
- - File-based storage needs
15
- - Simple applications without concurrency
16
- - Embedded databases
17
-
18
- ## Setup
19
-
20
- ```typescript
21
- import Database from 'better-sqlite3';
22
-
23
- // Production
24
- const db = new Database('./data/app.db');
25
-
26
- // Testing (in-memory)
27
- const testDb = new Database(':memory:');
28
-
29
- // Enable WAL mode for better performance
30
- db.pragma('journal_mode = WAL');
31
- ```
32
-
33
- ## Basic Patterns
34
-
35
- ### Query Single Row
36
-
37
- ```typescript
38
- function getUserById(id: number): User | null {
39
- const result = db.prepare(`
40
- SELECT id, email, name, created_at as createdAt
41
- FROM users
42
- WHERE id = ?
43
- `).get(id);
44
-
45
- return result ?? null; // better-sqlite3 returns undefined, not null
46
- }
47
- ```
48
-
49
- ### Query Multiple Rows
50
-
51
- ```typescript
52
- function getAllUsers(): User[] {
53
- return db.prepare(`
54
- SELECT id, email, name, created_at as createdAt
55
- FROM users
56
- ORDER BY created_at DESC
57
- `).all() as User[];
58
- }
59
- ```
60
-
61
- ### Insert and Get ID
62
-
63
- ```typescript
64
- function createUser(data: CreateUserDto): User {
65
- const stmt = db.prepare(`
66
- INSERT INTO users (email, password_hash, name)
67
- VALUES (?, ?, ?)
68
- `);
69
-
70
- const info = stmt.run(data.email, passwordHash, data.name);
71
- const id = info.lastInsertRowid;
72
-
73
- return getUserById(Number(id))!;
74
- }
75
- ```
76
-
77
- ### Update
78
-
79
- ```typescript
80
- function updateUser(id: number, updates: UpdateUserDto): User | null {
81
- const stmt = db.prepare(`
82
- UPDATE users
83
- SET email = COALESCE(?, email),
84
- name = COALESCE(?, name),
85
- updated_at = datetime('now')
86
- WHERE id = ?
87
- `);
88
-
89
- const info = stmt.run(updates.email, updates.name, id);
90
-
91
- if (info.changes === 0) return null;
92
- return getUserById(id);
93
- }
94
- ```
95
-
96
- ### Delete
97
-
98
- ```typescript
99
- function deleteUser(id: number): boolean {
100
- const info = db.prepare('DELETE FROM users WHERE id = ?').run(id);
101
- return info.changes > 0;
102
- }
103
- ```
104
-
105
- ## Transactions
106
-
107
- ```typescript
108
- function transferFunds(fromId: number, toId: number, amount: number): void {
109
- const transfer = db.transaction(() => {
110
- db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
111
- .run(amount, fromId);
112
-
113
- db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
114
- .run(amount, toId);
115
- });
116
-
117
- transfer(); // Automatically rolls back on error
118
- }
119
- ```
120
-
121
- ## JSON Fields
122
-
123
- ```typescript
124
- // Store JSON
125
- function saveSettings(userId: number, settings: Settings): void {
126
- db.prepare(`
127
- UPDATE users SET settings = ? WHERE id = ?
128
- `).run(JSON.stringify(settings), userId);
129
- }
130
-
131
- // Parse JSON
132
- function getSettings(userId: number): Settings {
133
- const result = db.prepare(`
134
- SELECT settings FROM users WHERE id = ?
135
- `).get(userId) as { settings: string } | undefined;
136
-
137
- return result ? JSON.parse(result.settings) : {};
138
- }
139
-
140
- // Helper for safe JSON parsing
141
- function parseJsonField<T>(value: string | null | undefined, fallback: T): T {
142
- if (!value) return fallback;
143
- try {
144
- return JSON.parse(value) as T;
145
- } catch {
146
- return fallback;
147
- }
148
- }
149
- ```
150
-
151
- ## Migrations
152
-
153
- ```sql
154
- -- migrations/001_create_users.sql
155
- CREATE TABLE IF NOT EXISTS users (
156
- id INTEGER PRIMARY KEY AUTOINCREMENT,
157
- email TEXT UNIQUE NOT NULL,
158
- password_hash TEXT NOT NULL,
159
- name TEXT NOT NULL,
160
- settings TEXT DEFAULT '{}',
161
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
162
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
163
- );
164
-
165
- CREATE INDEX idx_users_email ON users(email);
166
- ```
167
-
168
- ```typescript
169
- // Run migrations
170
- function runMigrations(db: Database.Database): void {
171
- const migrations = fs.readdirSync('./migrations')
172
- .filter(f => f.endsWith('.sql'))
173
- .sort();
174
-
175
- for (const file of migrations) {
176
- const sql = fs.readFileSync(`./migrations/${file}`, 'utf-8');
177
- db.exec(sql);
178
- }
179
- }
180
- ```
181
-
182
- ## Error Handling
183
-
184
- ```typescript
185
- function createUser(data: CreateUserDto): User {
186
- try {
187
- // ... insert logic
188
- } catch (error: unknown) {
189
- if (error instanceof Error && 'code' in error) {
190
- const sqliteError = error as { code: string };
191
- if (sqliteError.code === 'SQLITE_CONSTRAINT') {
192
- throw new AppError('Email already exists', 400);
193
- }
194
- }
195
- throw error;
196
- }
197
- }
198
- ```
199
-
200
- ## Timestamps
201
-
202
- ```typescript
203
- // SQLite datetime format
204
- const now = new Date().toISOString(); // '2024-01-15T10:30:00.000Z'
205
-
206
- // Or use SQLite's built-in
207
- db.prepare(`
208
- INSERT INTO logs (message, created_at)
209
- VALUES (?, datetime('now'))
210
- `).run(message);
211
- ```
212
-
213
- ## Best Practices
214
-
215
- 1. **Use prepared statements** - Prevents SQL injection
216
- 2. **Use transactions** - For multiple related operations
217
- 3. **Enable WAL mode** - Better concurrent read performance
218
- 4. **Use in-memory for tests** - Fast, isolated testing
219
- 5. **Handle undefined** - better-sqlite3 returns undefined, not null
220
- 6. **Index foreign keys** - Improve JOIN performance
221
- 7. **Use COALESCE for updates** - Partial updates without overwriting
222
-
223
- ## Common Gotchas
224
-
225
- - Returns `undefined` not `null` for missing rows
226
- - `lastInsertRowid` is a BigInt (convert with Number())
227
- - JSON must be stringified before storage
228
- - Datetime stored as TEXT (ISO format recommended)
229
- - No native boolean type (use INTEGER 0/1)
@@ -1,99 +0,0 @@
1
- ---
2
- name: test-caching
3
- description: API response caching for expensive AI API calls in tests. Use when writing tests that call Claude, OpenAI, or other AI APIs.
4
- auto_load: false
5
- priority: medium
6
- ---
7
-
8
- # Test Caching Skill
9
-
10
- ## Trigger
11
- When writing tests that make HTTP calls to AI/LLM APIs (Claude, OpenAI, etc.) or any expensive external API.
12
-
13
- ## How It Works
14
-
15
- The `mockExpensiveAPI()` helper intercepts HTTP requests matching a URL pattern.
16
-
17
- - **Default mode** (`pnpm test`): Replays responses from fixture files in `tests/fixtures/`. No network calls.
18
- - **Record mode** (`RECORD_FIXTURES=true` / `pnpm test:full`): Lets real requests through, captures responses to fixture files.
19
-
20
- ## Usage
21
-
22
- ```typescript
23
- import { mockExpensiveAPI } from '../../src/testing/cache';
24
-
25
- describe('my AI feature', () => {
26
- let mock: ReturnType<typeof mockExpensiveAPI>;
27
-
28
- beforeEach(() => {
29
- mock = mockExpensiveAPI({
30
- name: 'my-feature-openai', // fixture filename
31
- pattern: 'https://api.openai.com', // URL prefix to intercept
32
- service: 'openai', // metadata
33
- estimatedCostUSD: 0.02, // metadata
34
- });
35
- });
36
-
37
- afterEach(() => {
38
- mock.restore(); // always restore HTTP patches
39
- });
40
-
41
- it('calls the AI API', async () => {
42
- // In replay mode, this returns the cached response
43
- // In record mode, this hits the real API and saves the response
44
- const result = await callMyAIFeature();
45
- expect(result).toBeDefined();
46
- });
47
- });
48
- ```
49
-
50
- ## Options
51
-
52
- | Option | Required | Description |
53
- |--------|----------|-------------|
54
- | `name` | Yes | Unique fixture name (becomes `<name>.fixture.json`) |
55
- | `pattern` | Yes | URL prefix (string) or RegExp to intercept |
56
- | `service` | No | Service name for metadata (default: `"unknown"`) |
57
- | `estimatedCostUSD` | No | Cost per call for metadata (default: `0`) |
58
- | `fixturesDir` | No | Override fixture directory (default: `tests/fixtures/`) |
59
-
60
- ## Commands
61
-
62
- | Command | Description |
63
- |---------|-------------|
64
- | `pnpm test` | Run tests with cached API responses (default) |
65
- | `pnpm test:full` | Run tests with real API calls, re-record fixtures |
66
-
67
- ## Fixture Format
68
-
69
- Fixtures are stored as JSON arrays in `tests/fixtures/<name>.fixture.json`:
70
-
71
- ```json
72
- [
73
- {
74
- "request": { "url": "https://api.openai.com/v1/chat", "method": "POST", "body": { "model": "gpt-4" } },
75
- "response": { "status": 200, "headers": {}, "body": { "choices": [] } },
76
- "metadata": { "recordedAt": "2025-01-15T10:00:00.000Z", "service": "openai", "estimatedCostUSD": 0.02 }
77
- }
78
- ]
79
- ```
80
-
81
- ## Cache Invalidation
82
-
83
- Fixtures older than 30 days produce a warning at test startup. Re-record with `RECORD_FIXTURES=true` to refresh.
84
-
85
- ## In the Loop
86
-
87
- `scripts/loop.sh` uses cached mode by default. Pass `--run-full` to bypass cache:
88
-
89
- ```bash
90
- bash scripts/loop.sh --run-full # real API calls
91
- bash scripts/loop.sh # cached (default)
92
- ```
93
-
94
- ## Rules
95
-
96
- 1. Always call `mock.restore()` in `afterEach` to undo HTTP patches
97
- 2. Commit fixture files to git so CI can replay without API keys
98
- 3. Use descriptive `name` values — one fixture per test scenario
99
- 4. Check `mock.warnings` for staleness alerts in your test setup