@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.
@@ -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}\n`));
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
- printNextSteps();
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
@@ -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>;
@@ -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
- Haystack provides automated PR verification. When a PR is opened, an AI agent spins up the app in a sandbox and verifies changes work correctly.
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.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
+ }