@haystackeditor/cli 0.4.0 → 0.6.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.
@@ -6,236 +6,487 @@ import * as fs from 'fs/promises';
6
6
  import * as path from 'path';
7
7
  /**
8
8
  * Claude Code slash command - invoked with /haystack
9
- * This is the "one command" entry point for users.
10
- * Uses task decomposition - complete one step, validate, then next step.
11
9
  */
12
10
  const CLAUDE_COMMAND_CONTENT = `# Set Up Haystack Verification
13
11
 
14
- You are setting up Haystack PR verification. Complete each step IN ORDER. Do NOT skip ahead.
12
+ Follow .agents/skills/setup-haystack.md to set up Haystack verification for this repo.
13
+ `;
14
+ const SKILL_CONTENT = `# Haystack Verification Setup
15
+
16
+ **Your job**: Help the verification system understand this app so it can visually verify PRs work correctly.
17
+
18
+ The verification system has two parts:
19
+ 1. **Planner** - An AI that explores the codebase to figure out what to test
20
+ 2. **Executor** - Takes screenshots based on the plan
21
+
22
+ Your \`.haystack.json\` flows feed into the **corpus** - they're hints for the Planner about what routes exist, what selectors to use, and what the core user journey looks like.
15
23
 
16
24
  ---
17
25
 
18
- ## STEP 1: Initialize config
26
+ ## Step 1: Generate Base Config
19
27
 
20
28
  \`\`\`bash
21
29
  npx @haystackeditor/cli init --yes
22
30
  \`\`\`
23
31
 
24
- **Checkpoint**: \`.haystack.yml\` exists with dev_server config.
32
+ This detects your dev server command, port, and service dependencies.
33
+
34
+ ---
35
+
36
+ ## Step 2: Understand the App's Core Feature
37
+
38
+ Before writing any flows, answer this:
39
+
40
+ **What is the ONE main thing users do in this app?**
41
+
42
+ Read the codebase to understand the core user journey:
43
+ - E-commerce: browse products → add to cart → checkout
44
+ - Dashboard: view metrics → filter data → export
45
+ - Editor: create document → edit → save
46
+ - Social: view feed → create post → interact
47
+ - SaaS: sign up → configure → use feature
48
+
49
+ Your flows should describe THIS journey, not just "pages load".
25
50
 
26
51
  ---
27
52
 
28
- ## STEP 2: Discover all routes
53
+ ## Step 3: Assess Data Needs
29
54
 
30
- Find every route in the app:
31
55
  \`\`\`bash
32
- grep -r "path=\\|Route\\|<Link" src/ --include="*.tsx" | head -30
33
- ls src/pages/ src/app/ 2>/dev/null
56
+ # Find API calls
57
+ grep -r "fetch(\\|useQuery\\|useSWR\\|axios" src/ --include="*.tsx" --include="*.ts" | wc -l
58
+
59
+ # Find dynamic routes
60
+ grep -r "useParams\\|router.query\\|\\[.*\\]" src/ --include="*.tsx" | head -10
61
+
62
+ # Find external domains
63
+ grep -r "https://" src/ --include="*.ts" --include="*.tsx" | grep -v node_modules | head -10
34
64
  \`\`\`
35
65
 
36
- Add a flow for EACH route to \`.haystack.yml\`. Use \`trigger: always\` for main pages, \`trigger: on_change\` with \`watch_patterns\` for others.
66
+ ---
37
67
 
38
- **Checkpoint**: Count your flows. You should have one for every route.
68
+ ## STOP - Ask About Test Data Strategy
69
+
70
+ **You MUST ask the user before proceeding:**
71
+
72
+ > I analyzed the codebase and found:
73
+ > - X API fetch calls
74
+ > - Y dynamic routes with parameters
75
+ > - These external domains: [list them]
76
+ >
77
+ > **How should I handle test data for verification?**
78
+ >
79
+ > 1. **Passthrough** - Let API calls through to real servers (add domains to \`network.allow\`)
80
+ > 2. **Staging URL** - Point flows at your staging/demo environment
81
+ > 3. **Local fixtures** - JSON files in \`fixtures/\` directory
82
+ > 4. **Skip data pages** - Only test static pages that don't need API data
83
+
84
+ **Wait for the user's response before continuing.**
39
85
 
40
86
  ---
41
87
 
42
- ## STEP 3: Fix ALL selectors (CRITICAL)
88
+ ## Step 4: Write Flows
43
89
 
44
- **STOP**: Look at every \`wait_for\` selector in your flows.
90
+ Flows tell the Planner about your app's routes, UI elements, and user journeys.
45
91
 
46
- If ANY selector is \`#root\`, \`div\`, or \`h1\`, you MUST fix it now:
47
- \`\`\`bash
48
- # Find specific selectors in the codebase
49
- grep -r "data-testid\\|className=" src/components/ --include="*.tsx" | head -20
92
+ ### Structure
93
+
94
+ \`\`\`yaml
95
+ flows:
96
+ - name: "Descriptive name of what this tests"
97
+ trigger: always # or on_change with watch_patterns
98
+ steps:
99
+ - action: navigate
100
+ url: "/"
101
+ - action: wait_for
102
+ selector: "[data-testid='specific-element']"
103
+ - action: click
104
+ selector: "[data-testid='button']"
105
+ - action: screenshot
106
+ name: "result"
50
107
  \`\`\`
51
108
 
52
- Replace generic selectors with specific ones:
53
- - \`[data-testid='dashboard']\`
54
- - \`.dashboard-content\`
55
- - \`[role='main']\`
109
+ ### What to Include
56
110
 
57
- **Checkpoint**: Run \`grep "wait_for" .haystack.yml\` - NONE should have \`#root\`.
111
+ **Core journey flow** (trigger: always):
112
+ - The main thing users do in your app
113
+ - Multiple steps with interactions (click, type)
114
+ - Waits for meaningful state changes
58
115
 
59
- ---
116
+ **Route coverage flows**:
117
+ - One flow per major route
118
+ - Uses specific selectors the Planner can learn from
119
+ - \`watch_patterns\` to only run when relevant files change
60
120
 
61
- ## STEP 4: Add 3+ interactive flows (REQUIRED)
121
+ ### Finding Good Selectors
62
122
 
63
- Find interactive elements:
64
123
  \`\`\`bash
65
- grep -r "onClick\\|Modal\\|Dialog\\|toggle\\|Switch" src/ --include="*.tsx" | head -20
124
+ # Find data-testid attributes
125
+ grep -r "data-testid" src/ --include="*.tsx" | head -20
126
+
127
+ # Find aria-labels
128
+ grep -r "aria-label" src/ --include="*.tsx" | head -20
129
+
130
+ # Find component class names
131
+ grep -r "className=" src/components/ --include="*.tsx" | head -20
66
132
  \`\`\`
67
133
 
68
- Add AT LEAST 3 flows with \`click\` or \`type\` actions:
134
+ Use specific selectors like:
135
+ - \`[data-testid='dashboard-chart']\`
136
+ - \`[aria-label='Submit form']\`
137
+ - \`.pricing-table\`
138
+ - \`button[type='submit']\`
139
+
140
+ If good selectors don't exist, add \`data-testid\` to key components.
141
+
142
+ ### watch_patterns
143
+
144
+ For flows that only matter when certain files change:
69
145
 
70
146
  \`\`\`yaml
71
- - name: "Theme toggle works"
147
+ - name: "Settings page works"
148
+ trigger: on_change
149
+ watch_patterns:
150
+ - "src/pages/settings/**"
151
+ - "src/components/settings/**"
72
152
  steps:
73
153
  - action: navigate
74
- url: "/"
75
- - action: click
76
- selector: "[data-testid='theme-toggle']"
77
- - action: screenshot
78
- name: "after-toggle"
154
+ url: "/settings"
155
+ # ...
156
+ \`\`\`
79
157
 
80
- - name: "Modal opens"
81
- steps:
82
- - action: navigate
83
- url: "/dashboard"
84
- - action: click
85
- selector: "button[aria-label='Settings']"
86
- - action: wait_for
87
- selector: "[role='dialog']"
88
- - action: screenshot
89
- name: "modal-open"
158
+ ---
159
+
160
+ ## Step 5: Configure Fixtures (if needed)
161
+
162
+ Based on user's answer in Step 3:
163
+
164
+ **Passthrough**:
165
+ \`\`\`yaml
166
+ network:
167
+ allow:
168
+ - "api.example.com"
169
+ - "cdn.example.com"
170
+ \`\`\`
171
+
172
+ **Staging URL**:
173
+ \`\`\`yaml
174
+ flows:
175
+ - name: "Dashboard with real data"
176
+ steps:
177
+ - action: navigate
178
+ url: "https://staging.example.com/dashboard"
90
179
  \`\`\`
91
180
 
92
- **Checkpoint**: Run \`grep -c "action: click" .haystack.yml\` - must be ≥3.
181
+ **Local fixtures**:
182
+ \`\`\`yaml
183
+ fixtures:
184
+ - pattern: "/api/user"
185
+ source: "file://fixtures/user.json"
186
+ - pattern: "/api/data/*"
187
+ source: "file://fixtures/data.json"
188
+ \`\`\`
93
189
 
94
190
  ---
95
191
 
96
- ## STEP 5: Add fixtures for data-dependent pages
192
+ ## Step 6: Commit
97
193
 
98
- Find pages that fetch data:
99
194
  \`\`\`bash
100
- grep -r "useParams\\|fetch(\\|useQuery" src/ --include="*.tsx" | head -10
195
+ git add .haystack.json fixtures/
196
+ git commit -m "Add Haystack verification"
101
197
  \`\`\`
102
198
 
103
- For each data-dependent page, add fixtures:
199
+ Done! The Planner will use your flows to understand the app and create verification plans for PRs.
200
+ `;
201
+ const REFERENCE_CONTENT = `# Haystack Reference
202
+
203
+ Reference material for \`.haystack.json\` configuration. Only consult when needed for a specific step.
204
+
205
+ ---
206
+
207
+ ## Fixture Patterns
208
+
209
+ ### Option 1: Passthrough (Recommended - Easiest!)
104
210
  \`\`\`yaml
211
+ # Let API calls through to real servers - no mocking needed
105
212
  fixtures:
106
- - pattern: "/api/user/*"
107
- source: "https://staging.example.com/api/user/demo"
108
- - pattern: "/api/pr/*"
109
- source: "file://fixtures/sample-pr.json"
213
+ - pattern: "/api/*"
214
+ source: passthrough
215
+
216
+ network:
217
+ allow:
218
+ - "api.example.com"
219
+ - "cdn.example.com"
110
220
  \`\`\`
111
221
 
112
- **Checkpoint**: Every page with \`:id\` or API calls has a fixture.
222
+ **This is the easiest option.** Just allow the domains your app calls and let real APIs handle requests. No fixtures to maintain.
113
223
 
114
- ---
224
+ ### Option 2: Staging/Demo URL
225
+ \`\`\`yaml
226
+ # Point flows directly at staging - no fixtures needed
227
+ flows:
228
+ - name: "Dashboard loads"
229
+ url: "https://staging.example.com/dashboard"
230
+ wait_for_selector: ".dashboard-content"
115
231
 
116
- ## STEP 6: Final validation
232
+ network:
233
+ allow:
234
+ - "staging.example.com"
235
+ \`\`\`
117
236
 
118
- Count and verify:
119
- \`\`\`bash
120
- echo "=== Selector check (should be 0 #root) ==="
121
- grep "#root" .haystack.yml | wc -l
237
+ ### Option 3: Pre-signed URLs (S3/Cloud Storage)
238
+ \`\`\`yaml
239
+ fixtures:
240
+ - pattern: "/api/data"
241
+ source: "$FIXTURE_DATA_URL" # Pre-signed URL from CI
242
+ - pattern: "/api/users"
243
+ source: "$FIXTURE_USERS_URL"
244
+
245
+ secrets:
246
+ - FIXTURE_DATA_URL
247
+ - FIXTURE_USERS_URL
248
+ \`\`\`
122
249
 
123
- echo "=== Interactive flows (should be ≥3) ==="
124
- grep -c "action: click\\|action: type" .haystack.yml
250
+ See "Pre-signed URL Setup" below for CI configuration.
125
251
 
126
- echo "=== Fixtures (should be >0 for data pages) ==="
127
- grep -c "pattern:" .haystack.yml
252
+ ### Option 4: Local Fixtures (Simple Apps Only)
253
+ \`\`\`yaml
254
+ fixtures:
255
+ - pattern: "/api/user"
256
+ source: "file://fixtures/user.json"
257
+ - pattern: "/api/settings"
258
+ source: "file://fixtures/settings.json"
128
259
  \`\`\`
129
260
 
130
- If any check fails, GO BACK to that step and fix it.
261
+ Create matching JSON files in \`fixtures/\` directory. Only use for small, stable data.
262
+
263
+ ### Option 5: HTTP Endpoint (Self-hosted)
264
+ \`\`\`yaml
265
+ fixtures:
266
+ - pattern: "/api/*"
267
+ source: "https://fixtures.yourcompany.com/api"
268
+ headers:
269
+ Authorization: "Bearer $FIXTURES_TOKEN"
270
+
271
+ network:
272
+ allow:
273
+ - "fixtures.yourcompany.com"
274
+ \`\`\`
131
275
 
132
276
  ---
133
277
 
134
- ## STEP 7: Commit
278
+ ## Pre-signed URL Setup
135
279
 
280
+ Generate temporary URLs in CI, pass to Haystack. No cloud credentials in sandbox.
281
+
282
+ **Note:** URLs valid for 24 hours. Run on push to main to keep URLs fresh.
283
+
284
+ ### AWS S3
136
285
  \`\`\`bash
137
- git add .haystack.yml .agents/ .claude/ fixtures/
138
- git commit -m "Add Haystack verification"
286
+ # Generate pre-signed URL (valid 24 hours)
287
+ aws s3 presign s3://my-bucket/fixtures/data.json --expires-in 86400
139
288
  \`\`\`
140
289
 
141
- 🎉 Done!
142
- `;
143
- const SKILL_CONTENT = `# Haystack Verification
290
+ ### Google Cloud Storage
291
+ \`\`\`bash
292
+ # Generate signed URL (valid 24 hours)
293
+ gcloud storage sign-url gs://my-bucket/fixtures/data.json --duration=24h
294
+ \`\`\`
144
295
 
145
- ## What is Haystack?
296
+ ### Azure Blob Storage
297
+ \`\`\`bash
298
+ # Generate SAS URL (valid 24 hours)
299
+ az storage blob generate-sas --account-name myaccount --container fixtures \\
300
+ --name data.json --permissions r --expiry $(date -u -d '+1 day' +%Y-%m-%dT%H:%MZ) \\
301
+ --full-uri
302
+ \`\`\`
146
303
 
147
- Haystack provides **automated PR verification**. When a PR is opened:
304
+ ### Cloudflare R2 / DigitalOcean Spaces / MinIO
305
+ \`\`\`bash
306
+ # S3-compatible - use aws cli with custom endpoint
307
+ aws s3 presign s3://my-bucket/data.json --expires-in 86400 \\
308
+ --endpoint-url https://your-r2-endpoint.com
309
+ \`\`\`
148
310
 
149
- 1. A sandbox spins up with your app running (dev server + any backend services)
150
- 2. An AI agent reads the "flows" in \`.haystack.yml\`
151
- 3. The agent executes each flow to verify changes work correctly
152
- 4. Results (screenshots, API responses, errors) are posted to the PR
311
+ ---
153
312
 
154
- **Frontend flows**: The agent uses a browser to navigate pages, click buttons, fill forms, and take screenshots. This catches visual regressions and broken interactions.
313
+ ### GitHub Actions Example
314
+ \`\`\`yaml
315
+ # Run on push to main to keep URLs fresh
316
+ on:
317
+ push:
318
+ branches: [main]
319
+
320
+ jobs:
321
+ update-fixture-urls:
322
+ runs-on: ubuntu-latest
323
+ permissions:
324
+ id-token: write # OIDC - no long-lived secrets
325
+ steps:
326
+ - uses: aws-actions/configure-aws-credentials@v4
327
+ with:
328
+ role-to-assume: arn:aws:iam::123456789:role/haystack-fixtures
329
+ aws-region: us-east-1
330
+
331
+ - name: Generate pre-signed URLs (valid 24 hours)
332
+ run: |
333
+ URL=$(aws s3 presign s3://my-bucket/fixtures/data.json --expires-in 86400)
334
+ haystack secrets set FIXTURE_DATA_URL "$URL"
335
+ \`\`\`
155
336
 
156
- **Backend flows**: The agent makes HTTP requests to API endpoints and verifies responses. This catches broken endpoints and API contract changes.
337
+ ### General Approach
338
+ 1. Use OIDC to get temporary cloud credentials (no long-lived secrets)
339
+ 2. Generate pre-signed/signed URL for each fixture file (24 hour expiry)
340
+ 3. Store with \`haystack secrets set FIXTURE_URL "$URL"\`
341
+ 4. Run on push to main to keep URLs fresh
157
342
 
158
- **Without flows, Haystack has nothing to verify.** The config's \`dev_server\` settings just tell it how to start your app - the flows tell it what to actually test.
343
+ ---
159
344
 
160
- ## Setup Workflow
345
+ ## Flow Examples
161
346
 
162
- ### Step 1: Generate base config
163
- \`\`\`bash
164
- npx @haystackeditor/cli init
347
+ ### Basic Page Flow
348
+ \`\`\`yaml
349
+ flows:
350
+ - name: "Dashboard loads"
351
+ description: "Verify dashboard renders correctly"
352
+ trigger: always
353
+ watch_patterns:
354
+ - "src/components/dashboard/**"
355
+ steps:
356
+ - action: navigate
357
+ url: "/dashboard"
358
+ - action: wait_for
359
+ selector: "[data-testid='dashboard-content']"
360
+ - action: assert_no_errors
361
+ - action: screenshot
362
+ name: "dashboard"
165
363
  \`\`\`
166
- This auto-detects framework, ports, package manager and creates \`.haystack.yml\`.
167
364
 
168
- ### Step 2: Review and customize
169
- After init, review the generated config and customize based on the app:
365
+ ### Interactive Flow (click)
366
+ \`\`\`yaml
367
+ - name: "Modal opens"
368
+ trigger: on_change
369
+ watch_patterns:
370
+ - "src/components/settings/**"
371
+ steps:
372
+ - action: navigate
373
+ url: "/settings"
374
+ - action: wait_for
375
+ selector: ".settings-page"
376
+ - action: click
377
+ selector: "button[aria-label='Settings']"
378
+ - action: wait_for
379
+ selector: "[role='dialog']"
380
+ - action: screenshot
381
+ name: "settings-modal"
382
+ \`\`\`
170
383
 
171
- | If the app has... | Add this |
172
- |-------------------|----------|
173
- | Login/authentication | Auth bypass env var (see Auth Bypass section) |
174
- | Key user journeys | Flows describing what to verify (see Flows section) |
175
- | API calls needing auth | Fixtures to mock responses (see Fixtures section) |
384
+ ### Form Flow (type)
385
+ \`\`\`yaml
386
+ - name: "Contact form works"
387
+ trigger: on_change
388
+ watch_patterns:
389
+ - "src/components/contact/**"
390
+ steps:
391
+ - action: navigate
392
+ url: "/contact"
393
+ - action: wait_for
394
+ selector: "form"
395
+ - action: type
396
+ selector: "input[name='email']"
397
+ value: "test@example.com"
398
+ - action: click
399
+ selector: "button[type='submit']"
400
+ - action: wait_for
401
+ selector: ".success-message"
402
+ \`\`\`
176
403
 
177
- **Minimum viable config**: Just \`dev_server\` settings. Flows and fixtures can be added later as needed.
404
+ ### Backend API Flow
405
+ \`\`\`yaml
406
+ - name: "API health check"
407
+ trigger: always
408
+ steps:
409
+ - action: http_request
410
+ method: GET
411
+ url: "http://localhost:3001/health"
412
+ - action: assert_status
413
+ status: 200
414
+ \`\`\`
415
+
416
+ ---
417
+
418
+ ## Finding Good Selectors
178
419
 
179
- ### Step 3: Commit
420
+ **Priority order:**
421
+ 1. \`[data-testid='feature-name']\` - Best
422
+ 2. \`[role='main']\`, \`[aria-label='...']\` - Semantic
423
+ 3. \`.specific-class-name\` - Component-specific
424
+ 4. Avoid: \`#root\`, \`div\`, \`h1\` - Too generic
425
+
426
+ **How to find:**
180
427
  \`\`\`bash
181
- git add .haystack.yml .agents/
182
- git commit -m "Add Haystack verification"
428
+ # Find data-testid attributes
429
+ grep -r "data-testid" src/ --include="*.tsx"
430
+
431
+ # Find class names
432
+ grep -r "className=" src/components/Dashboard.tsx
433
+
434
+ # Find semantic roles
435
+ grep -r "role=\\|aria-label=" src/ --include="*.tsx"
183
436
  \`\`\`
184
437
 
185
- ## Config Reference
438
+ ---
439
+
440
+ ## Config Structure
441
+
442
+ ⚠️ **IMPORTANT**: \`flows\` must be at TOP LEVEL, not nested under \`verification\`!
186
443
 
187
444
  \`\`\`yaml
188
445
  version: "1"
189
446
  name: my-app
190
447
 
191
- # Dev server configuration
192
448
  dev_server:
193
449
  command: pnpm dev
194
450
  port: 3000
195
- ready_pattern: "Local:" # Text in stdout when server is ready
196
- env:
197
- SKIP_AUTH: "true" # Auth bypass for testing
451
+ ready_pattern: "ready|Local:|started" # Regex for server ready
198
452
 
199
- # Verification commands (run in PR checks)
453
+ # Verification commands - MUST include build
200
454
  verification:
201
455
  commands:
202
456
  - name: build
203
- run: pnpm build
457
+ run: pnpm build # ← REQUIRED
204
458
  - name: lint
205
459
  run: pnpm lint
206
460
  - name: typecheck
207
461
  run: pnpm tsc --noEmit
208
462
 
209
- # Flows: Advisory descriptions for AI verification agent
210
- # The agent reads these to understand WHAT to verify, then navigates autonomously
463
+ network:
464
+ allow:
465
+ - "api.example.com"
466
+
467
+ # ⚠️ flows at TOP LEVEL - NOT under verification!
211
468
  flows:
212
- - name: "Landing page loads"
213
- description: "Verify the landing page renders without errors"
469
+ - name: "Page loads"
214
470
  trigger: always
215
471
  steps:
216
472
  - action: navigate
217
473
  url: "/"
218
474
  - action: wait_for
219
- selector: "[data-testid='landing']"
475
+ selector: "[data-testid='main']"
220
476
  - action: screenshot
221
- name: "landing"
477
+ name: "home"
222
478
 
223
- - name: "Dashboard loads with data"
224
- description: "Verify dashboard shows user data correctly"
225
- trigger: on_change
226
- watch_patterns:
227
- - "src/components/dashboard/**"
228
- steps:
229
- - action: navigate
230
- url: "/dashboard"
231
- - action: wait_for
232
- selector: ".dashboard-content"
233
- - action: assert_no_errors
479
+ secrets:
480
+ - API_TOKEN
481
+
482
+ fixtures:
483
+ - pattern: "/api/*"
484
+ source: passthrough
234
485
  \`\`\`
235
486
 
236
- ## Monorepo Configuration
487
+ ---
237
488
 
238
- For monorepos with multiple services:
489
+ ## Monorepo Configuration
239
490
 
240
491
  \`\`\`yaml
241
492
  version: "1"
@@ -247,8 +498,6 @@ services:
247
498
  command: pnpm dev
248
499
  port: 3000
249
500
  ready_pattern: "Local:"
250
- env:
251
- VITE_SKIP_AUTH: "true"
252
501
 
253
502
  api:
254
503
  root: packages/api
@@ -260,410 +509,370 @@ services:
260
509
  root: infra/worker
261
510
  command: pnpm wrangler dev
262
511
  port: 8787
263
-
264
- # Batch jobs (run once, don't stay running)
265
- analysis:
266
- root: packages/analysis
267
- type: batch
268
- command: pnpm start
269
-
270
- verification:
271
- commands:
272
- - name: build
273
- run: pnpm build
274
- - name: lint
275
- run: pnpm lint
276
512
  \`\`\`
277
513
 
278
- ## Multi-Repo Configuration
279
-
280
- When services live in separate git repositories (not a monorepo), each repo gets its own \`.haystack.yml\`:
281
-
282
- **Frontend repo** - Mock the API it depends on:
283
- \`\`\`yaml
284
- version: "1"
285
- name: frontend
286
-
287
- dev_server:
288
- command: pnpm dev
289
- port: 3000
290
- env:
291
- VITE_API_URL: "http://localhost:8080" # Will be mocked
514
+ ---
292
515
 
293
- # Mock the API from the other repo
294
- fixtures:
295
- - pattern: "/api/*"
296
- source: "file://fixtures/api-responses.json"
297
- \`\`\`
516
+ ## Flow Triggers
298
517
 
299
- **API repo** - Standalone verification:
300
- \`\`\`yaml
301
- version: "1"
302
- name: api
518
+ | Trigger | When it runs |
519
+ |---------|--------------|
520
+ | \`always\` | Every PR |
521
+ | \`on_change\` | Only when \`watch_patterns\` match changed files |
522
+ `;
523
+ const PREPARE_VERIFICATION_CONTENT = `# Prepare Codebase for Verification
303
524
 
304
- dev_server:
305
- command: pnpm dev
306
- port: 8080
525
+ **Your job**: Make this codebase easy to verify by adding semantic identifiers that the verification system can target.
307
526
 
308
- verification:
309
- commands:
310
- - name: test
311
- run: pnpm test
312
- \`\`\`
527
+ The verification Planner needs to find UI elements by selectors. Generic selectors like \`div\` or \`.flex\` are useless. Your job is to add meaningful identifiers throughout the codebase.
313
528
 
314
- Each repo is verified independently. Use fixtures to mock dependencies on other services.
529
+ ---
315
530
 
316
- ## Fixtures (API Mocking)
531
+ ## What to Add
317
532
 
318
- Mock API responses so verification doesn't need real credentials:
533
+ ### 1. \`aria-label\` on Interactive Elements
319
534
 
320
- \`\`\`yaml
321
- fixtures:
322
- # Local file (small data, commit to repo)
323
- - pattern: "/api/user"
324
- source: "file://fixtures/user.json"
325
-
326
- # From staging server
327
- - pattern: "/api/dashboard"
328
- source: "https://staging.example.com/api/dashboard"
329
- headers:
330
- Authorization: "Bearer $STAGING_TOKEN"
535
+ Every clickable/interactive element should have an aria-label describing what it does:
331
536
 
332
- # Large data from S3
333
- - pattern: "/api/analytics"
334
- source: "s3://my-bucket/fixtures/analytics.json"
537
+ \`\`\`tsx
538
+ // Before
539
+ <button onClick={onSave}>💾</button>
540
+ <button onClick={() => setOpen(true)}>
541
+ <MenuIcon />
542
+ </button>
335
543
 
336
- # Use real API (no mocking)
337
- - pattern: "/api/public/*"
338
- source: passthrough
544
+ // After
545
+ <button onClick={onSave} aria-label="Save document">💾</button>
546
+ <button onClick={() => setOpen(true)} aria-label="Open menu">
547
+ <MenuIcon />
548
+ </button>
339
549
  \`\`\`
340
550
 
341
- ## Understanding Flows
342
-
343
- **Flows are advisory, not mechanical scripts.**
344
-
345
- When a PR is opened, an AI agent (running in a Modal sandbox with browser access):
346
- 1. Reads the flows to understand what to verify
347
- 2. Navigates the app autonomously
348
- 3. Determines if things work based on flow descriptions
349
- 4. Captures screenshots and evidence
350
- 5. Reports results
351
-
352
- The flow steps are hints like "check the landing page loads" - the AI figures out how to verify that. It can adapt if the UI changes slightly.
353
-
354
- ### Flow Triggers
355
-
356
- | Trigger | When it runs |
357
- |---------|--------------|
358
- | \`always\` | Every PR |
359
- | \`on_change\` | Only when \`watch_patterns\` match changed files |
360
-
361
- ### Flow Actions (Advisory)
362
-
363
- These describe what the agent should verify:
364
-
365
- **Browser:**
366
- - \`navigate\` - Go to URL
367
- - \`wait_for\` - Wait for element
368
- - \`click\` - Click element
369
- - \`type\` - Enter text
370
- - \`screenshot\` - Capture screenshot
371
- - \`assert_no_errors\` - Check for error states
551
+ **Target elements:**
552
+ - Buttons (especially icon-only buttons)
553
+ - Links without descriptive text
554
+ - Toggle switches
555
+ - Dropdown triggers
556
+ - Modal open/close buttons
557
+ - Form submit buttons
558
+
559
+ ### 2. \`data-testid\` on Key Sections
560
+
561
+ Major UI sections should have data-testid for easy targeting:
562
+
563
+ \`\`\`tsx
564
+ // Before
565
+ <div className="flex flex-col p-4">
566
+ <h1>Dashboard</h1>
567
+ {/* content */}
568
+ </div>
569
+
570
+ // After
571
+ <div className="flex flex-col p-4" data-testid="dashboard-container">
572
+ <h1>Dashboard</h1>
573
+ {/* content */}
574
+ </div>
575
+ \`\`\`
372
576
 
373
- **API:**
374
- - \`http_request\` - Make API call
375
- - \`assert_status\` - Check response code
376
- - \`websocket_connect\` - Test WebSocket
577
+ **Target sections:**
578
+ - Page containers (dashboard, settings, profile)
579
+ - Navigation bars/sidebars
580
+ - Modal/dialog content
581
+ - Form containers
582
+ - Data tables/lists
583
+ - Card components
584
+ - Loading states
585
+ - Error states
586
+ - Empty states
587
+
588
+ ### 3. \`role\` Attributes for Semantic Structure
589
+
590
+ Add ARIA roles where HTML semantics aren't clear:
591
+
592
+ \`\`\`tsx
593
+ // Before
594
+ <div className="modal-overlay">
595
+ <div className="modal-content">
596
+
597
+ // After
598
+ <div className="modal-overlay" role="presentation">
599
+ <div className="modal-content" role="dialog" aria-modal="true">
600
+ \`\`\`
377
601
 
378
- ## Auth Bypass
602
+ **Common roles:**
603
+ - \`role="dialog"\` - Modals/dialogs
604
+ - \`role="navigation"\` - Nav sections
605
+ - \`role="main"\` - Main content area
606
+ - \`role="alert"\` - Error/success messages
607
+ - \`role="status"\` - Loading indicators
608
+ - \`role="tablist"\`, \`role="tab"\`, \`role="tabpanel"\` - Tab interfaces
609
+
610
+ ### 4. State Indicators
611
+
612
+ Add attributes that indicate UI state:
613
+
614
+ \`\`\`tsx
615
+ // Before
616
+ <button onClick={toggle}>
617
+ {isOpen ? 'Close' : 'Open'}
618
+ </button>
619
+
620
+ // After
621
+ <button
622
+ onClick={toggle}
623
+ aria-expanded={isOpen}
624
+ aria-label={isOpen ? 'Close panel' : 'Open panel'}
625
+ >
626
+ {isOpen ? 'Close' : 'Open'}
627
+ </button>
628
+ \`\`\`
379
629
 
380
- Most apps need auth bypassed for testing. Common patterns:
630
+ **State attributes:**
631
+ - \`aria-expanded\` - Collapsible sections
632
+ - \`aria-selected\` - Selected items in lists
633
+ - \`aria-checked\` - Checkboxes/toggles
634
+ - \`aria-disabled\` - Disabled elements
635
+ - \`aria-busy\` - Loading states
636
+ - \`data-state="loading|error|success"\` - Custom states
637
+
638
+ ### 5. Form Accessibility
639
+
640
+ Forms should have proper labels and descriptions:
641
+
642
+ \`\`\`tsx
643
+ // Before
644
+ <input type="email" placeholder="Email" />
645
+ <span className="text-red-500">{error}</span>
646
+
647
+ // After
648
+ <input
649
+ type="email"
650
+ placeholder="Email"
651
+ aria-label="Email address"
652
+ aria-describedby={error ? "email-error" : undefined}
653
+ aria-invalid={!!error}
654
+ />
655
+ {error && <span id="email-error" role="alert" className="text-red-500">{error}</span>}
656
+ \`\`\`
381
657
 
382
- | Framework | Env Var |
383
- |-----------|---------|
384
- | Vite/React | \`VITE_SKIP_AUTH=true\` |
385
- | Next.js | \`NEXT_PUBLIC_SKIP_AUTH=true\` |
386
- | Express | \`SKIP_AUTH=true\` |
387
- | Rails | \`SKIP_AUTH=true\` |
658
+ ---
388
659
 
389
- Add to \`dev_server.env\` or \`services.*.env\` in your config.
660
+ ## Step 1: Scan for Missing Identifiers
390
661
 
391
- ## Codebase Discovery Guide
662
+ \`\`\`bash
663
+ # Find buttons without aria-label
664
+ grep -rn "<button" src/ --include="*.tsx" | grep -v "aria-label" | head -20
392
665
 
393
- **Follow these steps to create comprehensive verification flows.**
666
+ # Find icon-only buttons (likely missing labels)
667
+ grep -rn "<button.*Icon\\|<button.*>.*</.*Icon>" src/ --include="*.tsx" | head -20
394
668
 
395
- ### ⚠️ REQUIRED CHECKLIST - Complete ALL items before finishing:
669
+ # Find modals/dialogs without role
670
+ grep -rn "modal\\|dialog\\|Modal\\|Dialog" src/ --include="*.tsx" | grep -v "role=" | head -20
396
671
 
397
- 1. [ ] **Page flows**: Every route in the app has a flow
398
- 2. [ ] **Specific selectors**: Using \`[data-testid='x']\` or \`.specific-class\`, NOT \`#root\` or \`div\`
399
- 3. [ ] **Interactive flows**: At least 3 flows that click buttons, open modals, or submit forms
400
- 4. [ ] **Fixtures**: Pages with \`:id\` params or API fetches have fixtures (staging URL or local file)
401
- 5. [ ] **Backend API flows**: If app has API endpoints, add http_request flows to test them
402
- 6. [ ] **Watch patterns**: Each flow's \`watch_patterns\` matches the component file paths
672
+ # Find forms without proper labeling
673
+ grep -rn "<input\\|<select\\|<textarea" src/ --include="*.tsx" | grep -v "aria-label\\|id=" | head -20
403
674
 
404
- **You are NOT done until all 6 items are checked.**
675
+ # Find major components (likely need data-testid)
676
+ ls src/components/ src/pages/ 2>/dev/null
677
+ \`\`\`
405
678
 
406
679
  ---
407
680
 
408
- ### Step 1: Trace the Component Tree
681
+ ## Step 2: Prioritize by Impact
409
682
 
410
- Start from the entry point and trace imports to discover ALL features:
683
+ Focus on elements the verification system is most likely to need:
411
684
 
412
- \`\`\`bash
413
- # Find the entry point
414
- cat src/main.tsx # or src/index.tsx, pages/_app.tsx, etc.
685
+ **High Priority (do first):**
686
+ 1. Navigation elements (header, sidebar, menu buttons)
687
+ 2. Primary actions (submit buttons, save buttons, CTAs)
688
+ 3. Modal triggers and dialogs
689
+ 4. Form inputs and submit buttons
690
+ 5. Page-level containers
415
691
 
416
- # Trace the router to find all routes
417
- grep -r "Route\|path=" src/ --include="*.tsx"
692
+ **Medium Priority:**
693
+ 1. Toggle switches and checkboxes
694
+ 2. Dropdown menus
695
+ 3. Tab interfaces
696
+ 4. Cards and list items
697
+ 5. Loading/error states
418
698
 
419
- # Find all page/feature components
420
- ls src/pages/ src/components/ src/features/
421
- \`\`\`
699
+ **Lower Priority:**
700
+ 1. Decorative elements
701
+ 2. Static content sections
702
+ 3. Footer links
422
703
 
423
- ### Step 2: Find Good Selectors
704
+ ---
424
705
 
425
- **DON'T use generic selectors like \`#root\`.** The agent needs specific selectors to know the page loaded correctly.
706
+ ## Step 3: Add Identifiers Systematically
426
707
 
427
- Priority order for selectors:
428
- 1. \`[data-testid='feature-name']\` - Best, explicit test hooks
429
- 2. \`[role='main']\`, \`[role='navigation']\` - Semantic roles
430
- 3. \`.feature-specific-class\` - Component-specific classes
431
- 4. \`h1\`, \`.page-title\` - Unique page identifiers
708
+ Go component by component. For each component:
432
709
 
433
- **How to find selectors:**
434
- \`\`\`bash
435
- # Search for data-testid attributes
436
- grep -r "data-testid" src/ --include="*.tsx"
710
+ 1. **Check the component's purpose** - What does it DO?
711
+ 2. **Add aria-label** to interactive elements describing the ACTION
712
+ 3. **Add data-testid** to the container if it's a major section
713
+ 4. **Add role** if the semantic HTML isn't clear
714
+ 5. **Add state attributes** if the component has dynamic states
437
715
 
438
- # Search for unique classNames in a component
439
- grep -r "className=" src/components/Dashboard.tsx
716
+ ### Naming Conventions
440
717
 
441
- # Look for page-specific elements
442
- grep -r "<h1\|<header\|role=" src/pages/
443
- \`\`\`
718
+ **aria-label**: Describe the action, not the element
719
+ - ✅ \`aria-label="Close modal"\`
720
+ - ✅ \`aria-label="Submit contact form"\`
721
+ - ❌ \`aria-label="Button"\`
722
+ - ❌ \`aria-label="Click here"\`
444
723
 
445
- **Example - BAD vs GOOD:**
446
- \`\`\`yaml
447
- # BAD - too generic, every page has #root
448
- - action: wait_for
449
- selector: "#root"
724
+ **data-testid**: Use kebab-case, describe the section
725
+ - ✅ \`data-testid="user-profile-card"\`
726
+ - \`data-testid="search-results-list"\`
727
+ - \`data-testid="div1"\`
728
+ - ❌ \`data-testid="container"\`
450
729
 
451
- # GOOD - specific to this feature
452
- - action: wait_for
453
- selector: "[data-testid='dashboard-content']"
454
- # or
455
- - action: wait_for
456
- selector: ".dashboard-stats-grid"
457
- # or
458
- - action: wait_for
459
- selector: "h1:has-text('Dashboard')"
460
- \`\`\`
730
+ ---
461
731
 
462
- ### Step 3: Add Interactive Flows
732
+ ## Step 4: Verify Coverage
463
733
 
464
- Don't just screenshot static pages. Verify that interactions work:
734
+ After adding identifiers, check coverage:
465
735
 
466
- **Look for interactive elements:**
467
736
  \`\`\`bash
468
- # Find buttons and clickable elements
469
- grep -r "onClick\|button\|Button" src/ --include="*.tsx"
737
+ # Count aria-labels added
738
+ grep -r "aria-label" src/ --include="*.tsx" | wc -l
470
739
 
471
- # Find modals and dialogs
472
- grep -r "Modal\|Dialog\|Drawer" src/ --include="*.tsx"
740
+ # Count data-testid added
741
+ grep -r "data-testid" src/ --include="*.tsx" | wc -l
473
742
 
474
- # Find forms
475
- grep -r "<form\|onSubmit\|handleSubmit" src/ --include="*.tsx"
743
+ # Count role attributes
744
+ grep -r "role=" src/ --include="*.tsx" | wc -l
476
745
 
477
- # Find toggles and switches
478
- grep -r "toggle\|Switch\|theme" src/ --include="*.tsx"
746
+ # List all data-testid values (check for meaningful names)
747
+ grep -oh 'data-testid="[^"]*"' src/ -r --include="*.tsx" | sort -u
479
748
  \`\`\`
480
749
 
481
- **Example interactive flows:**
482
- \`\`\`yaml
483
- # Theme toggle
484
- - name: "Theme toggle works"
485
- steps:
486
- - action: navigate
487
- url: "/"
488
- - action: click
489
- selector: "[data-testid='theme-toggle']"
490
- - action: screenshot
491
- name: "dark-mode"
492
- - action: click
493
- selector: "[data-testid='theme-toggle']"
494
- - action: screenshot
495
- name: "light-mode"
496
-
497
- # Modal open/close
498
- - name: "Settings modal opens"
499
- steps:
500
- - action: navigate
501
- url: "/dashboard"
502
- - action: click
503
- selector: "[aria-label='Settings']"
504
- - action: wait_for
505
- selector: "[role='dialog']"
506
- - action: screenshot
507
- name: "settings-modal"
508
-
509
- # Form submission
510
- - name: "Contact form submits"
511
- steps:
512
- - action: navigate
513
- url: "/contact"
514
- - action: type
515
- selector: "input[name='email']"
516
- value: "test@example.com"
517
- - action: click
518
- selector: "button[type='submit']"
519
- - action: wait_for
520
- selector: ".success-message"
521
- \`\`\`
750
+ ---
522
751
 
523
- ### Step 4: Handle Data-Dependent Pages
752
+ ## Step 5: Commit
524
753
 
525
- **How to identify pages that need fixtures:**
526
754
  \`\`\`bash
527
- # Find components that fetch data
528
- grep -r "useQuery\|useSWR\|fetch(\|axios\|useEffect.*fetch" src/ --include="*.tsx"
755
+ git add src/
756
+ git commit -m "Add accessibility attributes for verification
529
757
 
530
- # Find API route parameters (these pages need data)
531
- grep -r "useParams\|router.query\|\[.*\]" src/pages/ src/app/ --include="*.tsx"
758
+ - Added aria-labels to interactive elements
759
+ - Added data-testid to major sections
760
+ - Added ARIA roles for semantic structure
761
+ - Added state indicators (aria-expanded, etc.)"
532
762
  \`\`\`
533
763
 
534
- If a page has \`:id\`, \`:slug\`, or fetches from \`/api/*\`, it needs fixtures.
535
-
536
- **Option A: Pull from staging (recommended for large/dynamic data)**
537
- \`\`\`yaml
538
- fixtures:
539
- # Pull real data from staging API
540
- - pattern: "/api/pr/*"
541
- source: "https://staging.example.com/api/pr/sample"
542
- headers:
543
- Authorization: "Bearer $STAGING_TOKEN"
764
+ ---
544
765
 
545
- # Or from S3 bucket
546
- - pattern: "/api/analytics/*"
547
- source: "s3://my-fixtures-bucket/analytics-sample.json"
548
- \`\`\`
766
+ ## Quick Reference: Common Patterns
549
767
 
550
- **Option B: Commit small fixture files**
551
- For small, stable data only:
552
- \`\`\`bash
553
- mkdir -p fixtures
554
- cat > fixtures/user.json << 'EOF'
555
- {"id": 1, "name": "Test User", "email": "test@example.com"}
556
- EOF
768
+ ### Icon Button
769
+ \`\`\`tsx
770
+ <button onClick={onAction} aria-label="Descriptive action name">
771
+ <Icon />
772
+ </button>
557
773
  \`\`\`
558
774
 
559
- \`\`\`yaml
560
- fixtures:
561
- - pattern: "/api/user"
562
- source: "file://fixtures/user.json"
775
+ ### Modal
776
+ \`\`\`tsx
777
+ <div role="dialog" aria-modal="true" aria-labelledby="modal-title" data-testid="settings-modal">
778
+ <h2 id="modal-title">Settings</h2>
779
+ <button onClick={onClose} aria-label="Close settings">×</button>
780
+ </div>
563
781
  \`\`\`
564
782
 
565
- **When to use each:**
566
- | Data Type | Use |
567
- |-----------|-----|
568
- | User profiles, settings | Local file (small, stable) |
569
- | PR data, analytics, lists | Staging API or S3 (large, dynamic) |
570
- | Auth tokens, sessions | Passthrough or mock inline |
571
-
572
- **Option B: Use demo/example routes**
573
- \`\`\`bash
574
- # Look for demo or example routes in the router
575
- grep -r "demo\|example\|sample" src/ --include="*.tsx"
783
+ ### Navigation
784
+ \`\`\`tsx
785
+ <nav aria-label="Main navigation" data-testid="main-nav">
786
+ <a href="/dashboard" aria-current={isActive ? "page" : undefined}>Dashboard</a>
787
+ </nav>
576
788
  \`\`\`
577
789
 
578
- **Option C: Use real test data**
579
- If the app has seeded test data, use those identifiers:
580
- \`\`\`yaml
581
- - name: "PR review page loads"
582
- steps:
583
- - action: navigate
584
- url: "/review/test-org/test-repo/1" # Known test PR
585
- - action: wait_for
586
- selector: "[data-testid='pr-diff']"
790
+ ### Toggle
791
+ \`\`\`tsx
792
+ <button
793
+ onClick={toggle}
794
+ aria-pressed={isOn}
795
+ aria-label={\`\${isOn ? 'Disable' : 'Enable'} notifications\`}
796
+ >
797
+ {isOn ? 'On' : 'Off'}
798
+ </button>
587
799
  \`\`\`
588
800
 
589
- ### Step 5: Add Backend API Flows (if applicable)
590
-
591
- If the app has API endpoints, test them directly:
592
-
593
- \`\`\`bash
594
- # Find API routes
595
- ls src/api/ app/api/ pages/api/ 2>/dev/null
596
- grep -r "app.get\|app.post\|router.get" --include="*.ts"
801
+ ### Loading State
802
+ \`\`\`tsx
803
+ <div data-testid="content-area" aria-busy={isLoading}>
804
+ {isLoading ? (
805
+ <div role="status" aria-label="Loading content">
806
+ <Spinner />
807
+ </div>
808
+ ) : (
809
+ content
810
+ )}
811
+ </div>
597
812
  \`\`\`
598
813
 
599
- **Example API flows:**
600
- \`\`\`yaml
601
- flows:
602
- - name: "API health check"
603
- description: "Verify API server responds"
604
- trigger: always
605
- steps:
606
- - action: http_request
607
- method: GET
608
- url: "http://localhost:3001/health"
609
- - action: assert_status
610
- status: 200
611
-
612
- - name: "API returns valid data"
613
- trigger: on_change
614
- watch_patterns:
615
- - "src/api/**"
616
- steps:
617
- - action: http_request
618
- method: GET
619
- url: "http://localhost:3001/api/users"
620
- - action: assert_status
621
- status: 200
814
+ ### Form Field
815
+ \`\`\`tsx
816
+ <div data-testid="email-field">
817
+ <label htmlFor="email">Email</label>
818
+ <input
819
+ id="email"
820
+ type="email"
821
+ aria-describedby={error ? "email-error" : "email-hint"}
822
+ aria-invalid={!!error}
823
+ />
824
+ <span id="email-hint">We'll never share your email</span>
825
+ {error && <span id="email-error" role="alert">{error}</span>}
826
+ </div>
622
827
  \`\`\`
623
828
 
624
- ### Step 6: Verify Your Flows
625
-
626
- After adding flows, validate the config:
627
- \`\`\`bash
628
- # Check YAML syntax
629
- npx @haystackeditor/cli validate
630
-
631
- # Or manually check
632
- cat .haystack.yml | head -50
829
+ ### Expandable Section
830
+ \`\`\`tsx
831
+ <div data-testid="faq-section">
832
+ <button
833
+ onClick={() => setExpanded(!expanded)}
834
+ aria-expanded={expanded}
835
+ aria-controls="faq-content"
836
+ >
837
+ FAQ
838
+ </button>
839
+ <div id="faq-content" hidden={!expanded}>
840
+ {content}
841
+ </div>
842
+ </div>
633
843
  \`\`\`
844
+ `;
845
+ const PREPARE_HAYSTACK_COMMAND = `# Prepare Codebase for Verification
634
846
 
635
- ### Final Checklist (ALL required):
636
-
637
- Before you finish, verify:
638
-
639
- 1. [ ] **Page flows**: Every route has a flow
640
- 2. [ ] **Specific selectors**: All use \`[data-testid='x']\` or \`.class-name\`, NOT \`#root\`/\`div\`/\`h1\`
641
- 3. [ ] **Interactive flows**: At least 3 flows with \`click\` or \`type\` actions
642
- 4. [ ] **Fixtures configured**: Data-dependent pages have fixtures (staging URL preferred, or local JSON)
643
- 5. [ ] **Backend API flows**: API endpoints have \`http_request\` flows (if app has backend)
644
- 6. [ ] **Watch patterns**: Each \`watch_patterns\` matches component file paths
847
+ Follow .agents/skills/prepare-haystack.md to add accessibility attributes that make verification easier.
645
848
 
646
- ⚠️ **If you have 0 interactive flows or 0 fixtures for data pages, you are not done.**
849
+ Run this BEFORE /setup-haystack to ensure your codebase has good selectors.
647
850
  `;
648
851
  export async function createSkillFile() {
649
852
  const skillDir = path.join(process.cwd(), '.agents', 'skills');
650
- const skillPath = path.join(skillDir, 'haystack.md');
853
+ const setupPath = path.join(skillDir, 'setup-haystack.md');
854
+ const refPath = path.join(skillDir, 'haystack-reference.md');
855
+ const prepPath = path.join(skillDir, 'prepare-haystack.md');
651
856
  // Create directory if needed
652
857
  await fs.mkdir(skillDir, { recursive: true });
653
- // Write skill file
654
- await fs.writeFile(skillPath, SKILL_CONTENT, 'utf-8');
655
- return skillPath;
858
+ // Write all skill files
859
+ await fs.writeFile(setupPath, SKILL_CONTENT, 'utf-8');
860
+ await fs.writeFile(refPath, REFERENCE_CONTENT, 'utf-8');
861
+ await fs.writeFile(prepPath, PREPARE_VERIFICATION_CONTENT, 'utf-8');
862
+ return setupPath;
656
863
  }
657
864
  /**
658
- * Create the .claude/commands/haystack.md file for Claude Code slash command
659
- * Users can invoke with /haystack to start the setup wizard
865
+ * Create the .claude/commands/ files for Claude Code slash commands
866
+ * Users can invoke with /setup-haystack or /prepare-haystack
660
867
  */
661
868
  export async function createClaudeCommand() {
662
869
  const commandDir = path.join(process.cwd(), '.claude', 'commands');
663
- const commandPath = path.join(commandDir, 'haystack.md');
870
+ const setupPath = path.join(commandDir, 'setup-haystack.md');
871
+ const prepPath = path.join(commandDir, 'prepare-haystack.md');
664
872
  // Create directory if needed
665
873
  await fs.mkdir(commandDir, { recursive: true });
666
- // Write command file
667
- await fs.writeFile(commandPath, CLAUDE_COMMAND_CONTENT, 'utf-8');
668
- return commandPath;
874
+ // Write command files
875
+ await fs.writeFile(setupPath, CLAUDE_COMMAND_CONTENT, 'utf-8');
876
+ await fs.writeFile(prepPath, PREPARE_HAYSTACK_COMMAND, 'utf-8');
877
+ return setupPath;
669
878
  }