@haystackeditor/cli 0.3.0 ā 0.4.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.
- package/dist/commands/init.js +26 -3
- package/dist/index.js +0 -0
- package/dist/utils/skill.d.ts +6 -0
- package/dist/utils/skill.js +421 -1
- package/package.json +8 -7
package/dist/commands/init.js
CHANGED
|
@@ -8,9 +8,15 @@ import chalk from 'chalk';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { detectProject } from '../utils/detect.js';
|
|
10
10
|
import { saveConfig, configExists } from '../utils/config.js';
|
|
11
|
-
import { createSkillFile } from '../utils/skill.js';
|
|
11
|
+
import { createSkillFile, createClaudeCommand } from '../utils/skill.js';
|
|
12
12
|
import { validateConfigSecurity, formatSecurityReport } from '../utils/secrets.js';
|
|
13
13
|
export async function initCommand(options) {
|
|
14
|
+
// Auto-use defaults when not in interactive terminal (e.g., when run by AI agents)
|
|
15
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
16
|
+
if (!isInteractive && !options.yes) {
|
|
17
|
+
console.log(chalk.dim('Non-interactive mode detected. Using --yes defaults.\n'));
|
|
18
|
+
options.yes = true;
|
|
19
|
+
}
|
|
14
20
|
console.log(chalk.cyan('\nš¾ Haystack Setup Wizard\n'));
|
|
15
21
|
// Check if config already exists
|
|
16
22
|
if (await configExists()) {
|
|
@@ -50,10 +56,27 @@ export async function initCommand(options) {
|
|
|
50
56
|
console.log(chalk.green(`ā Created ${configPath}`));
|
|
51
57
|
// Create skill file for agent discovery
|
|
52
58
|
const skillPath = await createSkillFile();
|
|
53
|
-
console.log(chalk.green(`ā Created ${skillPath}
|
|
59
|
+
console.log(chalk.green(`ā Created ${skillPath}`));
|
|
60
|
+
// Create Claude Code slash command
|
|
61
|
+
const commandPath = await createClaudeCommand();
|
|
62
|
+
console.log(chalk.green(`ā Created ${commandPath} (use /haystack in Claude Code)\n`));
|
|
54
63
|
// Security validation
|
|
55
64
|
await runSecurityCheck(configPath);
|
|
56
|
-
|
|
65
|
+
// Explain purpose and next steps
|
|
66
|
+
console.log(chalk.cyan('ā'.repeat(70)));
|
|
67
|
+
console.log(chalk.cyan.bold('\nš WHAT IS HAYSTACK?\n'));
|
|
68
|
+
console.log(chalk.white('When a PR is opened, Haystack spins up your app in a sandbox and'));
|
|
69
|
+
console.log(chalk.white('an AI agent verifies the changes work correctly by:\n'));
|
|
70
|
+
console.log(chalk.white(' ⢠Frontend: Navigating pages, clicking buttons, taking screenshots'));
|
|
71
|
+
console.log(chalk.white(' ⢠Backend: Calling API endpoints, checking responses\n'));
|
|
72
|
+
console.log(chalk.white('The "flows" in .haystack.yml tell the agent WHAT to verify.\n'));
|
|
73
|
+
console.log(chalk.cyan.bold('ā ļø ACTION REQUIRED:\n'));
|
|
74
|
+
console.log(chalk.white('The config above only has dev server settings. You MUST add flows.\n'));
|
|
75
|
+
console.log(chalk.white.bold('1. Run: cat .agents/skills/haystack.md'));
|
|
76
|
+
console.log(chalk.white.bold('2. Read the "Codebase Discovery Guide" section'));
|
|
77
|
+
console.log(chalk.white.bold('3. Follow it to discover features and add flows to .haystack.yml\n'));
|
|
78
|
+
console.log(chalk.dim('Without flows, Haystack has nothing to verify.\n'));
|
|
79
|
+
console.log(chalk.cyan('ā'.repeat(70)));
|
|
57
80
|
return;
|
|
58
81
|
}
|
|
59
82
|
// Interactive prompts
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/utils/skill.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Create the .agents/skills/haystack.md file for agent discovery
|
|
3
|
+
* and .claude/commands/haystack.md for Claude Code slash command
|
|
3
4
|
*/
|
|
4
5
|
export declare function createSkillFile(): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Create the .claude/commands/haystack.md file for Claude Code slash command
|
|
8
|
+
* Users can invoke with /haystack to start the setup wizard
|
|
9
|
+
*/
|
|
10
|
+
export declare function createClaudeCommand(): Promise<string>;
|
package/dist/utils/skill.js
CHANGED
|
@@ -1,11 +1,161 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Create the .agents/skills/haystack.md file for agent discovery
|
|
3
|
+
* and .claude/commands/haystack.md for Claude Code slash command
|
|
3
4
|
*/
|
|
4
5
|
import * as fs from 'fs/promises';
|
|
5
6
|
import * as path from 'path';
|
|
7
|
+
/**
|
|
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
|
+
*/
|
|
12
|
+
const CLAUDE_COMMAND_CONTENT = `# Set Up Haystack Verification
|
|
13
|
+
|
|
14
|
+
You are setting up Haystack PR verification. Complete each step IN ORDER. Do NOT skip ahead.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## STEP 1: Initialize config
|
|
19
|
+
|
|
20
|
+
\`\`\`bash
|
|
21
|
+
npx @haystackeditor/cli init --yes
|
|
22
|
+
\`\`\`
|
|
23
|
+
|
|
24
|
+
ā
**Checkpoint**: \`.haystack.yml\` exists with dev_server config.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## STEP 2: Discover all routes
|
|
29
|
+
|
|
30
|
+
Find every route in the app:
|
|
31
|
+
\`\`\`bash
|
|
32
|
+
grep -r "path=\\|Route\\|<Link" src/ --include="*.tsx" | head -30
|
|
33
|
+
ls src/pages/ src/app/ 2>/dev/null
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
Add a flow for EACH route to \`.haystack.yml\`. Use \`trigger: always\` for main pages, \`trigger: on_change\` with \`watch_patterns\` for others.
|
|
37
|
+
|
|
38
|
+
ā
**Checkpoint**: Count your flows. You should have one for every route.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## STEP 3: Fix ALL selectors (CRITICAL)
|
|
43
|
+
|
|
44
|
+
ā **STOP**: Look at every \`wait_for\` selector in your flows.
|
|
45
|
+
|
|
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
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
Replace generic selectors with specific ones:
|
|
53
|
+
- \`[data-testid='dashboard']\`
|
|
54
|
+
- \`.dashboard-content\`
|
|
55
|
+
- \`[role='main']\`
|
|
56
|
+
|
|
57
|
+
ā
**Checkpoint**: Run \`grep "wait_for" .haystack.yml\` - NONE should have \`#root\`.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## STEP 4: Add 3+ interactive flows (REQUIRED)
|
|
62
|
+
|
|
63
|
+
Find interactive elements:
|
|
64
|
+
\`\`\`bash
|
|
65
|
+
grep -r "onClick\\|Modal\\|Dialog\\|toggle\\|Switch" src/ --include="*.tsx" | head -20
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
Add AT LEAST 3 flows with \`click\` or \`type\` actions:
|
|
69
|
+
|
|
70
|
+
\`\`\`yaml
|
|
71
|
+
- name: "Theme toggle works"
|
|
72
|
+
steps:
|
|
73
|
+
- action: navigate
|
|
74
|
+
url: "/"
|
|
75
|
+
- action: click
|
|
76
|
+
selector: "[data-testid='theme-toggle']"
|
|
77
|
+
- action: screenshot
|
|
78
|
+
name: "after-toggle"
|
|
79
|
+
|
|
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"
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
ā
**Checkpoint**: Run \`grep -c "action: click" .haystack.yml\` - must be ā„3.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## STEP 5: Add fixtures for data-dependent pages
|
|
97
|
+
|
|
98
|
+
Find pages that fetch data:
|
|
99
|
+
\`\`\`bash
|
|
100
|
+
grep -r "useParams\\|fetch(\\|useQuery" src/ --include="*.tsx" | head -10
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
For each data-dependent page, add fixtures:
|
|
104
|
+
\`\`\`yaml
|
|
105
|
+
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"
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
ā
**Checkpoint**: Every page with \`:id\` or API calls has a fixture.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## STEP 6: Final validation
|
|
117
|
+
|
|
118
|
+
Count and verify:
|
|
119
|
+
\`\`\`bash
|
|
120
|
+
echo "=== Selector check (should be 0 #root) ==="
|
|
121
|
+
grep "#root" .haystack.yml | wc -l
|
|
122
|
+
|
|
123
|
+
echo "=== Interactive flows (should be ā„3) ==="
|
|
124
|
+
grep -c "action: click\\|action: type" .haystack.yml
|
|
125
|
+
|
|
126
|
+
echo "=== Fixtures (should be >0 for data pages) ==="
|
|
127
|
+
grep -c "pattern:" .haystack.yml
|
|
128
|
+
\`\`\`
|
|
129
|
+
|
|
130
|
+
If any check fails, GO BACK to that step and fix it.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## STEP 7: Commit
|
|
135
|
+
|
|
136
|
+
\`\`\`bash
|
|
137
|
+
git add .haystack.yml .agents/ .claude/ fixtures/
|
|
138
|
+
git commit -m "Add Haystack verification"
|
|
139
|
+
\`\`\`
|
|
140
|
+
|
|
141
|
+
š Done!
|
|
142
|
+
`;
|
|
6
143
|
const SKILL_CONTENT = `# Haystack Verification
|
|
7
144
|
|
|
8
|
-
|
|
145
|
+
## What is Haystack?
|
|
146
|
+
|
|
147
|
+
Haystack provides **automated PR verification**. When a PR is opened:
|
|
148
|
+
|
|
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
|
|
153
|
+
|
|
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.
|
|
155
|
+
|
|
156
|
+
**Backend flows**: The agent makes HTTP requests to API endpoints and verifies responses. This catches broken endpoints and API contract changes.
|
|
157
|
+
|
|
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.
|
|
9
159
|
|
|
10
160
|
## Setup Workflow
|
|
11
161
|
|
|
@@ -237,6 +387,263 @@ Most apps need auth bypassed for testing. Common patterns:
|
|
|
237
387
|
| Rails | \`SKIP_AUTH=true\` |
|
|
238
388
|
|
|
239
389
|
Add to \`dev_server.env\` or \`services.*.env\` in your config.
|
|
390
|
+
|
|
391
|
+
## Codebase Discovery Guide
|
|
392
|
+
|
|
393
|
+
**Follow these steps to create comprehensive verification flows.**
|
|
394
|
+
|
|
395
|
+
### ā ļø REQUIRED CHECKLIST - Complete ALL items before finishing:
|
|
396
|
+
|
|
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
|
|
403
|
+
|
|
404
|
+
**You are NOT done until all 6 items are checked.**
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
### Step 1: Trace the Component Tree
|
|
409
|
+
|
|
410
|
+
Start from the entry point and trace imports to discover ALL features:
|
|
411
|
+
|
|
412
|
+
\`\`\`bash
|
|
413
|
+
# Find the entry point
|
|
414
|
+
cat src/main.tsx # or src/index.tsx, pages/_app.tsx, etc.
|
|
415
|
+
|
|
416
|
+
# Trace the router to find all routes
|
|
417
|
+
grep -r "Route\|path=" src/ --include="*.tsx"
|
|
418
|
+
|
|
419
|
+
# Find all page/feature components
|
|
420
|
+
ls src/pages/ src/components/ src/features/
|
|
421
|
+
\`\`\`
|
|
422
|
+
|
|
423
|
+
### Step 2: Find Good Selectors
|
|
424
|
+
|
|
425
|
+
**DON'T use generic selectors like \`#root\`.** The agent needs specific selectors to know the page loaded correctly.
|
|
426
|
+
|
|
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
|
|
432
|
+
|
|
433
|
+
**How to find selectors:**
|
|
434
|
+
\`\`\`bash
|
|
435
|
+
# Search for data-testid attributes
|
|
436
|
+
grep -r "data-testid" src/ --include="*.tsx"
|
|
437
|
+
|
|
438
|
+
# Search for unique classNames in a component
|
|
439
|
+
grep -r "className=" src/components/Dashboard.tsx
|
|
440
|
+
|
|
441
|
+
# Look for page-specific elements
|
|
442
|
+
grep -r "<h1\|<header\|role=" src/pages/
|
|
443
|
+
\`\`\`
|
|
444
|
+
|
|
445
|
+
**Example - BAD vs GOOD:**
|
|
446
|
+
\`\`\`yaml
|
|
447
|
+
# BAD - too generic, every page has #root
|
|
448
|
+
- action: wait_for
|
|
449
|
+
selector: "#root"
|
|
450
|
+
|
|
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
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
### Step 3: Add Interactive Flows
|
|
463
|
+
|
|
464
|
+
Don't just screenshot static pages. Verify that interactions work:
|
|
465
|
+
|
|
466
|
+
**Look for interactive elements:**
|
|
467
|
+
\`\`\`bash
|
|
468
|
+
# Find buttons and clickable elements
|
|
469
|
+
grep -r "onClick\|button\|Button" src/ --include="*.tsx"
|
|
470
|
+
|
|
471
|
+
# Find modals and dialogs
|
|
472
|
+
grep -r "Modal\|Dialog\|Drawer" src/ --include="*.tsx"
|
|
473
|
+
|
|
474
|
+
# Find forms
|
|
475
|
+
grep -r "<form\|onSubmit\|handleSubmit" src/ --include="*.tsx"
|
|
476
|
+
|
|
477
|
+
# Find toggles and switches
|
|
478
|
+
grep -r "toggle\|Switch\|theme" src/ --include="*.tsx"
|
|
479
|
+
\`\`\`
|
|
480
|
+
|
|
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
|
+
\`\`\`
|
|
522
|
+
|
|
523
|
+
### Step 4: Handle Data-Dependent Pages
|
|
524
|
+
|
|
525
|
+
**How to identify pages that need fixtures:**
|
|
526
|
+
\`\`\`bash
|
|
527
|
+
# Find components that fetch data
|
|
528
|
+
grep -r "useQuery\|useSWR\|fetch(\|axios\|useEffect.*fetch" src/ --include="*.tsx"
|
|
529
|
+
|
|
530
|
+
# Find API route parameters (these pages need data)
|
|
531
|
+
grep -r "useParams\|router.query\|\[.*\]" src/pages/ src/app/ --include="*.tsx"
|
|
532
|
+
\`\`\`
|
|
533
|
+
|
|
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"
|
|
544
|
+
|
|
545
|
+
# Or from S3 bucket
|
|
546
|
+
- pattern: "/api/analytics/*"
|
|
547
|
+
source: "s3://my-fixtures-bucket/analytics-sample.json"
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
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
|
|
557
|
+
\`\`\`
|
|
558
|
+
|
|
559
|
+
\`\`\`yaml
|
|
560
|
+
fixtures:
|
|
561
|
+
- pattern: "/api/user"
|
|
562
|
+
source: "file://fixtures/user.json"
|
|
563
|
+
\`\`\`
|
|
564
|
+
|
|
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"
|
|
576
|
+
\`\`\`
|
|
577
|
+
|
|
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']"
|
|
587
|
+
\`\`\`
|
|
588
|
+
|
|
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"
|
|
597
|
+
\`\`\`
|
|
598
|
+
|
|
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
|
|
622
|
+
\`\`\`
|
|
623
|
+
|
|
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
|
|
633
|
+
\`\`\`
|
|
634
|
+
|
|
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
|
|
645
|
+
|
|
646
|
+
ā ļø **If you have 0 interactive flows or 0 fixtures for data pages, you are not done.**
|
|
240
647
|
`;
|
|
241
648
|
export async function createSkillFile() {
|
|
242
649
|
const skillDir = path.join(process.cwd(), '.agents', 'skills');
|
|
@@ -247,3 +654,16 @@ export async function createSkillFile() {
|
|
|
247
654
|
await fs.writeFile(skillPath, SKILL_CONTENT, 'utf-8');
|
|
248
655
|
return skillPath;
|
|
249
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Create the .claude/commands/haystack.md file for Claude Code slash command
|
|
659
|
+
* Users can invoke with /haystack to start the setup wizard
|
|
660
|
+
*/
|
|
661
|
+
export async function createClaudeCommand() {
|
|
662
|
+
const commandDir = path.join(process.cwd(), '.claude', 'commands');
|
|
663
|
+
const commandPath = path.join(commandDir, 'haystack.md');
|
|
664
|
+
// Create directory if needed
|
|
665
|
+
await fs.mkdir(commandDir, { recursive: true });
|
|
666
|
+
// Write command file
|
|
667
|
+
await fs.writeFile(commandPath, CLAUDE_COMMAND_CONTENT, 'utf-8');
|
|
668
|
+
return commandPath;
|
|
669
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haystackeditor/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Set up Haystack verification for your project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"haystack": "./dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
9
15
|
"keywords": [
|
|
10
16
|
"haystack",
|
|
11
17
|
"verification",
|
|
@@ -42,10 +48,5 @@
|
|
|
42
48
|
],
|
|
43
49
|
"engines": {
|
|
44
50
|
"node": ">=18"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsc",
|
|
48
|
-
"dev": "tsc --watch",
|
|
49
|
-
"start": "node dist/index.js"
|
|
50
51
|
}
|
|
51
|
-
}
|
|
52
|
+
}
|