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.
Files changed (117) hide show
  1. package/README.md +48 -18
  2. package/VERSION +1 -1
  3. package/docs/command-reference.md +97 -16
  4. package/docs/system-architecture.md +15 -0
  5. package/global/api/__pycache__/ralph_api.cpython-314.pyc +0 -0
  6. package/global/api/openapi.yaml +357 -0
  7. package/global/api/ralph_api.py +528 -0
  8. package/global/commands/anvil-settings.md +44 -18
  9. package/global/commands/coderabbit-fix.md +282 -0
  10. package/global/commands/evidence.md +23 -6
  11. package/global/commands/hud.md +24 -0
  12. package/global/commands/orient.md +22 -21
  13. package/global/commands/weekly-review.md +21 -1
  14. package/global/config/notifications.yaml.template +50 -0
  15. package/global/hooks/ralph_stop.sh +33 -1
  16. package/global/hooks/statusline.sh +67 -2
  17. package/global/lib/__pycache__/coderabbit_metrics.cpython-314.pyc +0 -0
  18. package/global/lib/__pycache__/command_tracker.cpython-314.pyc +0 -0
  19. package/global/lib/__pycache__/context_optimizer.cpython-314.pyc +0 -0
  20. package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
  21. package/global/lib/__pycache__/optimization_applier.cpython-314.pyc +0 -0
  22. package/global/lib/__pycache__/ralph_webhooks.cpython-314.pyc +0 -0
  23. package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
  24. package/global/lib/__pycache__/token_analyzer.cpython-314.pyc +0 -0
  25. package/global/lib/__pycache__/token_metrics.cpython-314.pyc +0 -0
  26. package/global/lib/coderabbit_metrics.py +647 -0
  27. package/global/lib/command_tracker.py +147 -0
  28. package/global/lib/log_rotation.py +287 -0
  29. package/global/lib/ralph_events.py +398 -0
  30. package/global/lib/ralph_notifier.py +366 -0
  31. package/global/lib/ralph_webhooks.py +470 -0
  32. package/global/lib/state_manager.py +121 -0
  33. package/global/lib/token_analyzer.py +28 -2
  34. package/global/lib/token_metrics.py +49 -3
  35. package/global/tests/__pycache__/test_command_tracker.cpython-314-pytest-9.0.2.pyc +0 -0
  36. package/global/tests/__pycache__/test_context_optimizer.cpython-314-pytest-9.0.2.pyc +0 -0
  37. package/global/tests/__pycache__/test_linear_filtering.cpython-314-pytest-9.0.2.pyc +0 -0
  38. package/global/tests/__pycache__/test_linear_provider.cpython-314-pytest-9.0.2.pyc +0 -0
  39. package/global/tests/__pycache__/test_optimization_applier.cpython-314-pytest-9.0.2.pyc +0 -0
  40. package/global/tests/__pycache__/test_token_analyzer.cpython-314-pytest-9.0.2.pyc +0 -0
  41. package/global/tests/__pycache__/test_token_analyzer_phase6.cpython-314-pytest-9.0.2.pyc +0 -0
  42. package/global/tests/__pycache__/test_token_metrics.cpython-314-pytest-9.0.2.pyc +0 -0
  43. package/global/tests/test_command_tracker.py +172 -0
  44. package/global/tests/test_token_metrics.py +38 -0
  45. package/global/tools/README.md +153 -0
  46. package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
  47. package/global/tools/__pycache__/orient_linear.cpython-314.pyc +0 -0
  48. package/global/tools/__pycache__/ralph-watchcpython-314.pyc +0 -0
  49. package/global/tools/anvil-hud.py +86 -1
  50. package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +472 -0
  51. package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +405 -0
  52. package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +36 -0
  53. package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +653 -0
  54. package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +727 -0
  55. package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +340 -0
  56. package/global/tools/anvil-memory/src/__tests__/commands.test.ts +218 -0
  57. package/global/tools/anvil-memory/src/commands/context.ts +322 -0
  58. package/global/tools/anvil-memory/src/db.ts +108 -0
  59. package/global/tools/anvil-memory/src/index.ts +2 -8
  60. package/global/tools/orient_linear.py +159 -0
  61. package/global/tools/ralph-watch +423 -0
  62. package/package.json +2 -1
  63. package/project/.anvil-project.yaml.template +93 -0
  64. package/project/CLAUDE.md.template +343 -0
  65. package/project/agents/README.md +119 -0
  66. package/project/agents/cross-layer-debugger.md +217 -0
  67. package/project/agents/security-code-reviewer.md +162 -0
  68. package/project/constitution.md.template +235 -0
  69. package/project/coordination.md +103 -0
  70. package/project/docs/background-tasks.md +258 -0
  71. package/project/docs/skills-frontmatter.md +243 -0
  72. package/project/examples/README.md +106 -0
  73. package/project/examples/api-route-template.ts +171 -0
  74. package/project/examples/component-template.tsx +110 -0
  75. package/project/examples/hook-template.ts +152 -0
  76. package/project/examples/service-template.ts +207 -0
  77. package/project/examples/test-template.test.tsx +249 -0
  78. package/project/hooks/README.md +491 -0
  79. package/project/hooks/__pycache__/notification.cpython-314.pyc +0 -0
  80. package/project/hooks/__pycache__/post_tool_use.cpython-314.pyc +0 -0
  81. package/project/hooks/__pycache__/pre_tool_use.cpython-314.pyc +0 -0
  82. package/project/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  83. package/project/hooks/__pycache__/stop.cpython-314.pyc +0 -0
  84. package/project/hooks/notification.py +183 -0
  85. package/project/hooks/permission_request.py +438 -0
  86. package/project/hooks/post_tool_use.py +397 -0
  87. package/project/hooks/pre_compact.py +126 -0
  88. package/project/hooks/pre_tool_use.py +454 -0
  89. package/project/hooks/session_start.py +656 -0
  90. package/project/hooks/stop.py +356 -0
  91. package/project/hooks/subagent_start.py +223 -0
  92. package/project/hooks/subagent_stop.py +215 -0
  93. package/project/hooks/user_prompt_submit.py +110 -0
  94. package/project/hooks/utils/llm/anth.py +114 -0
  95. package/project/hooks/utils/llm/oai.py +114 -0
  96. package/project/hooks/utils/tts/elevenlabs_tts.py +63 -0
  97. package/project/hooks/utils/tts/mlx_audio_tts.py +86 -0
  98. package/project/hooks/utils/tts/openai_tts.py +92 -0
  99. package/project/hooks/utils/tts/pyttsx3_tts.py +75 -0
  100. package/project/linear.yaml.template +23 -0
  101. package/project/product.md.template +238 -0
  102. package/project/retros/README.md +126 -0
  103. package/project/rules/README.md +90 -0
  104. package/project/rules/debugging.md +139 -0
  105. package/project/rules/security-review.md +115 -0
  106. package/project/settings.yaml.template +185 -0
  107. package/project/specs/SPEC-ANV-72-hud-kanban.md +525 -0
  108. package/project/templates/api-python/CLAUDE.md +547 -0
  109. package/project/templates/generic/CLAUDE.md +260 -0
  110. package/project/templates/saas/CLAUDE.md +478 -0
  111. package/project/tests/README.md +140 -0
  112. package/project/tests/__pycache__/test_transcript_parser.cpython-314-pytest-9.0.2.pyc +0 -0
  113. package/project/tests/fixtures/sample-transcript.jsonl +21 -0
  114. package/project/tests/test-hooks.sh +259 -0
  115. package/project/tests/test-lib.sh +248 -0
  116. package/project/tests/test-statusline.sh +165 -0
  117. 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/`.
@@ -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"}