@haystackeditor/cli 0.15.31 → 0.17.0

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 (136) hide show
  1. package/README.md +141 -61
  2. package/dist/assets/hooks/scripts/commit-msg.sh +3 -0
  3. package/dist/assets/hooks/scripts/post-commit.sh +3 -0
  4. package/dist/assets/hooks/scripts/pre-commit.sh +11 -6
  5. package/dist/assets/hooks/scripts/pre-push.sh +3 -0
  6. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
  7. package/dist/commands/case-batch-contract.js +694 -0
  8. package/dist/commands/case-batch.js +1011 -0
  9. package/dist/commands/cloud-verifier-identity-census.js +5 -2
  10. package/dist/commands/combination-search-hook.js +139 -0
  11. package/dist/commands/dismiss.js +2 -1
  12. package/dist/commands/hooks.js +66 -7
  13. package/dist/commands/install-session-hooks.js +131 -44
  14. package/dist/commands/policy.js +32 -45
  15. package/dist/commands/precompute-delivery.js +6 -1
  16. package/dist/commands/scaffold-provisional-universe.js +8 -10
  17. package/dist/commands/setup.js +32 -10
  18. package/dist/commands/submit.js +39 -71
  19. package/dist/commands/telemetry.js +17 -2
  20. package/dist/commands/triage.js +2 -1
  21. package/dist/commands/verify-explore.js +1 -0
  22. package/dist/commands/verify-hosted-mcp.js +3 -12
  23. package/dist/commands/verify-hosted-reproducibility.js +49 -547
  24. package/dist/commands/verify-hosted.js +66 -168
  25. package/dist/commands/verify-precompute.js +64 -11
  26. package/dist/commands/verify.js +51 -139
  27. package/dist/index.js +290 -366
  28. package/dist/lazy.js +8 -0
  29. package/dist/schema.js +2 -1
  30. package/dist/tools/detect.js +3 -24
  31. package/dist/triage/astra.js +199 -0
  32. package/dist/triage/prompts.js +145 -179
  33. package/dist/triage/runner.js +104 -300
  34. package/dist/triage/types.js +2 -2
  35. package/dist/types.js +2 -6
  36. package/dist/utils/auth.js +14 -2
  37. package/dist/utils/git.js +60 -29
  38. package/dist/utils/github-api.js +14 -1
  39. package/dist/utils/haystack-api.js +38 -7
  40. package/dist/utils/hooks.js +43 -6
  41. package/dist/utils/prompter.js +25 -10
  42. package/dist/utils/safe-write.js +31 -0
  43. package/dist/utils/secret-paths.js +115 -0
  44. package/dist/utils/secrets.js +0 -1
  45. package/dist/utils/telemetry.js +34 -14
  46. package/dist/utils/update-check.js +151 -0
  47. package/package.json +19 -14
  48. package/schemas/case-batch.v1.json +235 -0
  49. package/schemas/cloud-verifier.v1.json +55 -100
  50. package/schemas/submit.v1.json +4 -2
  51. package/dist/commands/ask.d.ts +0 -14
  52. package/dist/commands/cloud-verifier-behaviors.d.ts +0 -27
  53. package/dist/commands/cloud-verifier-data-store-census.d.ts +0 -47
  54. package/dist/commands/cloud-verifier-data-store-drift.d.ts +0 -42
  55. package/dist/commands/cloud-verifier-identity-census.d.ts +0 -88
  56. package/dist/commands/cloud-verifier-materialization.d.ts +0 -16
  57. package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +0 -29
  58. package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +0 -27
  59. package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +0 -51
  60. package/dist/commands/cloud-verifier-universe.d.ts +0 -31
  61. package/dist/commands/config.d.ts +0 -46
  62. package/dist/commands/design-verify.d.ts +0 -33
  63. package/dist/commands/dismiss.d.ts +0 -29
  64. package/dist/commands/hooks.d.ts +0 -13
  65. package/dist/commands/inbox.d.ts +0 -65
  66. package/dist/commands/init.d.ts +0 -10
  67. package/dist/commands/install-session-hooks.d.ts +0 -17
  68. package/dist/commands/login.d.ts +0 -8
  69. package/dist/commands/mcp.d.ts +0 -1
  70. package/dist/commands/policy.d.ts +0 -31
  71. package/dist/commands/pr-status.d.ts +0 -144
  72. package/dist/commands/pr.d.ts +0 -40
  73. package/dist/commands/precompute-delivery-contract.d.ts +0 -68
  74. package/dist/commands/precompute-delivery.d.ts +0 -20
  75. package/dist/commands/prepare-universe-review.d.ts +0 -115
  76. package/dist/commands/production-source-deny-policy.d.ts +0 -15
  77. package/dist/commands/request-review.d.ts +0 -26
  78. package/dist/commands/review.d.ts +0 -25
  79. package/dist/commands/rules.d.ts +0 -4
  80. package/dist/commands/scaffold-provisional-universe.d.ts +0 -468
  81. package/dist/commands/schema-cmd.d.ts +0 -2
  82. package/dist/commands/setup.d.ts +0 -28
  83. package/dist/commands/skills.d.ts +0 -8
  84. package/dist/commands/status.d.ts +0 -4
  85. package/dist/commands/submit.d.ts +0 -30
  86. package/dist/commands/system-map.d.ts +0 -42
  87. package/dist/commands/telemetry.d.ts +0 -53
  88. package/dist/commands/tokens.d.ts +0 -14
  89. package/dist/commands/triage.d.ts +0 -35
  90. package/dist/commands/verify-core.d.ts +0 -449
  91. package/dist/commands/verify-core.js +0 -789
  92. package/dist/commands/verify-explore.d.ts +0 -14
  93. package/dist/commands/verify-history.d.ts +0 -14
  94. package/dist/commands/verify-hosted-mcp.d.ts +0 -12
  95. package/dist/commands/verify-hosted-reproducibility.d.ts +0 -90
  96. package/dist/commands/verify-hosted.d.ts +0 -92
  97. package/dist/commands/verify-mcp.d.ts +0 -8
  98. package/dist/commands/verify-mcp.js +0 -517
  99. package/dist/commands/verify-ops.d.ts +0 -158
  100. package/dist/commands/verify-ops.js +0 -1148
  101. package/dist/commands/verify-precompute.d.ts +0 -30
  102. package/dist/commands/verify-reproducibility.d.ts +0 -85
  103. package/dist/commands/verify-reproducibility.js +0 -494
  104. package/dist/commands/verify-reseal.d.ts +0 -9
  105. package/dist/commands/verify-reseal.js +0 -148
  106. package/dist/commands/verify-sandboxes.d.ts +0 -95
  107. package/dist/commands/verify-sandboxes.js +0 -352
  108. package/dist/commands/verify.d.ts +0 -28
  109. package/dist/commands/webhooks.d.ts +0 -30
  110. package/dist/index.d.ts +0 -22
  111. package/dist/schema.d.ts +0 -28
  112. package/dist/states.d.ts +0 -29
  113. package/dist/tools/detect.d.ts +0 -50
  114. package/dist/triage/prompts.d.ts +0 -24
  115. package/dist/triage/runner.d.ts +0 -34
  116. package/dist/triage/types.d.ts +0 -42
  117. package/dist/types/verify-history.d.ts +0 -38
  118. package/dist/types.d.ts +0 -1684
  119. package/dist/utils/action-output.d.ts +0 -24
  120. package/dist/utils/analysis-api.d.ts +0 -187
  121. package/dist/utils/auth.d.ts +0 -79
  122. package/dist/utils/config.d.ts +0 -24
  123. package/dist/utils/design-verifier-api.d.ts +0 -208
  124. package/dist/utils/design-verifier-history.d.ts +0 -46
  125. package/dist/utils/design-verifier-result.d.ts +0 -74
  126. package/dist/utils/detect.d.ts +0 -43
  127. package/dist/utils/git.d.ts +0 -135
  128. package/dist/utils/github-api.d.ts +0 -104
  129. package/dist/utils/haystack-api.d.ts +0 -37
  130. package/dist/utils/hooks.d.ts +0 -12
  131. package/dist/utils/pending-state.d.ts +0 -40
  132. package/dist/utils/pr-ref.d.ts +0 -27
  133. package/dist/utils/prompter.d.ts +0 -85
  134. package/dist/utils/secrets.d.ts +0 -47
  135. package/dist/utils/telemetry.d.ts +0 -19
  136. /package/dist/commands/{precompute-delivery-worker.d.ts → combination-search-hook-contract.js} +0 -0
@@ -1,30 +0,0 @@
1
- import type { DeliverPrecomputeRequest } from './precompute-delivery-contract.js';
2
- export type RepoWalkHookStatus = {
3
- status: 'queued' | 'existing';
4
- runId: string;
5
- } | {
6
- status: 'unavailable';
7
- reason: 'runner-unavailable' | 'enqueue-failed';
8
- };
9
- export type PrecomputeOutcome = 'pending' | 'enqueued' | 'cached' | 'existing' | 'skipped_no_auth' | 'error';
10
- export interface VerifyPrecomputeOptions {
11
- hook?: boolean;
12
- }
13
- export interface FrontHalfEnqueueRequest {
14
- owner: string;
15
- repository: string;
16
- baseCommit: string;
17
- workCommit: string;
18
- observedAt: number;
19
- cacheKey: string;
20
- patchGzBase64: string;
21
- patchSha256: string;
22
- }
23
- export interface PrecomputeDerivation {
24
- treeSha: string;
25
- request: FrontHalfEnqueueRequest;
26
- }
27
- export declare function derivePrecomputeRequest(gitRoot: string, owner: string, repository: string, deadline?: number): Promise<PrecomputeDerivation>;
28
- /** One authenticated submission of the already captured request. No source reads or retries. */
29
- export declare const deliverPrecomputeRequest: DeliverPrecomputeRequest;
30
- export declare function verifyPrecomputeCommand(options: VerifyPrecomputeOptions): Promise<void>;
@@ -1,85 +0,0 @@
1
- import { type RiskCell, type VerificationRun } from "./verify-core.js";
2
- import { type VerifyOptions } from "./verify.js";
3
- export type ReproducibilityScope = "executor" | "planner" | "full";
4
- export interface CellOutcomeObservation {
5
- run_id: string;
6
- status: string;
7
- verdict: string | null;
8
- }
9
- export interface CellPlanObservation {
10
- run_id: string;
11
- fingerprint: string;
12
- }
13
- export interface ReproducibilityCell {
14
- cell_id: string;
15
- chapter_id: string;
16
- title: string;
17
- outcomes: CellOutcomeObservation[];
18
- plans: CellPlanObservation[];
19
- missing_run_ids: string[];
20
- }
21
- export interface BisectOutcomeObservation {
22
- run_id: string;
23
- status: string;
24
- culprit_sha: string | null;
25
- parent_sha: string | null;
26
- incident: {
27
- commit: string;
28
- status: string;
29
- } | null;
30
- probes: Array<{
31
- commit: string;
32
- status: string;
33
- }>;
34
- }
35
- export interface ReproducibilityBisect {
36
- chapter_id: string;
37
- label: string;
38
- outcomes: BisectOutcomeObservation[];
39
- missing_run_ids: string[];
40
- }
41
- export interface ReproducibilityReport {
42
- schema_version: "1.0.0";
43
- scope: ReproducibilityScope;
44
- requested_runs: number;
45
- completed_runs: number;
46
- run_ids: string[];
47
- plan_source_run_id: string | null;
48
- compared_cells: number;
49
- stable_cells: number;
50
- compared_bisects: number;
51
- stable_bisects: number;
52
- plan_drift_cells: ReproducibilityCell[];
53
- outcome_drift_cells: ReproducibilityCell[];
54
- unrunnable_cells: ReproducibilityCell[];
55
- bisect_drift_chapters: ReproducibilityBisect[];
56
- unrunnable_bisects: ReproducibilityBisect[];
57
- planner_incomplete_run_ids: string[];
58
- executor_incomplete_run_ids: string[];
59
- incomplete_run_ids: string[];
60
- timed_out_run_ids: string[];
61
- planner_reproducible: boolean;
62
- executor_reproducible: boolean;
63
- reproducible: boolean;
64
- }
65
- export declare function cellPlanFingerprint(cell: RiskCell): string;
66
- /**
67
- * Plan identity and runtime outcomes are independent axes:
68
- *
69
- * - missing IDs or changed frozen contracts are planner drift;
70
- * - status/judgment changes under an identical contract are executor drift.
71
- *
72
- * Titles and other prose are fingerprinted to detect a changed judge input,
73
- * but cells are correlated only by their exact stable IDs.
74
- */
75
- export declare function analyzeReproducibility(runs: VerificationRun[], requestedRuns?: number, timedOutRunIds?: string[], scope?: ReproducibilityScope): ReproducibilityReport;
76
- export interface VerifyReproducibilityOptions extends VerifyOptions {
77
- runs?: string;
78
- interval?: string;
79
- timeout?: string;
80
- repoRoot?: string;
81
- scope?: string;
82
- planSource?: string;
83
- }
84
- export declare function runHasFrozenRiskPlan(run: VerificationRun): boolean;
85
- export declare function verifyReproducibilityCommand(specimen: string, options: VerifyReproducibilityOptions): Promise<void>;
@@ -1,494 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import chalk from "chalk";
3
- import { discoverVerifyRoot, resolveServer, summarizeRun, waitForTerminal, } from "./verify-core.js";
4
- import { startVerificationRun, } from "./verify.js";
5
- function compareCodeUnits(left, right) {
6
- return left < right ? -1 : left > right ? 1 : 0;
7
- }
8
- function requiredPlanString(value, label) {
9
- if (typeof value !== "string" || value.trim() === "") {
10
- throw new Error(`Frozen plan ${label} must be a non-empty string`);
11
- }
12
- return value;
13
- }
14
- function requiredPlanStrings(value, label) {
15
- if (!Array.isArray(value)) {
16
- throw new Error(`Frozen plan ${label} must be an array`);
17
- }
18
- return value.map((item, index) => requiredPlanString(item, `${label}[${index}]`));
19
- }
20
- function canonicalCellPlan(cell) {
21
- if (!Array.isArray(cell.sourceEvidence)) {
22
- throw new Error("Frozen plan sourceEvidence must be an array");
23
- }
24
- if (!Array.isArray(cell.oracles)) {
25
- throw new Error("Frozen plan oracles must be an array");
26
- }
27
- if (typeof cell.confidence !== "number" || !Number.isFinite(cell.confidence)) {
28
- throw new Error("Frozen plan confidence must be a finite number");
29
- }
30
- return {
31
- risk_source_id: requiredPlanString(cell.riskSourceId, "riskSourceId"),
32
- capability_id: requiredPlanString(cell.capabilityId, "capabilityId"),
33
- title: requiredPlanString(cell.title, "title"),
34
- rationale: requiredPlanString(cell.rationale, "rationale"),
35
- observable_consequence: requiredPlanString(cell.observableConsequence, "observableConsequence"),
36
- activation_conditions: requiredPlanStrings(cell.activationConditions, "activationConditions").sort(compareCodeUnits),
37
- causal_path: requiredPlanStrings(cell.causalPath, "causalPath"),
38
- severity: requiredPlanString(cell.severity, "severity"),
39
- confidence: cell.confidence,
40
- source_evidence: cell.sourceEvidence.map((item, index) => ({
41
- evidence_id: requiredPlanString(item.evidenceId, `sourceEvidence[${index}].evidenceId`),
42
- file: requiredPlanString(item.file, `sourceEvidence[${index}].file`),
43
- line: item.line ?? null,
44
- excerpt: requiredPlanString(item.excerpt, `sourceEvidence[${index}].excerpt`),
45
- relationship: requiredPlanString(item.relationship, `sourceEvidence[${index}].relationship`),
46
- })).sort((left, right) => compareCodeUnits(JSON.stringify(left), JSON.stringify(right))),
47
- oracles: cell.oracles.map((oracle, index) => ({
48
- oracle_id: requiredPlanString(oracle.oracleId, `oracles[${index}].oracleId`),
49
- kind: requiredPlanString(oracle.kind, `oracles[${index}].kind`),
50
- measurement: requiredPlanString(oracle.measurement, `oracles[${index}].measurement`),
51
- threshold: oracle.threshold ?? null,
52
- description: requiredPlanString(oracle.description, `oracles[${index}].description`),
53
- })).sort((left, right) => compareCodeUnits(JSON.stringify(left), JSON.stringify(right))),
54
- };
55
- }
56
- function bisectOutcomeSignature(observation) {
57
- if (!observation)
58
- return "missing";
59
- return JSON.stringify({
60
- status: observation.status,
61
- culprit_sha: observation.culprit_sha,
62
- parent_sha: observation.parent_sha,
63
- incident: observation.incident,
64
- probes: observation.probes,
65
- });
66
- }
67
- function canonicalBisectObservation(runId, chapter) {
68
- const bisect = chapter.bisect;
69
- if (!bisect || !Array.isArray(bisect.probes))
70
- return null;
71
- return {
72
- run_id: runId,
73
- status: bisect.status,
74
- culprit_sha: bisect.culpritSha ?? null,
75
- parent_sha: bisect.parentSha ?? null,
76
- incident: bisect.incidentProbe
77
- ? {
78
- commit: bisect.incidentProbe.commit,
79
- status: bisect.incidentProbe.status,
80
- }
81
- : null,
82
- probes: bisect.probes
83
- .map(probe => ({
84
- commit: probe.commit,
85
- status: probe.status,
86
- }))
87
- .sort((left, right) => compareCodeUnits(left.commit, right.commit)),
88
- };
89
- }
90
- function reportBisectOf(bisect, runIds) {
91
- return {
92
- chapter_id: bisect.chapter_id,
93
- label: bisect.label,
94
- outcomes: runIds.flatMap((runId) => {
95
- const observation = bisect.byRun.get(runId);
96
- return observation ? [observation] : [];
97
- }),
98
- missing_run_ids: runIds.filter((runId) => !bisect.byRun.has(runId)),
99
- };
100
- }
101
- export function cellPlanFingerprint(cell) {
102
- return createHash("sha256")
103
- .update(JSON.stringify(canonicalCellPlan(cell)))
104
- .digest("hex");
105
- }
106
- function outcomeSignature(observation) {
107
- if (!observation)
108
- return "missing";
109
- return JSON.stringify({
110
- status: observation.status,
111
- verdict: observation.verdict,
112
- });
113
- }
114
- function reportCellOf(cell, runIds) {
115
- return {
116
- cell_id: cell.cell_id,
117
- chapter_id: cell.chapter_id,
118
- title: cell.title,
119
- outcomes: runIds.flatMap((runId) => {
120
- const observation = cell.byRun.get(runId);
121
- return observation ? [observation] : [];
122
- }),
123
- plans: runIds.flatMap((runId) => {
124
- const observation = cell.planByRun.get(runId);
125
- return observation ? [observation] : [];
126
- }),
127
- missing_run_ids: runIds.filter((runId) => !cell.byRun.has(runId)),
128
- };
129
- }
130
- /**
131
- * Plan identity and runtime outcomes are independent axes:
132
- *
133
- * - missing IDs or changed frozen contracts are planner drift;
134
- * - status/judgment changes under an identical contract are executor drift.
135
- *
136
- * Titles and other prose are fingerprinted to detect a changed judge input,
137
- * but cells are correlated only by their exact stable IDs.
138
- */
139
- export function analyzeReproducibility(runs, requestedRuns = runs.length, timedOutRunIds = [], scope = "full") {
140
- const runIds = runs.map((run) => run.runId);
141
- const cells = new Map();
142
- const bisects = new Map();
143
- const plannerIncompleteRunIds = new Set(timedOutRunIds);
144
- const executorIncompleteRunIds = new Set(timedOutRunIds);
145
- for (const run of runs) {
146
- if (!["complete", "failed"].includes(run.status)
147
- || run.events.some(event => event.eventId.startsWith("evt-stale-"))) {
148
- plannerIncompleteRunIds.add(run.runId);
149
- executorIncompleteRunIds.add(run.runId);
150
- }
151
- const seenInRun = new Set();
152
- const seenBisectsInRun = new Set();
153
- for (const chapter of run.chapters) {
154
- if (chapter.executionMode === "bisect") {
155
- if (seenBisectsInRun.has(chapter.chapterId)) {
156
- throw new Error(`Run ${run.runId} contains duplicate stable bisect chapter id ${chapter.chapterId}`);
157
- }
158
- seenBisectsInRun.add(chapter.chapterId);
159
- const observation = canonicalBisectObservation(run.runId, chapter);
160
- if (!observation) {
161
- executorIncompleteRunIds.add(run.runId);
162
- continue;
163
- }
164
- const indexed = bisects.get(chapter.chapterId) ?? {
165
- chapter_id: chapter.chapterId,
166
- label: chapter.label,
167
- byRun: new Map(),
168
- };
169
- indexed.byRun.set(run.runId, observation);
170
- bisects.set(chapter.chapterId, indexed);
171
- if (observation.status !== "found") {
172
- executorIncompleteRunIds.add(run.runId);
173
- }
174
- continue;
175
- }
176
- if (!Array.isArray(chapter.riskCells)) {
177
- plannerIncompleteRunIds.add(run.runId);
178
- executorIncompleteRunIds.add(run.runId);
179
- continue;
180
- }
181
- for (const cell of chapter.riskCells) {
182
- const stableKey = `${chapter.chapterId}:${cell.riskCellId}`;
183
- if (seenInRun.has(stableKey)) {
184
- throw new Error(`Run ${run.runId} contains duplicate stable cell id ${stableKey}`);
185
- }
186
- seenInRun.add(stableKey);
187
- const indexed = cells.get(stableKey) ?? {
188
- cell_id: cell.riskCellId,
189
- chapter_id: chapter.chapterId,
190
- title: cell.title,
191
- byRun: new Map(),
192
- planByRun: new Map(),
193
- };
194
- indexed.byRun.set(run.runId, {
195
- run_id: run.runId,
196
- status: cell.status,
197
- verdict: cell.agentJudgment?.verdict ?? null,
198
- });
199
- indexed.planByRun.set(run.runId, {
200
- run_id: run.runId,
201
- fingerprint: cellPlanFingerprint(cell),
202
- });
203
- cells.set(stableKey, indexed);
204
- if (cell.status === "infra_error"
205
- || cell.status === "inconclusive"
206
- || !["passed", "failed"].includes(cell.status)) {
207
- executorIncompleteRunIds.add(run.runId);
208
- }
209
- }
210
- }
211
- }
212
- const planDriftCells = [];
213
- const outcomeDriftCells = [];
214
- const unrunnableCells = [];
215
- const bisectDriftChapters = [];
216
- const unrunnableBisects = [];
217
- let stableCells = 0;
218
- let stableBisects = 0;
219
- for (const cell of [...cells.values()].sort((left, right) => compareCodeUnits(`${left.chapter_id}:${left.cell_id}`, `${right.chapter_id}:${right.cell_id}`))) {
220
- const reportCell = reportCellOf(cell, runIds);
221
- const planSignatures = new Set(runIds.map((runId) => cell.planByRun.get(runId)?.fingerprint ?? "missing"));
222
- const planDrift = planSignatures.size > 1;
223
- if (planDrift)
224
- planDriftCells.push(reportCell);
225
- const outcomeSignatures = new Set(runIds.map((runId) => outcomeSignature(cell.byRun.get(runId))));
226
- const outcomeDrift = !planDrift && outcomeSignatures.size > 1;
227
- if (outcomeDrift)
228
- outcomeDriftCells.push(reportCell);
229
- const outcomes = [...cell.byRun.values()];
230
- const unrunnable = outcomes.some((outcome) => !["passed", "failed"].includes(outcome.status));
231
- if (unrunnable)
232
- unrunnableCells.push(reportCell);
233
- if (!planDrift
234
- && !outcomeDrift
235
- && !unrunnable
236
- && reportCell.missing_run_ids.length === 0) {
237
- stableCells += 1;
238
- }
239
- }
240
- for (const bisect of [...bisects.values()].sort((left, right) => compareCodeUnits(left.chapter_id, right.chapter_id))) {
241
- const reportBisect = reportBisectOf(bisect, runIds);
242
- const outcomeSignatures = new Set(runIds.map((runId) => bisectOutcomeSignature(bisect.byRun.get(runId))));
243
- const outcomeDrift = outcomeSignatures.size > 1;
244
- if (outcomeDrift)
245
- bisectDriftChapters.push(reportBisect);
246
- const unrunnable = [...bisect.byRun.values()].some(observation => observation.status !== "found");
247
- if (unrunnable)
248
- unrunnableBisects.push(reportBisect);
249
- if (!outcomeDrift
250
- && !unrunnable
251
- && reportBisect.missing_run_ids.length === 0) {
252
- stableBisects += 1;
253
- }
254
- }
255
- const plannerHasComparableWork = cells.size > 0;
256
- const executorHasComparableWork = cells.size > 0 || bisects.size > 0;
257
- const plannerIncomplete = plannerIncompleteRunIds.size > 0
258
- || runs.length < requestedRuns
259
- || !plannerHasComparableWork;
260
- const executorIncomplete = executorIncompleteRunIds.size > 0
261
- || runs.length < requestedRuns
262
- || !executorHasComparableWork;
263
- const plannerReproducible = !plannerIncomplete
264
- && planDriftCells.length === 0;
265
- const executorReproducible = !executorIncomplete
266
- && planDriftCells.length === 0
267
- && outcomeDriftCells.length === 0
268
- && unrunnableCells.length === 0
269
- && bisectDriftChapters.length === 0
270
- && unrunnableBisects.length === 0;
271
- const reproducible = scope === "planner"
272
- ? plannerReproducible
273
- : scope === "executor"
274
- ? executorReproducible
275
- : plannerReproducible && executorReproducible;
276
- const incompleteRunIds = scope === "planner"
277
- ? plannerIncompleteRunIds
278
- : executorIncompleteRunIds;
279
- return {
280
- schema_version: "1.0.0",
281
- scope,
282
- requested_runs: requestedRuns,
283
- completed_runs: runs.filter((run) => ["complete", "failed"].includes(run.status)).length,
284
- run_ids: runIds,
285
- plan_source_run_id: scope === "executor" && cells.size > 0
286
- ? runs[0]?.runId ?? null
287
- : null,
288
- compared_cells: cells.size,
289
- stable_cells: stableCells,
290
- compared_bisects: bisects.size,
291
- stable_bisects: stableBisects,
292
- plan_drift_cells: planDriftCells,
293
- outcome_drift_cells: outcomeDriftCells,
294
- unrunnable_cells: unrunnableCells,
295
- bisect_drift_chapters: bisectDriftChapters,
296
- unrunnable_bisects: unrunnableBisects,
297
- planner_incomplete_run_ids: [...plannerIncompleteRunIds],
298
- executor_incomplete_run_ids: [...executorIncompleteRunIds],
299
- incomplete_run_ids: [...incompleteRunIds],
300
- timed_out_run_ids: timedOutRunIds,
301
- planner_reproducible: plannerReproducible,
302
- executor_reproducible: executorReproducible,
303
- reproducible,
304
- };
305
- }
306
- function positiveNumber(raw, fallback, flag) {
307
- const value = raw === undefined ? fallback : Number(raw);
308
- if (!Number.isFinite(value) || value <= 0) {
309
- throw new Error(`${flag} takes a positive number`);
310
- }
311
- return value;
312
- }
313
- function parseScope(raw) {
314
- const scope = raw ?? "executor";
315
- if (scope === "executor" || scope === "planner" || scope === "full") {
316
- return scope;
317
- }
318
- throw new Error("--scope must be executor, planner, or full");
319
- }
320
- function renderCell(cell, kind) {
321
- const glyph = kind === "unrunnable" ? chalk.yellow("!") : chalk.red("✗");
322
- console.log(` ${glyph} ${cell.chapter_id}/${cell.cell_id} ${chalk.dim(cell.title)}`);
323
- if (kind === "plan") {
324
- for (const plan of cell.plans) {
325
- console.log(` ${plan.run_id}: plan ${plan.fingerprint.slice(0, 12)}`);
326
- }
327
- }
328
- else {
329
- for (const outcome of cell.outcomes) {
330
- console.log(` ${outcome.run_id}: ${outcome.status}`
331
- + `${outcome.verdict ? ` / ${outcome.verdict}` : ""}`);
332
- }
333
- }
334
- for (const runId of cell.missing_run_ids) {
335
- console.log(` ${runId}: ${chalk.yellow("missing from frozen plan")}`);
336
- }
337
- }
338
- function renderBisect(bisect, kind) {
339
- const glyph = kind === "unrunnable" ? chalk.yellow("!") : chalk.red("✗");
340
- console.log(` ${glyph} ${bisect.chapter_id} ${chalk.dim(bisect.label)}`);
341
- for (const outcome of bisect.outcomes) {
342
- console.log(` ${outcome.run_id}: ${outcome.status}`
343
- + `${outcome.culprit_sha
344
- ? ` / ${outcome.parent_sha ?? "?"}..${outcome.culprit_sha}`
345
- : ""}`);
346
- }
347
- for (const runId of bisect.missing_run_ids) {
348
- console.log(` ${runId}: ${chalk.yellow("missing bisect result")}`);
349
- }
350
- }
351
- export function runHasFrozenRiskPlan(run) {
352
- return run.chapters.some(chapter => chapter.executionMode !== "bisect"
353
- && (chapter.riskCells?.length ?? 0) > 0);
354
- }
355
- export async function verifyReproducibilityCommand(specimen, options) {
356
- const requestedRuns = positiveNumber(options.runs, 3, "--runs");
357
- if (!Number.isInteger(requestedRuns) || requestedRuns < 2 || requestedRuns > 20) {
358
- throw new Error("--runs must be an integer from 2 to 20");
359
- }
360
- const scope = parseScope(options.scope);
361
- if (options.planSource && scope !== "executor") {
362
- throw new Error("--plan-source is only valid with --scope executor");
363
- }
364
- const intervalMs = positiveNumber(options.interval, 3, "--interval") * 1_000;
365
- const timeoutMs = options.timeout
366
- ? positiveNumber(options.timeout, 0, "--timeout") * 60_000
367
- : undefined;
368
- const server = resolveServer(options.server);
369
- const repoRoot = discoverVerifyRoot(options.repoRoot);
370
- const runs = [];
371
- const timedOutRunIds = [];
372
- let executorPlanSourceRunId = options.planSource;
373
- for (let index = 0; index < requestedRuns; index += 1) {
374
- const planSourceRunId = scope === "executor"
375
- ? executorPlanSourceRunId
376
- : undefined;
377
- if (!options.json) {
378
- console.error(chalk.dim(`Reproducibility run ${index + 1}/${requestedRuns}: `
379
- + `${planSourceRunId
380
- ? `replaying plan from ${planSourceRunId}`
381
- : `planning ${specimen}`}…`));
382
- }
383
- const started = await startVerificationRun(specimen, {
384
- ...options,
385
- json: true,
386
- open: false,
387
- planSourceRunId,
388
- });
389
- const waited = await waitForTerminal({
390
- server,
391
- repoRoot,
392
- selector: started.runId,
393
- intervalMs,
394
- timeoutMs,
395
- });
396
- runs.push(waited.loaded.run);
397
- if (scope === "executor"
398
- && !executorPlanSourceRunId
399
- && runHasFrozenRiskPlan(waited.loaded.run)) {
400
- executorPlanSourceRunId = waited.loaded.run.runId;
401
- }
402
- if (!options.json) {
403
- const summary = summarizeRun(waited.loaded, server);
404
- console.error(` ${started.runId}: ${summary.counts.passed} passed, `
405
- + `${summary.counts.failed} failed, `
406
- + `${summary.counts.could_not_run} could not run`);
407
- }
408
- if (waited.timedOut) {
409
- timedOutRunIds.push(started.runId);
410
- break;
411
- }
412
- if (waited.endReason === "stale")
413
- break;
414
- }
415
- const report = analyzeReproducibility(runs, requestedRuns, timedOutRunIds, scope);
416
- if (scope === "executor") {
417
- report.plan_source_run_id = executorPlanSourceRunId ?? null;
418
- }
419
- if (options.json) {
420
- process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
421
- }
422
- else {
423
- const scopeHasComparableWork = scope === "planner"
424
- ? report.compared_cells > 0
425
- : report.compared_cells > 0 || report.compared_bisects > 0;
426
- console.log("");
427
- console.log(chalk.bold(`Compared ${report.compared_cells} stable cell ID`
428
- + `${report.compared_cells === 1 ? "" : "s"} and `
429
- + `${report.compared_bisects} stable bisect chapter ID`
430
- + `${report.compared_bisects === 1 ? "" : "s"} across `
431
- + `${report.completed_runs}/${report.requested_runs} runs (${scope}).`));
432
- if (report.plan_drift_cells.length > 0) {
433
- console.log(chalk.red.bold(`${report.plan_drift_cells.length} planner-drift cell`
434
- + `${report.plan_drift_cells.length === 1 ? "" : "s"}:`));
435
- for (const cell of report.plan_drift_cells)
436
- renderCell(cell, "plan");
437
- }
438
- else if (report.compared_cells > 0) {
439
- console.log(chalk.green("✓ Frozen plan contracts were identical."));
440
- }
441
- if (scope !== "planner") {
442
- if (report.outcome_drift_cells.length > 0) {
443
- console.log(chalk.red.bold(`${report.outcome_drift_cells.length} runtime/judgment-drift cell`
444
- + `${report.outcome_drift_cells.length === 1 ? "" : "s"}:`));
445
- for (const cell of report.outcome_drift_cells) {
446
- renderCell(cell, "outcome");
447
- }
448
- }
449
- else if (report.executor_reproducible && report.compared_cells > 0) {
450
- console.log(chalk.green(`✓ All ${report.stable_cells} cells produced the same status and judgment.`));
451
- }
452
- if (report.bisect_drift_chapters.length > 0) {
453
- console.log(chalk.red.bold(`${report.bisect_drift_chapters.length} bisect chapter`
454
- + `${report.bisect_drift_chapters.length === 1 ? "" : "s"} changed outcomes:`));
455
- for (const bisect of report.bisect_drift_chapters) {
456
- renderBisect(bisect, "drift");
457
- }
458
- }
459
- else if (report.compared_bisects > 0) {
460
- console.log(chalk.green(`✓ All ${report.stable_bisects} bisect chapters found the same exact boundary.`));
461
- }
462
- }
463
- const driftIds = new Set([...report.plan_drift_cells, ...report.outcome_drift_cells].map(cell => `${cell.chapter_id}:${cell.cell_id}`));
464
- const repeatablyUnrunnable = report.unrunnable_cells.filter(cell => !driftIds.has(`${cell.chapter_id}:${cell.cell_id}`));
465
- if (scope !== "planner" && repeatablyUnrunnable.length > 0) {
466
- console.log(chalk.yellow.bold(`${repeatablyUnrunnable.length} cell`
467
- + `${repeatablyUnrunnable.length === 1 ? "" : "s"} never produced a verdict:`));
468
- for (const cell of repeatablyUnrunnable) {
469
- renderCell(cell, "unrunnable");
470
- }
471
- }
472
- if (scope !== "planner" && report.unrunnable_bisects.length > 0) {
473
- console.log(chalk.yellow.bold(`${report.unrunnable_bisects.length} bisect chapter`
474
- + `${report.unrunnable_bisects.length === 1 ? "" : "s"} did not find a boundary:`));
475
- for (const bisect of report.unrunnable_bisects) {
476
- renderBisect(bisect, "unrunnable");
477
- }
478
- }
479
- if (report.incomplete_run_ids.length > 0
480
- || !scopeHasComparableWork) {
481
- console.log(chalk.yellow("Reproducibility is inconclusive because at least one run or cell did not produce the required result."));
482
- }
483
- }
484
- const scopeHasComparableWork = scope === "planner"
485
- ? report.compared_cells > 0
486
- : report.compared_cells > 0 || report.compared_bisects > 0;
487
- process.exitCode = report.incomplete_run_ids.length > 0
488
- || report.completed_runs < report.requested_runs
489
- || !scopeHasComparableWork
490
- ? 2
491
- : report.reproducible
492
- ? 0
493
- : 1;
494
- }
@@ -1,9 +0,0 @@
1
- export interface VerifyResealOptions {
2
- server?: string;
3
- repoRoot?: string;
4
- token?: string;
5
- source?: string;
6
- /** Skip the restart and only rebuild the bundle. */
7
- noRestart?: boolean;
8
- }
9
- export declare function verifyResealCommand(specimen: string, options: VerifyResealOptions): Promise<void>;