anvil-dev-framework 0.1.8 → 0.1.9
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/README.md +48 -18
- package/VERSION +1 -1
- package/docs/command-reference.md +97 -16
- package/docs/system-architecture.md +15 -0
- package/global/api/__pycache__/ralph_api.cpython-314.pyc +0 -0
- package/global/api/openapi.yaml +357 -0
- package/global/api/ralph_api.py +528 -0
- package/global/commands/anvil-settings.md +44 -18
- package/global/commands/coderabbit-fix.md +282 -0
- package/global/commands/evidence.md +23 -6
- package/global/commands/hud.md +24 -0
- package/global/commands/orient.md +22 -21
- package/global/commands/weekly-review.md +21 -1
- package/global/config/notifications.yaml.template +50 -0
- package/global/hooks/ralph_stop.sh +33 -1
- package/global/hooks/statusline.sh +67 -2
- package/global/lib/__pycache__/coderabbit_metrics.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/command_tracker.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/context_optimizer.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/optimization_applier.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_webhooks.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/token_analyzer.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/token_metrics.cpython-314.pyc +0 -0
- package/global/lib/coderabbit_metrics.py +647 -0
- package/global/lib/command_tracker.py +147 -0
- package/global/lib/log_rotation.py +287 -0
- package/global/lib/ralph_events.py +398 -0
- package/global/lib/ralph_notifier.py +366 -0
- package/global/lib/ralph_webhooks.py +470 -0
- package/global/lib/state_manager.py +121 -0
- package/global/lib/token_analyzer.py +28 -2
- package/global/lib/token_metrics.py +49 -3
- package/global/tests/__pycache__/test_command_tracker.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_context_optimizer.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_linear_filtering.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_linear_provider.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_optimization_applier.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_token_analyzer.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_token_analyzer_phase6.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/__pycache__/test_token_metrics.cpython-314-pytest-9.0.2.pyc +0 -0
- package/global/tests/test_command_tracker.py +172 -0
- package/global/tests/test_token_metrics.py +38 -0
- package/global/tools/README.md +153 -0
- package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
- package/global/tools/__pycache__/orient_linear.cpython-314.pyc +0 -0
- package/global/tools/__pycache__/ralph-watchcpython-314.pyc +0 -0
- package/global/tools/anvil-hud.py +86 -1
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +472 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +405 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +36 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +653 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +727 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +340 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +218 -0
- package/global/tools/anvil-memory/src/commands/context.ts +322 -0
- package/global/tools/anvil-memory/src/db.ts +108 -0
- package/global/tools/anvil-memory/src/index.ts +2 -8
- package/global/tools/orient_linear.py +159 -0
- package/global/tools/ralph-watch +423 -0
- package/package.json +2 -1
- package/project/.anvil-project.yaml.template +93 -0
- package/project/CLAUDE.md.template +343 -0
- package/project/agents/README.md +119 -0
- package/project/agents/cross-layer-debugger.md +217 -0
- package/project/agents/security-code-reviewer.md +162 -0
- package/project/constitution.md.template +235 -0
- package/project/coordination.md +103 -0
- package/project/docs/background-tasks.md +258 -0
- package/project/docs/skills-frontmatter.md +243 -0
- package/project/examples/README.md +106 -0
- package/project/examples/api-route-template.ts +171 -0
- package/project/examples/component-template.tsx +110 -0
- package/project/examples/hook-template.ts +152 -0
- package/project/examples/service-template.ts +207 -0
- package/project/examples/test-template.test.tsx +249 -0
- package/project/hooks/README.md +491 -0
- package/project/hooks/__pycache__/notification.cpython-314.pyc +0 -0
- package/project/hooks/__pycache__/post_tool_use.cpython-314.pyc +0 -0
- package/project/hooks/__pycache__/pre_tool_use.cpython-314.pyc +0 -0
- package/project/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/project/hooks/__pycache__/stop.cpython-314.pyc +0 -0
- package/project/hooks/notification.py +183 -0
- package/project/hooks/permission_request.py +438 -0
- package/project/hooks/post_tool_use.py +397 -0
- package/project/hooks/pre_compact.py +126 -0
- package/project/hooks/pre_tool_use.py +454 -0
- package/project/hooks/session_start.py +656 -0
- package/project/hooks/stop.py +356 -0
- package/project/hooks/subagent_start.py +223 -0
- package/project/hooks/subagent_stop.py +215 -0
- package/project/hooks/user_prompt_submit.py +110 -0
- package/project/hooks/utils/llm/anth.py +114 -0
- package/project/hooks/utils/llm/oai.py +114 -0
- package/project/hooks/utils/tts/elevenlabs_tts.py +63 -0
- package/project/hooks/utils/tts/mlx_audio_tts.py +86 -0
- package/project/hooks/utils/tts/openai_tts.py +92 -0
- package/project/hooks/utils/tts/pyttsx3_tts.py +75 -0
- package/project/linear.yaml.template +23 -0
- package/project/product.md.template +238 -0
- package/project/retros/README.md +126 -0
- package/project/rules/README.md +90 -0
- package/project/rules/debugging.md +139 -0
- package/project/rules/security-review.md +115 -0
- package/project/settings.yaml.template +185 -0
- package/project/specs/SPEC-ANV-72-hud-kanban.md +525 -0
- package/project/templates/api-python/CLAUDE.md +547 -0
- package/project/templates/generic/CLAUDE.md +260 -0
- package/project/templates/saas/CLAUDE.md +478 -0
- package/project/tests/README.md +140 -0
- package/project/tests/__pycache__/test_transcript_parser.cpython-314-pytest-9.0.2.pyc +0 -0
- package/project/tests/fixtures/sample-transcript.jsonl +21 -0
- package/project/tests/test-hooks.sh +259 -0
- package/project/tests/test-lib.sh +248 -0
- package/project/tests/test-statusline.sh +165 -0
- package/project/tests/test_transcript_parser.py +323 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# [Project Name]
|
|
2
|
+
|
|
3
|
+
> [One-line description of the project]
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Project Overview
|
|
8
|
+
|
|
9
|
+
[2-3 sentences describing what this project is and who it's for]
|
|
10
|
+
|
|
11
|
+
**Status**: [Active Development / Maintenance / Beta / etc.]
|
|
12
|
+
**Repository**: [URL]
|
|
13
|
+
**Live URL**: [URL if applicable]
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Tech Stack
|
|
18
|
+
|
|
19
|
+
| Layer | Technology | Version |
|
|
20
|
+
|-------|------------|---------|
|
|
21
|
+
| Framework | Next.js (App Router) | 14.x |
|
|
22
|
+
| Language | TypeScript | 5.x |
|
|
23
|
+
| Database | Supabase (PostgreSQL) | — |
|
|
24
|
+
| Auth | Supabase Auth | — |
|
|
25
|
+
| Styling | Tailwind CSS + shadcn/ui | 3.x |
|
|
26
|
+
| State | React Query + Zustand | — |
|
|
27
|
+
| Testing | Vitest + Testing Library | 1.x |
|
|
28
|
+
| Deployment | Vercel | — |
|
|
29
|
+
|
|
30
|
+
### Optional Integrations
|
|
31
|
+
- **Payments**: Stripe (if applicable)
|
|
32
|
+
- **Email**: Resend or Postmark
|
|
33
|
+
- **Analytics**: Vercel Analytics or PostHog
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
### Directory Structure
|
|
40
|
+
```
|
|
41
|
+
src/
|
|
42
|
+
├── app/ # Next.js App Router
|
|
43
|
+
│ ├── (auth)/ # Auth-related routes (login, signup)
|
|
44
|
+
│ ├── (dashboard)/ # Protected dashboard routes
|
|
45
|
+
│ ├── api/ # API routes
|
|
46
|
+
│ └── layout.tsx # Root layout
|
|
47
|
+
├── components/
|
|
48
|
+
│ ├── ui/ # shadcn/ui components
|
|
49
|
+
│ ├── forms/ # Form components
|
|
50
|
+
│ └── [feature]/ # Feature-specific components
|
|
51
|
+
├── lib/
|
|
52
|
+
│ ├── supabase/ # Supabase client & helpers
|
|
53
|
+
│ │ ├── client.ts # Browser client
|
|
54
|
+
│ │ ├── server.ts # Server client
|
|
55
|
+
│ │ └── middleware.ts # Auth middleware
|
|
56
|
+
│ ├── utils.ts # Utility functions
|
|
57
|
+
│ └── constants.ts # App constants
|
|
58
|
+
├── services/ # Business logic layer
|
|
59
|
+
├── hooks/ # Custom React hooks
|
|
60
|
+
├── types/ # TypeScript type definitions
|
|
61
|
+
└── styles/ # Global styles
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Key Patterns
|
|
65
|
+
|
|
66
|
+
**Server vs Client Components**
|
|
67
|
+
- Default to Server Components for data fetching
|
|
68
|
+
- Use `'use client'` only for interactivity (forms, state, effects)
|
|
69
|
+
- Fetch data in Server Components, pass to Client Components as props
|
|
70
|
+
|
|
71
|
+
**Data Fetching**
|
|
72
|
+
- Server Components: Direct Supabase queries
|
|
73
|
+
- Client Components: React Query with Supabase client
|
|
74
|
+
- Mutations: Server Actions or API routes
|
|
75
|
+
|
|
76
|
+
**Authentication Flow**
|
|
77
|
+
```
|
|
78
|
+
middleware.ts → Check session → Redirect if needed
|
|
79
|
+
↓
|
|
80
|
+
Supabase Auth
|
|
81
|
+
↓
|
|
82
|
+
RLS policies enforce access
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**State Management**
|
|
86
|
+
- Server state: React Query (caching, revalidation)
|
|
87
|
+
- Client state: Zustand (UI state, preferences)
|
|
88
|
+
- Form state: React Hook Form + Zod validation
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Agent Approach
|
|
93
|
+
|
|
94
|
+
### Session Startup
|
|
95
|
+
```
|
|
96
|
+
1. /orient → Check handoffs, understand context
|
|
97
|
+
2. /ready → See unblocked work
|
|
98
|
+
3. /validate → Ensure clean environment
|
|
99
|
+
4. Await direction → Don't start work without confirmation
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Implementation Flow
|
|
103
|
+
```
|
|
104
|
+
1. /explore → Discovery before any new feature
|
|
105
|
+
2. /spec → Formal specification (if needed)
|
|
106
|
+
3. /plan → Implementation plan
|
|
107
|
+
4. /tasks → Linear issues from plan
|
|
108
|
+
5. [implement] → Do the work
|
|
109
|
+
6. /evidence → Capture quality proof
|
|
110
|
+
7. /handoff → Session continuity
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### During Work
|
|
114
|
+
- Read existing code before writing new code
|
|
115
|
+
- Cite file paths and line numbers as evidence
|
|
116
|
+
- File discovered work immediately with `/discover`
|
|
117
|
+
- Update Linear status at phase transitions
|
|
118
|
+
- Stop and report if stuck >5 minutes
|
|
119
|
+
|
|
120
|
+
### Anti-Patterns to Avoid
|
|
121
|
+
|
|
122
|
+
Before implementing, verify you're NOT doing:
|
|
123
|
+
|
|
124
|
+
| Anti-Pattern | Check |
|
|
125
|
+
|--------------|-------|
|
|
126
|
+
| Premature abstraction | Is this the 3rd instance? (Rule of Three) |
|
|
127
|
+
| Adding files without need | Can this be added to existing file? |
|
|
128
|
+
| Over-engineering | Is this the simplest solution? |
|
|
129
|
+
| Ignoring existing patterns | Did I check examples first? |
|
|
130
|
+
| Large PRs | Can I break this into smaller chunks? |
|
|
131
|
+
| Skipping exploration | Did I run `/explore` first? |
|
|
132
|
+
| Committing without branch verification | Did I run `git branch --show-current`? |
|
|
133
|
+
| Post-edit without git diff | Did I run `git diff` to verify edits persisted? |
|
|
134
|
+
| Addressing merged PR feedback | Did I run `gh pr view --json state` before committing? |
|
|
135
|
+
| Implementing consumer without verifying producer | Did I trace the full data flow? |
|
|
136
|
+
|
|
137
|
+
### Project-Learned Patterns
|
|
138
|
+
|
|
139
|
+
> Patterns discovered from retrospectives. Updated via `/insights`.
|
|
140
|
+
|
|
141
|
+
| Pattern | Evidence | Added |
|
|
142
|
+
|---------|----------|-------|
|
|
143
|
+
| *Example: Verify Before Edit* | *ISSUE-1, branch-fix* | *YYYY-MM-DD* |
|
|
144
|
+
|
|
145
|
+
*Replace with patterns discovered from your project's retrospectives. Run `/insights` to update.*
|
|
146
|
+
|
|
147
|
+
**Pre-Implementation Checklist**
|
|
148
|
+
|
|
149
|
+
Before building something new, search for existing infrastructure:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Check for existing services
|
|
153
|
+
grep -r "function.*[FeatureName]" src/services/
|
|
154
|
+
|
|
155
|
+
# Check for existing hooks
|
|
156
|
+
grep -r "use[FeatureName]" src/hooks/
|
|
157
|
+
|
|
158
|
+
# Check for existing components
|
|
159
|
+
grep -r "export.*function.*[ComponentName]" src/components/
|
|
160
|
+
|
|
161
|
+
# Check for existing utilities
|
|
162
|
+
grep -r "[utility-name]" src/lib/
|
|
163
|
+
|
|
164
|
+
# Check for existing API routes
|
|
165
|
+
ls -la src/app/api/
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
*Customize these commands based on patterns discovered in retros.*
|
|
169
|
+
|
|
170
|
+
### Confidence Checkpoints
|
|
171
|
+
|
|
172
|
+
Rate your confidence at each stage (1-10):
|
|
173
|
+
|
|
174
|
+
| Confidence | Action |
|
|
175
|
+
|------------|--------|
|
|
176
|
+
| 8-10 | Proceed normally |
|
|
177
|
+
| 5-7 | Document uncertainty, proceed cautiously, note assumptions |
|
|
178
|
+
| 1-4 | **STOP**. Explain the uncertainty. Ask for guidance. |
|
|
179
|
+
|
|
180
|
+
**Escalation rule**: After 2 failed attempts at the same task → Stop. Reassess. Ask for help.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Slash Commands
|
|
185
|
+
|
|
186
|
+
### Session Commands
|
|
187
|
+
| Command | Purpose |
|
|
188
|
+
|---------|---------|
|
|
189
|
+
| `/orient` | Session startup orientation |
|
|
190
|
+
| `/ready` | Calculate ready work |
|
|
191
|
+
| `/sprint` | Quick session prioritization |
|
|
192
|
+
| `/handoff` | Generate session continuity doc |
|
|
193
|
+
|
|
194
|
+
### Workflow Commands
|
|
195
|
+
| Command | Purpose |
|
|
196
|
+
|---------|---------|
|
|
197
|
+
| `/explore` | Discovery phase |
|
|
198
|
+
| `/spec` | Generate specification |
|
|
199
|
+
| `/plan` | Create implementation plan |
|
|
200
|
+
| `/tasks` | Create Linear issues |
|
|
201
|
+
| `/change` | Brownfield change proposal |
|
|
202
|
+
| `/discover` | File discovered work |
|
|
203
|
+
|
|
204
|
+
### Quality Commands
|
|
205
|
+
| Command | Purpose |
|
|
206
|
+
|---------|---------|
|
|
207
|
+
| `/validate` | Environment validation |
|
|
208
|
+
| `/evidence` | Capture quality proof |
|
|
209
|
+
|
|
210
|
+
### Maintenance Commands
|
|
211
|
+
| Command | Purpose |
|
|
212
|
+
|---------|---------|
|
|
213
|
+
| `/retro` | Write retrospective |
|
|
214
|
+
| `/insights` | Synthesize learnings |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Linear Workflow
|
|
219
|
+
|
|
220
|
+
### Issue States
|
|
221
|
+
| State | Meaning |
|
|
222
|
+
|-------|---------|
|
|
223
|
+
| Backlog | Not yet prioritized |
|
|
224
|
+
| Todo | Prioritized, ready to start |
|
|
225
|
+
| In Progress | Actively being worked on |
|
|
226
|
+
| In Review | PR created, awaiting review |
|
|
227
|
+
| Done | Merged and deployed |
|
|
228
|
+
|
|
229
|
+
### Labels
|
|
230
|
+
- `bug` — Something isn't working
|
|
231
|
+
- `feature` — New functionality
|
|
232
|
+
- `chore` — Maintenance task
|
|
233
|
+
- `discovered` — Found during other work
|
|
234
|
+
- `blocked` — Cannot proceed
|
|
235
|
+
|
|
236
|
+
### Priorities
|
|
237
|
+
- **P0 (Urgent)**: Drop everything, fix now
|
|
238
|
+
- **P1 (High)**: Next up after current task
|
|
239
|
+
- **P2 (Medium)**: This sprint
|
|
240
|
+
- **P3 (Low)**: Backlog
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## File Conventions
|
|
245
|
+
|
|
246
|
+
### Naming
|
|
247
|
+
- **Components**: PascalCase (`UserProfile.tsx`)
|
|
248
|
+
- **Hooks**: camelCase with `use` prefix (`useAuth.ts`)
|
|
249
|
+
- **Utilities**: camelCase (`formatDate.ts`)
|
|
250
|
+
- **Types**: PascalCase (`User.ts`)
|
|
251
|
+
- **API Routes**: `route.ts` in appropriate directory
|
|
252
|
+
- **Directories**: kebab-case (`user-settings/`)
|
|
253
|
+
|
|
254
|
+
### Component Structure
|
|
255
|
+
```typescript
|
|
256
|
+
// Imports (external, then internal)
|
|
257
|
+
import { useState } from 'react'
|
|
258
|
+
import { Button } from '@/components/ui/button'
|
|
259
|
+
|
|
260
|
+
// Types
|
|
261
|
+
interface Props {
|
|
262
|
+
user: User
|
|
263
|
+
onUpdate: (user: User) => void
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Component
|
|
267
|
+
export function UserProfile({ user, onUpdate }: Props) {
|
|
268
|
+
// Hooks first
|
|
269
|
+
const { data, isLoading } = useUserData(user.id)
|
|
270
|
+
|
|
271
|
+
// State second
|
|
272
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
273
|
+
|
|
274
|
+
// Effects third (minimize usage)
|
|
275
|
+
|
|
276
|
+
// Handlers fourth
|
|
277
|
+
const handleSave = async () => {
|
|
278
|
+
// ...
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Render last
|
|
282
|
+
if (isLoading) return <Skeleton />
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div className="space-y-4">
|
|
286
|
+
{/* Component JSX */}
|
|
287
|
+
</div>
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Server Action Pattern
|
|
293
|
+
```typescript
|
|
294
|
+
// src/app/actions/user.ts
|
|
295
|
+
'use server'
|
|
296
|
+
|
|
297
|
+
import { createClient } from '@/lib/supabase/server'
|
|
298
|
+
import { revalidatePath } from 'next/cache'
|
|
299
|
+
|
|
300
|
+
export async function updateUser(formData: FormData) {
|
|
301
|
+
const supabase = await createClient()
|
|
302
|
+
|
|
303
|
+
const { error } = await supabase
|
|
304
|
+
.from('users')
|
|
305
|
+
.update({ name: formData.get('name') })
|
|
306
|
+
.eq('id', formData.get('id'))
|
|
307
|
+
|
|
308
|
+
if (error) throw new Error(error.message)
|
|
309
|
+
|
|
310
|
+
revalidatePath('/dashboard')
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Database Patterns
|
|
317
|
+
|
|
318
|
+
### Supabase Client Setup
|
|
319
|
+
```typescript
|
|
320
|
+
// src/lib/supabase/client.ts (browser)
|
|
321
|
+
import { createBrowserClient } from '@supabase/ssr'
|
|
322
|
+
|
|
323
|
+
export function createClient() {
|
|
324
|
+
return createBrowserClient(
|
|
325
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
326
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/lib/supabase/server.ts (server)
|
|
331
|
+
import { createServerClient } from '@supabase/ssr'
|
|
332
|
+
import { cookies } from 'next/headers'
|
|
333
|
+
|
|
334
|
+
export async function createClient() {
|
|
335
|
+
const cookieStore = await cookies()
|
|
336
|
+
return createServerClient(
|
|
337
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
338
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
|
339
|
+
{
|
|
340
|
+
cookies: {
|
|
341
|
+
getAll() { return cookieStore.getAll() },
|
|
342
|
+
setAll(cookiesToSet) {
|
|
343
|
+
cookiesToSet.forEach(({ name, value, options }) =>
|
|
344
|
+
cookieStore.set(name, value, options)
|
|
345
|
+
)
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### RLS Policy Pattern
|
|
354
|
+
```sql
|
|
355
|
+
-- Enable RLS
|
|
356
|
+
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
|
|
357
|
+
|
|
358
|
+
-- Users can only see their own posts
|
|
359
|
+
CREATE POLICY "Users can view own posts"
|
|
360
|
+
ON posts FOR SELECT
|
|
361
|
+
USING (auth.uid() = user_id);
|
|
362
|
+
|
|
363
|
+
-- Users can only insert their own posts
|
|
364
|
+
CREATE POLICY "Users can insert own posts"
|
|
365
|
+
ON posts FOR INSERT
|
|
366
|
+
WITH CHECK (auth.uid() = user_id);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Error Handling
|
|
372
|
+
|
|
373
|
+
### AppError Class
|
|
374
|
+
```typescript
|
|
375
|
+
// src/lib/errors.ts
|
|
376
|
+
export class AppError extends Error {
|
|
377
|
+
constructor(
|
|
378
|
+
public code: string,
|
|
379
|
+
message: string,
|
|
380
|
+
public statusCode: number = 400,
|
|
381
|
+
public cause?: unknown
|
|
382
|
+
) {
|
|
383
|
+
super(message)
|
|
384
|
+
this.name = 'AppError'
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Usage
|
|
389
|
+
throw new AppError('NOT_FOUND', 'User not found', 404)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### API Route Error Handling
|
|
393
|
+
```typescript
|
|
394
|
+
// src/app/api/users/route.ts
|
|
395
|
+
import { NextResponse } from 'next/server'
|
|
396
|
+
import { AppError } from '@/lib/errors'
|
|
397
|
+
|
|
398
|
+
export async function GET() {
|
|
399
|
+
try {
|
|
400
|
+
// ... operation
|
|
401
|
+
return NextResponse.json(data)
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error instanceof AppError) {
|
|
404
|
+
return NextResponse.json(
|
|
405
|
+
{ error: error.message, code: error.code },
|
|
406
|
+
{ status: error.statusCode }
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
return NextResponse.json(
|
|
410
|
+
{ error: 'Internal server error' },
|
|
411
|
+
{ status: 500 }
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Error Codes
|
|
418
|
+
| Code | Meaning |
|
|
419
|
+
|------|---------|
|
|
420
|
+
| `AUTH_REQUIRED` | User must be logged in |
|
|
421
|
+
| `NOT_FOUND` | Resource doesn't exist |
|
|
422
|
+
| `VALIDATION_ERROR` | Invalid input data |
|
|
423
|
+
| `PERMISSION_DENIED` | User lacks permission |
|
|
424
|
+
| `RATE_LIMITED` | Too many requests |
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Key Files Reference
|
|
429
|
+
|
|
430
|
+
| Purpose | Location |
|
|
431
|
+
|---------|----------|
|
|
432
|
+
| Project config | `CLAUDE.md` (this file) |
|
|
433
|
+
| Non-negotiables | `.claude/constitution.md` |
|
|
434
|
+
| Product definition | `.claude/product.md` |
|
|
435
|
+
| Active specs | `.claude/specs/current/` |
|
|
436
|
+
| Handoffs | `.claude/handoffs/` |
|
|
437
|
+
| Convention examples | `.claude/examples/` |
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Environment Setup
|
|
442
|
+
|
|
443
|
+
### Required Environment Variables
|
|
444
|
+
```bash
|
|
445
|
+
# .env.local
|
|
446
|
+
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
|
|
447
|
+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
|
|
448
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJ... # Server-side only
|
|
449
|
+
|
|
450
|
+
# Optional
|
|
451
|
+
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Local Development
|
|
455
|
+
```bash
|
|
456
|
+
pnpm install # Install dependencies
|
|
457
|
+
pnpm dev # Start dev server (localhost:3000)
|
|
458
|
+
pnpm build # Production build
|
|
459
|
+
pnpm test # Run tests
|
|
460
|
+
pnpm lint # Run linter
|
|
461
|
+
pnpm typecheck # Check types
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Supabase Local Development
|
|
465
|
+
```bash
|
|
466
|
+
supabase start # Start local Supabase
|
|
467
|
+
supabase db reset # Reset database with migrations
|
|
468
|
+
supabase gen types # Generate TypeScript types
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Deployment (Vercel)
|
|
472
|
+
- Push to main branch triggers automatic deployment
|
|
473
|
+
- Preview deployments for PRs
|
|
474
|
+
- Environment variables set in Vercel dashboard
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
*Update this file as the project evolves. This is your project's source of truth for AI assistance.*
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Anvil Test Harnesses
|
|
2
|
+
|
|
3
|
+
Reusable test harnesses for testing Anvil framework scripts.
|
|
4
|
+
|
|
5
|
+
## Why Test Harnesses?
|
|
6
|
+
|
|
7
|
+
Testing bash scripts and Python hooks that process JSON input is error-prone due to shell escaping issues. These harnesses use **heredocs** to define JSON fixtures, eliminating escaping problems entirely.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run all statusline tests
|
|
13
|
+
./test-statusline.sh
|
|
14
|
+
|
|
15
|
+
# Run all hook tests
|
|
16
|
+
./test-hooks.sh
|
|
17
|
+
|
|
18
|
+
# Run specific hook tests
|
|
19
|
+
./test-hooks.sh pre_tool_use
|
|
20
|
+
./test-hooks.sh session_start
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Available Test Harnesses
|
|
24
|
+
|
|
25
|
+
| Script | Tests | Purpose |
|
|
26
|
+
|--------|-------|---------|
|
|
27
|
+
| `test-statusline.sh` | 7 tests | StatusLine hook JSON processing |
|
|
28
|
+
| `test-hooks.sh` | 8 tests | Pre-tool-use safety, session hooks |
|
|
29
|
+
| `test-lib.sh` | (library) | Shared test utilities |
|
|
30
|
+
|
|
31
|
+
## Writing Custom Tests
|
|
32
|
+
|
|
33
|
+
### 1. Source the test library
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
#!/bin/bash
|
|
37
|
+
source "$(dirname "$0")/test-lib.sh"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Define JSON fixtures using heredocs
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Heredocs avoid ALL shell escaping issues
|
|
44
|
+
read -r -d '' MY_FIXTURE << 'EOF' || true
|
|
45
|
+
{
|
|
46
|
+
"tool_name": "Bash",
|
|
47
|
+
"tool_input": {
|
|
48
|
+
"command": "echo 'hello world'"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
EOF
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Write test functions
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
test_my_feature() {
|
|
58
|
+
local output
|
|
59
|
+
output=$(echo "$MY_FIXTURE" | python3 "$HOOK" 2>&1)
|
|
60
|
+
|
|
61
|
+
# Use assertion helpers
|
|
62
|
+
assert_contains "$output" "expected pattern" "Should contain pattern"
|
|
63
|
+
assert_exit_code 0 echo "$MY_FIXTURE" | python3 "$HOOK"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 4. Run tests with `run_test`
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
run_test "my_feature_works" test_my_feature
|
|
71
|
+
print_summary
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Assertion Functions
|
|
75
|
+
|
|
76
|
+
| Function | Description | Example |
|
|
77
|
+
|----------|-------------|---------|
|
|
78
|
+
| `assert_contains` | Check output contains pattern | `assert_contains "$out" "OK"` |
|
|
79
|
+
| `assert_not_contains` | Check output doesn't contain | `assert_not_contains "$out" "ERROR"` |
|
|
80
|
+
| `assert_equals` | Check values are equal | `assert_equals "$a" "$b"` |
|
|
81
|
+
| `assert_exit_code` | Check command exit code | `assert_exit_code 0 ./script.sh` |
|
|
82
|
+
|
|
83
|
+
## Test Environment Functions
|
|
84
|
+
|
|
85
|
+
| Function | Description |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| `setup_test_env` | Create temp dir, set mock env vars |
|
|
88
|
+
| `cleanup_test_env` | Remove temp dir, unset env vars |
|
|
89
|
+
| `print_summary` | Print pass/fail counts |
|
|
90
|
+
|
|
91
|
+
## Example Output
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
Hook Test Harness
|
|
95
|
+
=================
|
|
96
|
+
Hooks Dir: /path/to/project/hooks
|
|
97
|
+
|
|
98
|
+
Testing: pre_tool_use.py
|
|
99
|
+
------------------------
|
|
100
|
+
✅ safe_bash_allowed
|
|
101
|
+
✅ blocks_rm_rf_root
|
|
102
|
+
✅ blocks_rm_rf_home
|
|
103
|
+
✅ blocks_env_read
|
|
104
|
+
✅ allows_env_sample
|
|
105
|
+
✅ allows_normal_edit
|
|
106
|
+
✅ performance
|
|
107
|
+
|
|
108
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
109
|
+
Test Summary
|
|
110
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
111
|
+
Total: 7
|
|
112
|
+
Passed: 7
|
|
113
|
+
Failed: 0
|
|
114
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Best Practices
|
|
118
|
+
|
|
119
|
+
1. **Always use heredocs for JSON** - Never inline JSON with quotes
|
|
120
|
+
2. **One assertion per test** - Makes failures easier to debug
|
|
121
|
+
3. **Test both success and failure** - Verify blocked commands are blocked
|
|
122
|
+
4. **Include performance tests** - Hooks should complete in <100ms
|
|
123
|
+
5. **Use descriptive test names** - `blocks_rm_rf_root` not `test1`
|
|
124
|
+
|
|
125
|
+
## Adding New Hook Tests
|
|
126
|
+
|
|
127
|
+
1. Add JSON fixture with descriptive name
|
|
128
|
+
2. Write test function that checks behavior
|
|
129
|
+
3. Add `run_test` call in appropriate section
|
|
130
|
+
4. Run full test suite to verify
|
|
131
|
+
|
|
132
|
+
## Integration with Anvil
|
|
133
|
+
|
|
134
|
+
These test harnesses are copied to new projects via `anvil init`:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
anvil init # Creates .claude/tests/ with these files
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Test your hooks locally before committing changes to `project/hooks/`.
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{"type": "tool_use", "tool_use_id": "tool_001", "name": "Read", "input": {"file_path": "/Users/test/project/src/auth.ts"}, "timestamp": "2026-01-04T10:00:00Z"}
|
|
2
|
+
{"type": "tool_result", "tool_use_id": "tool_001", "content": "file contents...", "is_error": false, "timestamp": "2026-01-04T10:00:01Z"}
|
|
3
|
+
{"type": "tool_use", "tool_use_id": "tool_002", "name": "Edit", "input": {"file_path": "/Users/test/project/src/auth.ts", "old_string": "foo", "new_string": "bar"}, "timestamp": "2026-01-04T10:00:02Z"}
|
|
4
|
+
{"type": "tool_result", "tool_use_id": "tool_002", "content": "success", "is_error": false, "timestamp": "2026-01-04T10:00:03Z"}
|
|
5
|
+
{"type": "tool_use", "tool_use_id": "tool_003", "name": "TodoWrite", "input": {"todos": [{"content": "Read existing auth code", "status": "completed", "activeForm": "Reading auth code"}, {"content": "Implement OAuth flow", "status": "in_progress", "activeForm": "Implementing OAuth"}, {"content": "Add tests", "status": "pending", "activeForm": "Adding tests"}, {"content": "Update docs", "status": "pending", "activeForm": "Updating docs"}]}, "timestamp": "2026-01-04T10:00:04Z"}
|
|
6
|
+
{"type": "tool_result", "tool_use_id": "tool_003", "content": "Todos updated", "is_error": false, "timestamp": "2026-01-04T10:00:04Z"}
|
|
7
|
+
{"type": "tool_use", "tool_use_id": "tool_004", "name": "Bash", "input": {"command": "npm run test"}, "timestamp": "2026-01-04T10:00:05Z"}
|
|
8
|
+
{"type": "tool_result", "tool_use_id": "tool_004", "content": "All tests passed", "is_error": false, "timestamp": "2026-01-04T10:00:10Z"}
|
|
9
|
+
{"type": "tool_use", "tool_use_id": "tool_005", "name": "Edit", "input": {"file_path": "/Users/test/project/src/login.ts", "old_string": "x", "new_string": "y"}, "timestamp": "2026-01-04T10:00:11Z"}
|
|
10
|
+
{"type": "tool_result", "tool_use_id": "tool_005", "content": "success", "is_error": false, "timestamp": "2026-01-04T10:00:12Z"}
|
|
11
|
+
{"type": "tool_use", "tool_use_id": "tool_006", "name": "Read", "input": {"file_path": "/Users/test/project/src/utils.ts"}, "timestamp": "2026-01-04T10:00:13Z"}
|
|
12
|
+
{"type": "tool_result", "tool_use_id": "tool_006", "content": "utils content", "is_error": false, "timestamp": "2026-01-04T10:00:14Z"}
|
|
13
|
+
{"type": "tool_use", "tool_use_id": "tool_007", "name": "Grep", "input": {"pattern": "TODO", "path": "/Users/test/project/src"}, "timestamp": "2026-01-04T10:00:15Z"}
|
|
14
|
+
{"type": "tool_result", "tool_use_id": "tool_007", "content": "Found 3 matches", "is_error": false, "timestamp": "2026-01-04T10:00:16Z"}
|
|
15
|
+
{"type": "tool_use", "tool_use_id": "tool_008", "name": "Edit", "input": {"file_path": "/Users/test/project/src/auth.ts", "old_string": "a", "new_string": "b"}, "timestamp": "2026-01-04T10:00:17Z"}
|
|
16
|
+
{"type": "tool_result", "tool_use_id": "tool_008", "content": "Failed to edit", "is_error": true, "timestamp": "2026-01-04T10:00:18Z"}
|
|
17
|
+
{"type": "tool_use", "tool_use_id": "tool_009", "name": "Edit", "input": {"file_path": "/Users/test/project/src/auth.ts", "old_string": "c", "new_string": "d"}, "timestamp": "2026-01-04T10:00:19Z"}
|
|
18
|
+
{"type": "tool_result", "tool_use_id": "tool_009", "content": "success", "is_error": false, "timestamp": "2026-01-04T10:00:20Z"}
|
|
19
|
+
{"type": "tool_use", "tool_use_id": "tool_010", "name": "TodoWrite", "input": {"todos": [{"content": "Read existing auth code", "status": "completed", "activeForm": "Reading auth code"}, {"content": "Implement OAuth flow", "status": "completed", "activeForm": "Implementing OAuth"}, {"content": "Add tests", "status": "in_progress", "activeForm": "Adding tests"}, {"content": "Update docs", "status": "pending", "activeForm": "Updating docs"}]}, "timestamp": "2026-01-04T10:00:21Z"}
|
|
20
|
+
{"type": "tool_result", "tool_use_id": "tool_010", "content": "Todos updated", "is_error": false, "timestamp": "2026-01-04T10:00:21Z"}
|
|
21
|
+
{"type": "tool_use", "tool_use_id": "tool_011", "name": "Read", "input": {"file_path": "/Users/test/project/tests/auth.test.ts"}, "timestamp": "2026-01-04T10:00:22Z"}
|