@haystackeditor/cli 0.15.26 → 0.15.28

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 (87) hide show
  1. package/README.md +52 -23
  2. package/dist/assets/hooks/agent-context/detect.ts +7 -7
  3. package/dist/assets/hooks/agent-context/parsers/claude.ts +1 -1
  4. package/dist/assets/hooks/package-lock.json +598 -0
  5. package/dist/assets/hooks/scripts/commit-msg.sh +1 -3
  6. package/dist/assets/hooks/scripts/post-commit.sh +1 -3
  7. package/dist/assets/hooks/scripts/pre-push.sh +1 -4
  8. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +1 -2
  9. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +2051 -0
  10. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +4 -0
  11. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +3411 -0
  12. package/dist/assets/telemetry/browser-runtime.js +1469 -0
  13. package/dist/assets/telemetry/runtime.cjs +1503 -0
  14. package/dist/commands/cloud-verifier-behaviors.d.ts +27 -0
  15. package/dist/commands/cloud-verifier-behaviors.js +218 -0
  16. package/dist/commands/cloud-verifier-data-store-census.d.ts +47 -0
  17. package/dist/commands/cloud-verifier-data-store-census.js +539 -0
  18. package/dist/commands/cloud-verifier-data-store-drift.d.ts +42 -0
  19. package/dist/commands/cloud-verifier-data-store-drift.js +158 -0
  20. package/dist/commands/cloud-verifier-identity-census.d.ts +88 -0
  21. package/dist/commands/cloud-verifier-identity-census.js +4057 -0
  22. package/dist/commands/cloud-verifier-materialization.d.ts +16 -0
  23. package/dist/commands/cloud-verifier-materialization.js +704 -0
  24. package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +29 -0
  25. package/dist/commands/cloud-verifier-pascal-selector-census.js +1382 -0
  26. package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +27 -0
  27. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +2015 -0
  28. package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +51 -0
  29. package/dist/commands/cloud-verifier-specialized-operational-census.js +11432 -0
  30. package/dist/commands/cloud-verifier-universe.d.ts +31 -0
  31. package/dist/commands/cloud-verifier-universe.js +10178 -0
  32. package/dist/commands/design-verify.d.ts +31 -0
  33. package/dist/commands/design-verify.js +285 -0
  34. package/dist/commands/hooks.d.ts +1 -5
  35. package/dist/commands/hooks.js +12 -114
  36. package/dist/commands/install-session-hooks.d.ts +2 -2
  37. package/dist/commands/install-session-hooks.js +71 -21
  38. package/dist/commands/mcp.js +1 -34
  39. package/dist/commands/pr.js +2 -7
  40. package/dist/commands/prepare-universe-review.d.ts +115 -0
  41. package/dist/commands/prepare-universe-review.js +1092 -0
  42. package/dist/commands/production-source-deny-policy.d.ts +15 -0
  43. package/dist/commands/production-source-deny-policy.js +100 -0
  44. package/dist/commands/scaffold-provisional-universe.d.ts +468 -0
  45. package/dist/commands/scaffold-provisional-universe.js +808 -0
  46. package/dist/commands/schema-cmd.js +32 -0
  47. package/dist/commands/setup.d.ts +1 -2
  48. package/dist/commands/setup.js +18 -272
  49. package/dist/commands/skills.d.ts +2 -2
  50. package/dist/commands/skills.js +415 -47
  51. package/dist/commands/submit.js +1 -2
  52. package/dist/commands/telemetry.d.ts +53 -0
  53. package/dist/commands/telemetry.js +832 -0
  54. package/dist/commands/verify-hosted.d.ts +13 -0
  55. package/dist/commands/verify-hosted.js +49 -1
  56. package/dist/commands/verify-precompute.d.ts +19 -0
  57. package/dist/commands/verify-precompute.js +328 -0
  58. package/dist/index.js +168 -47
  59. package/dist/schema.d.ts +1 -2
  60. package/dist/schema.js +1 -2
  61. package/dist/triage/prompts.d.ts +0 -7
  62. package/dist/triage/prompts.js +0 -145
  63. package/dist/triage/runner.js +10 -28
  64. package/dist/triage/types.d.ts +1 -8
  65. package/dist/types.d.ts +8 -8
  66. package/dist/types.js +1 -1
  67. package/dist/utils/design-verifier-api.d.ts +200 -0
  68. package/dist/utils/design-verifier-api.js +271 -0
  69. package/dist/utils/design-verifier-result.d.ts +57 -0
  70. package/dist/utils/design-verifier-result.js +345 -0
  71. package/dist/utils/git.d.ts +5 -1
  72. package/dist/utils/git.js +19 -9
  73. package/dist/utils/github-api.js +11 -14
  74. package/dist/utils/haystack-api.d.ts +2 -0
  75. package/dist/utils/haystack-api.js +10 -3
  76. package/dist/utils/hooks.d.ts +1 -15
  77. package/dist/utils/hooks.js +3 -128
  78. package/dist/utils/telemetry.d.ts +2 -0
  79. package/dist/utils/telemetry.js +81 -8
  80. package/package.json +8 -2
  81. package/schemas/cloud-verifier.v1.json +69 -3
  82. package/schemas/{pr.v2.json → pr.v3.json} +3 -12
  83. package/dist/commands/traces.d.ts +0 -48
  84. package/dist/commands/traces.js +0 -92
  85. package/dist/triage/traces.d.ts +0 -20
  86. package/dist/triage/traces.js +0 -311
  87. package/schemas/traces.v1.json +0 -53
@@ -16,6 +16,18 @@ export interface HostedStatusOptions {
16
16
  timeout?: string;
17
17
  json?: boolean;
18
18
  }
19
+ export interface HostedRunnerResult {
20
+ verdict: string;
21
+ summary: string;
22
+ casesTotal: number;
23
+ casesFailed: number;
24
+ regressions: Array<{
25
+ caseId: string;
26
+ title: string;
27
+ }>;
28
+ outputLocation: string;
29
+ wallSeconds: number;
30
+ }
19
31
  export interface HostedRunState {
20
32
  runId: string;
21
33
  status: string;
@@ -25,6 +37,7 @@ export interface HostedRunState {
25
37
  createdAt?: string;
26
38
  completedAt?: string;
27
39
  output?: Record<string, unknown>;
40
+ result?: HostedRunnerResult;
28
41
  error?: string;
29
42
  }
30
43
  export interface HostedPollOptions {
@@ -73,10 +73,22 @@ export function parseHostedRunState(value, expectedRunId) {
73
73
  const output = outputValue === undefined
74
74
  ? undefined
75
75
  : requireRecord(outputValue, 'Hosted verifier output');
76
+ const resultValue = response.result;
77
+ const result = resultValue === undefined
78
+ ? undefined
79
+ : parseHostedRunnerResult(resultValue);
76
80
  const createdAt = optionalTimestamp(response, 'createdAt');
77
81
  const completedAt = optionalTimestamp(response, 'completedAt');
78
82
  if (status === 'complete') {
79
- if (!repository
83
+ // Two terminal shapes exist: the Workflow receipt envelope (`output`)
84
+ // and the runner-queue result envelope (`result`). Exactly one must
85
+ // validate.
86
+ if (result) {
87
+ if (!repository || !baseCommit || !headCommit || !createdAt) {
88
+ throw new Error('Hosted verifier returned a complete run without a matching terminal receipt.');
89
+ }
90
+ }
91
+ else if (!repository
80
92
  || !baseCommit
81
93
  || !headCommit
82
94
  || !createdAt
@@ -100,9 +112,35 @@ export function parseHostedRunState(value, expectedRunId) {
100
112
  createdAt,
101
113
  completedAt,
102
114
  output,
115
+ result,
103
116
  error: optionalString(response, 'error'),
104
117
  };
105
118
  }
119
+ function parseHostedRunnerResult(value) {
120
+ const record = requireRecord(value, 'Hosted verifier runner result');
121
+ const regressionsValue = record.regressions;
122
+ if (typeof record.verdict !== 'string'
123
+ || typeof record.summary !== 'string'
124
+ || !Number.isInteger(record.casesTotal)
125
+ || !Number.isInteger(record.casesFailed)
126
+ || typeof record.outputLocation !== 'string'
127
+ || typeof record.wallSeconds !== 'number'
128
+ || !Array.isArray(regressionsValue)
129
+ || !regressionsValue.every(item => !!item && typeof item === 'object' && !Array.isArray(item)
130
+ && typeof item.caseId === 'string'
131
+ && typeof item.title === 'string')) {
132
+ throw new Error('Hosted verifier runner result is malformed.');
133
+ }
134
+ return {
135
+ verdict: record.verdict,
136
+ summary: record.summary,
137
+ casesTotal: record.casesTotal,
138
+ casesFailed: record.casesFailed,
139
+ regressions: regressionsValue,
140
+ outputLocation: record.outputLocation,
141
+ wallSeconds: record.wallSeconds,
142
+ };
143
+ }
106
144
  export function parseHostedRepository(value) {
107
145
  const parts = value.split('/');
108
146
  if (parts.length !== 2
@@ -242,6 +280,7 @@ export function buildHostedJson(operation, state, extras = {}) {
242
280
  ...(extras.nextCommand ? { next_command: extras.nextCommand } : {}),
243
281
  ...(extras.timedOut !== undefined ? { timed_out: extras.timedOut } : {}),
244
282
  ...(state.output ? { output: state.output } : {}),
283
+ ...(state.result ? { result: state.result } : {}),
245
284
  ...(state.error ? { error: state.error } : {}),
246
285
  });
247
286
  }
@@ -273,6 +312,15 @@ function printHostedState(operation, state, options) {
273
312
  if (state.baseCommit && state.headCommit) {
274
313
  console.log(` ${chalk.dim('Commits:')} ${state.baseCommit.slice(0, 12)} → ${state.headCommit.slice(0, 12)}`);
275
314
  }
315
+ if (state.result) {
316
+ const passed = state.result.casesTotal - state.result.casesFailed;
317
+ console.log(` ${chalk.dim('Verdict:')} ${state.result.verdict} `
318
+ + `(${passed}/${state.result.casesTotal} cases clean, ${Math.round(state.result.wallSeconds)}s)`);
319
+ for (const regression of state.result.regressions) {
320
+ console.log(` ${chalk.red('✗')} ${regression.caseId}: ${regression.title}`);
321
+ }
322
+ console.log(` ${chalk.dim('Artifacts:')} ${state.result.outputLocation}`);
323
+ }
276
324
  if (state.error)
277
325
  console.log(` ${chalk.dim('Error:')} ${state.error}`);
278
326
  if (options.timedOut) {
@@ -0,0 +1,19 @@
1
+ export type PrecomputeOutcome = 'enqueued' | 'cached' | 'existing' | 'skipped_no_auth' | 'error';
2
+ export interface VerifyPrecomputeOptions {
3
+ hook?: boolean;
4
+ }
5
+ export interface FrontHalfEnqueueRequest {
6
+ owner: string;
7
+ repository: string;
8
+ baseCommit: string;
9
+ workCommit: string;
10
+ cacheKey: string;
11
+ patchGzBase64: string;
12
+ patchSha256: string;
13
+ }
14
+ export interface PrecomputeDerivation {
15
+ treeSha: string;
16
+ request: FrontHalfEnqueueRequest;
17
+ }
18
+ export declare function derivePrecomputeRequest(gitRoot: string, owner: string, repository: string, deadline?: number): Promise<PrecomputeDerivation>;
19
+ export declare function verifyPrecomputeCommand(options: VerifyPrecomputeOptions): Promise<void>;
@@ -0,0 +1,328 @@
1
+ import { spawn, execFileSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { cpSync, existsSync, mkdtempSync, rmSync } from 'node:fs';
4
+ import { createGzip } from 'node:zlib';
5
+ import { isAbsolute, join, resolve } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ import chalk from 'chalk';
8
+ import { NotLoggedInError, resolveAuthContext } from '../utils/auth.js';
9
+ import { findGitRoot, parseRemoteUrl, resolveDiffBaseRef, } from '../utils/git.js';
10
+ import { HaystackApiError, haystackJson } from '../utils/haystack-api.js';
11
+ import { trackPrecomputeEvent } from '../utils/telemetry.js';
12
+ const TOTAL_WALL_MS = 5_000;
13
+ const REMOTE_COMMAND_MS = 750;
14
+ const COMMIT_SHA = /^[0-9a-f]{40}$/;
15
+ const REPOSITORY_COMPONENT = /^[A-Za-z0-9_.-]{1,100}$/;
16
+ const RUN_ID = /^cv_[0-9a-f]{48}$/;
17
+ const FIXED_COMMIT_ENV = {
18
+ GIT_AUTHOR_NAME: 'haystack-stop-hook',
19
+ GIT_AUTHOR_EMAIL: 'stop-hook@haystack.invalid',
20
+ GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
21
+ GIT_COMMITTER_NAME: 'haystack-stop-hook',
22
+ GIT_COMMITTER_EMAIL: 'stop-hook@haystack.invalid',
23
+ GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z',
24
+ };
25
+ class PrecomputeFailure extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = 'PrecomputeFailure';
29
+ }
30
+ }
31
+ function remainingMs(deadline) {
32
+ const remaining = deadline - Date.now();
33
+ if (remaining <= 0) {
34
+ throw new PrecomputeFailure('Precompute timed out.');
35
+ }
36
+ return remaining;
37
+ }
38
+ function gitOutput(args, gitRoot, deadline, options = {}) {
39
+ const timeout = Math.max(1, Math.min(remainingMs(deadline), options.timeoutCapMs ?? TOTAL_WALL_MS));
40
+ try {
41
+ return execFileSync('git', args, {
42
+ cwd: gitRoot,
43
+ encoding: 'utf8',
44
+ env: options.env ?? process.env,
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ timeout,
47
+ }).trim();
48
+ }
49
+ catch {
50
+ throw new PrecomputeFailure('Could not derive the repository snapshot.');
51
+ }
52
+ }
53
+ function resolveOriginRepository(gitRoot) {
54
+ try {
55
+ const remote = parseRemoteUrl('origin');
56
+ if (!REPOSITORY_COMPONENT.test(remote.owner)
57
+ || !REPOSITORY_COMPONENT.test(remote.repo)) {
58
+ throw new Error('invalid repository identity');
59
+ }
60
+ return { owner: remote.owner, repository: remote.repo };
61
+ }
62
+ catch {
63
+ // Keep remote URLs and local paths out of errors.
64
+ throw new PrecomputeFailure('No supported origin repository was found.');
65
+ }
66
+ }
67
+ function resolveOriginDefaultBranch(gitRoot, deadline) {
68
+ try {
69
+ const remoteHead = gitOutput(['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], gitRoot, deadline);
70
+ if (remoteHead.startsWith('origin/') && remoteHead.length > 'origin/'.length) {
71
+ return remoteHead.slice('origin/'.length);
72
+ }
73
+ }
74
+ catch {
75
+ // A clone does not have to carry origin/HEAD; ask the remote below.
76
+ }
77
+ const remoteSummary = gitOutput(['remote', 'show', 'origin'], gitRoot, deadline, {
78
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
79
+ timeoutCapMs: REMOTE_COMMAND_MS,
80
+ });
81
+ const match = remoteSummary.match(/^\s*HEAD branch:\s*(\S+)\s*$/m);
82
+ if (!match) {
83
+ throw new PrecomputeFailure('Could not resolve the origin default branch.');
84
+ }
85
+ return match[1];
86
+ }
87
+ function deriveBaseSha(gitRoot, deadline) {
88
+ const defaultBranch = resolveOriginDefaultBranch(gitRoot, deadline);
89
+ const baseRef = resolveDiffBaseRef(defaultBranch, {
90
+ cwd: gitRoot,
91
+ timeoutMs: Math.max(1, Math.min(remainingMs(deadline), REMOTE_COMMAND_MS)),
92
+ });
93
+ const baseSha = gitOutput(['merge-base', 'HEAD', baseRef], gitRoot, deadline);
94
+ if (!COMMIT_SHA.test(baseSha)) {
95
+ throw new PrecomputeFailure('Could not derive the base commit.');
96
+ }
97
+ return baseSha;
98
+ }
99
+ function deriveWorktreeTreeSha(gitRoot, deadline) {
100
+ const scratchDirectory = mkdtempSync(join(tmpdir(), 'haystack-precompute-index-'));
101
+ const temporaryIndex = join(scratchDirectory, 'index');
102
+ try {
103
+ const gitIndexPath = gitOutput(['rev-parse', '--git-path', 'index'], gitRoot, deadline);
104
+ const sourceIndex = isAbsolute(gitIndexPath)
105
+ ? gitIndexPath
106
+ : resolve(gitRoot, gitIndexPath);
107
+ const indexEnv = { ...process.env, GIT_INDEX_FILE: temporaryIndex };
108
+ if (existsSync(sourceIndex)) {
109
+ // blast_cache_worktree_tree_sha uses `cp -p`: preserving timestamps keeps
110
+ // git's racy-clean handling byte-identical to the bash implementation.
111
+ cpSync(sourceIndex, temporaryIndex, { preserveTimestamps: true });
112
+ }
113
+ else {
114
+ gitOutput(['read-tree', 'HEAD'], gitRoot, deadline, { env: indexEnv });
115
+ }
116
+ gitOutput(['add', '-A'], gitRoot, deadline, { env: indexEnv });
117
+ const treeSha = gitOutput(['write-tree'], gitRoot, deadline, { env: indexEnv });
118
+ if (!COMMIT_SHA.test(treeSha)) {
119
+ throw new PrecomputeFailure('Could not derive the working-tree snapshot.');
120
+ }
121
+ return treeSha;
122
+ }
123
+ finally {
124
+ rmSync(scratchDirectory, { recursive: true, force: true });
125
+ }
126
+ }
127
+ function deriveWorkCommit(gitRoot, baseSha, treeSha, deadline) {
128
+ const headSha = gitOutput(['rev-parse', 'HEAD'], gitRoot, deadline);
129
+ const headTreeSha = gitOutput(['rev-parse', 'HEAD^{tree}'], gitRoot, deadline);
130
+ if (!COMMIT_SHA.test(headSha) || !COMMIT_SHA.test(headTreeSha)) {
131
+ throw new PrecomputeFailure('Could not resolve the current commit.');
132
+ }
133
+ // A clean checkout of the base already has the exact required identity.
134
+ if (baseSha === headSha && treeSha === headTreeSha)
135
+ return headSha;
136
+ const workCommit = gitOutput(['commit-tree', treeSha, '-p', baseSha, '-m', 'front-half-snapshot'], gitRoot, deadline, { env: { ...process.env, ...FIXED_COMMIT_ENV } });
137
+ if (!COMMIT_SHA.test(workCommit)) {
138
+ throw new PrecomputeFailure('Could not derive the working commit.');
139
+ }
140
+ return workCommit;
141
+ }
142
+ function slimInputHash() {
143
+ // Mirrors printf '%s\0%s\0%s\0%s\0' slim codex-harness none none.
144
+ return createHash('sha256')
145
+ .update(Buffer.from('slim\0codex-harness\0none\0none\0'))
146
+ .digest('hex');
147
+ }
148
+ async function buildCompressedPatch(gitRoot, baseSha, workCommit, deadline) {
149
+ return new Promise((resolvePatch, rejectPatch) => {
150
+ const child = spawn('git', ['diff', '--binary', baseSha, workCommit], {
151
+ cwd: gitRoot,
152
+ stdio: ['ignore', 'pipe', 'pipe'],
153
+ });
154
+ const gzip = createGzip();
155
+ const chunks = [];
156
+ let compressedBytes = 0;
157
+ let childClosed = false;
158
+ let gzipEnded = false;
159
+ let childSucceeded = false;
160
+ let settled = false;
161
+ const timer = setTimeout(() => {
162
+ child.kill('SIGKILL');
163
+ fail(new PrecomputeFailure('Precompute timed out.'));
164
+ }, remainingMs(deadline));
165
+ const clear = () => clearTimeout(timer);
166
+ const fail = (error) => {
167
+ if (settled)
168
+ return;
169
+ settled = true;
170
+ clear();
171
+ child.kill('SIGKILL');
172
+ gzip.destroy();
173
+ rejectPatch(error);
174
+ };
175
+ const finish = () => {
176
+ if (settled || !childClosed || !gzipEnded)
177
+ return;
178
+ if (!childSucceeded) {
179
+ fail(new PrecomputeFailure('Could not build the working-tree patch.'));
180
+ return;
181
+ }
182
+ settled = true;
183
+ clear();
184
+ resolvePatch(Buffer.concat(chunks, compressedBytes));
185
+ };
186
+ child.stderr.resume();
187
+ child.once('error', () => fail(new PrecomputeFailure('Could not build the working-tree patch.')));
188
+ child.once('close', code => {
189
+ childClosed = true;
190
+ childSucceeded = code === 0;
191
+ finish();
192
+ });
193
+ gzip.on('data', (chunk) => {
194
+ compressedBytes += chunk.length;
195
+ chunks.push(chunk);
196
+ });
197
+ gzip.once('error', () => fail(new PrecomputeFailure('Could not compress the working-tree patch.')));
198
+ gzip.once('end', () => {
199
+ gzipEnded = true;
200
+ finish();
201
+ });
202
+ child.stdout.pipe(gzip);
203
+ });
204
+ }
205
+ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadline = Date.now() + TOTAL_WALL_MS) {
206
+ const baseSha = deriveBaseSha(gitRoot, deadline);
207
+ const treeSha = deriveWorktreeTreeSha(gitRoot, deadline);
208
+ const workCommit = deriveWorkCommit(gitRoot, baseSha, treeSha, deadline);
209
+ const patchGzip = await buildCompressedPatch(gitRoot, baseSha, workCommit, deadline);
210
+ const cacheKey = [
211
+ baseSha.slice(0, 12),
212
+ workCommit.slice(0, 12),
213
+ treeSha.slice(0, 16),
214
+ 'slim',
215
+ slimInputHash(),
216
+ ].join('-');
217
+ return {
218
+ treeSha,
219
+ request: {
220
+ owner,
221
+ repository,
222
+ baseCommit: baseSha,
223
+ workCommit,
224
+ cacheKey,
225
+ patchGzBase64: patchGzip.toString('base64'),
226
+ patchSha256: createHash('sha256').update(patchGzip).digest('hex'),
227
+ },
228
+ };
229
+ }
230
+ function precomputeResult(response) {
231
+ if (!response || typeof response !== 'object' || Array.isArray(response)) {
232
+ throw new PrecomputeFailure('The precompute service returned an invalid response.');
233
+ }
234
+ const { status, runId } = response;
235
+ if (status === 'queued' && typeof runId === 'string' && RUN_ID.test(runId)) {
236
+ return { outcome: 'enqueued', runId };
237
+ }
238
+ if (status === 'cached')
239
+ return { outcome: 'cached' };
240
+ if (status === 'existing')
241
+ return { outcome: 'existing' };
242
+ throw new PrecomputeFailure('The precompute service returned an invalid response.');
243
+ }
244
+ function normalModeFailure(error) {
245
+ if (error instanceof PrecomputeFailure)
246
+ return error;
247
+ if (error instanceof NotLoggedInError) {
248
+ return new PrecomputeFailure('Authentication is required. Run `haystack login` first.');
249
+ }
250
+ if (error instanceof HaystackApiError) {
251
+ if (error.status === 401 || error.status === 403) {
252
+ return new PrecomputeFailure('Authentication was rejected. Run `haystack login` and try again.');
253
+ }
254
+ if (error.status === 429) {
255
+ return new PrecomputeFailure('Precompute requests are temporarily rate limited. Try again later.');
256
+ }
257
+ return new PrecomputeFailure(`The precompute request failed (HTTP ${error.status}).`);
258
+ }
259
+ return new PrecomputeFailure('The precompute request failed.');
260
+ }
261
+ function printResult(outcome, runId) {
262
+ if (outcome === 'enqueued') {
263
+ console.log(chalk.green(`Precompute request queued${runId ? ` (${runId})` : ''}.`));
264
+ }
265
+ else if (outcome === 'cached') {
266
+ console.log(chalk.green('Precompute result is already cached.'));
267
+ }
268
+ else if (outcome === 'existing') {
269
+ console.log(chalk.green('A matching precompute request already exists.'));
270
+ }
271
+ }
272
+ export async function verifyPrecomputeCommand(options) {
273
+ const startedAt = Date.now();
274
+ const deadline = startedAt + TOTAL_WALL_MS;
275
+ let outcome = 'error';
276
+ try {
277
+ const gitRoot = findGitRoot();
278
+ if (!gitRoot) {
279
+ throw new PrecomputeFailure('Run this command from a git repository.');
280
+ }
281
+ const repository = resolveOriginRepository(gitRoot);
282
+ const derivation = await derivePrecomputeRequest(gitRoot, repository.owner, repository.repository, deadline);
283
+ let token;
284
+ try {
285
+ const auth = await resolveAuthContext({
286
+ owner: repository.owner,
287
+ repo: repository.repository,
288
+ });
289
+ token = auth.token;
290
+ }
291
+ catch (error) {
292
+ if (error instanceof NotLoggedInError) {
293
+ outcome = 'skipped_no_auth';
294
+ if (options.hook)
295
+ return;
296
+ }
297
+ throw error;
298
+ }
299
+ const controller = new AbortController();
300
+ const fetchTimer = setTimeout(() => controller.abort(), remainingMs(deadline));
301
+ let response;
302
+ try {
303
+ response = await haystackJson('/api/agent/cloud-verifier/front-half', token, {
304
+ method: 'POST',
305
+ headers: { 'Content-Type': 'application/json' },
306
+ body: JSON.stringify(derivation.request),
307
+ signal: controller.signal,
308
+ });
309
+ }
310
+ finally {
311
+ clearTimeout(fetchTimer);
312
+ }
313
+ const result = precomputeResult(response);
314
+ outcome = result.outcome;
315
+ if (!options.hook)
316
+ printResult(outcome, result.runId);
317
+ }
318
+ catch (error) {
319
+ if (!options.hook)
320
+ throw normalModeFailure(error);
321
+ }
322
+ finally {
323
+ const wallMs = Date.now() - startedAt;
324
+ trackPrecomputeEvent(outcome, wallMs, Math.max(1, TOTAL_WALL_MS - wallMs));
325
+ if (options.hook)
326
+ process.exitCode = 0;
327
+ }
328
+ }