@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
package/dist/index.js CHANGED
@@ -30,11 +30,18 @@ import { initCommand } from './commands/init.js';
30
30
  import { authListCommand, authUseCommand, loginCommand, logoutCommand, whoamiCommand, } from './commands/login.js';
31
31
  import { handleAgenticTool, handleAutoMerge, handleAutoFix, handleWaitForReviewers, isAutoMergeEnabled, isAutoFixEnabled } from './commands/config.js';
32
32
  import { installSkills, listSkills } from './commands/skills.js';
33
+ import { cloudVerifierUniverseValidateCommand } from './commands/cloud-verifier-universe.js';
34
+ import { cloudVerifierIdentityCensusCommand } from './commands/cloud-verifier-identity-census.js';
35
+ import { cloudVerifierDataStoreDriftCommand } from './commands/cloud-verifier-data-store-drift.js';
36
+ import { cloudVerifierBehaviorsValidateCommand } from './commands/cloud-verifier-behaviors.js';
37
+ import { prepareUniverseReviewCommand } from './commands/prepare-universe-review.js';
38
+ import { scaffoldProvisionalUniverseCommand } from './commands/scaffold-provisional-universe.js';
33
39
  import { hooksInstall, hooksStatus, hooksUpdate } from './commands/hooks.js';
34
40
  import { submitCommand } from './commands/submit.js';
35
41
  import { installSessionHooks, sessionHooksStatus } from './commands/install-session-hooks.js';
36
42
  import { listPolicies, addPolicy, removePolicy, initPolicies, addInstruction } from './commands/policy.js';
37
43
  import { triageCommand } from './commands/triage.js';
44
+ import { designVerifyCommand } from './commands/design-verify.js';
38
45
  import { dismissCommand, markReviewedCommand, undismissCommand } from './commands/dismiss.js';
39
46
  import { requestReviewCommand } from './commands/request-review.js';
40
47
  import { reviewCommand } from './commands/review.js';
@@ -43,6 +50,7 @@ import { prReadCommand } from './commands/pr.js';
43
50
  import { inboxListCommand } from './commands/inbox.js';
44
51
  import { askHaystackCommand } from './commands/ask.js';
45
52
  import { tracesGetCommand, tracesListCommand } from './commands/traces.js';
53
+ import { telemetryInstrumentCommand } from './commands/telemetry.js';
46
54
  import { setupCommand } from './commands/setup.js';
47
55
  import { schemaCommand, listSchemas } from './commands/schema-cmd.js';
48
56
  import { registerWebhook, listWebhooks, rotateWebhookSecret, setWebhookEnabled, listDeliveries, replayDelivery, } from './commands/webhooks.js';
@@ -804,6 +812,32 @@ traces
804
812
  .option('--limit <count>', 'Chunks per page', '20')
805
813
  .option('--json', 'Output as JSON')
806
814
  .action((ref, checkpoint, options) => runPublicCommand(() => tracesGetCommand(ref, checkpoint, options), options.json));
815
+ const telemetry = program.command('telemetry').description('Add privacy-safe production telemetry without an application SDK');
816
+ telemetry
817
+ .command('instrument <dist>')
818
+ .description('Instrument compiled Node.js output without changing application source')
819
+ .requiredOption('--entry <file>', 'Entry file relative to the compiled output directory')
820
+ .option('--source-root <dir>', 'Source directory whose files correspond exactly to compiled output (default: sibling src/)')
821
+ .option('--include-symbols <file>', 'Experimental JSON allow-list of exact sourcePath + qualifiedName symbols')
822
+ .option('--json', 'Machine-readable instrumentation manifest')
823
+ .addHelpText('after', `
824
+ Run this once after the normal application build. It rewrites only compiled
825
+ JavaScript and copies a dependency-free runtime beside it; application source
826
+ does not import Haystack.
827
+
828
+ At runtime, telemetry stays off unless all three variables are set:
829
+ HAYSTACK_TELEMETRY=1
830
+ HAYSTACK_TELEMETRY_ENDPOINT=https://.../v3/telemetry
831
+ HAYSTACK_TELEMETRY_TOKEN=...
832
+
833
+ Example:
834
+ npm run build && haystack telemetry instrument dist --entry index.js
835
+ `)
836
+ .action((dist, options) => runPublicCommand(() => telemetryInstrumentCommand(dist, {
837
+ ...options,
838
+ includeSymbolsFile: options.includeSymbols,
839
+ includeSymbols: undefined,
840
+ }), options.json));
807
841
  program
808
842
  .command('dismiss <pr>')
809
843
  .description('Dismiss analysis findings for a PR')
@@ -826,6 +860,31 @@ Examples:
826
860
  haystack dismiss acme/widgets#99 # Dismiss for specific repo
827
861
  `)
828
862
  .action(dismissCommand);
863
+ program
864
+ .command('design-verify <pr>')
865
+ // "verification" is a banned word in top-level help (public-contract test
866
+ // keeps retired auto-fix/verification wording out of the CLI surface).
867
+ .description('Run a blind design-by-execution review of a PR')
868
+ .option('--json', 'Machine-readable output')
869
+ .option('--no-wait', 'Return after the run is admitted instead of waiting for its verdict')
870
+ .option('--run <run-id>', 'Resume and wait for an existing exact run id')
871
+ .option('--poll-interval <seconds>', 'Status polling interval', '3')
872
+ .option('--timeout <seconds>', 'Maximum time to wait; the server run continues after timeout', '7200')
873
+ .addHelpText('after', `
874
+ Executes the change instead of reading it: infers intent from the diff
875
+ alone, designs pre-registered behavioral test cases, runs them against
876
+ both sides of the PR in isolated sandboxes, and reports a mechanical
877
+ verdict of unexpected behavior changes.
878
+
879
+ The command waits for a behavioral verdict by default. Use --no-wait to
880
+ return the run id immediately, or --run <run-id> to resume waiting.
881
+
882
+ PR identifier formats:
883
+ 123 PR number (uses current repo)
884
+ owner/repo#123 Fully qualified
885
+ https://github.com/owner/repo/pull/123 GitHub URL
886
+ `)
887
+ .action(designVerifyCommand);
829
888
  program
830
889
  .command('mark-reviewed <pr>')
831
890
  .description('Mark human review as not needed for a PR')
@@ -1035,32 +1094,95 @@ Examples:
1035
1094
  // Skills subcommands
1036
1095
  const skills = program
1037
1096
  .command('skills')
1038
- .description('Manage AI skills for Claude Code');
1097
+ .description('Manage AI skills for coding agents');
1039
1098
  skills
1040
1099
  .command('install')
1041
- .description('Install Haystack skills into your coding CLI')
1100
+ .description('Install portable Haystack skills and optional coding-CLI shims')
1042
1101
  .option('--cli <name>', 'Target CLI: claude, codex, cursor, or manual')
1043
1102
  .addHelpText('after', `
1044
- This registers the Haystack MCP server with your coding CLI, enabling:
1045
- /setup-haystack - AI-assisted project setup
1103
+ This installs portable skills in the Git repository's .agents/skills directory:
1046
1104
  /submit - Submit a PR via Haystack
1105
+ /map-your-system - Map how Haystack QA can run this system
1106
+ /map-cloud-verifier-universe - Map a production-derived hermetic universe
1047
1107
  Supported CLIs:
1048
- claude Claude Code (auto-detected)
1049
- codex Codex CLI (auto-detected)
1050
- cursor Cursor IDE (auto-detected)
1051
- manual Show manual setup instructions
1108
+ claude Also install Claude Code command shims
1109
+ codex Use portable .agents/skills discovery only
1110
+ cursor Use portable .agents/skills discovery only
1111
+ manual Install portable skills and show their location
1052
1112
 
1053
1113
  Examples:
1054
- haystack skills install # Auto-detect and install
1114
+ haystack skills install # Install portable skills only
1055
1115
  haystack skills install --cli codex # Install for Codex only
1056
- haystack skills install --cli manual # Show manual instructions
1116
+ haystack skills install --cli claude # Also install Claude command shims
1057
1117
  `)
1058
- .action(installSkills);
1118
+ .action(async (opts) => {
1119
+ try {
1120
+ await installSkills(opts);
1121
+ }
1122
+ catch (err) {
1123
+ console.error(chalk.red('skills install failed:'), err instanceof Error ? err.message : err);
1124
+ process.exit(1);
1125
+ }
1126
+ });
1059
1127
  skills
1060
1128
  .command('list')
1061
1129
  .description('List available Haystack skills')
1062
1130
  .action(listSkills);
1063
- // Hooks subcommands
1131
+ skills
1132
+ .command('census-universe')
1133
+ .description('Deterministically census Cloud Verifier source identities before mapping')
1134
+ .option('--json', 'Machine-readable result')
1135
+ .option('--root <path>', 'Repository root (default: current Git worktree)')
1136
+ .action((opts) => runPublicCommand(async () => cloudVerifierIdentityCensusCommand(opts), opts.json));
1137
+ skills
1138
+ .command('data-store-drift')
1139
+ .description('Check whether the detected data stores still match the checked-in map (per-PR drift gate)')
1140
+ .option('--json', 'Machine-readable result')
1141
+ .option('--root <path>', 'Repository root (default: current Git worktree)')
1142
+ .option('--baseline <path>', 'Baseline map path (default: .haystack/cloud-verifier/data-stores.json)')
1143
+ .option('--base-ref <ref>', 'Only run when the diff against this ref touches a store-relevant file')
1144
+ .option('--update', 'Write the current detected stores as the new baseline')
1145
+ .action((opts) => runPublicCommand(async () => cloudVerifierDataStoreDriftCommand(opts), opts.json));
1146
+ skills
1147
+ .command('validate-universe')
1148
+ .description('Validate Cloud Verifier universe artifacts and stable-ID references')
1149
+ .option('--json', 'Machine-readable result')
1150
+ .action(async (opts) => {
1151
+ try {
1152
+ await cloudVerifierUniverseValidateCommand(opts);
1153
+ }
1154
+ catch (err) {
1155
+ console.error(chalk.red('validate-universe failed:'), err instanceof Error ? err.message : err);
1156
+ process.exit(1);
1157
+ }
1158
+ });
1159
+ skills
1160
+ .command('validate-behaviors')
1161
+ .description('Validate customer actions and their exact runtime identities')
1162
+ .option('--json', 'Machine-readable result')
1163
+ .action(async (opts) => {
1164
+ try {
1165
+ await cloudVerifierBehaviorsValidateCommand(opts);
1166
+ }
1167
+ catch (err) {
1168
+ console.error(chalk.red('validate-behaviors failed:'), err instanceof Error ? err.message : err);
1169
+ process.exit(1);
1170
+ }
1171
+ });
1172
+ skills
1173
+ .command('scaffold-provisional-universe')
1174
+ .description('Create a compact missing-authority receipt from strict JSON facts')
1175
+ .requiredOption('--input <path>', 'Strict provisional-universe JSON input file')
1176
+ .action((opts) => runPublicCommand(async () => scaffoldProvisionalUniverseCommand(opts), true));
1177
+ skills
1178
+ .command('prepare-universe-review')
1179
+ .description('Create a tracked-source snapshot with conventional test paths removed')
1180
+ .option('--json', 'Machine-readable isolation result and snapshot manifest')
1181
+ .option('--exclude-inactive-submodule <path...>', 'Omit exact tracked gitlink paths explicitly known to be inactive')
1182
+ .option('--omit-unscannable-files', 'Omit files whose test content cannot be ruled out, instead of blocking the review. '
1183
+ + 'The reviewer still never sees a test; the omissions are recorded as uncovered scope')
1184
+ .option('--full-receipt', 'Include complete non-blocking path receipts in JSON output')
1185
+ .action((opts) => runPublicCommand(() => prepareUniverseReviewCommand(opts), opts.json));
1064
1186
  const hooks = program
1065
1187
  .command('hooks')
1066
1188
  .description('Manage git hooks for AI agent quality checks');
@@ -101,7 +101,13 @@ export function buildCommand(cli, prompt, maxTurns) {
101
101
  case 'codex':
102
102
  return {
103
103
  command: 'codex',
104
- args: ['exec', prompt, '--full-auto'],
104
+ args: [
105
+ '--sandbox', 'workspace-write',
106
+ '--ask-for-approval', 'never',
107
+ 'exec',
108
+ '--ephemeral',
109
+ prompt,
110
+ ],
105
111
  };
106
112
  case 'gemini':
107
113
  return {
@@ -119,17 +125,12 @@ function spawnChecker(cli, config, cwd, timeoutMs) {
119
125
  const { command, args } = buildCommand(cli, config.prompt, config.maxTurns);
120
126
  let stdout = '';
121
127
  let stderr = '';
122
- const codexHome = join(cwd, '.haystack', 'triage', 'codex-home');
123
- if (cli === 'codex') {
124
- mkdirSync(codexHome, { recursive: true });
125
- }
126
128
  const proc = spawn(command, args, {
127
129
  cwd,
128
130
  stdio: ['ignore', 'pipe', 'pipe'],
129
131
  env: {
130
132
  ...process.env,
131
133
  CLAUDECODE: undefined,
132
- CODEX_HOME: cli === 'codex' ? codexHome : process.env.CODEX_HOME,
133
134
  },
134
135
  });
135
136
  proc.stdout?.on('data', (data) => {
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Client for the design-verifier trigger route on the auth worker.
3
+ *
4
+ * The worker owns everything sensitive: it authenticates the CLI token,
5
+ * enforces hsk_live repo scope, resolves the PR's base/head SHAs via the
6
+ * GitHub App install token, and forwards to the AWS design-verifier API with
7
+ * the server-side shared secret. This client just POSTs and interprets the
8
+ * response.
9
+ */
10
+ export type DesignVerifierTriggerResult = {
11
+ status: 'started' | 'queued' | 'duplicate';
12
+ runId: string;
13
+ } | {
14
+ status: 'denied';
15
+ message: string;
16
+ } | {
17
+ status: 'rate_limited';
18
+ message: string;
19
+ } | {
20
+ status: 'unavailable';
21
+ message: string;
22
+ } | {
23
+ status: 'error';
24
+ message: string;
25
+ };
26
+ export interface DesignVerifierCommandSpec {
27
+ cwd: string;
28
+ argv: string[];
29
+ env: Record<string, string>;
30
+ timeoutMs?: number;
31
+ }
32
+ export interface DesignVerifierExecutionDiagnostic {
33
+ diagnosticId: string;
34
+ kind: string;
35
+ message: string;
36
+ type?: string;
37
+ code?: string;
38
+ }
39
+ export interface DesignVerifierExecutionSummary {
40
+ side: 'base1' | 'base2' | 'head';
41
+ status: 'completed' | 'failed' | 'missing';
42
+ commandExitCode: number | null;
43
+ durationMs: number | null;
44
+ scenarioError: string | null;
45
+ diagnostics: DesignVerifierExecutionDiagnostic[];
46
+ diagnosticsOmitted: number;
47
+ outputExcerpt: Array<{
48
+ stream: 'stdout' | 'stderr' | 'system' | 'output';
49
+ text: string;
50
+ }>;
51
+ outputLinesOmitted: number;
52
+ traceArtifact: string;
53
+ outputArtifact: string;
54
+ }
55
+ export interface DesignVerifierStageTiming {
56
+ stage: 'prepare' | 'bake' | 'blast-radius' | 'prefetch' | 'case-design' | 'verdict';
57
+ status: string;
58
+ durationMs: number | null;
59
+ completedAt: string | null;
60
+ source: 'phase-artifact' | 'phase-artifact-clock' | 'cache-hit' | 'missing';
61
+ breakdownMs: Record<string, number>;
62
+ pollCount?: number;
63
+ }
64
+ export interface DesignVerifierTimingSummary {
65
+ admissionMs: number | null;
66
+ pipelineDurationMs: number | null;
67
+ recordedStageMs: number;
68
+ unattributedMs: number | null;
69
+ stages: DesignVerifierStageTiming[];
70
+ }
71
+ export interface DesignVerifierStatusResult {
72
+ version: 'dv-cli-status-v1';
73
+ runId: string;
74
+ owner: string;
75
+ repo: string;
76
+ pr: number;
77
+ baseSha: string | null;
78
+ headSha: string | null;
79
+ status: string;
80
+ stage: string | null;
81
+ createdAt: string | null;
82
+ startedAt: string | null;
83
+ finishedAt: string | null;
84
+ durationMs: number | null;
85
+ error: string | null;
86
+ result?: {
87
+ verdict: Record<string, any>;
88
+ cases: Array<Record<string, any>>;
89
+ scenarios: Array<Record<string, any> & {
90
+ executions?: DesignVerifierExecutionSummary[];
91
+ }>;
92
+ changedFiles: string[];
93
+ timings?: DesignVerifierTimingSummary;
94
+ probes: Array<{
95
+ caseId: string;
96
+ title: string;
97
+ files: string[];
98
+ validate: DesignVerifierCommandSpec | null;
99
+ run: DesignVerifierCommandSpec | null;
100
+ }>;
101
+ replay: {
102
+ artifactPrefix: string;
103
+ keys: Record<string, string>;
104
+ };
105
+ };
106
+ }
107
+ export type DesignVerifierStatusFetchResult = {
108
+ status: 'ok';
109
+ run: DesignVerifierStatusResult;
110
+ } | {
111
+ status: 'not_found' | 'denied' | 'unavailable' | 'error';
112
+ message: string;
113
+ };
114
+ export declare function triggerDesignVerifier(owner: string, repo: string, prNumber: number, token: string): Promise<DesignVerifierTriggerResult>;
115
+ export declare function getDesignVerifierStatus(owner: string, repo: string, prNumber: number, runId: string, token: string): Promise<DesignVerifierStatusFetchResult>;
116
+ export declare function waitForDesignVerifier(owner: string, repo: string, prNumber: number, runId: string, token: string, options?: {
117
+ intervalMs?: number;
118
+ timeoutMs?: number;
119
+ onProgress?: (run: DesignVerifierStatusResult) => void;
120
+ sleep?: (ms: number) => Promise<void>;
121
+ }): Promise<{
122
+ status: 'terminal';
123
+ run: DesignVerifierStatusResult;
124
+ } | {
125
+ status: 'timeout';
126
+ } | {
127
+ status: 'error';
128
+ message: string;
129
+ }>;
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Client for the design-verifier trigger route on the auth worker.
3
+ *
4
+ * The worker owns everything sensitive: it authenticates the CLI token,
5
+ * enforces hsk_live repo scope, resolves the PR's base/head SHAs via the
6
+ * GitHub App install token, and forwards to the AWS design-verifier API with
7
+ * the server-side shared secret. This client just POSTs and interprets the
8
+ * response.
9
+ */
10
+ import { haystackApiUrl } from './haystack-api.js';
11
+ function isDuration(value) {
12
+ return value === null || (typeof value === 'number' && Number.isFinite(value) && value >= 0);
13
+ }
14
+ function isTimestamp(value) {
15
+ return value === null || (typeof value === 'string' && Number.isFinite(Date.parse(value)));
16
+ }
17
+ function isTimingSummary(value) {
18
+ if (!value || typeof value !== 'object' || Array.isArray(value))
19
+ return false;
20
+ const timing = value;
21
+ if (!isDuration(timing.admissionMs) || !isDuration(timing.pipelineDurationMs) ||
22
+ typeof timing.recordedStageMs !== 'number' || !isDuration(timing.recordedStageMs) ||
23
+ !isDuration(timing.unattributedMs) ||
24
+ !Array.isArray(timing.stages))
25
+ return false;
26
+ const expectedStages = ['prepare', 'bake', 'blast-radius', 'prefetch', 'case-design', 'verdict'];
27
+ return timing.stages.length === expectedStages.length && timing.stages.every((stage, index) => stage && typeof stage === 'object' && stage.stage === expectedStages[index] &&
28
+ typeof stage.status === 'string' && isDuration(stage.durationMs) && isTimestamp(stage.completedAt) &&
29
+ ['phase-artifact', 'phase-artifact-clock', 'cache-hit', 'missing'].includes(stage.source) &&
30
+ stage.breakdownMs && typeof stage.breakdownMs === 'object' && !Array.isArray(stage.breakdownMs) &&
31
+ Object.values(stage.breakdownMs).every((duration) => typeof duration === 'number' && isDuration(duration)));
32
+ }
33
+ function isDesignVerifierStatusResult(value, expected) {
34
+ if (!value || typeof value !== 'object' || Array.isArray(value))
35
+ return false;
36
+ const run = value;
37
+ if (run.version !== 'dv-cli-status-v1' || run.runId !== expected.runId ||
38
+ run.owner !== expected.owner || run.repo !== expected.repo || run.pr !== expected.pr ||
39
+ typeof run.status !== 'string')
40
+ return false;
41
+ if (run.status !== 'succeeded')
42
+ return true;
43
+ return !!run.result && typeof run.result === 'object' &&
44
+ run.result.verdict && typeof run.result.verdict === 'object' &&
45
+ Array.isArray(run.result.cases) && Array.isArray(run.result.scenarios) &&
46
+ Array.isArray(run.result.changedFiles) && Array.isArray(run.result.probes) &&
47
+ (run.result.timings === undefined || isTimingSummary(run.result.timings)) &&
48
+ run.result.replay && typeof run.result.replay === 'object';
49
+ }
50
+ function apiHeaders(token) {
51
+ return {
52
+ 'Cache-Control': 'no-cache',
53
+ 'User-Agent': 'Haystack-CLI',
54
+ Authorization: `Bearer ${token}`,
55
+ };
56
+ }
57
+ export async function triggerDesignVerifier(owner, repo, prNumber, token) {
58
+ const url = haystackApiUrl(`/api/design-verifier/trigger/${owner}/${repo}/${prNumber}`);
59
+ let response;
60
+ try {
61
+ response = await fetch(url, {
62
+ method: 'POST',
63
+ headers: { ...apiHeaders(token), 'Content-Type': 'application/json' },
64
+ body: '{}',
65
+ });
66
+ }
67
+ catch (err) {
68
+ return { status: 'error', message: err.message };
69
+ }
70
+ let data = {};
71
+ let parseFailed = false;
72
+ try {
73
+ data = await response.json();
74
+ }
75
+ catch {
76
+ // Recorded, not swallowed: non-OK statuses are classified below; a 2xx
77
+ // with an unparseable body becomes an explicit error rather than a
78
+ // silently-empty success.
79
+ parseFailed = true;
80
+ }
81
+ if (parseFailed && response.ok) {
82
+ return { status: 'error', message: `Trigger endpoint returned invalid JSON (HTTP ${response.status})` };
83
+ }
84
+ if (response.ok) {
85
+ const outcome = data.outcome === 'queued' || data.outcome === 'duplicate' ? data.outcome : 'started';
86
+ if (!data.runId) {
87
+ return { status: 'error', message: `Trigger succeeded but no runId was returned (HTTP ${response.status})` };
88
+ }
89
+ return { status: outcome, runId: data.runId };
90
+ }
91
+ const message = data.message || data.error || `HTTP ${response.status}`;
92
+ // API Gateway answers 403 "Missing Authentication Token" for routes that
93
+ // do not exist — which is exactly what the flag-gated backend looks like
94
+ // before enable_design_verifier is on. The worker's own denials carry
95
+ // specific error codes/messages, so this signature is unambiguous.
96
+ if (response.status === 403 && data.message === 'Missing Authentication Token') {
97
+ return { status: 'unavailable', message: 'Design verifier is not enabled in this environment yet.' };
98
+ }
99
+ if (response.status === 401 || response.status === 403)
100
+ return { status: 'denied', message };
101
+ if (response.status === 429)
102
+ return { status: 'rate_limited', message };
103
+ if (response.status === 404) {
104
+ // Two distinct 404s share this status: the worker's own pr_not_found
105
+ // (carries an error code and a specific message) versus the AWS gateway
106
+ // 404 for the flag-gated /v3/design-verifier route not being enabled
107
+ // yet. Only the latter means "feature unavailable".
108
+ if (data.error === 'pr_not_found') {
109
+ return { status: 'error', message };
110
+ }
111
+ return { status: 'unavailable', message: 'Design verifier is not enabled in this environment yet.' };
112
+ }
113
+ return { status: 'error', message };
114
+ }
115
+ export async function getDesignVerifierStatus(owner, repo, prNumber, runId, token) {
116
+ const url = haystackApiUrl(`/api/design-verifier/status/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${prNumber}/${encodeURIComponent(runId)}`);
117
+ let response;
118
+ try {
119
+ response = await fetch(url, { method: 'GET', headers: apiHeaders(token) });
120
+ }
121
+ catch (err) {
122
+ return { status: 'error', message: err.message };
123
+ }
124
+ let data = {};
125
+ try {
126
+ data = await response.json();
127
+ }
128
+ catch {
129
+ return { status: 'error', message: `Status endpoint returned invalid JSON (HTTP ${response.status})` };
130
+ }
131
+ if (response.ok) {
132
+ if (!isDesignVerifierStatusResult(data, { owner, repo, pr: prNumber, runId })) {
133
+ return { status: 'error', message: `Status endpoint returned an invalid or mismatched result (HTTP ${response.status})` };
134
+ }
135
+ return { status: 'ok', run: data };
136
+ }
137
+ const message = data.message || data.error || `HTTP ${response.status}`;
138
+ if (response.status === 401 || response.status === 403)
139
+ return { status: 'denied', message };
140
+ if (response.status === 404)
141
+ return { status: 'not_found', message };
142
+ if (response.status === 502 || response.status === 503)
143
+ return { status: 'unavailable', message };
144
+ return { status: 'error', message };
145
+ }
146
+ export async function waitForDesignVerifier(owner, repo, prNumber, runId, token, options = {}) {
147
+ const intervalMs = options.intervalMs ?? 3000;
148
+ const timeoutMs = options.timeoutMs ?? 2 * 60 * 60 * 1000;
149
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
150
+ const deadline = Date.now() + timeoutMs;
151
+ let consecutiveErrors = 0;
152
+ let lastStage;
153
+ for (;;) {
154
+ const loaded = await getDesignVerifierStatus(owner, repo, prNumber, runId, token);
155
+ if (loaded.status !== 'ok') {
156
+ consecutiveErrors += 1;
157
+ if (loaded.status === 'denied' || loaded.status === 'not_found' || consecutiveErrors >= 3) {
158
+ return { status: 'error', message: loaded.message };
159
+ }
160
+ }
161
+ else {
162
+ consecutiveErrors = 0;
163
+ if (loaded.run.stage !== lastStage) {
164
+ lastStage = loaded.run.stage;
165
+ options.onProgress?.(loaded.run);
166
+ }
167
+ if (['succeeded', 'failed', 'failed-stale'].includes(loaded.run.status)) {
168
+ return { status: 'terminal', run: loaded.run };
169
+ }
170
+ }
171
+ if (Date.now() >= deadline)
172
+ return { status: 'timeout' };
173
+ await sleep(intervalMs);
174
+ }
175
+ }
@@ -0,0 +1,52 @@
1
+ import type { DesignVerifierCommandSpec, DesignVerifierExecutionSummary, DesignVerifierStatusResult, DesignVerifierTimingSummary } from './design-verifier-api.js';
2
+ export type DesignVerifierAgentVerdict = 'fix_confirmed' | 'fix_needed' | 'no_unexpected_change' | 'inconclusive';
3
+ export interface DesignVerifierAgentCase {
4
+ id: string;
5
+ title: string;
6
+ story: string;
7
+ expectedHead: string;
8
+ verdict: 'confirmed' | 'finding' | 'clean' | 'inconclusive';
9
+ findingKind: string | null;
10
+ baseStable: boolean | null;
11
+ observations: Array<{
12
+ path: string;
13
+ base: unknown;
14
+ baseRepeat: unknown;
15
+ head: unknown;
16
+ }>;
17
+ generatedFiles: string[];
18
+ executedCommand: DesignVerifierCommandSpec | null;
19
+ executions: DesignVerifierExecutionSummary[];
20
+ infrastructure: {
21
+ verified: boolean;
22
+ recipeId: string | null;
23
+ freshInstances: number;
24
+ baseRepeat: string | null;
25
+ errors: string[];
26
+ } | null;
27
+ }
28
+ export interface DesignVerifierAgentReport {
29
+ ok: boolean;
30
+ status: 'complete' | 'failed';
31
+ verdict: DesignVerifierAgentVerdict;
32
+ headline: string;
33
+ explanation: string;
34
+ runId: string;
35
+ repository: string;
36
+ pullRequest: number;
37
+ revisions: {
38
+ base: string | null;
39
+ head: string | null;
40
+ };
41
+ durationMs: number | null;
42
+ timings: DesignVerifierTimingSummary | null;
43
+ changedFiles: string[];
44
+ cases: DesignVerifierAgentCase[];
45
+ inspectCommand: string;
46
+ replay: Record<string, unknown> | null;
47
+ error: string | null;
48
+ exitCode: 0 | 1 | 2;
49
+ }
50
+ export declare function buildDesignVerifierAgentReport(run: DesignVerifierStatusResult): DesignVerifierAgentReport;
51
+ export declare function formatDesignVerifierAgentReport(report: DesignVerifierAgentReport): string;
52
+ export declare function designVerifierStageLabel(stage: string | null): string;