@haystackeditor/cli 0.8.0 → 0.9.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 (76) hide show
  1. package/README.md +105 -12
  2. package/dist/assets/hooks/agent-context/detect.ts +136 -0
  3. package/dist/assets/hooks/agent-context/format.ts +99 -0
  4. package/dist/assets/hooks/agent-context/index.ts +39 -0
  5. package/dist/assets/hooks/agent-context/parsers/claude.ts +253 -0
  6. package/dist/assets/hooks/agent-context/parsers/gemini.ts +155 -0
  7. package/dist/assets/hooks/agent-context/parsers/opencode.ts +174 -0
  8. package/dist/assets/hooks/agent-context/tsconfig.json +13 -0
  9. package/dist/assets/hooks/agent-context/types.ts +58 -0
  10. package/dist/assets/hooks/llm-rules-template.md +56 -0
  11. package/dist/assets/hooks/package.json +11 -0
  12. package/dist/assets/hooks/scripts/commit-msg.sh +4 -0
  13. package/dist/assets/hooks/scripts/post-commit.sh +4 -0
  14. package/dist/assets/hooks/scripts/pre-commit.sh +92 -0
  15. package/dist/assets/hooks/scripts/pre-push.sh +25 -0
  16. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
  17. package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +528 -0
  18. package/dist/assets/hooks/truncation-checker/index.ts +595 -0
  19. package/dist/assets/hooks/truncation-checker/tsconfig.json +13 -0
  20. package/dist/assets/skills/prepare-haystack.md +323 -0
  21. package/dist/assets/skills/secrets.md +164 -0
  22. package/dist/assets/skills/setup-external-sandbox.md +243 -0
  23. package/dist/assets/skills/setup-haystack.md +639 -0
  24. package/dist/assets/skills/submit.md +154 -0
  25. package/dist/assets/templates/CLAUDE.md.snippet +42 -0
  26. package/dist/assets/templates/haystack.yml +193 -0
  27. package/dist/commands/check-pending.d.ts +19 -0
  28. package/dist/commands/check-pending.js +217 -0
  29. package/dist/commands/config.d.ts +18 -12
  30. package/dist/commands/config.js +327 -52
  31. package/dist/commands/hooks.d.ts +17 -0
  32. package/dist/commands/hooks.js +269 -0
  33. package/dist/commands/install-session-hooks.d.ts +16 -0
  34. package/dist/commands/install-session-hooks.js +302 -0
  35. package/dist/commands/login.js +1 -1
  36. package/dist/commands/policy.d.ts +31 -0
  37. package/dist/commands/policy.js +365 -0
  38. package/dist/commands/skills.d.ts +8 -0
  39. package/dist/commands/skills.js +80 -0
  40. package/dist/commands/submit.d.ts +22 -0
  41. package/dist/commands/submit.js +428 -0
  42. package/dist/commands/triage.d.ts +16 -0
  43. package/dist/commands/triage.js +354 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.js +317 -2
  46. package/dist/tools/detect.d.ts +50 -0
  47. package/dist/tools/detect.js +853 -0
  48. package/dist/tools/fixtures.d.ts +38 -0
  49. package/dist/tools/fixtures.js +199 -0
  50. package/dist/tools/setup.d.ts +43 -0
  51. package/dist/tools/setup.js +597 -0
  52. package/dist/triage/prompts.d.ts +31 -0
  53. package/dist/triage/prompts.js +266 -0
  54. package/dist/triage/runner.d.ts +21 -0
  55. package/dist/triage/runner.js +325 -0
  56. package/dist/triage/traces.d.ts +20 -0
  57. package/dist/triage/traces.js +305 -0
  58. package/dist/triage/types.d.ts +47 -0
  59. package/dist/triage/types.js +7 -0
  60. package/dist/types.d.ts +1387 -191
  61. package/dist/types.js +254 -2
  62. package/dist/utils/analysis-api.d.ts +108 -0
  63. package/dist/utils/analysis-api.js +194 -0
  64. package/dist/utils/config.js +1 -1
  65. package/dist/utils/git.d.ts +80 -0
  66. package/dist/utils/git.js +302 -0
  67. package/dist/utils/github-api.d.ts +83 -0
  68. package/dist/utils/github-api.js +266 -0
  69. package/dist/utils/hooks.d.ts +26 -0
  70. package/dist/utils/hooks.js +226 -0
  71. package/dist/utils/pending-state.d.ts +38 -0
  72. package/dist/utils/pending-state.js +86 -0
  73. package/dist/utils/secrets.js +3 -3
  74. package/dist/utils/skill.d.ts +1 -1
  75. package/dist/utils/skill.js +658 -1
  76. package/package.json +5 -3
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Fixture definition for loading.
3
+ * This is constructed from HaystackConfig's verification.fixtures Record.
4
+ */
5
+ export interface Fixture {
6
+ /** URL pattern to intercept (e.g., "/api/users/*") */
7
+ pattern: string;
8
+ /** Source to load data from (file://, https://, s3://, r2://, script://, or passthrough) */
9
+ source: string;
10
+ /** Optional headers for remote sources */
11
+ headers?: Record<string, string>;
12
+ /** Optional cookies for remote sources (for login-required endpoints) */
13
+ cookies?: string;
14
+ }
15
+ export interface FixtureData {
16
+ pattern: string;
17
+ data: unknown;
18
+ source: string;
19
+ }
20
+ export interface FixtureLoaderOptions {
21
+ /** Base directory for relative file paths */
22
+ baseDir: string;
23
+ /** Environment variables for token substitution */
24
+ env: Record<string, string>;
25
+ }
26
+ /**
27
+ * Load fixture data from various sources
28
+ */
29
+ export declare function loadFixture(fixture: Fixture, options: FixtureLoaderOptions): Promise<FixtureData>;
30
+ /**
31
+ * Load all fixtures from config
32
+ */
33
+ export declare function loadAllFixtures(fixtures: Fixture[], options: FixtureLoaderOptions): Promise<FixtureData[]>;
34
+ /**
35
+ * Generate a mock fixture based on a pattern
36
+ * Useful for scaffolding fixture files
37
+ */
38
+ export declare function generateMockFixture(pattern: string): unknown;
@@ -0,0 +1,199 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ /**
4
+ * Load fixture data from various sources
5
+ */
6
+ export async function loadFixture(fixture, options) {
7
+ const { source, pattern, headers, cookies } = fixture;
8
+ // Substitute env vars in source, headers, and cookies
9
+ const resolvedSource = substituteEnvVars(source, options.env);
10
+ const resolvedHeaders = headers
11
+ ? Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, substituteEnvVars(v, options.env)]))
12
+ : undefined;
13
+ const resolvedCookies = cookies ? substituteEnvVars(cookies, options.env) : undefined;
14
+ let data;
15
+ if (source === 'passthrough') {
16
+ // Passthrough means don't intercept - return marker
17
+ return {
18
+ pattern,
19
+ data: { __passthrough: true },
20
+ source: 'passthrough',
21
+ };
22
+ }
23
+ if (source.startsWith('file://')) {
24
+ data = await loadFromFile(resolvedSource.slice(7), options.baseDir);
25
+ }
26
+ else if (source.startsWith('https://') || source.startsWith('http://')) {
27
+ data = await loadFromUrl(resolvedSource, resolvedHeaders, resolvedCookies);
28
+ }
29
+ else if (source.startsWith('s3://')) {
30
+ data = await loadFromS3(resolvedSource.slice(5));
31
+ }
32
+ else if (source.startsWith('r2://')) {
33
+ data = await loadFromR2(resolvedSource.slice(5));
34
+ }
35
+ else if (source.startsWith('script://')) {
36
+ data = await loadFromScript(resolvedSource.slice(9), options.baseDir);
37
+ }
38
+ else {
39
+ throw new Error(`Unknown fixture source type: ${source}`);
40
+ }
41
+ return { pattern, data, source: resolvedSource };
42
+ }
43
+ /**
44
+ * Load all fixtures from config
45
+ */
46
+ export async function loadAllFixtures(fixtures, options) {
47
+ const results = [];
48
+ for (const fixture of fixtures) {
49
+ try {
50
+ const data = await loadFixture(fixture, options);
51
+ results.push(data);
52
+ }
53
+ catch (error) {
54
+ console.error(`Failed to load fixture for pattern ${fixture.pattern}:`, error);
55
+ throw error;
56
+ }
57
+ }
58
+ return results;
59
+ }
60
+ function substituteEnvVars(str, env) {
61
+ return str.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, varName) => {
62
+ return env[varName] || process.env[varName] || '';
63
+ });
64
+ }
65
+ async function loadFromFile(filePath, baseDir) {
66
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(baseDir, filePath);
67
+ const content = await fs.readFile(fullPath, 'utf-8');
68
+ return JSON.parse(content);
69
+ }
70
+ async function loadFromUrl(url, headers, cookies) {
71
+ // Build headers, including Cookie header if provided
72
+ const requestHeaders = { ...headers };
73
+ if (cookies) {
74
+ requestHeaders['Cookie'] = cookies;
75
+ }
76
+ const response = await fetch(url, {
77
+ headers: requestHeaders,
78
+ });
79
+ if (!response.ok) {
80
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
81
+ }
82
+ return response.json();
83
+ }
84
+ async function loadFromS3(s3Path) {
85
+ // s3Path format: bucket/key
86
+ const [bucket, ...keyParts] = s3Path.split('/');
87
+ const key = keyParts.join('/');
88
+ // Use AWS SDK if available, otherwise fall back to presigned URL approach
89
+ try {
90
+ // Dynamic import to make SDK optional
91
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
92
+ const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
93
+ const client = new S3Client({});
94
+ const command = new GetObjectCommand({ Bucket: bucket, Key: key });
95
+ const response = await client.send(command);
96
+ const body = await response.Body?.transformToString();
97
+ if (!body)
98
+ throw new Error('Empty response from S3');
99
+ return JSON.parse(body);
100
+ }
101
+ catch (error) {
102
+ // If SDK not available, provide helpful error
103
+ if (error.code === 'MODULE_NOT_FOUND') {
104
+ throw new Error('S3 fixtures require @aws-sdk/client-s3. Install it with: pnpm add @aws-sdk/client-s3');
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+ async function loadFromR2(r2Path) {
110
+ // r2Path format: bucket/key
111
+ // R2 uses S3-compatible API, so we can use the same SDK
112
+ const [bucket, ...keyParts] = r2Path.split('/');
113
+ const key = keyParts.join('/');
114
+ const accountId = process.env.CLOUDFLARE_ACCOUNT_ID || process.env.CF_ACCOUNT_ID;
115
+ if (!accountId) {
116
+ throw new Error('R2 fixtures require CLOUDFLARE_ACCOUNT_ID or CF_ACCOUNT_ID env var');
117
+ }
118
+ try {
119
+ // Dynamic import to make SDK optional
120
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
121
+ const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
122
+ const client = new S3Client({
123
+ region: 'auto',
124
+ endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
125
+ credentials: {
126
+ accessKeyId: process.env.R2_ACCESS_KEY_ID || '',
127
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY || '',
128
+ },
129
+ });
130
+ const command = new GetObjectCommand({ Bucket: bucket, Key: key });
131
+ const response = await client.send(command);
132
+ const body = await response.Body?.transformToString();
133
+ if (!body)
134
+ throw new Error('Empty response from R2');
135
+ return JSON.parse(body);
136
+ }
137
+ catch (error) {
138
+ if (error.code === 'MODULE_NOT_FOUND') {
139
+ throw new Error('R2 fixtures require @aws-sdk/client-s3. Install it with: pnpm add @aws-sdk/client-s3');
140
+ }
141
+ throw error;
142
+ }
143
+ }
144
+ async function loadFromScript(scriptPath, baseDir) {
145
+ const fullPath = path.isAbsolute(scriptPath)
146
+ ? scriptPath
147
+ : path.join(baseDir, scriptPath);
148
+ // Dynamic import the script - it should export a default function or 'generate' function
149
+ const module = await import(fullPath);
150
+ const generator = module.default || module.generate;
151
+ if (typeof generator !== 'function') {
152
+ throw new Error(`Script ${scriptPath} must export a default function or 'generate' function`);
153
+ }
154
+ return generator();
155
+ }
156
+ /**
157
+ * Generate a mock fixture based on a pattern
158
+ * Useful for scaffolding fixture files
159
+ */
160
+ export function generateMockFixture(pattern) {
161
+ // Generate sensible mock data based on the pattern
162
+ if (pattern.includes('user')) {
163
+ return {
164
+ id: 1,
165
+ name: 'Test User',
166
+ email: 'test@example.com',
167
+ avatar: 'https://placekitten.com/100/100',
168
+ };
169
+ }
170
+ if (pattern.includes('dashboard') || pattern.includes('analytics')) {
171
+ return {
172
+ totalUsers: 1234,
173
+ activeUsers: 567,
174
+ revenue: 12345.67,
175
+ chartData: [
176
+ { date: '2024-01-01', value: 100 },
177
+ { date: '2024-01-02', value: 120 },
178
+ { date: '2024-01-03', value: 115 },
179
+ ],
180
+ };
181
+ }
182
+ if (pattern.includes('config') || pattern.includes('settings')) {
183
+ return {
184
+ theme: 'light',
185
+ language: 'en',
186
+ notifications: true,
187
+ features: {
188
+ darkMode: true,
189
+ betaFeatures: false,
190
+ },
191
+ };
192
+ }
193
+ // Default generic response
194
+ return {
195
+ success: true,
196
+ data: [],
197
+ message: `Mock data for ${pattern}`,
198
+ };
199
+ }
@@ -0,0 +1,43 @@
1
+ import { HaystackConfig } from '../types.js';
2
+ import { ProjectDetection, DetectedService } from './detect.js';
3
+ /**
4
+ * Interactive setup flow for .haystack.json
5
+ *
6
+ * The MCP tool will return questions one at a time.
7
+ * The agent asks the user, collects answers, and calls back
8
+ * until the config is complete.
9
+ */
10
+ export interface SetupQuestion {
11
+ id: string;
12
+ question: string;
13
+ type: 'text' | 'select' | 'multiselect' | 'confirm';
14
+ options?: string[];
15
+ default?: string;
16
+ hint?: string;
17
+ }
18
+ export interface SetupState {
19
+ step: 'project_name' | 'is_monorepo' | 'services_list' | 'service_details' | 'dev_command' | 'dev_port' | 'dev_ready_pattern' | 'verification_commands' | 'auth_needs_signin' | 'auth_strategy' | 'auth_env' | 'auth_token_type' | 'auth_token_name' | 'auth_token_secret' | 'db_needs_database' | 'db_strategy' | 'db_env' | 'db_seed_command' | 'complete';
20
+ answers: Record<string, unknown>;
21
+ config: Partial<HaystackConfig>;
22
+ /** For monorepo setup: which service we're configuring */
23
+ currentServiceIndex?: number;
24
+ /** For monorepo setup: list of services to configure */
25
+ serviceNames?: string[];
26
+ /** Auto-detected project info */
27
+ detection?: ProjectDetection;
28
+ /** Auto-detected services (for smart defaults) */
29
+ detectedServices?: DetectedService[];
30
+ }
31
+ export declare function createInitialState(rootDir?: string): SetupState;
32
+ /**
33
+ * Get a default value based on auto-detection.
34
+ */
35
+ export declare function getDetectedDefault(state: SetupState, questionId: string): string | undefined;
36
+ export declare function getNextQuestion(state: SetupState): SetupQuestion | null;
37
+ export declare function advanceStep(state: SetupState): SetupState;
38
+ /**
39
+ * Build the final .haystack.json config from collected answers.
40
+ * This generates the schema that sandbox.py expects.
41
+ */
42
+ export declare function buildConfig(state: SetupState): HaystackConfig;
43
+ export declare function configToJson(config: HaystackConfig): string;