@haystackeditor/cli 0.8.1 → 0.10.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.
Files changed (63) hide show
  1. package/README.md +93 -87
  2. package/dist/assets/hooks/llm-rules-template.md +21 -0
  3. package/dist/assets/hooks/package.json +2 -2
  4. package/dist/assets/hooks/scripts/pre-push.sh +20 -0
  5. package/dist/assets/skills/prepare-haystack.md +323 -0
  6. package/dist/assets/skills/secrets.md +164 -0
  7. package/dist/assets/skills/setup-external-sandbox.md +243 -0
  8. package/dist/assets/skills/setup-haystack.md +639 -0
  9. package/dist/assets/skills/submit.md +154 -0
  10. package/dist/assets/templates/CLAUDE.md.snippet +42 -0
  11. package/dist/assets/templates/haystack.yml +193 -0
  12. package/dist/commands/check-pending.d.ts +19 -0
  13. package/dist/commands/check-pending.js +217 -0
  14. package/dist/commands/config.d.ts +13 -21
  15. package/dist/commands/config.js +278 -92
  16. package/dist/commands/dismiss.d.ts +17 -0
  17. package/dist/commands/dismiss.js +201 -0
  18. package/dist/commands/init.js +25 -28
  19. package/dist/commands/install-session-hooks.d.ts +16 -0
  20. package/dist/commands/install-session-hooks.js +302 -0
  21. package/dist/commands/login.js +1 -1
  22. package/dist/commands/policy.d.ts +31 -0
  23. package/dist/commands/policy.js +365 -0
  24. package/dist/commands/pr-status.d.ts +16 -0
  25. package/dist/commands/pr-status.js +188 -0
  26. package/dist/commands/setup.d.ts +13 -0
  27. package/dist/commands/setup.js +496 -0
  28. package/dist/commands/skills.d.ts +2 -2
  29. package/dist/commands/skills.js +51 -186
  30. package/dist/commands/submit.d.ts +23 -0
  31. package/dist/commands/submit.js +456 -0
  32. package/dist/commands/triage.d.ts +16 -0
  33. package/dist/commands/triage.js +354 -0
  34. package/dist/index.d.ts +7 -0
  35. package/dist/index.js +344 -4
  36. package/dist/tools/detect.d.ts +50 -0
  37. package/dist/tools/detect.js +853 -0
  38. package/dist/tools/fixtures.d.ts +38 -0
  39. package/dist/tools/fixtures.js +199 -0
  40. package/dist/tools/setup.d.ts +43 -0
  41. package/dist/tools/setup.js +597 -0
  42. package/dist/triage/prompts.d.ts +31 -0
  43. package/dist/triage/prompts.js +296 -0
  44. package/dist/triage/runner.d.ts +21 -0
  45. package/dist/triage/runner.js +339 -0
  46. package/dist/triage/traces.d.ts +20 -0
  47. package/dist/triage/traces.js +305 -0
  48. package/dist/triage/types.d.ts +47 -0
  49. package/dist/triage/types.js +7 -0
  50. package/dist/types.d.ts +1387 -191
  51. package/dist/types.js +254 -2
  52. package/dist/utils/analysis-api.d.ts +108 -0
  53. package/dist/utils/analysis-api.js +194 -0
  54. package/dist/utils/config.js +1 -1
  55. package/dist/utils/git.d.ts +80 -0
  56. package/dist/utils/git.js +302 -0
  57. package/dist/utils/github-api.d.ts +83 -0
  58. package/dist/utils/github-api.js +266 -0
  59. package/dist/utils/pending-state.d.ts +40 -0
  60. package/dist/utils/pending-state.js +86 -0
  61. package/dist/utils/secrets.js +3 -3
  62. package/dist/utils/skill.js +257 -0
  63. package/package.json +11 -9
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Pending submit state persistence.
3
+ *
4
+ * Manages `.haystack/pending-submit.json` in the git root directory.
5
+ * This file persists across CLI sessions so that analysis results
6
+ * can be retrieved after disconnection.
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { findGitRoot } from './git.js';
11
+ // ============================================================================
12
+ // Constants
13
+ // ============================================================================
14
+ const PENDING_FILE = 'pending-submit.json';
15
+ const HAYSTACK_DIR = '.haystack';
16
+ const STALE_THRESHOLD_MS = 2 * 60 * 60 * 1000; // 2 hours
17
+ // ============================================================================
18
+ // Functions
19
+ // ============================================================================
20
+ function getPendingPath(gitRoot) {
21
+ const root = gitRoot || findGitRoot();
22
+ if (!root)
23
+ return null;
24
+ return join(root, HAYSTACK_DIR, PENDING_FILE);
25
+ }
26
+ /**
27
+ * Save pending submit state to disk.
28
+ */
29
+ export function savePendingSubmit(state, gitRoot) {
30
+ const root = gitRoot || findGitRoot();
31
+ if (!root)
32
+ throw new Error('Not a git repository');
33
+ const dir = join(root, HAYSTACK_DIR);
34
+ if (!existsSync(dir)) {
35
+ mkdirSync(dir, { recursive: true });
36
+ }
37
+ const filePath = join(dir, PENDING_FILE);
38
+ writeFileSync(filePath, JSON.stringify(state, null, 2));
39
+ }
40
+ /**
41
+ * Load pending submit state from disk.
42
+ * Returns null if no pending state exists or if it's stale.
43
+ */
44
+ export function loadPendingSubmit(gitRoot) {
45
+ const filePath = getPendingPath(gitRoot);
46
+ if (!filePath || !existsSync(filePath))
47
+ return null;
48
+ try {
49
+ const content = readFileSync(filePath, 'utf-8');
50
+ const state = JSON.parse(content);
51
+ // Check version
52
+ if (state.version !== 1)
53
+ return null;
54
+ // Auto-mark stale entries
55
+ if (state.status === 'polling') {
56
+ const age = Date.now() - new Date(state.submittedAt).getTime();
57
+ if (age > STALE_THRESHOLD_MS) {
58
+ state.status = 'stale';
59
+ savePendingSubmit(state, gitRoot);
60
+ }
61
+ }
62
+ return state;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ }
68
+ /**
69
+ * Clear pending submit state (delete the file).
70
+ */
71
+ export function clearPendingSubmit(gitRoot) {
72
+ const filePath = getPendingPath(gitRoot);
73
+ if (filePath && existsSync(filePath)) {
74
+ unlinkSync(filePath);
75
+ }
76
+ }
77
+ /**
78
+ * Update the status and optionally the analysis result of a pending submit.
79
+ */
80
+ export function updatePendingSubmit(updates, gitRoot) {
81
+ const state = loadPendingSubmit(gitRoot);
82
+ if (!state)
83
+ return;
84
+ Object.assign(state, updates);
85
+ savePendingSubmit(state, gitRoot);
86
+ }
@@ -18,7 +18,7 @@ const SECRET_PATTERNS = [
18
18
  // API Keys
19
19
  {
20
20
  name: 'generic-api-key',
21
- pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi,
21
+ pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi,
22
22
  description: 'Potential API key detected',
23
23
  severity: 'high',
24
24
  },
@@ -43,7 +43,7 @@ const SECRET_PATTERNS = [
43
43
  },
44
44
  {
45
45
  name: 'cloudflare-api-token',
46
- pattern: /(?:cloudflare|cf)[_-]?(?:api)?[_-]?token\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{40,})['"]?/gi,
46
+ pattern: /(?:cloudflare|cf)[_-]?(?:api)?[_-]?token\s*[:=]\s*['"]?([a-zA-Z0-9_-]{40,})['"]?/gi,
47
47
  description: 'Cloudflare API Token detected',
48
48
  severity: 'high',
49
49
  },
@@ -122,7 +122,7 @@ const SECRET_PATTERNS = [
122
122
  // Generic high-entropy strings in config values
123
123
  {
124
124
  name: 'high-entropy-string',
125
- pattern: /(?:token|key|secret|password|credential|auth)\s*[:=]\s*['"]([a-zA-Z0-9+/=_\-]{32,})['"]?/gi,
125
+ pattern: /(?:token|key|secret|password|credential|auth)\s*[:=]\s*['"]([a-zA-Z0-9+/=_-]{32,})['"]?/gi,
126
126
  description: 'High-entropy string in sensitive field',
127
127
  severity: 'medium',
128
128
  },
@@ -1380,12 +1380,266 @@ npx @haystackeditor/cli secrets get SECRET_NAME --show-metadata
1380
1380
  - For CI, use GitHub Secrets or organization-level Haystack secrets
1381
1381
  - Check \`npx @haystackeditor/cli secrets list --scope=org\`
1382
1382
  `;
1383
+ const SETUP_RULES_CONTENT = `# Set Up Review Policies & Rules
1384
+
1385
+ **Your job**: Help the user configure two systems that control how Haystack evaluates their PRs:
1386
+
1387
+ 1. **Review Policies** — deterministic, path-based triggers that **always** require human review
1388
+ 2. **PR Rules** — LLM-evaluated code invariants that **flag violations** in the diff
1389
+
1390
+ These are distinct systems. Make sure the user understands the difference before proceeding.
1391
+
1392
+ ---
1393
+
1394
+ ## The Two Systems
1395
+
1396
+ ### Review Policies (\`.haystack/review-policy.md\`)
1397
+
1398
+ **"If a PR touches X → human review needed, period."**
1399
+
1400
+ Policies are simple glob patterns. When a PR changes files matching a policy's paths, Haystack will always recommend human review for that PR, regardless of what the code changes look like.
1401
+
1402
+ **Good candidates for policies:**
1403
+ - Infrastructure (Terraform, Kubernetes, CI/CD pipelines)
1404
+ - Authentication and authorization code
1405
+ - Database migrations and schemas
1406
+ - API contracts (OpenAPI specs, GraphQL schemas, protobuf)
1407
+ - Security-sensitive code (crypto, secrets handling)
1408
+ - Billing/payment logic
1409
+ - Code that bypasses feature flags or launches directly to production
1410
+
1411
+ **Format:**
1412
+ \`\`\`markdown
1413
+ ## Policy name
1414
+ - **Paths**: \\\`glob/pattern/**\\\`, \\\`other/pattern/*.ts\\\`
1415
+ - **Severity**: critical | high | medium | low
1416
+ - **Reason**: Why this always needs human eyes
1417
+ \`\`\`
1418
+
1419
+ ### PR Rules (\`.haystack/pr-rules.yml\`)
1420
+
1421
+ **"If this invariant is violated in the diff → flag it."**
1422
+
1423
+ Rules are checked by an LLM that reads the PR diff and looks for violations. They catch patterns that shouldn't appear in new code, regardless of which files are touched.
1424
+
1425
+ **Good candidates for rules:**
1426
+ - Error handling conventions (no silent catches, no swallowed errors)
1427
+ - Logging requirements (all API calls must be logged)
1428
+ - Security patterns (no hardcoded secrets, no SQL string concatenation)
1429
+ - Code style invariants (no TODOs without issue refs, no console.log in production)
1430
+ - Architecture boundaries (service A must not import from service B)
1431
+ - Naming conventions specific to your team
1432
+
1433
+ **Format:**
1434
+ \`\`\`yaml
1435
+ - id: PR001
1436
+ name: Short rule name
1437
+ type: llm
1438
+ severity: warning | error
1439
+ message: Human-readable description of the violation
1440
+ llm:
1441
+ prompt: >
1442
+ Detailed instructions for the LLM about what to look for
1443
+ in the PR diff. Be specific about what counts as a violation
1444
+ and what doesn't.
1445
+ files: "optional/glob/pattern/**/*.ts" # optional file scope
1446
+ \`\`\`
1447
+
1448
+ ---
1449
+
1450
+ ## Step 1: Explore the Codebase
1451
+
1452
+ Before suggesting anything, understand the repo structure:
1453
+
1454
+ \`\`\`bash
1455
+ # Understand repo layout
1456
+ ls -la
1457
+ find . -maxdepth 2 -type d -not -path '*/node_modules/*' -not -path '*/.git/*' | head -40
1458
+
1459
+ # Find infrastructure files
1460
+ find . -name "*.tf" -o -name "Dockerfile*" -o -name "docker-compose*" -o -name "*.yml" -path "*/.github/*" | head -20
1461
+
1462
+ # Find auth-related code
1463
+ grep -rl "auth\\|login\\|session\\|token\\|credential" --include="*.ts" --include="*.py" --include="*.go" --include="*.js" -l | grep -v node_modules | head -20
1464
+
1465
+ # Find database/migration files
1466
+ find . -path "*/migration*" -o -path "*/schema*" -o -name "*.sql" | grep -v node_modules | head -20
1467
+
1468
+ # Find API contract files
1469
+ find . -name "*.proto" -o -name "openapi*" -o -name "swagger*" -o -name "*.graphql" | head -20
1470
+
1471
+ # Find feature flag patterns
1472
+ grep -rl "feature.?flag\\|feature.?toggle\\|isEnabled\\|FEATURE_" --include="*.ts" --include="*.py" --include="*.go" --include="*.js" -l | grep -v node_modules | head -20
1473
+
1474
+ # Check for existing config
1475
+ cat .haystack/review-policy.md 2>/dev/null || echo "No review-policy.md yet"
1476
+ cat .haystack/pr-rules.yml 2>/dev/null || echo "No pr-rules.yml yet"
1477
+ \`\`\`
1478
+
1479
+ Also look for:
1480
+ - CI/CD pipeline files
1481
+ - Payment or billing directories
1482
+ - Secrets or credential management code
1483
+ - Environment configuration patterns
1484
+ - Key shared libraries or core utilities that many things depend on
1485
+
1486
+ ---
1487
+
1488
+ ## STOP — Propose Review Policies
1489
+
1490
+ Present your findings to the user. Be specific about what you found and why each area warrants a policy.
1491
+
1492
+ **Format your proposal like this:**
1493
+
1494
+ > Based on the codebase, here are the areas I'd recommend always requiring human review:
1495
+ >
1496
+ > | # | Policy | Paths | Severity | Why |
1497
+ > |---|--------|-------|----------|-----|
1498
+ > | 1 | Infrastructure | \\\`terraform/**\\\`, \\\`*.tf\\\` | high | Cost and security implications |
1499
+ > | 2 | Auth & secrets | \\\`src/auth/**\\\`, \\\`**/*.secret*\\\` | critical | Security-sensitive |
1500
+ > | 3 | ... | ... | ... | ... |
1501
+ >
1502
+ > **Notes:**
1503
+ > - I didn't find any database migrations in this repo, so I'm not including that
1504
+ > - The \`src/core/\` directory is imported by everything — should changes there always need review?
1505
+ >
1506
+ > Which of these should I include? Any to add, remove, or modify?
1507
+
1508
+ **Wait for the user to confirm before writing anything.**
1509
+
1510
+ ---
1511
+
1512
+ ## Step 2: Write review-policy.md
1513
+
1514
+ Once the user confirms, write the policies to \`.haystack/review-policy.md\`:
1515
+
1516
+ \`\`\`markdown
1517
+ # Review Policies
1518
+
1519
+ Review policies define file patterns that always require human attention. When a PR touches files matching these patterns, Haystack will flag it for human review.
1520
+
1521
+ ## [Policy name]
1522
+ - **Paths**: \\\`pattern/**\\\`
1523
+ - **Severity**: [level]
1524
+ - **Reason**: [why]
1525
+ \`\`\`
1526
+
1527
+ Create the \`.haystack/\` directory if it doesn't exist.
1528
+
1529
+ ---
1530
+
1531
+ ## Step 3: Understand Team Conventions
1532
+
1533
+ Now shift to rules. Ask the user about their code quality invariants:
1534
+
1535
+ > Now let's set up **PR rules** — these are code invariants that Haystack checks in every diff.
1536
+ >
1537
+ > Unlike policies (which trigger on file paths), rules look at *what the code does*. For example:
1538
+ > - "Don't add empty catch blocks"
1539
+ > - "Don't add TODOs without issue references"
1540
+ > - "Don't hardcode API URLs"
1541
+ >
1542
+ > **What conventions does your team care about?** Some common categories:
1543
+ >
1544
+ > 1. **Error handling** — e.g., no swallowed errors, no silent fallbacks
1545
+ > 2. **Security** — e.g., no hardcoded secrets, no raw SQL strings
1546
+ > 3. **Logging** — e.g., all API calls must have logging
1547
+ > 4. **Code hygiene** — e.g., no TODOs without issues, no console.log in production
1548
+ > 5. **Architecture** — e.g., no cross-service imports, no direct DB access from UI layer
1549
+ > 6. **Testing** — e.g., no skipped tests without reason, no snapshot-only tests
1550
+ >
1551
+ > Tell me which categories matter to your team, or describe your own conventions.
1552
+
1553
+ Also explore the codebase for clues:
1554
+
1555
+ \`\`\`bash
1556
+ # Check for linter configs (reveals team conventions)
1557
+ cat .eslintrc* .eslintrc.json .eslintrc.js eslint.config.* 2>/dev/null | head -50
1558
+
1559
+ # Check for existing code review guidelines
1560
+ find . -name "CONTRIBUTING*" -o -name "REVIEW*" -o -name "STYLE*" | head -5
1561
+
1562
+ # Look for common anti-patterns
1563
+ grep -rn "catch.*{\\s*}" --include="*.ts" --include="*.js" | head -5
1564
+ grep -rn "console\\.log" --include="*.ts" --include="*.tsx" | grep -v test | head -5
1565
+ grep -rn "TODO\\|FIXME\\|HACK\\|XXX" --include="*.ts" --include="*.tsx" | head -10
1566
+ \`\`\`
1567
+
1568
+ ---
1569
+
1570
+ ## STOP — Propose PR Rules
1571
+
1572
+ Based on the user's input and your codebase exploration, propose 2-5 custom rules.
1573
+
1574
+ **Format your proposal like this:**
1575
+
1576
+ > Here are the PR rules I'd suggest:
1577
+ >
1578
+ > **1. PR001 — No silent error handling** (warning)
1579
+ > > Flag: Adding catch blocks that swallow errors without logging or re-throwing
1580
+ >
1581
+ > **2. PR002 — No TODOs without tracking issues** (warning)
1582
+ > > Flag: Adding TODO/FIXME comments without referencing an issue number
1583
+ >
1584
+ > **3. PR003 — No hardcoded API endpoints** (warning)
1585
+ > > Flag: Adding hardcoded URLs or API endpoints instead of using config/env vars
1586
+ >
1587
+ > Should I add, remove, or modify any of these?
1588
+
1589
+ **Wait for the user to confirm before writing.**
1590
+
1591
+ ---
1592
+
1593
+ ## Step 4: Write pr-rules.yml
1594
+
1595
+ Once confirmed, write to \`.haystack/pr-rules.yml\`:
1596
+
1597
+ \`\`\`yaml
1598
+ version: 1
1599
+
1600
+ rules:
1601
+ - id: PR001
1602
+ name: [Short name]
1603
+ type: llm
1604
+ severity: warning
1605
+ message: [Human-readable description]
1606
+ llm:
1607
+ prompt: >
1608
+ [Detailed LLM instructions — be specific about what counts as
1609
+ a violation and what doesn't. Include examples of violations
1610
+ and non-violations when helpful.]
1611
+ \`\`\`
1612
+
1613
+ **Tips for writing good rule prompts:**
1614
+ - Be specific about what counts as a violation
1615
+ - Mention common false positives to ignore (e.g., "test files are exempt")
1616
+ - Use the \`files\` field to scope rules to relevant file types
1617
+ - \`severity: error\` caps the Haystack rating at 3 (needs review); \`warning\` is advisory
1618
+
1619
+ ---
1620
+
1621
+ ## Step 5: Commit
1622
+
1623
+ \`\`\`bash
1624
+ git add .haystack/review-policy.md .haystack/pr-rules.yml
1625
+ git commit -m "Add Haystack review policies and PR rules"
1626
+ \`\`\`
1627
+
1628
+ Done! Haystack will now:
1629
+ - Flag PRs touching policy-matched files for human review
1630
+ - Check every PR diff against your custom rules and report violations
1631
+ `;
1632
+ const SETUP_RULES_COMMAND = `# Set Up Rules
1633
+
1634
+ Follow .agents/skills/setup-haystack-rules.md to configure review policies (what always needs human review) and PR rules (what code invariants to enforce).
1635
+ `;
1383
1636
  export async function createSkillFile() {
1384
1637
  const skillDir = path.join(process.cwd(), '.agents', 'skills');
1385
1638
  const setupPath = path.join(skillDir, 'setup-haystack.md');
1386
1639
  const refPath = path.join(skillDir, 'haystack-reference.md');
1387
1640
  const prepPath = path.join(skillDir, 'prepare-haystack.md');
1388
1641
  const secretsPath = path.join(skillDir, 'setup-haystack-secrets.md');
1642
+ const rulesPath = path.join(skillDir, 'setup-haystack-rules.md');
1389
1643
  // Create directory if needed
1390
1644
  await fs.mkdir(skillDir, { recursive: true });
1391
1645
  // Write all skill files
@@ -1393,6 +1647,7 @@ export async function createSkillFile() {
1393
1647
  await fs.writeFile(refPath, REFERENCE_CONTENT, 'utf-8');
1394
1648
  await fs.writeFile(prepPath, PREPARE_VERIFICATION_CONTENT, 'utf-8');
1395
1649
  await fs.writeFile(secretsPath, SECRETS_SKILL_CONTENT, 'utf-8');
1650
+ await fs.writeFile(rulesPath, SETUP_RULES_CONTENT, 'utf-8');
1396
1651
  return setupPath;
1397
1652
  }
1398
1653
  /**
@@ -1404,11 +1659,13 @@ export async function createClaudeCommand() {
1404
1659
  const setupPath = path.join(commandDir, 'setup-haystack.md');
1405
1660
  const prepPath = path.join(commandDir, 'prepare-haystack.md');
1406
1661
  const secretsPath = path.join(commandDir, 'setup-haystack-secrets.md');
1662
+ const rulesPath = path.join(commandDir, 'setup-haystack-rules.md');
1407
1663
  // Create directory if needed
1408
1664
  await fs.mkdir(commandDir, { recursive: true });
1409
1665
  // Write command files
1410
1666
  await fs.writeFile(setupPath, CLAUDE_COMMAND_CONTENT, 'utf-8');
1411
1667
  await fs.writeFile(prepPath, PREPARE_HAYSTACK_COMMAND, 'utf-8');
1412
1668
  await fs.writeFile(secretsPath, SECRETS_COMMAND_CONTENT, 'utf-8');
1669
+ await fs.writeFile(rulesPath, SETUP_RULES_COMMAND, 'utf-8');
1413
1670
  return setupPath;
1414
1671
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "Set up Haystack verification for your project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,16 +32,18 @@
32
32
  "url": "https://github.com/haystackeditor/haystack-review/issues"
33
33
  },
34
34
  "dependencies": {
35
- "chalk": "^5.3.0",
36
- "commander": "^12.0.0",
37
- "fast-glob": "^3.3.0",
38
- "inquirer": "^9.2.0",
39
- "yaml": "^2.3.4"
35
+ "chalk": "5.6.2",
36
+ "commander": "12.1.0",
37
+ "fast-glob": "3.3.3",
38
+ "glob": "10.5.0",
39
+ "inquirer": "9.3.8",
40
+ "yaml": "2.8.2",
41
+ "zod": "3.25.76"
40
42
  },
41
43
  "devDependencies": {
42
- "@types/inquirer": "^9.0.0",
43
- "@types/node": "^20.10.0",
44
- "typescript": "^5.3.0"
44
+ "@types/inquirer": "9.0.9",
45
+ "@types/node": "20.19.30",
46
+ "typescript": "5.9.3"
45
47
  },
46
48
  "files": [
47
49
  "dist"