@haystackeditor/cli 0.15.26 → 0.15.27

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 (49) hide show
  1. package/README.md +39 -3
  2. package/dist/assets/hooks/package-lock.json +598 -0
  3. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +2051 -0
  4. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +4 -0
  5. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +3411 -0
  6. package/dist/assets/telemetry/browser-runtime.js +861 -0
  7. package/dist/assets/telemetry/runtime.cjs +1509 -0
  8. package/dist/commands/cloud-verifier-behaviors.d.ts +27 -0
  9. package/dist/commands/cloud-verifier-behaviors.js +218 -0
  10. package/dist/commands/cloud-verifier-data-store-census.d.ts +47 -0
  11. package/dist/commands/cloud-verifier-data-store-census.js +539 -0
  12. package/dist/commands/cloud-verifier-data-store-drift.d.ts +42 -0
  13. package/dist/commands/cloud-verifier-data-store-drift.js +158 -0
  14. package/dist/commands/cloud-verifier-identity-census.d.ts +88 -0
  15. package/dist/commands/cloud-verifier-identity-census.js +4057 -0
  16. package/dist/commands/cloud-verifier-materialization.d.ts +16 -0
  17. package/dist/commands/cloud-verifier-materialization.js +704 -0
  18. package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +29 -0
  19. package/dist/commands/cloud-verifier-pascal-selector-census.js +1382 -0
  20. package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +27 -0
  21. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +2015 -0
  22. package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +51 -0
  23. package/dist/commands/cloud-verifier-specialized-operational-census.js +11432 -0
  24. package/dist/commands/cloud-verifier-universe.d.ts +31 -0
  25. package/dist/commands/cloud-verifier-universe.js +10178 -0
  26. package/dist/commands/design-verify.d.ts +23 -0
  27. package/dist/commands/design-verify.js +142 -0
  28. package/dist/commands/hooks.js +2 -2
  29. package/dist/commands/prepare-universe-review.d.ts +115 -0
  30. package/dist/commands/prepare-universe-review.js +1092 -0
  31. package/dist/commands/production-source-deny-policy.d.ts +15 -0
  32. package/dist/commands/production-source-deny-policy.js +100 -0
  33. package/dist/commands/scaffold-provisional-universe.d.ts +468 -0
  34. package/dist/commands/scaffold-provisional-universe.js +808 -0
  35. package/dist/commands/schema-cmd.js +32 -0
  36. package/dist/commands/skills.d.ts +2 -2
  37. package/dist/commands/skills.js +415 -47
  38. package/dist/commands/telemetry.d.ts +53 -0
  39. package/dist/commands/telemetry.js +832 -0
  40. package/dist/index.js +134 -12
  41. package/dist/triage/runner.js +7 -6
  42. package/dist/utils/design-verifier-api.d.ts +129 -0
  43. package/dist/utils/design-verifier-api.js +175 -0
  44. package/dist/utils/design-verifier-result.d.ts +52 -0
  45. package/dist/utils/design-verifier-result.js +299 -0
  46. package/dist/utils/haystack-api.d.ts +2 -0
  47. package/dist/utils/haystack-api.js +10 -3
  48. package/dist/utils/hooks.js +2 -1
  49. package/package.json +7 -2
@@ -0,0 +1,299 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ function findingExplanation(kind) {
3
+ if (kind === 'declared-change-not-observed') {
4
+ return 'The intended behavior change was not observed on the proposed revision.';
5
+ }
6
+ if (kind === 'unexpected-change') {
7
+ return 'The proposed revision changed behavior that was registered to remain stable.';
8
+ }
9
+ return 'Executed behavior contradicted the registered expectation.';
10
+ }
11
+ function sameRecordedValue(left, right) {
12
+ return isDeepStrictEqual(left, right);
13
+ }
14
+ function executionSummaries(value) {
15
+ if (!Array.isArray(value))
16
+ return [];
17
+ return value.filter((execution) => !!execution && typeof execution === 'object' &&
18
+ ['base1', 'base2', 'head'].includes(execution.side) &&
19
+ ['completed', 'failed', 'missing'].includes(execution.status) &&
20
+ Array.isArray(execution.diagnostics) && Array.isArray(execution.outputExcerpt));
21
+ }
22
+ export function buildDesignVerifierAgentReport(run) {
23
+ const repository = `${run.owner}/${run.repo}`;
24
+ if (run.status !== 'succeeded' || !run.result) {
25
+ return {
26
+ ok: false,
27
+ status: 'failed',
28
+ verdict: 'inconclusive',
29
+ headline: 'VERIFICATION INCONCLUSIVE',
30
+ explanation: run.error || 'The verifier did not produce a terminal behavioral verdict.',
31
+ runId: run.runId,
32
+ repository,
33
+ pullRequest: run.pr,
34
+ revisions: { base: run.baseSha, head: run.headSha },
35
+ durationMs: run.durationMs,
36
+ timings: null,
37
+ changedFiles: [],
38
+ cases: [],
39
+ inspectCommand: `haystack design-verify ${repository}#${run.pr} --run ${run.runId}`,
40
+ replay: null,
41
+ error: run.error,
42
+ exitCode: 2,
43
+ };
44
+ }
45
+ const verdict = run.result.verdict || {};
46
+ const summary = verdict.summary || {};
47
+ const findings = Array.isArray(verdict.findings) ? verdict.findings : [];
48
+ const confirmations = Array.isArray(verdict.confirmations) ? verdict.confirmations : [];
49
+ const findingByCase = new Map(findings.map((finding) => [finding.caseId, finding]));
50
+ const confirmedIds = new Set(confirmations.map((confirmation) => confirmation.caseId));
51
+ const scenarioByCase = new Map(run.result.scenarios.map((scenario) => [scenario.caseId, scenario]));
52
+ const probeByCase = new Map(run.result.probes.map((probe) => [probe.caseId, probe]));
53
+ const cases = run.result.cases.map((testCase) => {
54
+ const id = String(testCase.id || '');
55
+ const finding = findingByCase.get(id);
56
+ const scenario = scenarioByCase.get(id);
57
+ const probe = probeByCase.get(id);
58
+ const observations = (Array.isArray(scenario?.divergences) ? scenario.divergences : []).map((item) => ({
59
+ path: String(item.display || (Array.isArray(item.path) ? item.path.join('.') : 'observation')),
60
+ base: item.base,
61
+ baseRepeat: item.base2,
62
+ head: item.head,
63
+ }));
64
+ return {
65
+ id,
66
+ title: String(testCase.title || scenario?.scenario || id),
67
+ story: String(testCase.story || ''),
68
+ expectedHead: String(testCase.expected_head_observation || ''),
69
+ verdict: finding
70
+ ? 'finding'
71
+ : confirmedIds.has(id)
72
+ ? 'confirmed'
73
+ : scenario?.status === 'clean'
74
+ ? 'clean'
75
+ : 'inconclusive',
76
+ findingKind: finding?.kind || null,
77
+ baseStable: observations.length
78
+ ? observations.every((item) => sameRecordedValue(item.base, item.baseRepeat))
79
+ : scenario?.status === 'clean' ? true : null,
80
+ observations,
81
+ generatedFiles: probe?.files || [],
82
+ executedCommand: probe?.run || null,
83
+ executions: executionSummaries(scenario?.executions),
84
+ infrastructure: scenario?.infrastructureReceipt &&
85
+ typeof scenario.infrastructureReceipt === 'object'
86
+ ? {
87
+ verified: scenario.infrastructureReceipt.status === 'passed',
88
+ recipeId: typeof scenario.infrastructureReceipt.recipeId === 'string'
89
+ ? scenario.infrastructureReceipt.recipeId
90
+ : null,
91
+ freshInstances: Array.isArray(scenario.infrastructureReceipt.environmentInstanceIds)
92
+ ? scenario.infrastructureReceipt.environmentInstanceIds.length
93
+ : 0,
94
+ baseRepeat: typeof scenario.infrastructureReceipt.baseRepeat === 'string'
95
+ ? scenario.infrastructureReceipt.baseRepeat
96
+ : null,
97
+ errors: Array.isArray(scenario.infrastructureReceipt.errors)
98
+ ? scenario.infrastructureReceipt.errors.filter((item) => typeof item === 'string')
99
+ : [],
100
+ }
101
+ : null,
102
+ };
103
+ });
104
+ let agentVerdict;
105
+ let headline;
106
+ let explanation;
107
+ let exitCode;
108
+ if (findings.length > 0) {
109
+ agentVerdict = 'fix_needed';
110
+ headline = 'FIX NEEDED';
111
+ explanation = findingExplanation(String(findings[0]?.kind || ''));
112
+ exitCode = 1;
113
+ }
114
+ else if (summary.caseDesignOutcome === 'no-supported-cases' || Number(summary.scenarios || 0) === 0) {
115
+ agentVerdict = 'inconclusive';
116
+ headline = 'VERIFICATION INCONCLUSIVE';
117
+ explanation = 'The verifier could not design and execute a supported behavioral case.';
118
+ exitCode = 2;
119
+ }
120
+ else if (confirmations.length > 0) {
121
+ agentVerdict = 'fix_confirmed';
122
+ headline = 'FIX CONFIRMED';
123
+ explanation = 'The intended behavior change was observed on head and reproduced across two stable base runs.';
124
+ exitCode = 0;
125
+ }
126
+ else {
127
+ agentVerdict = 'no_unexpected_change';
128
+ headline = 'NO UNEXPECTED CHANGE';
129
+ explanation = 'Executed cases found no behavior change outside the registered expectations.';
130
+ exitCode = 0;
131
+ }
132
+ return {
133
+ ok: exitCode === 0,
134
+ status: 'complete',
135
+ verdict: agentVerdict,
136
+ headline,
137
+ explanation,
138
+ runId: run.runId,
139
+ repository,
140
+ pullRequest: run.pr,
141
+ revisions: { base: run.baseSha, head: run.headSha },
142
+ durationMs: run.durationMs,
143
+ timings: run.result.timings || null,
144
+ changedFiles: run.result.changedFiles,
145
+ cases,
146
+ inspectCommand: `haystack design-verify ${repository}#${run.pr} --run ${run.runId}`,
147
+ replay: run.result.replay,
148
+ error: null,
149
+ exitCode,
150
+ };
151
+ }
152
+ function displayValue(value) {
153
+ const encoded = JSON.stringify(value);
154
+ return encoded === undefined ? String(value) : encoded;
155
+ }
156
+ function durationLabel(durationMs) {
157
+ if (durationMs === null)
158
+ return '';
159
+ const totalSeconds = Math.round(durationMs / 1000);
160
+ const minutes = Math.floor(totalSeconds / 60);
161
+ const seconds = totalSeconds % 60;
162
+ return minutes ? `${minutes}m ${seconds}s` : `${seconds}s`;
163
+ }
164
+ function preciseDurationLabel(durationMs) {
165
+ if (durationMs === null)
166
+ return 'not recorded';
167
+ if (durationMs < 1000)
168
+ return `${Number(durationMs.toFixed(3))}ms`;
169
+ const minutes = Math.floor(durationMs / 60_000);
170
+ const seconds = Number(((durationMs % 60_000) / 1000).toFixed(3));
171
+ return minutes ? `${minutes}m ${seconds}s` : `${seconds}s`;
172
+ }
173
+ function timingStageLabel(stage) {
174
+ if (stage === 'blast-radius')
175
+ return 'blast radius';
176
+ if (stage === 'case-design')
177
+ return 'case design';
178
+ return stage;
179
+ }
180
+ function commandLabel(command) {
181
+ if (!command)
182
+ return null;
183
+ return command.argv.map((arg) => (/^[A-Za-z0-9_./:=+-]+$/.test(arg) ? arg : JSON.stringify(arg))).join(' ');
184
+ }
185
+ function executionSideLabel(side) {
186
+ if (side === 'base1')
187
+ return 'base';
188
+ if (side === 'base2')
189
+ return 'base repeat';
190
+ return 'head';
191
+ }
192
+ function executionNeedsAttention(execution) {
193
+ return execution.status !== 'completed' || execution.diagnostics.length > 0;
194
+ }
195
+ export function formatDesignVerifierAgentReport(report) {
196
+ const timing = durationLabel(report.durationMs);
197
+ const lines = [
198
+ `${report.exitCode === 0 ? '✓' : report.exitCode === 1 ? '✗' : '?'} ${report.headline}${timing ? ` ${timing}` : ''}`,
199
+ '',
200
+ report.explanation,
201
+ ];
202
+ if (report.revisions.base || report.revisions.head) {
203
+ lines.push('', `Revisions: base ${report.revisions.base || 'unknown'} (executed twice)`, ` head ${report.revisions.head || 'unknown'} (executed once)`);
204
+ }
205
+ if (report.timings) {
206
+ lines.push('', 'Timing breakdown:');
207
+ if (report.timings.admissionMs !== null) {
208
+ lines.push(` admission: ${preciseDurationLabel(report.timings.admissionMs)}`);
209
+ }
210
+ for (const stage of report.timings.stages) {
211
+ lines.push(` ${timingStageLabel(stage.stage)}: ${preciseDurationLabel(stage.durationMs)} (${stage.status})`);
212
+ }
213
+ lines.push(` pipeline total: ${preciseDurationLabel(report.timings.pipelineDurationMs)}`);
214
+ if (report.timings.unattributedMs !== null) {
215
+ lines.push(` orchestration/finalization not recorded in a phase: ${preciseDurationLabel(report.timings.unattributedMs)}`);
216
+ }
217
+ }
218
+ for (const testCase of report.cases) {
219
+ lines.push('', `Test: ${testCase.title}`);
220
+ if (testCase.story)
221
+ lines.push(` Behavior: ${testCase.story}`);
222
+ if (testCase.expectedHead)
223
+ lines.push(` Expected on head: ${testCase.expectedHead}`);
224
+ if (testCase.observations.length) {
225
+ lines.push(' Observed:');
226
+ for (const observation of testCase.observations) {
227
+ lines.push(` ${observation.path}`);
228
+ lines.push(` base: ${displayValue(observation.base)} (repeat: ${displayValue(observation.baseRepeat)})`);
229
+ lines.push(` head: ${displayValue(observation.head)}`);
230
+ }
231
+ }
232
+ if (testCase.baseStable !== null) {
233
+ lines.push(` Base stability: ${testCase.baseStable ? 'passed; both runs agreed' : 'failed; base runs disagreed'}`);
234
+ }
235
+ if (testCase.infrastructure) {
236
+ lines.push(testCase.infrastructure.verified
237
+ ? ` Infrastructure: verified; same recipe, ${testCase.infrastructure.freshInstances} fresh instances`
238
+ : ` Infrastructure: unverified; ${testCase.infrastructure.errors.join('; ') || 'receipt missing'}`);
239
+ }
240
+ const executionsWithDiagnostics = testCase.executions.filter(executionNeedsAttention);
241
+ if (executionsWithDiagnostics.length) {
242
+ lines.push(' Execution diagnostics:');
243
+ for (const execution of executionsWithDiagnostics) {
244
+ const exit = execution.commandExitCode === null ? 'exit unknown' : `exit ${execution.commandExitCode}`;
245
+ const elapsed = execution.durationMs === null ? '' : `, ${durationLabel(execution.durationMs)}`;
246
+ lines.push(` ${executionSideLabel(execution.side)} (${exit}${elapsed}):`);
247
+ if (execution.scenarioError)
248
+ lines.push(` Scenario error: ${execution.scenarioError}`);
249
+ for (const diagnostic of execution.diagnostics) {
250
+ const identity = [diagnostic.type, diagnostic.code].filter(Boolean).join(', ');
251
+ lines.push(` ${diagnostic.kind}${identity ? ` (${identity})` : ''}: ${diagnostic.message}`);
252
+ }
253
+ if (execution.diagnosticsOmitted > 0) {
254
+ lines.push(` ... ${execution.diagnosticsOmitted} additional diagnostics omitted`);
255
+ }
256
+ if (execution.outputExcerpt.length) {
257
+ lines.push(' Relevant command output:');
258
+ for (const output of execution.outputExcerpt) {
259
+ lines.push(` ${output.stream}: ${output.text}`);
260
+ }
261
+ if (execution.outputLinesOmitted > 0) {
262
+ lines.push(` ... ${execution.outputLinesOmitted} output lines omitted`);
263
+ }
264
+ }
265
+ lines.push(` Full output artifact: ${execution.outputArtifact}`);
266
+ lines.push(` Structured trace artifact: ${execution.traceArtifact}`);
267
+ }
268
+ }
269
+ if (testCase.generatedFiles.length)
270
+ lines.push(` Generated probe artifact: ${testCase.generatedFiles.join(', ')}`);
271
+ const command = commandLabel(testCase.executedCommand);
272
+ if (command)
273
+ lines.push(` Executed in the sandbox: ${command}`);
274
+ }
275
+ if (report.changedFiles.length) {
276
+ lines.push('', 'Changed files to inspect:', ...report.changedFiles.map((file) => ` ${file}`));
277
+ }
278
+ lines.push('', `Inspect this exact result again: ${report.inspectCommand}`, `Run: ${report.runId}`);
279
+ return lines.join('\n');
280
+ }
281
+ export function designVerifierStageLabel(stage) {
282
+ if (!stage)
283
+ return 'Waiting for the run to start';
284
+ if (stage === 'prepare' || stage === 'bake')
285
+ return 'Preparing the repository';
286
+ if (stage === 'blast-radius')
287
+ return 'Mapping affected behavior';
288
+ if (stage === 'prefetch')
289
+ return 'Retrieving relevant failure patterns';
290
+ if (stage === 'stage1')
291
+ return 'Designing behavioral test cases';
292
+ if (stage.startsWith('build:'))
293
+ return 'Building an executable probe';
294
+ if (stage.startsWith('execute:'))
295
+ return 'Running base/base/head comparisons';
296
+ if (stage === 'verdict')
297
+ return 'Comparing observations';
298
+ return stage;
299
+ }
@@ -1,3 +1,5 @@
1
+ export declare function haystackApiBase(): string;
2
+ export declare function haystackApiUrl(path: string): string;
1
3
  export interface UserInstallation {
2
4
  id: number;
3
5
  account?: {
@@ -1,4 +1,11 @@
1
- const HAYSTACK_API_BASE = process.env.HAYSTACK_API_BASE ?? 'https://haystackeditor.com';
1
+ export function haystackApiBase() {
2
+ return process.env.HAYSTACK_API_BASE ?? 'https://haystackeditor.com';
3
+ }
4
+ export function haystackApiUrl(path) {
5
+ if (!path.startsWith('/'))
6
+ throw new Error('Haystack API path must be absolute');
7
+ return `${haystackApiBase()}${path}`;
8
+ }
2
9
  /**
3
10
  * Typed error for non-OK Haystack API responses. Callers classify by
4
11
  * `status` (never by message substring) and the message always carries a
@@ -70,7 +77,7 @@ export async function classifyHttpError(response, context) {
70
77
  return new HaystackApiError(response.status, `${context}: unexpected response (HTTP ${response.status})${suffix}.`);
71
78
  }
72
79
  export async function haystackJson(path, token, init = {}, acceptedStatuses = []) {
73
- const response = await fetch(`${HAYSTACK_API_BASE}${path}`, {
80
+ const response = await fetch(haystackApiUrl(path), {
74
81
  ...init,
75
82
  headers: headers(token, init.headers),
76
83
  });
@@ -91,7 +98,7 @@ export async function fetchGitHubJson(path, token, init = {}) {
91
98
  }
92
99
  export async function fetchAnalysisArtifact(owner, repo, prNumber, file, token) {
93
100
  const ref = encodeURIComponent(`${owner}/${repo}#${prNumber}`);
94
- const response = await fetch(`${HAYSTACK_API_BASE}/api/analysis/${ref}/${file}`, {
101
+ const response = await fetch(haystackApiUrl(`/api/analysis/${ref}/${file}`), {
95
102
  headers: headers(token, { 'Cache-Control': 'no-cache' }),
96
103
  });
97
104
  if (response.status === 404)
@@ -169,10 +169,11 @@ export async function copyHookFiles(hooksDir) {
169
169
  await copyDirRecursive(path.join(assetsDir, 'truncation-checker'), path.join(hooksDir, 'truncation-checker'));
170
170
  // Copy hooks package.json
171
171
  await fs.copyFile(path.join(assetsDir, 'package.json'), path.join(hooksDir, 'package.json'));
172
+ await fs.copyFile(path.join(assetsDir, 'package-lock.json'), path.join(hooksDir, 'package-lock.json'));
172
173
  }
173
174
  /** Install npm dependencies in the hooks directory */
174
175
  export async function installHookDeps(hooksDir) {
175
- execSync('npm install --no-fund --no-audit', {
176
+ execSync('npm ci --ignore-scripts --no-fund --no-audit', {
176
177
  cwd: hooksDir,
177
178
  stdio: 'pipe',
178
179
  timeout: 60_000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.15.26",
3
+ "version": "0.15.27",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,10 +10,13 @@
10
10
  "build": "rm -rf dist && tsc && cp -r src/assets dist/assets",
11
11
  "dev": "tsc --watch",
12
12
  "start": "node dist/index.js",
13
+ "benchmark:telemetry": "pnpm build && node scripts/benchmark-telemetry-overhead.mjs",
14
+ "benchmark:telemetry:corpus": "pnpm build && node scripts/benchmark-open-source-telemetry.mjs",
13
15
  "check:schemas": "node scripts/check-schemas.mjs",
14
16
  "release": "node scripts/release.mjs",
15
17
  "prepublishOnly": "node scripts/no-direct-publish.mjs",
16
- "test": "pnpm build && vitest run && node --test tests/*.test.mjs"
18
+ "test": "pnpm build && vitest run && node --test tests/*.test.mjs",
19
+ "test:package": "node scripts/package-smoke.mjs"
17
20
  },
18
21
  "keywords": [
19
22
  "haystack",
@@ -35,6 +38,7 @@
35
38
  "url": "https://github.com/haystackeditor/haystack-review/issues"
36
39
  },
37
40
  "dependencies": {
41
+ "@babel/core": "7.29.0",
38
42
  "@modelcontextprotocol/sdk": "1.26.0",
39
43
  "chalk": "5.6.2",
40
44
  "commander": "12.1.0",
@@ -56,6 +60,7 @@
56
60
  "dist",
57
61
  "schemas"
58
62
  ],
63
+ "bundledDependencies": [],
59
64
  "engines": {
60
65
  "node": ">=18"
61
66
  }