@haystackeditor/cli 0.15.27 → 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.
Files changed (55) hide show
  1. package/README.md +13 -20
  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/scripts/commit-msg.sh +1 -3
  5. package/dist/assets/hooks/scripts/post-commit.sh +1 -3
  6. package/dist/assets/hooks/scripts/pre-push.sh +1 -4
  7. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +1 -2
  8. package/dist/assets/telemetry/browser-runtime.js +668 -43
  9. package/dist/assets/telemetry/runtime.cjs +62 -68
  10. package/dist/commands/design-verify.d.ts +11 -3
  11. package/dist/commands/design-verify.js +147 -4
  12. package/dist/commands/hooks.d.ts +1 -5
  13. package/dist/commands/hooks.js +10 -112
  14. package/dist/commands/install-session-hooks.d.ts +2 -2
  15. package/dist/commands/install-session-hooks.js +71 -21
  16. package/dist/commands/mcp.js +1 -34
  17. package/dist/commands/pr.js +2 -7
  18. package/dist/commands/setup.d.ts +1 -2
  19. package/dist/commands/setup.js +18 -272
  20. package/dist/commands/submit.js +1 -2
  21. package/dist/commands/verify-hosted-mcp.js +12 -2
  22. package/dist/commands/verify-hosted-reproducibility.d.ts +1 -0
  23. package/dist/commands/verify-hosted-reproducibility.js +4 -1
  24. package/dist/commands/verify-hosted.d.ts +20 -0
  25. package/dist/commands/verify-hosted.js +119 -11
  26. package/dist/commands/verify-precompute.d.ts +19 -0
  27. package/dist/commands/verify-precompute.js +328 -0
  28. package/dist/index.js +45 -44
  29. package/dist/schema.d.ts +1 -2
  30. package/dist/schema.js +1 -2
  31. package/dist/triage/prompts.d.ts +0 -7
  32. package/dist/triage/prompts.js +0 -145
  33. package/dist/triage/runner.js +3 -22
  34. package/dist/triage/types.d.ts +1 -8
  35. package/dist/types.d.ts +8 -8
  36. package/dist/types.js +1 -1
  37. package/dist/utils/design-verifier-api.d.ts +71 -0
  38. package/dist/utils/design-verifier-api.js +96 -0
  39. package/dist/utils/design-verifier-result.d.ts +7 -2
  40. package/dist/utils/design-verifier-result.js +67 -21
  41. package/dist/utils/git.d.ts +5 -1
  42. package/dist/utils/git.js +19 -9
  43. package/dist/utils/github-api.js +11 -14
  44. package/dist/utils/hooks.d.ts +1 -15
  45. package/dist/utils/hooks.js +1 -127
  46. package/dist/utils/telemetry.d.ts +2 -0
  47. package/dist/utils/telemetry.js +81 -8
  48. package/package.json +2 -1
  49. package/schemas/cloud-verifier.v1.json +69 -3
  50. package/schemas/{pr.v2.json → pr.v3.json} +3 -12
  51. package/dist/commands/traces.d.ts +0 -48
  52. package/dist/commands/traces.js +0 -92
  53. package/dist/triage/traces.d.ts +0 -20
  54. package/dist/triage/traces.js +0 -311
  55. package/schemas/traces.v1.json +0 -53
@@ -51,6 +51,7 @@ const safeBufferByteLength = Buffer.byteLength;
51
51
  const safeObjectKeys = Object.keys;
52
52
  const safeObjectEntries = Object.entries;
53
53
  const safeObjectCreate = Object.create;
54
+ const safeObjectFreeze = Object.freeze;
54
55
  const safeGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
55
56
  const safeGetPrototypeOf = Object.getPrototypeOf;
56
57
  const safeHasOwn = Function.call.bind(Object.prototype.hasOwnProperty);
@@ -95,7 +96,7 @@ const safeResponseStatus = typeof Response === 'function'
95
96
  : null;
96
97
  const safeClearInterval = clearInterval;
97
98
  const SafeError = Error;
98
- const nativeErrorPrototypes = Object.freeze({
99
+ const nativeErrorPrototypes = safeObjectFreeze({
99
100
  type: TypeError.prototype,
100
101
  range: RangeError.prototype,
101
102
  syntax: SyntaxError.prototype,
@@ -173,10 +174,12 @@ function responseStatus(response) {
173
174
  }
174
175
 
175
176
  const RUNTIME_VERSION = 1;
176
- const SAMPLER_VERSION = 3;
177
+ const SAMPLER_VERSION = 4;
177
178
  const DEFAULT_FLUSH_INTERVAL_MS = 60_000;
178
179
  const DEFAULT_SITE_OBSERVATIONS = 8;
179
180
  const MAX_SITE_OBSERVATIONS = 4_096;
181
+ const DEFAULT_PROCESS_PROBE_OBSERVATIONS = 4_096;
182
+ const MAX_PROCESS_PROBE_OBSERVATIONS = 65_536;
180
183
  const MAX_SAFE_COUNT = Number.MAX_SAFE_INTEGER;
181
184
  const MAX_COUNTER_KEYS = 50_000;
182
185
  const MAX_EXECUTION_FAMILIES = 64;
@@ -195,7 +198,7 @@ const MAX_CONSECUTIVE_FAILURES = 3;
195
198
  const CONTROL_GLOBAL = '__haystackTelemetryControlV1';
196
199
  const CONTROL_CHANNEL = '__HAYSTACK_TELEMETRY_CONTROL_CHANNEL__';
197
200
  const RUNTIME_INTEGRITY = '__HAYSTACK_TELEMETRY_RUNTIME_INTEGRITY__';
198
- const SUPPRESSED_ACTION = Object.freeze({ suppressed: true });
201
+ const SUPPRESSED_ACTION = safeObjectFreeze({ suppressed: true });
199
202
 
200
203
  let installed = false;
201
204
  let activeControl = null;
@@ -401,7 +404,7 @@ function loadInstrumentation() {
401
404
  const manifest = safeJsonParse(readFileSync(join(__dirname, 'instrumentation.json'), 'utf8'));
402
405
  if (manifest.schema_version !== 'haystack-node-telemetry-instrumentation-v3' ||
403
406
  !safeArrayIsArray(manifest.sites)) return { sites: new SafeMap(), entrySourcePath: null };
404
- const siteEntries = safeArrayMap(manifest.sites, site => [site.probe_key, Object.freeze({
407
+ const siteEntries = safeArrayMap(manifest.sites, site => [site.probe_key, safeObjectFreeze({
405
408
  probeKey: site.probe_key,
406
409
  siteId: site.site_id,
407
410
  sourcePath: site.source_path,
@@ -461,7 +464,6 @@ function installRuntime(options = {}) {
461
464
  metadata,
462
465
  calls: 0,
463
466
  saturated: false,
464
- omitted: 0,
465
467
  });
466
468
  }
467
469
  if (safeMapSize(sitesByProbe) === 0) return { enabled: false, reason: 'missing-instrumentation-manifest' };
@@ -481,6 +483,15 @@ function installRuntime(options = {}) {
481
483
  requestedSiteObservations >= 1 && requestedSiteObservations <= MAX_SITE_OBSERVATIONS
482
484
  ? requestedSiteObservations
483
485
  : DEFAULT_SITE_OBSERVATIONS;
486
+ const requestedProcessProbeObservations = safeNumberParseInt(
487
+ process.env.HAYSTACK_TELEMETRY_PROCESS_PROBE_OBSERVATIONS ?? '',
488
+ 10,
489
+ );
490
+ const processProbeObservationLimit = safeNumberIsSafeInteger(requestedProcessProbeObservations) &&
491
+ requestedProcessProbeObservations >= 1 &&
492
+ requestedProcessProbeObservations <= MAX_PROCESS_PROBE_OBSERVATIONS
493
+ ? requestedProcessProbeObservations
494
+ : DEFAULT_PROCESS_PROBE_OBSERVATIONS;
484
495
  const requestedProcessSampleRate = safeNumberParseInt(
485
496
  process.env.HAYSTACK_TELEMETRY_PROCESS_SAMPLE_RATE ?? '',
486
497
  10,
@@ -499,6 +510,8 @@ function installRuntime(options = {}) {
499
510
  : DEFAULT_ACTION_SAMPLE_RATE;
500
511
 
501
512
  let enabled = true;
513
+ let collecting = true;
514
+ let processProbeObservations = 0;
502
515
  let sequence = 0;
503
516
  let consecutiveFailures = 0;
504
517
  let currentShape = new SafeMap();
@@ -509,7 +522,6 @@ function installRuntime(options = {}) {
509
522
  let sampledActions = 0;
510
523
  let unsampledActions = 0;
511
524
  let currentOmissions = new SafeMap();
512
- let saturatedProbeOmissions = 0;
513
525
  let pending = null;
514
526
  let pendingQueue = [];
515
527
  let flushInFlight = null;
@@ -743,11 +755,15 @@ function installRuntime(options = {}) {
743
755
  }
744
756
 
745
757
  function sampleWeight(state) {
746
- if (state.saturated) {
747
- if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
748
- if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
758
+ if (processProbeObservations >= processProbeObservationLimit) {
759
+ // Attribute the one observation that exhausted the process-wide budget.
760
+ // Later probes are deliberately not inspected or counted: the sampler
761
+ // version declares that this is a bounded, lossy collection mode.
762
+ recordOmission(state.metadata.probeKey, 'probeObservations');
763
+ collecting = false;
749
764
  return -2;
750
765
  }
766
+ processProbeObservations++;
751
767
  const calls = ++state.calls;
752
768
  if (calls > siteObservationLimit) {
753
769
  state.saturated = true;
@@ -766,7 +782,11 @@ function installRuntime(options = {}) {
766
782
  }
767
783
 
768
784
  function observeState(state, value, fields) {
769
- if (!enabled) return value;
785
+ // Saturation is deliberately lossy: the first over-limit observation
786
+ // records an explicit saturation marker, then this probe becomes a
787
+ // constant-time pass-through. Counting every later omission caused severe
788
+ // amplification in customer hot loops while adding no new shape evidence.
789
+ if (!enabled || !collecting || state.saturated) return value;
770
790
  try {
771
791
  const site = state.metadata.probeKey;
772
792
  const action = actionForProbe();
@@ -877,7 +897,7 @@ function installRuntime(options = {}) {
877
897
  }
878
898
 
879
899
  function branchState(state, value) {
880
- if (!enabled) return value;
900
+ if (!enabled || !collecting || state.saturated) return value;
881
901
  try {
882
902
  const site = state.metadata.probeKey;
883
903
  const action = actionForProbe();
@@ -909,7 +929,7 @@ function installRuntime(options = {}) {
909
929
  }
910
930
 
911
931
  function thrownState(state, value) {
912
- if (!enabled) return value;
932
+ if (!enabled || !collecting || state.saturated) return value;
913
933
  try {
914
934
  const site = state.metadata.probeKey;
915
935
  const action = actionForProbe();
@@ -939,7 +959,7 @@ function installRuntime(options = {}) {
939
959
  if (pending || pendingQueue.length > 0 || (safeMapSize(currentShape) === 0 &&
940
960
  safeMapSize(currentBranch) === 0 && safeMapSize(currentExecutions) === 0 &&
941
961
  omittedExecutionFamilyObservations === 0 && sampledActions === 0 && unsampledActions === 0 &&
942
- safeMapSize(currentOmissions) === 0 && saturatedProbeOmissions === 0)) return;
962
+ safeMapSize(currentOmissions) === 0)) return;
943
963
  const shape = objectFromMap(currentShape);
944
964
  const branchCounters = objectFromMap(currentBranch);
945
965
  currentShape = new SafeMap();
@@ -958,25 +978,6 @@ function installRuntime(options = {}) {
958
978
  sampledActions = 0;
959
979
  unsampledActions = 0;
960
980
  omittedExecutionFamilyObservations = 0;
961
- if (saturatedProbeOmissions > 0) {
962
- const countEntries = mapEntriesArray(probeStates);
963
- for (let index = 0; index < countEntries.length; index++) {
964
- const site = countEntries[index][0];
965
- const state = countEntries[index][1];
966
- if (!state || state.omitted <= 0) continue;
967
- const current = safeMapGet(currentOmissions, site) || {
968
- counterObservations: 0,
969
- probeObservations: 0,
970
- };
971
- current.probeObservations = safeMathMin(
972
- MAX_SAFE_COUNT,
973
- current.probeObservations + state.omitted,
974
- );
975
- safeMapSet(currentOmissions, site, current);
976
- state.omitted = 0;
977
- }
978
- saturatedProbeOmissions = 0;
979
- }
980
981
  const omittedEntries = safeArraySort(
981
982
  mapEntriesArray(currentOmissions),
982
983
  (leftEntry, rightEntry) => compareStrings(leftEntry[0], rightEntry[0]),
@@ -1389,7 +1390,7 @@ function installRuntime(options = {}) {
1389
1390
  void flush('before-exit');
1390
1391
  });
1391
1392
 
1392
- const control = Object.freeze({
1393
+ const control = safeObjectFreeze({
1393
1394
  enabled: true,
1394
1395
  runtimeVersion: RUNTIME_VERSION,
1395
1396
  buildId,
@@ -1409,55 +1410,48 @@ function installRuntime(options = {}) {
1409
1410
  if (name === '__hstValue') {
1410
1411
  const fields = state.metadata.allowedFields;
1411
1412
  return (value) => {
1412
- if (!enabled) return value;
1413
- if (state.saturated) {
1414
- const action = actionForProbe();
1415
- if (action?.suppressed) return value;
1416
- const executionValue = state.metadata.probeKind === 'parameter' ||
1417
- state.metadata.probeKind === 'return';
1418
- if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
1419
- if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
1420
- if (executionValue) {
1421
- const skippedEvents = value !== null && typeof value === 'object' && !safeArrayIsArray(value)
1422
- ? 1 + safeMathMin(fields.length, 48)
1423
- : 1;
1424
- omitExecutionEvents(action, skippedEvents);
1425
- }
1426
- return value;
1427
- }
1413
+ if (!enabled || !collecting || state.saturated) return value;
1428
1414
  return observeState(state, value, fields);
1429
1415
  };
1430
1416
  }
1431
1417
  if (name === '__hstBranch') return (value) => {
1432
- if (!enabled) return value;
1433
- if (state.saturated) {
1434
- const action = actionForProbe();
1435
- if (action?.suppressed) return value;
1436
- if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
1437
- if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
1438
- omitExecutionEvents(action);
1439
- return value;
1440
- }
1418
+ if (!enabled || !collecting || state.saturated) return value;
1441
1419
  return branchState(state, value);
1442
1420
  };
1443
1421
  if (name === '__hstThrow') return (value) => {
1444
- if (!enabled) return value;
1445
- if (state.saturated) {
1446
- const action = actionForProbe();
1447
- if (action?.suppressed) return value;
1448
- if (state.omitted < MAX_SAFE_COUNT) state.omitted++;
1449
- if (saturatedProbeOmissions < MAX_SAFE_COUNT) saturatedProbeOmissions++;
1450
- omitExecutionEvents(action);
1451
- return value;
1452
- }
1422
+ if (!enabled || !collecting || state.saturated) return value;
1453
1423
  return thrownState(state, value);
1454
1424
  };
1455
1425
  return null;
1456
1426
  },
1427
+ snapshotCurrentAction() {
1428
+ try {
1429
+ const action = currentAction();
1430
+ if (!action || action.actionKind !== 'http-server') return null;
1431
+ return safeObjectFreeze({
1432
+ actionKind: action.actionKind,
1433
+ truncated: action.truncated,
1434
+ omittedEvents: action.omittedEvents,
1435
+ events: safeObjectFreeze(safeArrayMap(action.events, event => safeObjectFreeze({
1436
+ siteId: event.siteId,
1437
+ kind: event.kind,
1438
+ scope: event.scope,
1439
+ outcome: event.outcome,
1440
+ observations: event.observations,
1441
+ }))),
1442
+ });
1443
+ } catch (error) {
1444
+ debug('current action snapshot failed open', error);
1445
+ return null;
1446
+ }
1447
+ },
1457
1448
  status() {
1458
1449
  return {
1459
1450
  enabled,
1451
+ collecting,
1460
1452
  sequence,
1453
+ processProbeObservations,
1454
+ processProbeObservationLimit,
1461
1455
  pending: pending !== null || pendingQueue.length > 0,
1462
1456
  shapeKeys: safeMapSize(currentShape),
1463
1457
  branchKeys: safeMapSize(currentBranch),
@@ -1496,7 +1490,7 @@ function install(options = {}) {
1496
1490
  }
1497
1491
  }
1498
1492
 
1499
- const publicRuntime = Object.freeze({ install, selectProbe, safeSelectProbe, safeSelectSiteProbe });
1493
+ const publicRuntime = safeObjectFreeze({ install, selectProbe, safeSelectProbe, safeSelectSiteProbe });
1500
1494
  // Keep the exported capability immutable. The generated CommonJS entry locks
1501
1495
  // its cache slot after genuine evaluation; the ESM wrapper evaluates this
1502
1496
  // source outside CommonJS's public cache.
@@ -6,18 +6,26 @@
6
6
  *
7
7
  * Unlike `haystack review` (finding-oriented analysis of the diff), design
8
8
  * verification executes the change: it infers intent from the diff alone,
9
- * designs pre-registered behavioral test cases, runs them against both sides
10
- * of the PR in isolated sandboxes, and produces a mechanical verdict of
11
- * unexpected behavior changes.
9
+ * designs pre-registered behavioral test cases, runs control-before, before,
10
+ * and after in isolated sandboxes, and reports evidence-backed anomaly
11
+ * candidates outside the registered intent.
12
12
  *
13
13
  * The command waits by default and returns an agent-facing behavioral result.
14
14
  * --no-wait preserves the asynchronous trigger-only path.
15
15
  */
16
+ import { type DesignVerifierUniverseRole } from '../utils/design-verifier-api.js';
16
17
  export interface DesignVerifyOptions {
17
18
  json?: boolean;
18
19
  wait?: boolean;
19
20
  run?: string;
20
21
  pollInterval?: string;
21
22
  timeout?: string;
23
+ case?: string;
24
+ universe?: DesignVerifierUniverseRole;
25
+ replay?: boolean;
26
+ execJson?: string;
27
+ cwd?: string;
28
+ commandTimeout?: string;
29
+ action?: string;
22
30
  }
23
31
  export declare function designVerifyCommand(identifier: string, options: DesignVerifyOptions): Promise<void>;
@@ -6,9 +6,9 @@
6
6
  *
7
7
  * Unlike `haystack review` (finding-oriented analysis of the diff), design
8
8
  * verification executes the change: it infers intent from the diff alone,
9
- * designs pre-registered behavioral test cases, runs them against both sides
10
- * of the PR in isolated sandboxes, and produces a mechanical verdict of
11
- * unexpected behavior changes.
9
+ * designs pre-registered behavioral test cases, runs control-before, before,
10
+ * and after in isolated sandboxes, and reports evidence-backed anomaly
11
+ * candidates outside the registered intent.
12
12
  *
13
13
  * The command waits by default and returns an agent-facing behavioral result.
14
14
  * --no-wait preserves the asynchronous trigger-only path.
@@ -16,7 +16,7 @@
16
16
  import chalk from 'chalk';
17
17
  import { loadToken } from '../utils/auth.js';
18
18
  import { parsePrRef } from '../utils/pr-ref.js';
19
- import { triggerDesignVerifier, waitForDesignVerifier } from '../utils/design-verifier-api.js';
19
+ import { getDesignVerifierUniverseAction, startDesignVerifierUniverseAction, triggerDesignVerifier, waitForDesignVerifier, waitForDesignVerifierUniverseAction, } from '../utils/design-verifier-api.js';
20
20
  import { buildDesignVerifierAgentReport, designVerifierStageLabel, formatDesignVerifierAgentReport, } from '../utils/design-verifier-result.js';
21
21
  function emit(json, payload, human) {
22
22
  if (json) {
@@ -26,6 +26,138 @@ function emit(json, payload, human) {
26
26
  human();
27
27
  }
28
28
  }
29
+ function parseExecArgv(source) {
30
+ let value;
31
+ try {
32
+ value = JSON.parse(source);
33
+ }
34
+ catch (error) {
35
+ throw new Error(`--exec-json is not valid JSON: ${error.message}`);
36
+ }
37
+ if (!Array.isArray(value) || value.length === 0 ||
38
+ value.some(argument => typeof argument !== 'string')) {
39
+ throw new Error('--exec-json must be a non-empty JSON array of string arguments');
40
+ }
41
+ return value;
42
+ }
43
+ function terminalSafe(value) {
44
+ // Preserve ordinary whitespace while removing C0/C1 controls, including
45
+ // ESC/OSC introducers. JSON output retains the server-returned strings.
46
+ let safe = '';
47
+ for (const character of value) {
48
+ const codePoint = character.codePointAt(0) ?? 0;
49
+ const ordinaryWhitespace = codePoint === 9 || codePoint === 10 || codePoint === 13;
50
+ if ((codePoint < 32 && !ordinaryWhitespace) ||
51
+ (codePoint >= 127 && codePoint <= 159))
52
+ continue;
53
+ safe += character;
54
+ }
55
+ return safe;
56
+ }
57
+ function printUniverseAction(action) {
58
+ const result = action.result;
59
+ if (action.status === 'queued' || action.status === 'running') {
60
+ console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} is ${action.status}; action ${action.actionId}`));
61
+ return;
62
+ }
63
+ if (action.status === 'failed') {
64
+ console.error(chalk.red(`Universe ${action.action} failed: ${action.error || 'unknown error'}`));
65
+ return;
66
+ }
67
+ if (!result) {
68
+ console.error(chalk.red(`Universe ${action.action} completed without a result.`));
69
+ return;
70
+ }
71
+ console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} (${result.durationMs}ms, exit ${result.exitCode ?? 'unknown'})`));
72
+ if (result.stdout) {
73
+ const stdout = terminalSafe(result.stdout);
74
+ process.stdout.write(stdout.endsWith('\n') ? stdout : `${stdout}\n`);
75
+ }
76
+ if (result.stderr) {
77
+ const stderr = terminalSafe(result.stderr);
78
+ process.stderr.write(stderr.endsWith('\n') ? stderr : `${stderr}\n`);
79
+ }
80
+ if (result.stdoutOmittedBytes || result.stderrOmittedBytes) {
81
+ console.error(chalk.dim(`Output truncated: ${result.stdoutOmittedBytes} stdout bytes, ${result.stderrOmittedBytes} stderr bytes omitted.`));
82
+ }
83
+ }
84
+ async function runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds) {
85
+ const requested = Boolean(options.action || options.case || options.universe ||
86
+ options.replay || options.execJson || options.cwd || options.commandTimeout);
87
+ if (!requested)
88
+ return false;
89
+ if (!options.run)
90
+ throw new Error('universe exploration requires --run <run-id>');
91
+ let actionId = options.action;
92
+ if (actionId) {
93
+ if (options.case || options.universe || options.replay || options.execJson ||
94
+ options.cwd || options.commandTimeout) {
95
+ throw new Error('--action cannot be combined with a new universe command');
96
+ }
97
+ if (options.wait === false) {
98
+ const loaded = await getDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token);
99
+ if (loaded.status !== 'ok')
100
+ throw new Error(loaded.message);
101
+ emit(options.json, { ok: true, pr: prLabel, ...loaded.action }, () => printUniverseAction(loaded.action));
102
+ return true;
103
+ }
104
+ }
105
+ else {
106
+ if (!options.case || !/^case-\d{5}$/.test(options.case)) {
107
+ throw new Error('a canonical --case case-00000 is required');
108
+ }
109
+ if (!options.universe || !['control-before', 'before', 'after'].includes(options.universe)) {
110
+ throw new Error('--universe must be control-before, before, or after');
111
+ }
112
+ if (Boolean(options.replay) === Boolean(options.execJson)) {
113
+ throw new Error('choose exactly one of --replay or --exec-json');
114
+ }
115
+ if (options.replay && (options.cwd || options.commandTimeout)) {
116
+ throw new Error('--cwd and --command-timeout apply only to --exec-json');
117
+ }
118
+ const commandTimeoutMs = options.commandTimeout === undefined
119
+ ? undefined
120
+ : Number(options.commandTimeout) * 1000;
121
+ if (commandTimeoutMs !== undefined &&
122
+ !(Number.isInteger(commandTimeoutMs) && commandTimeoutMs >= 1_000 && commandTimeoutMs <= 300_000)) {
123
+ throw new Error('--command-timeout must be between 1 and 300 seconds');
124
+ }
125
+ const started = await startDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, token, {
126
+ caseId: options.case,
127
+ universe: options.universe,
128
+ action: options.replay ? 'replay' : 'exec',
129
+ ...(options.execJson ? { argv: parseExecArgv(options.execJson) } : {}),
130
+ ...(options.cwd ? { cwd: options.cwd } : {}),
131
+ ...(commandTimeoutMs === undefined ? {} : { timeoutMs: commandTimeoutMs }),
132
+ });
133
+ if (started.status !== 'ok')
134
+ throw new Error(started.message);
135
+ actionId = started.action.actionId;
136
+ if (options.wait === false) {
137
+ emit(options.json, { ok: true, pr: prLabel, ...started.action }, () => {
138
+ console.log(chalk.green('✓ Universe action queued'));
139
+ console.log(chalk.dim(` Action: ${actionId}`));
140
+ console.log(chalk.dim(` Resume: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`));
141
+ });
142
+ return true;
143
+ }
144
+ }
145
+ const waited = await waitForDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token, { intervalMs: pollIntervalSeconds * 1000, timeoutMs: timeoutSeconds * 1000 });
146
+ if (waited.status === 'timeout') {
147
+ throw new Error(`Timed out waiting; resume with: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`);
148
+ }
149
+ if (waited.status === 'error')
150
+ throw new Error(waited.message);
151
+ emit(options.json, {
152
+ ok: waited.action.status === 'completed' && waited.action.result?.exitCode === 0,
153
+ pr: prLabel,
154
+ ...waited.action,
155
+ }, () => printUniverseAction(waited.action));
156
+ process.exitCode = waited.action.status === 'failed'
157
+ ? 2
158
+ : waited.action.result?.exitCode === 0 ? 0 : 1;
159
+ return true;
160
+ }
29
161
  export async function designVerifyCommand(identifier, options) {
30
162
  let pr;
31
163
  try {
@@ -73,6 +205,17 @@ export async function designVerifyCommand(identifier, options) {
73
205
  process.exitCode = 2;
74
206
  return;
75
207
  }
208
+ try {
209
+ if (await runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds))
210
+ return;
211
+ }
212
+ catch (error) {
213
+ emit(options.json, { ok: false, pr: prLabel, error: error.message }, () => {
214
+ console.error(chalk.red(error.message));
215
+ });
216
+ process.exitCode = 2;
217
+ return;
218
+ }
76
219
  if (!options.json) {
77
220
  console.log(chalk.dim(options.run
78
221
  ? `\nResuming design verification ${options.run}...`
@@ -2,16 +2,12 @@
2
2
  * haystack hooks - Install and manage git hooks for AI agent quality checks
3
3
  *
4
4
  * Installs:
5
- * - Entire CLI binary (session tracking, powered by https://entire.dev)
6
5
  * - Pre-commit hook (agent detection, truncation checking, LLM rules review)
7
- * - Git hooks for session logging (commit-msg, post-commit, pre-push, prepare-commit-msg)
6
+ * - Git hooks for Haystack quality checks and workflow guidance
8
7
  */
9
8
  interface HooksInstallOptions {
10
- version?: string;
11
9
  force?: boolean;
12
- skipEntire?: boolean;
13
10
  }
14
11
  export declare function hooksInstall(options: HooksInstallOptions): Promise<void>;
15
12
  export declare function hooksStatus(): Promise<void>;
16
- export declare function hooksUpdate(): Promise<void>;
17
13
  export {};