@haystackeditor/cli 0.15.16 → 0.15.18

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 (42) hide show
  1. package/README.md +45 -6
  2. package/dist/commands/ask.d.ts +14 -0
  3. package/dist/commands/ask.js +20 -0
  4. package/dist/commands/inbox.d.ts +65 -0
  5. package/dist/commands/inbox.js +137 -0
  6. package/dist/commands/mcp.js +88 -134
  7. package/dist/commands/pr.d.ts +37 -1
  8. package/dist/commands/pr.js +95 -77
  9. package/dist/commands/schema-cmd.js +1 -1
  10. package/dist/commands/traces.d.ts +48 -0
  11. package/dist/commands/traces.js +92 -0
  12. package/dist/commands/triage.d.ts +12 -0
  13. package/dist/commands/triage.js +90 -58
  14. package/dist/commands/webhooks.js +3 -6
  15. package/dist/index.js +50 -148
  16. package/dist/schema.d.ts +5 -2
  17. package/dist/schema.js +5 -2
  18. package/dist/triage/prompts.js +4 -0
  19. package/dist/utils/analysis-api.d.ts +7 -0
  20. package/dist/utils/haystack-api.d.ts +20 -0
  21. package/dist/utils/haystack-api.js +66 -0
  22. package/package.json +4 -4
  23. package/schemas/ask.v1.json +40 -0
  24. package/schemas/inbox.v1.json +27 -0
  25. package/schemas/pr.v2.json +54 -0
  26. package/schemas/traces.v1.json +53 -0
  27. package/schemas/triage.v2.json +64 -0
  28. package/dist/assets/skills/install-verification.md +0 -67
  29. package/dist/commands/verification.d.ts +0 -21
  30. package/dist/commands/verification.js +0 -291
  31. package/dist/verification/contract.d.ts +0 -240
  32. package/dist/verification/contract.js +0 -115
  33. package/dist/verification/init.d.ts +0 -10
  34. package/dist/verification/init.js +0 -243
  35. package/dist/verification/manifest.d.ts +0 -49
  36. package/dist/verification/manifest.js +0 -186
  37. package/dist/verification/runner.d.ts +0 -20
  38. package/dist/verification/runner.js +0 -102
  39. package/dist/verification/safety.d.ts +0 -10
  40. package/dist/verification/safety.js +0 -252
  41. package/dist/verification/validate.d.ts +0 -12
  42. package/dist/verification/validate.js +0 -219
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://docs.haystackeditor.com/cli/schemas/traces.v1.json",
4
+ "title": "haystack traces list|get --json",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "operation", "ref", "head_sha"],
8
+ "allOf": [
9
+ {
10
+ "if": { "properties": { "operation": { "const": "list" } } },
11
+ "then": { "required": ["checkpoints"] }
12
+ },
13
+ {
14
+ "if": { "properties": { "operation": { "const": "get" } } },
15
+ "then": { "required": ["checkpoint_id", "checkpoint", "chunks", "total_chunks", "total_chars", "next_cursor"] }
16
+ }
17
+ ],
18
+ "$defs": {
19
+ "checkpoint": {
20
+ "type": "object",
21
+ "additionalProperties": false,
22
+ "required": ["checkpoint_id", "commit_sha", "commit_message", "timestamp", "summary", "user_prompt", "files_touched", "branch"],
23
+ "properties": {
24
+ "checkpoint_id": { "type": "string" },
25
+ "commit_sha": { "type": "string" },
26
+ "commit_message": { "type": "string" },
27
+ "timestamp": { "type": ["string", "null"] },
28
+ "summary": { "type": "string" },
29
+ "user_prompt": { "type": ["string", "null"] },
30
+ "files_touched": { "type": "array", "items": { "type": "string" } },
31
+ "branch": { "type": ["string", "null"] }
32
+ }
33
+ }
34
+ },
35
+ "properties": {
36
+ "schema_version": { "type": "string", "const": "1.0.0" },
37
+ "operation": { "type": "string", "enum": ["list", "get"] },
38
+ "ref": { "type": "string" },
39
+ "head_sha": { "type": "string" },
40
+ "checkpoints": { "type": "array", "items": { "$ref": "#/$defs/checkpoint" } },
41
+ "checkpoint_id": { "type": "string" },
42
+ "checkpoint": {
43
+ "anyOf": [
44
+ { "$ref": "#/$defs/checkpoint" },
45
+ { "type": "null" }
46
+ ]
47
+ },
48
+ "chunks": { "type": "array", "items": { "type": "string" } },
49
+ "total_chunks": { "type": "integer" },
50
+ "total_chars": { "type": "integer" },
51
+ "next_cursor": { "type": ["string", "null"] }
52
+ }
53
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://docs.haystackeditor.com/cli/schemas/triage.v2.json",
4
+ "title": "haystack triage <ref> --json",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "ref", "head_sha", "status", "verdict", "rating", "human_review_reasons", "findings"],
8
+ "properties": {
9
+ "schema_version": { "type": "string", "const": "2.0.0" },
10
+ "ref": { "type": "string" },
11
+ "head_sha": { "type": ["string", "null"] },
12
+ "status": { "type": "string", "enum": ["complete", "pending", "failed", "stale", "unavailable"] },
13
+ "verdict": {
14
+ "anyOf": [
15
+ { "type": "string", "enum": ["good_to_merge", "needs_review", "needs_input"] },
16
+ { "type": "null" }
17
+ ]
18
+ },
19
+ "rating": {
20
+ "anyOf": [
21
+ { "type": "integer", "minimum": 1, "maximum": 5 },
22
+ { "type": "null" }
23
+ ]
24
+ },
25
+ "message": { "type": "string" },
26
+ "human_review_reasons": { "type": "array" },
27
+ "findings": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["id", "category", "summary", "detail", "suggested_fix", "source", "location", "severity"],
33
+ "properties": {
34
+ "id": { "type": ["string", "null"] },
35
+ "category": { "type": "string" },
36
+ "summary": { "type": "string" },
37
+ "detail": { "type": "string" },
38
+ "suggested_fix": { "type": ["string", "null"] },
39
+ "source": { "type": ["string", "null"] },
40
+ "severity": { "type": ["string", "null"] },
41
+ "location": {
42
+ "type": ["object", "null"],
43
+ "additionalProperties": false,
44
+ "properties": {
45
+ "path": { "type": ["string", "null"] },
46
+ "start_line": { "type": ["integer", "null"] },
47
+ "end_line": { "type": ["integer", "null"] }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ },
53
+ "fixer": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": ["status", "result_sha", "summary"],
57
+ "properties": {
58
+ "status": { "type": "string" },
59
+ "result_sha": { "type": ["string", "null"] },
60
+ "summary": { "type": "string" }
61
+ }
62
+ }
63
+ }
64
+ }
@@ -1,67 +0,0 @@
1
- # Install Haystack Verification
2
-
3
- Add a Haystack verification contract to this repository so Haystack (and any
4
- coding agent) can safely verify risky PRs and produce reviewable evidence.
5
-
6
- The deliverable is a small, reviewable PR containing:
7
-
8
- - `.haystack/verification.yml` — the verification contract
9
- - `scripts/haystack-preflight` — proves the app is up and answering
10
- - `scripts/haystack-smoke` — boots the app, runs a basic logged-in check, captures evidence
11
- - `.haystack/AGENTS.md` — how agents should use the contract
12
-
13
- ## Ground rules
14
-
15
- 1. **No production behavior changes.** Anything you add must be dev/test-only,
16
- clearly isolated, and easy for a reviewer to verify as inert in production.
17
- 2. **Prefer existing test utilities** (seed scripts, factories, test logins,
18
- fixtures) over new application code.
19
- 3. **Only add dev-only endpoints when necessary**, and gate them so they cannot
20
- run in production (`NODE_ENV !== 'production'` or the framework equivalent).
21
- If you add one, also add a test proving it is disabled in production mode,
22
- and wire that test into `safety.production_disabled_check`.
23
- 4. **Never reference real credentials, real payment providers, real email
24
- delivery, or customer data.** Sandbox or mock everything external.
25
- 5. **Keep the integration small.** A working smoke scenario beats ten
26
- aspirational ones.
27
-
28
- ## Steps
29
-
30
- 1. **Scaffold.** Run `npx @haystackeditor/cli verification init` (or
31
- `haystack verification init` if installed). This detects the framework and
32
- writes starter files. Review everything it generated.
33
-
34
- 2. **Inspect the repo.** Identify:
35
- - framework, package manager, and how the app starts locally
36
- - how the app knows it's "ready" (port, health endpoint, log line)
37
- - existing seed/test data and how tests authenticate
38
- - existing E2E/smoke tests you can reuse as scenarios
39
-
40
- 3. **Fix the environment section.** Make `setup`, `start`, and `preflight`
41
- real: `setup` must work on a clean clone; `preflight` must exit 0 only when
42
- the app is actually usable (prefer a health endpoint over a port check).
43
-
44
- 4. **Wire personas if a safe login path exists.** Personas are seeded fake
45
- users (admin/member/viewer). Use an existing test-login helper if there is
46
- one. If none exists and one is genuinely needed, add a dev-only helper per
47
- ground rule 3 — otherwise leave personas empty.
48
-
49
- 5. **Make the smoke scenario real.** It should boot the app, reach a
50
- meaningful logged-in (or at least rendered) state, and leave evidence in the
51
- artifact directory: server logs, the captured page, and screenshots if a
52
- browser runner is available. If part of the flow can't be tested, append a
53
- line to `<artifact_dir>/gaps.txt` saying what was skipped.
54
-
55
- 6. **Validate until clean.** Iterate until both pass:
56
- ```bash
57
- haystack verification validate
58
- haystack verification check-safety
59
- haystack verification run smoke
60
- ```
61
- `validate` must not report `not_ready` or `unsafe`; `run smoke` must end
62
- `verified` and write a manifest + review packet under the artifact dir.
63
-
64
- 7. **Open a reviewable PR.** Keep it focused on the verification contract.
65
- In the description, state explicitly: what was added, why each piece is
66
- dev-only, and what the smoke scenario proves. Include the review packet
67
- from your successful `run smoke` as evidence.
@@ -1,21 +0,0 @@
1
- export declare function verificationInitCommand(options: {
2
- force?: boolean;
3
- skill?: boolean;
4
- }): Promise<void>;
5
- export declare function verificationValidateCommand(options: {
6
- json?: boolean;
7
- }): Promise<void>;
8
- export declare function verificationPreflightCommand(): Promise<void>;
9
- export declare function verificationRunCommand(scenarioName: string, options: {
10
- json?: boolean;
11
- skipPreflight?: boolean;
12
- setup?: boolean;
13
- timeout?: number;
14
- }): Promise<void>;
15
- export declare function verificationCollectCommand(options: {
16
- scenario?: string;
17
- json?: boolean;
18
- }): Promise<void>;
19
- export declare function verificationCheckSafetyCommand(options: {
20
- json?: boolean;
21
- }): Promise<void>;
@@ -1,291 +0,0 @@
1
- /**
2
- * `haystack verification ...` — repo-local verification contract commands.
3
- *
4
- * init scaffold .haystack/verification.yml + scripts + skill
5
- * validate can Haystack trust this repo's verification setup?
6
- * preflight is the app up and answering?
7
- * run <scenario> run a scenario, capture artifacts, write manifest + review packet
8
- * collect-artifacts rebuild a manifest from artifacts already on disk
9
- * check-safety prove the verification setup can't hurt anyone
10
- *
11
- * See docs/VERIFICATION-CONTRACT.md for the product framing and schema.
12
- */
13
- import * as path from 'node:path';
14
- import chalk from 'chalk';
15
- import { CONTRACT_RELATIVE_PATH, ensureArtifactDir, loadContract, } from '../verification/contract.js';
16
- import { initVerification } from '../verification/init.js';
17
- import { validateContract } from '../verification/validate.js';
18
- import { checkSafety } from '../verification/safety.js';
19
- import { runCommand } from '../verification/runner.js';
20
- import { collectArtifacts, getHeadSha, readGaps, writeManifest, writeReviewPacket, } from '../verification/manifest.js';
21
- const DEFAULT_SCENARIO_TIMEOUT_S = 900;
22
- const PREFLIGHT_TIMEOUT_S = 300;
23
- // ─── init ─────────────────────────────────────────────────────────────────────
24
- export async function verificationInitCommand(options) {
25
- const result = await initVerification(process.cwd(), {
26
- force: options.force,
27
- skillOnly: options.skill,
28
- });
29
- for (const file of result.created) {
30
- console.log(chalk.green(' created ') + file);
31
- }
32
- for (const file of result.skipped) {
33
- console.log(chalk.yellow(' skipped ') + file + chalk.dim(' (exists, use --force to overwrite)'));
34
- }
35
- if (result.created.length > 0) {
36
- console.log();
37
- console.log(chalk.white('Next steps:'));
38
- console.log(chalk.cyan(' 1.') + ` Review ${CONTRACT_RELATIVE_PATH} — detection is a starting point, not truth`);
39
- console.log(chalk.cyan(' 2.') + ' haystack verification validate');
40
- console.log(chalk.cyan(' 3.') + ' haystack verification run smoke');
41
- if (options.skill) {
42
- console.log();
43
- console.log(chalk.dim('Hand .haystack/skills/install-verification/SKILL.md to your coding agent to complete the integration.'));
44
- }
45
- }
46
- }
47
- // ─── validate ─────────────────────────────────────────────────────────────────
48
- export async function verificationValidateCommand(options) {
49
- const report = await validateContract();
50
- if (options.json) {
51
- console.log(JSON.stringify(report, null, 2));
52
- }
53
- else {
54
- printValidationReport(report);
55
- }
56
- if (report.readiness === 'unsafe')
57
- process.exit(2);
58
- if (report.readiness === 'not_ready')
59
- process.exit(1);
60
- }
61
- function printValidationReport(report) {
62
- const styles = {
63
- ready: chalk.green,
64
- partially_ready: chalk.yellow,
65
- not_ready: chalk.red,
66
- unsafe: chalk.red,
67
- };
68
- console.log(chalk.white('Haystack Verification Contract: ') + styles[report.readiness](report.readiness));
69
- console.log();
70
- const sections = [
71
- { title: 'Passed', level: 'pass', bullet: chalk.green('✓') },
72
- { title: 'Missing', level: 'warn', bullet: chalk.yellow('•') },
73
- { title: 'Broken', level: 'fail', bullet: chalk.red('✗') },
74
- { title: 'Unsafe', level: 'unsafe', bullet: chalk.red('⚠') },
75
- ];
76
- for (const { title, level, bullet } of sections) {
77
- const items = report.items.filter((i) => i.level === level);
78
- if (items.length === 0)
79
- continue;
80
- console.log(chalk.white(`${title}:`));
81
- for (const item of items) {
82
- console.log(`${bullet} ${item.message}`);
83
- }
84
- console.log();
85
- }
86
- }
87
- // ─── preflight ────────────────────────────────────────────────────────────────
88
- export async function verificationPreflightCommand() {
89
- const contract = await requireContract();
90
- const artifactDir = await ensureArtifactDir(contract);
91
- const record = await runCommand(contract.environment.preflight, {
92
- cwd: process.cwd(),
93
- timeoutMs: PREFLIGHT_TIMEOUT_S * 1000,
94
- logFile: path.join(artifactDir, 'logs', 'preflight.log'),
95
- env: { ARTIFACT_DIR: artifactDir },
96
- });
97
- if (record.status === 'passed') {
98
- console.log(chalk.green('✓ preflight passed') + chalk.dim(` (${(record.duration_ms / 1000).toFixed(1)}s)`));
99
- }
100
- else {
101
- console.error(chalk.red(`✗ preflight ${record.status}`) + chalk.dim(` — see ${record.log_path}`));
102
- process.exit(1);
103
- }
104
- }
105
- // ─── run <scenario> ───────────────────────────────────────────────────────────
106
- export async function verificationRunCommand(scenarioName, options) {
107
- const rootDir = process.cwd();
108
- const contract = await requireContract();
109
- const scenario = contract.scenarios[scenarioName];
110
- if (!scenario) {
111
- console.error(chalk.red(`Unknown scenario "${scenarioName}".`) +
112
- ` Available: ${Object.keys(contract.scenarios).join(', ')}`);
113
- process.exit(1);
114
- }
115
- const artifactDir = await ensureArtifactDir(contract);
116
- const startedAt = new Date();
117
- const commandsRun = [];
118
- const env = { ARTIFACT_DIR: artifactDir };
119
- // In --json mode stdout must carry ONLY the manifest — send progress to
120
- // stderr and keep command streaming off (output still lands in log files).
121
- const quiet = Boolean(options.json);
122
- const progress = (msg) => (quiet ? console.error(msg) : console.log(msg));
123
- const finish = async (status) => {
124
- const manifest = {
125
- contract_version: '1',
126
- status,
127
- scenario: scenarioName,
128
- commit_sha: getHeadSha(rootDir),
129
- started_at: startedAt.toISOString(),
130
- completed_at: new Date().toISOString(),
131
- commands_run: commandsRun,
132
- artifacts: await collectArtifacts({
133
- rootDir,
134
- artifactDir,
135
- extraPaths: contract.environment.log_paths,
136
- since: startedAt,
137
- }),
138
- gaps: await readGaps(artifactDir),
139
- safety: {
140
- real_email_sent: contract.safety.allow_real_email,
141
- real_payment_attempted: contract.safety.allow_real_payments,
142
- customer_data_accessed: contract.safety.allow_customer_data,
143
- },
144
- };
145
- const manifestPath = await writeManifest(manifest, artifactDir);
146
- const packetPath = await writeReviewPacket(manifest, artifactDir);
147
- if (options.json) {
148
- console.log(JSON.stringify(manifest, null, 2));
149
- }
150
- else {
151
- printRunSummary(manifest, manifestPath, packetPath, rootDir);
152
- }
153
- process.exit(status === 'verified' ? 0 : status === 'unsafe_to_verify' ? 2 : 1);
154
- };
155
- // Safety gate before anything runs.
156
- const { safety } = contract;
157
- if (safety.allow_real_email || safety.allow_real_payments || safety.allow_customer_data) {
158
- console.error(chalk.red('Refusing to run: the safety policy permits real external side effects.') +
159
- ' Fix the safety section of the contract first.');
160
- await finish('unsafe_to_verify');
161
- }
162
- if (options.setup && contract.environment.setup) {
163
- progress(chalk.dim(`$ ${contract.environment.setup}`));
164
- const setup = await runCommand(contract.environment.setup, {
165
- cwd: rootDir,
166
- timeoutMs: 15 * 60 * 1000,
167
- logFile: path.join(artifactDir, 'logs', 'setup.log'),
168
- env,
169
- quiet,
170
- });
171
- commandsRun.push(setup);
172
- if (setup.status !== 'passed')
173
- await finish('environment_not_ready');
174
- }
175
- if (!options.skipPreflight && scenario.preflight) {
176
- progress(chalk.dim(`$ ${contract.environment.preflight}`));
177
- const preflight = await runCommand(contract.environment.preflight, {
178
- cwd: rootDir,
179
- timeoutMs: PREFLIGHT_TIMEOUT_S * 1000,
180
- logFile: path.join(artifactDir, 'logs', 'preflight.log'),
181
- env,
182
- quiet,
183
- });
184
- commandsRun.push(preflight);
185
- if (preflight.status !== 'passed')
186
- await finish('environment_not_ready');
187
- }
188
- const timeoutS = options.timeout ?? scenario.timeout_seconds ?? DEFAULT_SCENARIO_TIMEOUT_S;
189
- progress(chalk.dim(`$ ${scenario.command}`));
190
- const run = await runCommand(scenario.command, {
191
- cwd: rootDir,
192
- timeoutMs: timeoutS * 1000,
193
- logFile: path.join(artifactDir, 'logs', `scenario-${scenarioName}.log`),
194
- env,
195
- quiet,
196
- });
197
- commandsRun.push(run);
198
- await finish(run.status === 'passed' ? 'verified' : 'failed');
199
- }
200
- function printRunSummary(manifest, manifestPath, packetPath, rootDir) {
201
- const statusStyle = {
202
- verified: chalk.green,
203
- failed: chalk.red,
204
- inconclusive: chalk.yellow,
205
- environment_not_ready: chalk.yellow,
206
- unsafe_to_verify: chalk.red,
207
- missing_contract: chalk.red,
208
- };
209
- console.log();
210
- console.log(chalk.white('Scenario: ') + manifest.scenario);
211
- console.log(chalk.white('Status: ') + statusStyle[manifest.status](manifest.status));
212
- console.log(chalk.white('Evidence: ') + `${manifest.artifacts.length} artifact(s)`);
213
- if (manifest.gaps.length > 0) {
214
- console.log(chalk.white('Gaps: ') + chalk.yellow(`${manifest.gaps.length} declared`));
215
- }
216
- console.log();
217
- console.log(chalk.dim(`Manifest: ${path.relative(rootDir, manifestPath)}`));
218
- console.log(chalk.dim(`Review packet: ${path.relative(rootDir, packetPath)}`));
219
- }
220
- // ─── collect-artifacts ────────────────────────────────────────────────────────
221
- export async function verificationCollectCommand(options) {
222
- const rootDir = process.cwd();
223
- const contract = await requireContract();
224
- const artifactDir = await ensureArtifactDir(contract);
225
- const now = new Date().toISOString();
226
- const manifest = {
227
- contract_version: '1',
228
- // Artifacts without a recorded run prove presence, not success.
229
- status: 'inconclusive',
230
- scenario: options.scenario ?? 'collected',
231
- commit_sha: getHeadSha(rootDir),
232
- started_at: now,
233
- completed_at: now,
234
- commands_run: [],
235
- artifacts: await collectArtifacts({
236
- rootDir,
237
- artifactDir,
238
- extraPaths: contract.environment.log_paths,
239
- }),
240
- gaps: await readGaps(artifactDir),
241
- safety: {
242
- real_email_sent: contract.safety.allow_real_email,
243
- real_payment_attempted: contract.safety.allow_real_payments,
244
- customer_data_accessed: contract.safety.allow_customer_data,
245
- },
246
- };
247
- const manifestPath = await writeManifest(manifest, artifactDir);
248
- const packetPath = await writeReviewPacket(manifest, artifactDir);
249
- if (options.json) {
250
- console.log(JSON.stringify(manifest, null, 2));
251
- }
252
- else {
253
- printRunSummary(manifest, manifestPath, packetPath, rootDir);
254
- }
255
- }
256
- // ─── check-safety ─────────────────────────────────────────────────────────────
257
- export async function verificationCheckSafetyCommand(options) {
258
- const report = await checkSafety();
259
- if (options.json) {
260
- console.log(JSON.stringify(report, null, 2));
261
- }
262
- else {
263
- for (const check of report.checks) {
264
- const bullet = check.status === 'pass' ? chalk.green('✓') : check.status === 'warn' ? chalk.yellow('•') : chalk.red('✗');
265
- console.log(`${bullet} ${chalk.white(check.name)}: ${check.detail}`);
266
- }
267
- console.log();
268
- console.log(report.ok
269
- ? chalk.green('Safety checks passed.')
270
- : chalk.red('Safety checks failed — fix the items above before running verification.'));
271
- }
272
- if (!report.ok)
273
- process.exit(1);
274
- }
275
- // ─── shared ───────────────────────────────────────────────────────────────────
276
- async function requireContract() {
277
- const loaded = await loadContract();
278
- if (loaded.status === 'missing') {
279
- console.error(chalk.red(`No verification contract found at ${CONTRACT_RELATIVE_PATH}.`) +
280
- ' Run `haystack verification init` to scaffold one.');
281
- process.exit(1);
282
- }
283
- if (loaded.status === 'invalid') {
284
- console.error(chalk.red(`Invalid ${CONTRACT_RELATIVE_PATH}:`));
285
- for (const error of loaded.errors) {
286
- console.error(chalk.red(` ✗ ${error}`));
287
- }
288
- process.exit(1);
289
- }
290
- return loaded.contract;
291
- }