@haystackeditor/cli 0.15.28 → 0.15.29

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.
@@ -868,7 +868,7 @@ var __haystackWebAtlasModuleApi = (function installHaystackWebAtlas(host) {
868
868
  return result;
869
869
  }
870
870
 
871
- function axiosAt(token, owner, callable, receiver, configIndex, safeConfig, applicationArguments) {
871
+ function axiosAt(token, owner, callable, receiver, ownerProperty, configIndex, safeConfig, applicationArguments) {
872
872
  if (!nativeArrayIsArray(applicationArguments) || typeof callable !== 'function') {
873
873
  return nativeReflectApply(callable, receiver, applicationArguments);
874
874
  }
@@ -877,7 +877,10 @@ var __haystackWebAtlasModuleApi = (function installHaystackWebAtlas(host) {
877
877
  if (context && !admitProbe(context)) return nativeReflectApply(callable, receiver, applicationArguments);
878
878
  var sites = context && context.runtimeConfig && context.runtimeConfig.fetchSites[token];
879
879
  if (!context || !sites) return nativeReflectApply(callable, receiver, applicationArguments);
880
- if (safeConfig !== true || !Number.isInteger(configIndex) || configIndex < 0 || configIndex > 8 ||
880
+ if ((ownerProperty !== null && (typeof ownerProperty !== 'string' || ownerProperty.length === 0 ||
881
+ ownerProperty.length > 256 || ownerProperty === '__proto__' || ownerProperty === 'constructor' ||
882
+ ownerProperty === 'prototype')) || safeConfig !== true || !Number.isInteger(configIndex) ||
883
+ configIndex < 0 || configIndex > 8 ||
881
884
  !platformFetchCorrelationAllowed()) {
882
885
  omit(context);
883
886
  return nativeReflectApply(callable, receiver, applicationArguments);
@@ -886,6 +889,20 @@ var __haystackWebAtlasModuleApi = (function installHaystackWebAtlas(host) {
886
889
  var args = [];
887
890
  var config;
888
891
  try {
892
+ if (ownerProperty !== null) {
893
+ if ((typeof receiver !== 'object' || receiver === null) && typeof receiver !== 'function') {
894
+ axiosOmission(state);
895
+ return nativeReflectApply(callable, receiver, applicationArguments);
896
+ }
897
+ var ownerDescriptor = nativeGetOwnPropertyDescriptor(receiver, ownerProperty);
898
+ if (!ownerDescriptor || !nativeReflectApply(nativeHasOwnProperty, ownerDescriptor, ['value']) ||
899
+ ((typeof ownerDescriptor.value !== 'object' || ownerDescriptor.value === null) &&
900
+ typeof ownerDescriptor.value !== 'function')) {
901
+ axiosOmission(state);
902
+ return nativeReflectApply(callable, receiver, applicationArguments);
903
+ }
904
+ owner = ownerDescriptor.value;
905
+ }
889
906
  for (var argumentIndex = 0; argumentIndex < applicationArguments.length; argumentIndex++) {
890
907
  args[argumentIndex] = applicationArguments[argumentIndex];
891
908
  }
@@ -7,7 +7,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7
7
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
8
8
  import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
9
9
  import { resolveAuthContext } from '../utils/auth.js';
10
- import { buildHostedJson, getHostedVerification, hostedResumeCommand, parseHostedRepository, startHostedVerification, waitForHostedVerification, } from './verify-hosted.js';
10
+ import { buildHostedJson, getHostedVerification, hostedResumeCommand, parseHostedRepository, readHostedIntentFile, startHostedVerification, waitForHostedVerification, } from './verify-hosted.js';
11
11
  const TERMINAL_STATUSES = new Set([
12
12
  'complete',
13
13
  'errored',
@@ -48,6 +48,11 @@ export const HOSTED_VERIFY_TOOLS = [
48
48
  pattern: '^[0-9a-f]{40}$',
49
49
  description: 'Exact 40-character lowercase head commit SHA.',
50
50
  },
51
+ intent_file: {
52
+ type: 'string',
53
+ minLength: 1,
54
+ description: 'Path to agent-supplied JSON containing exactly problem, goal, and intended_outcomes (a non-empty array of non-empty strings).',
55
+ },
51
56
  idempotency_key: {
52
57
  type: 'string',
53
58
  pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$',
@@ -59,7 +64,7 @@ export const HOSTED_VERIFY_TOOLS = [
59
64
  },
60
65
  account: accountInput,
61
66
  },
62
- required: ['repository', 'base_commit', 'head_commit'],
67
+ required: ['repository', 'base_commit', 'head_commit', 'intent_file'],
63
68
  },
64
69
  },
65
70
  {
@@ -124,6 +129,7 @@ function assertToolInputFields(name, input) {
124
129
  'base_commit',
125
130
  'head_commit',
126
131
  'idempotency_key',
132
+ 'intent_file',
127
133
  'replay_plan_from_run_id',
128
134
  'repository',
129
135
  ]);
@@ -151,6 +157,7 @@ export async function executeHostedVerifyTool(name, input, context) {
151
157
  const started = await startHostedVerification(repository, {
152
158
  base: requireInputString(input, 'base_commit'),
153
159
  head: requireInputString(input, 'head_commit'),
160
+ intentFile: requireInputString(input, 'intent_file'),
154
161
  idempotencyKey: optionalInputString(input, 'idempotency_key'),
155
162
  replayPlanFrom: optionalInputString(input, 'replay_plan_from_run_id'),
156
163
  wait: false,
@@ -217,6 +224,9 @@ export async function runHostedVerifyMcpServer(options) {
217
224
  // Reject undeclared fields before account resolution so callers cannot
218
225
  // use an invalid request to trigger credential or repository lookup.
219
226
  assertToolInputFields(name, input);
227
+ if (name === 'verify_start') {
228
+ readHostedIntentFile(requireInputString(input, 'intent_file'));
229
+ }
220
230
  const context = await resolveToolContext(name, input, options);
221
231
  const result = await executeHostedVerifyTool(name, input, context);
222
232
  return {
@@ -2,6 +2,7 @@ import { type HostedRunState } from './verify-hosted.js';
2
2
  export interface HostedReproducibilityOptions {
3
3
  base: string;
4
4
  head: string;
5
+ intentFile: string;
5
6
  runs?: string;
6
7
  seriesKey?: string;
7
8
  freshPlans?: boolean;
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
2
2
  import chalk from 'chalk';
3
3
  import { withSchema } from '../schema.js';
4
4
  import { resolveAuthContext } from '../utils/auth.js';
5
- import { hostedPollingOptions, parseHostedRepository, startHostedVerification, waitForHostedVerification, } from './verify-hosted.js';
5
+ import { hostedPollingOptions, parseHostedRepository, readHostedIntentFile, startHostedVerification, waitForHostedVerification, } from './verify-hosted.js';
6
6
  const CELL_ID = /^cell_[0-9a-f]{32}$/;
7
7
  const CAPABILITY_ID = /^repository_driver_[0-9a-f]{64}$/;
8
8
  const COMPARISON_ID = /^driver_comparison_[0-9a-f]{64}$/;
@@ -502,6 +502,8 @@ function renderHostedReproducibility(report) {
502
502
  }
503
503
  export async function verifyHostedReproducibilityCommand(repositoryArg, options) {
504
504
  const repository = parseHostedRepository(repositoryArg);
505
+ // A reproducibility series reuses one explicit agent intent for every run.
506
+ readHostedIntentFile(options.intentFile);
505
507
  const requestedRuns = boundedInteger(options.runs, DEFAULT_RUNS, '--runs', 2, MAX_RUNS);
506
508
  const seriesKey = requireSeriesKey(options.seriesKey);
507
509
  const mode = options.freshPlans
@@ -521,6 +523,7 @@ export async function verifyHostedReproducibilityCommand(repositoryArg, options)
521
523
  const started = await startHostedVerification(repositoryArg, {
522
524
  base: options.base,
523
525
  head: options.head,
526
+ intentFile: options.intentFile,
524
527
  idempotencyKey: hostedReproducibilityRunKey(seriesKey, repository.fullName, options.base, options.head, index + 1, mode),
525
528
  replayPlanFrom: options.freshPlans || index === 0
526
529
  ? undefined
@@ -1,6 +1,12 @@
1
+ export interface HostedIntent {
2
+ problem: string;
3
+ goal: string;
4
+ intended_outcomes: string[];
5
+ }
1
6
  export interface HostedStartOptions {
2
7
  base: string;
3
8
  head: string;
9
+ intentFile: string;
4
10
  idempotencyKey?: string;
5
11
  replayPlanFrom?: string;
6
12
  account?: string;
@@ -55,6 +61,7 @@ export declare function parseHostedRepository(value: string): {
55
61
  repository: string;
56
62
  fullName: string;
57
63
  };
64
+ export declare function readHostedIntentFile(intentFile: string | undefined): HostedIntent;
58
65
  export declare function defaultHostedIdempotencyKey(repository: string, baseCommit: string, headCommit: string, replayPlanFrom?: string): string;
59
66
  export declare function hostedPollingOptions(interval: string | undefined, timeout: string | undefined, onStatus?: (state: HostedRunState) => void): HostedPollOptions;
60
67
  export declare function startHostedVerification(repositoryArg: string, options: HostedStartOptions, token: string): Promise<{
@@ -1,4 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
2
3
  import chalk from 'chalk';
3
4
  import { withSchema } from '../schema.js';
4
5
  import { resolveAuthContext } from '../utils/auth.js';
@@ -11,6 +12,11 @@ const STATUS = /^[a-z][a-z0-9_-]{1,31}$/;
11
12
  const TERMINAL_STATUSES = new Set(['complete', 'errored', 'expired', 'terminated']);
12
13
  const DEFAULT_INTERVAL_SECONDS = 5;
13
14
  const DEFAULT_TIMEOUT_MINUTES = 35;
15
+ const INTENT_FIELDS = [
16
+ 'problem',
17
+ 'goal',
18
+ 'intended_outcomes',
19
+ ];
14
20
  function requireRecord(value, context) {
15
21
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
16
22
  throw new Error(`${context} returned an invalid response object.`);
@@ -159,6 +165,54 @@ function requireCommit(value, flag) {
159
165
  }
160
166
  return value;
161
167
  }
168
+ export function readHostedIntentFile(intentFile) {
169
+ if (typeof intentFile !== 'string' || intentFile.length === 0) {
170
+ throw new Error('--intent-file is required and must name a JSON file containing exactly problem, goal, and intended_outcomes.');
171
+ }
172
+ let source;
173
+ try {
174
+ source = readFileSync(intentFile, 'utf8');
175
+ }
176
+ catch (error) {
177
+ const reason = error instanceof Error ? error.message : String(error);
178
+ throw new Error(`Could not read --intent-file "${intentFile}": ${reason}`);
179
+ }
180
+ let parsed;
181
+ try {
182
+ parsed = JSON.parse(source);
183
+ }
184
+ catch {
185
+ throw new Error(`--intent-file "${intentFile}" must contain valid JSON.`);
186
+ }
187
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
188
+ throw new Error(`--intent-file "${intentFile}" must contain exactly problem, goal, and intended_outcomes.`);
189
+ }
190
+ const record = parsed;
191
+ const unknownField = Object.keys(record)
192
+ .find(field => !INTENT_FIELDS.includes(field));
193
+ if (unknownField) {
194
+ throw new Error(`--intent-file "${intentFile}" contains unknown field "${unknownField}"; expected exactly problem, goal, and intended_outcomes.`);
195
+ }
196
+ for (const field of ['problem', 'goal']) {
197
+ if (typeof record[field] !== 'string' || record[field].trim().length === 0) {
198
+ throw new Error(`--intent-file "${intentFile}" field "${field}" must be a non-empty string.`);
199
+ }
200
+ }
201
+ const outcomes = record.intended_outcomes;
202
+ if (!Array.isArray(outcomes)
203
+ || outcomes.length === 0
204
+ || outcomes.some(item => typeof item !== 'string' || item.trim().length === 0)) {
205
+ throw new Error(`--intent-file "${intentFile}" field "intended_outcomes" must be a non-empty array of non-empty strings.`);
206
+ }
207
+ if (Object.keys(record).length !== INTENT_FIELDS.length) {
208
+ throw new Error(`--intent-file "${intentFile}" must contain exactly problem, goal, and intended_outcomes.`);
209
+ }
210
+ return {
211
+ problem: record.problem,
212
+ goal: record.goal,
213
+ intended_outcomes: record.intended_outcomes,
214
+ };
215
+ }
162
216
  export function defaultHostedIdempotencyKey(repository, baseCommit, headCommit, replayPlanFrom) {
163
217
  const digest = createHash('sha256')
164
218
  .update(JSON.stringify(replayPlanFrom
@@ -193,25 +247,28 @@ export async function startHostedVerification(repositoryArg, options, token) {
193
247
  const repository = parseHostedRepository(repositoryArg);
194
248
  const baseCommit = requireCommit(options.base, '--base');
195
249
  const headCommit = requireCommit(options.head, '--head');
250
+ const intent = readHostedIntentFile(options.intentFile);
196
251
  const replayPlanFrom = options.replayPlanFrom;
197
252
  if (replayPlanFrom !== undefined && !RUN_ID.test(replayPlanFrom)) {
198
253
  throw new Error('--replay-plan-from must match cv_ followed by 48 lowercase hex characters.');
199
254
  }
200
255
  const idempotencyKey = requireIdempotencyKey(options.idempotencyKey
201
256
  ?? defaultHostedIdempotencyKey(repository.fullName, baseCommit, headCommit, replayPlanFrom));
257
+ const launchBody = {
258
+ owner: repository.owner,
259
+ repository: repository.repository,
260
+ baseCommit,
261
+ headCommit,
262
+ idempotencyKey,
263
+ intent,
264
+ ...(replayPlanFrom
265
+ ? { replayPlanFromRunId: replayPlanFrom }
266
+ : {}),
267
+ };
202
268
  const response = await haystackJson('/api/agent/cloud-verifier/runs', token, {
203
269
  method: 'POST',
204
270
  headers: { 'Content-Type': 'application/json' },
205
- body: JSON.stringify({
206
- owner: repository.owner,
207
- repository: repository.repository,
208
- baseCommit,
209
- headCommit,
210
- idempotencyKey,
211
- ...(replayPlanFrom
212
- ? { replayPlanFromRunId: replayPlanFrom }
213
- : {}),
214
- }),
271
+ body: JSON.stringify(launchBody),
215
272
  });
216
273
  const state = parseHostedRunState(response);
217
274
  return {
@@ -337,6 +394,9 @@ function markNonSuccess(state, timedOut = false) {
337
394
  }
338
395
  export async function verifyHostedStartCommand(repositoryArg, options) {
339
396
  const repository = parseHostedRepository(repositoryArg);
397
+ // Fail before account resolution so an invalid local intent never advances
398
+ // toward a hosted launch.
399
+ readHostedIntentFile(options.intentFile);
340
400
  const auth = await resolveAuthContext({
341
401
  preferredLogin: options.account,
342
402
  owner: repository.owner,
@@ -140,9 +140,9 @@ function deriveWorkCommit(gitRoot, baseSha, treeSha, deadline) {
140
140
  return workCommit;
141
141
  }
142
142
  function slimInputHash() {
143
- // Mirrors printf '%s\0%s\0%s\0%s\0' slim codex-harness none none.
143
+ // Mirrors printf '%s\0%s\0%s\0%s\0' slim agent-deferred none none.
144
144
  return createHash('sha256')
145
- .update(Buffer.from('slim\0codex-harness\0none\0none\0'))
145
+ .update(Buffer.from('slim\0agent-deferred\0none\0none\0'))
146
146
  .digest('hex');
147
147
  }
148
148
  async function buildCompressedPatch(gitRoot, baseSha, workCommit, deadline) {
package/dist/index.js CHANGED
@@ -192,7 +192,7 @@ The local control plane must be running. From the Haystack repository:
192
192
  pnpm dev
193
193
 
194
194
  Examples:
195
- haystack verify hosted start owner/repo --base <sha> --head <sha>
195
+ haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path>
196
196
  haystack verify posthog-js-478 # start a run, then:
197
197
  haystack verify reproducibility posthog-js-478 --runs 3
198
198
  haystack verify watch # block until it finishes (exit 1 = tests failed)
@@ -235,6 +235,7 @@ hostedVerify
235
235
  // validation below accepts the inherited values and still fails closed.
236
236
  .option('--base <sha>', 'Required: exact 40-character lowercase base commit SHA')
237
237
  .option('--head <sha>', 'Required: exact 40-character lowercase head commit SHA')
238
+ .requiredOption('--intent-file <path>', 'Agent-supplied JSON with exactly problem, goal, and intended_outcomes')
238
239
  .option('--idempotency-key <key>', 'Stable retry key (default: deterministic from repository and commits)')
239
240
  .option('--replay-plan-from <run-id>', 'Reuse the exact validated risk plan from a completed owned run')
240
241
  .option('--account <login>', 'Use a specific saved Haystack account')
@@ -250,9 +251,9 @@ fresh production execution of the same commit pair.
250
251
  a schema-9 terminal receipt with replay provenance.
251
252
 
252
253
  Examples:
253
- haystack verify hosted start owner/repo --base <sha> --head <sha>
254
- haystack verify hosted start owner/repo --base <sha> --head <sha> --replay-plan-from cv_<48 lowercase hex characters>
255
- haystack verify hosted start owner/repo --base <sha> --head <sha> --no-wait --json
254
+ haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path>
255
+ haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path> --replay-plan-from cv_<48 lowercase hex characters>
256
+ haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path> --no-wait --json
256
257
  `)
257
258
  .action(async (repository, _options, cmd) => {
258
259
  const options = { ...cmd.optsWithGlobals(), ...cmd.opts() };
@@ -283,6 +284,7 @@ hostedVerify
283
284
  .argument('<repository>', 'Exact GitHub owner/repository name')
284
285
  .option('--base <sha>', 'Required: exact 40-character lowercase base commit SHA')
285
286
  .option('--head <sha>', 'Required: exact 40-character lowercase head commit SHA')
287
+ .requiredOption('--intent-file <path>', 'Agent-supplied JSON reused for every hosted run in the series')
286
288
  .option('--runs <n>', 'Number of sequential production runs (default 3; min 2, max 10)')
287
289
  .option('--series-key <key>', 'Stable series key for resumable idempotent retries')
288
290
  .option('--fresh-plans', 'Run the planner independently for every run instead of replaying the first plan')
@@ -302,8 +304,8 @@ provenance, and no inconclusive cells. Use this mode to detect planner drift;
302
304
  use the default mode to isolate executor or assessment drift.
303
305
 
304
306
  Example:
305
- haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --runs 3
306
- haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --runs 3 --fresh-plans
307
+ haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --intent-file <path> --runs 3
308
+ haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --intent-file <path> --runs 3 --fresh-plans
307
309
  `)
308
310
  .action(async (repository, _options, cmd) => {
309
311
  const options = { ...cmd.optsWithGlobals(), ...cmd.opts() };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.15.28",
3
+ "version": "0.15.29",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {