@yagni-app/code-staging 1.2.6-staging.1952.1 → 1.2.6-staging.1956.1

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 (30) hide show
  1. package/dist/extension/branding.d.ts +20 -0
  2. package/dist/extension/branding.js +49 -8
  3. package/dist/extension/index.js +5 -0
  4. package/dist/extension/pipeline/changeScan.d.ts +62 -0
  5. package/dist/extension/pipeline/changeScan.js +307 -0
  6. package/dist/extension/pipeline/cloud.d.ts +205 -0
  7. package/dist/extension/pipeline/cloud.js +382 -0
  8. package/dist/extension/pipeline/continuation.d.ts +7 -3
  9. package/dist/extension/pipeline/continuation.js +29 -13
  10. package/dist/extension/pipeline/findings.d.ts +9 -4
  11. package/dist/extension/pipeline/findings.js +25 -10
  12. package/dist/extension/pipeline/finish.d.ts +11 -1
  13. package/dist/extension/pipeline/finish.js +51 -5
  14. package/dist/extension/pipeline/headlessGo.d.ts +20 -1
  15. package/dist/extension/pipeline/headlessGo.js +205 -33
  16. package/dist/extension/pipeline/orchestrator.d.ts +8 -2
  17. package/dist/extension/pipeline/orchestrator.js +249 -68
  18. package/dist/extension/pipeline/personas.d.ts +8 -3
  19. package/dist/extension/pipeline/personas.js +42 -7
  20. package/dist/extension/pipeline/planOutput.d.ts +42 -0
  21. package/dist/extension/pipeline/planOutput.js +94 -0
  22. package/dist/extension/pipeline/stages.d.ts +34 -5
  23. package/dist/extension/pipeline/stages.js +136 -7
  24. package/dist/extension/pipeline/tierCap.d.ts +22 -1
  25. package/dist/extension/pipeline/tierCap.js +48 -1
  26. package/dist/extension/pipeline/types.d.ts +31 -0
  27. package/dist/extension/pipeline/types.js +2 -0
  28. package/dist/extension/pipeline/verify.d.ts +33 -0
  29. package/dist/extension/pipeline/verify.js +101 -9
  30. package/package.json +2 -2
@@ -34,16 +34,24 @@ export function parseContinuation(raw) {
34
34
  const obj = data;
35
35
  if (obj.version !== 1)
36
36
  return { ok: false, reason: `unknown version ${JSON.stringify(obj.version)}` };
37
- const stage = obj.stageAtStop;
37
+ // The cloud lane's `continue.json` names the same facts `stage` and
38
+ // `roundsDone`; either spelling reads the same.
39
+ const stage = "stage" in obj ? obj.stage : obj.stageAtStop;
38
40
  if (stage !== null && !CONTINUATION_STAGES.includes(stage)) {
39
41
  return { ok: false, reason: `unknown stageAtStop ${JSON.stringify(stage)}` };
40
42
  }
41
- const rounds = obj.reviewRoundsDone;
43
+ const rounds = "roundsDone" in obj ? obj.roundsDone : obj.reviewRoundsDone;
42
44
  if (typeof rounds !== "number" || !Number.isInteger(rounds) || rounds < 0) {
43
- return { ok: false, reason: `reviewRoundsDone is not a whole number (${JSON.stringify(rounds)})` };
45
+ return {
46
+ ok: false,
47
+ reason: `reviewRoundsDone is not a whole number (${JSON.stringify(rounds)})`,
48
+ };
44
49
  }
45
50
  const priorStop = typeof obj.priorStop === "string" ? obj.priorStop.trim() : "";
46
- return { ok: true, value: { stageAtStop: stage, reviewRoundsDone: rounds, priorStop } };
51
+ return {
52
+ ok: true,
53
+ value: { stageAtStop: stage, reviewRoundsDone: rounds, priorStop },
54
+ };
47
55
  }
48
56
  /** True when the earlier attempt got past implement, so the run re-enters the review loop. */
49
57
  export function continuesInLoop(continuation) {
@@ -56,27 +64,35 @@ export function continuesInLoop(continuation) {
56
64
  * continuation has no summary, so it says where the change is instead and pins
57
65
  * the same design context.
58
66
  */
59
- export function continuationReviewInput(continuation, mission) {
67
+ export function continuationReviewInput(continuation, mission, committed = false) {
60
68
  const why = continuation.priorStop ? ` (${continuation.priorStop})` : "";
61
- const summary = `No implement summary: this build continues an earlier attempt that stopped during its ${continuation.stageAtStop ?? "unknown"} stage${why}. ` +
62
- "That attempt's change is already in the working tree as uncommitted edits on top of HEAD. " +
63
- "Review the whole change against HEAD (`git diff HEAD`, plus any untracked files `git status` lists).";
69
+ const stopped = `this build continues an earlier attempt that stopped during its ${continuation.stageAtStop ?? "unknown"} stage${why}. `;
70
+ // A cloud execution starts from the attempt's pushed branch: the earlier
71
+ // work is in its commits, not in the working tree.
72
+ const summary = committed
73
+ ? `No implement summary: ${stopped}` +
74
+ "That attempt's change is committed on this branch. Review the whole change against the base commit the ticket names " +
75
+ "(`git diff <base>...HEAD`), plus anything `git status` lists."
76
+ : `No implement summary: ${stopped}` +
77
+ "That attempt's change is already in the working tree as uncommitted edits on top of HEAD. " +
78
+ "Review the whole change against HEAD (`git diff HEAD`, plus any untracked files `git status` lists).";
64
79
  const design = missionDesignContext(mission);
65
80
  return design ? `${summary}\n\n${design}` : summary;
66
81
  }
67
82
  /**
68
83
  * The resume plan that re-enters the review→fix loop for a continuation that
69
84
  * stopped in review, fix or verify; undefined when implement must run again.
70
- * The round continues the earlier attempt's count, capped at the last round so
71
- * a continuation always gets one review.
85
+ * The round continues the earlier attempt's count, capped at the lane's last
86
+ * round so a continuation always gets one review. `committed` is the cloud
87
+ * lane's resume, whose earlier work is on the branch.
72
88
  */
73
- export function continuationResumePlan(continuation, ctx) {
89
+ export function continuationResumePlan(continuation, ctx, opts = {}) {
74
90
  if (!continuesInLoop(continuation))
75
91
  return undefined;
76
92
  return {
77
93
  mode: "resume_loop",
78
- round: Math.min(continuation.reviewRoundsDone + 1, MAX_REVIEW_ROUNDS),
79
- reviewInput: continuationReviewInput(continuation, ctx.mission),
94
+ round: Math.min(continuation.reviewRoundsDone + 1, opts.maxRounds ?? MAX_REVIEW_ROUNDS),
95
+ reviewInput: continuationReviewInput(continuation, ctx.mission, opts.committed),
80
96
  priorRounds: [],
81
97
  ticket: ctx.ticket,
82
98
  ...(ctx.runId ? { runId: ctx.runId } : {}),
@@ -24,9 +24,13 @@ export declare function extractFindingsBlock(raw: string): string | null;
24
24
  /**
25
25
  * Parse a reviewer's raw output into structured findings. Tries the JSON-array
26
26
  * form first, then the fenced ```findings block, then a tolerant scan of every
27
- * line. Never throws; never fabricates a finding from prose.
27
+ * line. Never throws; never fabricates a finding from prose. `lensColumn` reads
28
+ * the one reviewer's per-finding lens; `lens` is then the default for a line
29
+ * that names none.
28
30
  */
29
- export declare function parseFindings(raw: string, lens?: ReviewLens): Finding[];
31
+ export declare function parseFindings(raw: string, lens?: ReviewLens, opts?: {
32
+ lensColumn?: boolean;
33
+ }): Finding[];
30
34
  /** Concatenate per-lens findings, preserving order and each lens tag. */
31
35
  export declare function unionFindings(perLens: Finding[][]): Finding[];
32
36
  /** True when this one finding blocks: Critical or High on a blocking lens. */
@@ -38,9 +42,10 @@ export declare function hasBlockingFindings(findings: Finding[]): boolean;
38
42
  * blocks, else stop 'round_cap' once the round hits MAX_REVIEW_ROUNDS, else
39
43
  * keep going. Always returns an explicit reason (no silent truncation). So a
40
44
  * second round runs only when round one found a blocker; nits, needs-person
41
- * findings and business-fit findings alone never start it.
45
+ * findings and business-fit findings alone never start it. `maxRounds` is the
46
+ * lane's cap (`PipelineLane.maxReviewRounds`).
42
47
  */
43
- export declare function shouldStop(findings: Finding[], round: number): {
48
+ export declare function shouldStop(findings: Finding[], round: number, maxRounds?: number): {
44
49
  stop: boolean;
45
50
  reason: "clean" | "round_cap" | null;
46
51
  };
@@ -18,7 +18,10 @@ const SEVERITIES = ["critical", "high", "medium", "low", "needs_person"];
18
18
  const DEFAULT_LENS = "correctness";
19
19
  /** `NEEDS PERSON`, `needs-person` and `needs_person` all read as `needs_person`. */
20
20
  function normalizeSeverity(value) {
21
- return value.trim().toLowerCase().replace(/[\s-]+/g, "_");
21
+ return value
22
+ .trim()
23
+ .toLowerCase()
24
+ .replace(/[\s-]+/g, "_");
22
25
  }
23
26
  function isSeverity(value) {
24
27
  return SEVERITIES.includes(value);
@@ -33,14 +36,23 @@ function parseLocation(loc) {
33
36
  return { file: m[1], line: Number(m[2]) };
34
37
  return { file: trimmed };
35
38
  }
36
- /** Parse one `SEVERITY | file:line | message` line, or null if it is not one. */
37
- function parsePipeLine(line, lens) {
38
- const parts = line.split("|").map((p) => p.trim());
39
+ /**
40
+ * Parse one `SEVERITY | file:line | message` line, or null if it is not one.
41
+ * With `lensColumn` (the one reviewer of the cloud lane), a lens name after the
42
+ * severity (`SEVERITY | LENS | file:line | message`) tags the finding.
43
+ */
44
+ function parsePipeLine(line, lens, lensColumn = false) {
45
+ let parts = line.split("|").map((p) => p.trim());
39
46
  if (parts.length < 2)
40
47
  return null;
41
48
  const severity = normalizeSeverity(parts[0]);
42
49
  if (!isSeverity(severity))
43
50
  return null;
51
+ const named = lensColumn && parts.length >= 3 ? normalizeSeverity(parts[1]) : "";
52
+ if (isLens(named)) {
53
+ lens = named;
54
+ parts = [parts[0], ...parts.slice(2)];
55
+ }
44
56
  let finding;
45
57
  if (parts.length >= 3) {
46
58
  const { file, line: lineNo } = parseLocation(parts[1]);
@@ -108,9 +120,11 @@ function isLens(value) {
108
120
  /**
109
121
  * Parse a reviewer's raw output into structured findings. Tries the JSON-array
110
122
  * form first, then the fenced ```findings block, then a tolerant scan of every
111
- * line. Never throws; never fabricates a finding from prose.
123
+ * line. Never throws; never fabricates a finding from prose. `lensColumn` reads
124
+ * the one reviewer's per-finding lens; `lens` is then the default for a line
125
+ * that names none.
112
126
  */
113
- export function parseFindings(raw, lens = DEFAULT_LENS) {
127
+ export function parseFindings(raw, lens = DEFAULT_LENS, opts = {}) {
114
128
  if (!raw || !raw.trim())
115
129
  return [];
116
130
  const json = parseJsonFindings(raw, lens);
@@ -120,7 +134,7 @@ export function parseFindings(raw, lens = DEFAULT_LENS) {
120
134
  const source = block ?? raw;
121
135
  const findings = [];
122
136
  for (const line of source.split("\n")) {
123
- const parsed = parsePipeLine(line, lens);
137
+ const parsed = parsePipeLine(line, lens, opts.lensColumn === true);
124
138
  if (parsed)
125
139
  findings.push(parsed);
126
140
  }
@@ -152,12 +166,13 @@ export function hasBlockingFindings(findings) {
152
166
  * blocks, else stop 'round_cap' once the round hits MAX_REVIEW_ROUNDS, else
153
167
  * keep going. Always returns an explicit reason (no silent truncation). So a
154
168
  * second round runs only when round one found a blocker; nits, needs-person
155
- * findings and business-fit findings alone never start it.
169
+ * findings and business-fit findings alone never start it. `maxRounds` is the
170
+ * lane's cap (`PipelineLane.maxReviewRounds`).
156
171
  */
157
- export function shouldStop(findings, round) {
172
+ export function shouldStop(findings, round, maxRounds = MAX_REVIEW_ROUNDS) {
158
173
  if (!hasBlockingFindings(findings))
159
174
  return { stop: true, reason: "clean" };
160
- if (round >= MAX_REVIEW_ROUNDS)
175
+ if (round >= maxRounds)
161
176
  return { stop: true, reason: "round_cap" };
162
177
  return { stop: false, reason: null };
163
178
  }
@@ -85,6 +85,8 @@ export interface FinishResult {
85
85
  /** Honest skip/failure notes, surfaced verbatim in the /go handoff. */
86
86
  notes: string[];
87
87
  }
88
+ /** FINISH's own git seam, for the cloud session's reads of what it committed. */
89
+ export declare const finishExec: WorktreeExec;
88
90
  /** `fix` when the ticket brief (or, blind, the raw arg) says bug; else `feat` (spec §3c). */
89
91
  export declare function commitType(ticket: string, ticketBrief?: string): "fix" | "feat";
90
92
  /**
@@ -93,6 +95,12 @@ export declare function commitType(ticket: string, ticketBrief?: string): "fix"
93
95
  * already carries the identifier), falling back to the raw arg. Capped.
94
96
  */
95
97
  export declare function commitTitle(ticket: string, ticketBrief?: string): string;
98
+ /**
99
+ * The title a run with a plan and no ticket title commits under (the cloud
100
+ * session reads its ticket from a file): the line under the plan's Goal
101
+ * heading, else its first line of prose. Undefined when the plan has neither.
102
+ */
103
+ export declare function planTitle(planText: string | undefined): string | undefined;
96
104
  /**
97
105
  * A conventional-commit scope from the run's changed paths: the package name
98
106
  * when every changed path agrees on one (`packages/<name>/…`, `apps/…`,
@@ -111,11 +119,13 @@ export declare function summarizePlan(planText: string | undefined, ticket: stri
111
119
  /**
112
120
  * The trailer's YAGNI-Verify value, derived honestly from the pipeline result:
113
121
  * `passed (<cmds>)` when the final round's verify gate ran (verify-FAILED can't
114
- * reach `clean` by construction), else `skipped (<reason>)`.
122
+ * reach `clean` by construction), else `skipped (<reason>)`. A run that did not
123
+ * stop `clean` (the cloud lane commits every run's work) is `not passed (<stop>)`.
115
124
  */
116
125
  export declare function verifyTrailerValue(result: {
117
126
  verifyCommand?: string;
118
127
  verifyNote?: string;
128
+ stopReason?: string;
119
129
  }): string;
120
130
  /** The exact provenance trailer block (spec §3c), closed by the org's commit attribution. */
121
131
  export declare function provenanceTrailer(t: TrailerInputs): string;
@@ -58,6 +58,8 @@ const defaultExec = (argv, cwd, signal) => new Promise((resolve, reject) => {
58
58
  // so quitting pi mid-FINISH never leaves it running in the background.
59
59
  trackChild(child);
60
60
  });
61
+ /** FINISH's own git seam, for the cloud session's reads of what it committed. */
62
+ export const finishExec = defaultExec;
61
63
  // ---------------------------------------------------------------------------
62
64
  // Pure message derivation
63
65
  // ---------------------------------------------------------------------------
@@ -71,12 +73,30 @@ export function commitType(ticket, ticketBrief) {
71
73
  * already carries the identifier), falling back to the raw arg. Capped.
72
74
  */
73
75
  export function commitTitle(ticket, ticketBrief) {
74
- let title = ticketBrief?.split("\n").find((l) => l.trim().length > 0)?.trim() ?? ticket;
76
+ let title = ticketBrief
77
+ ?.split("\n")
78
+ .find((l) => l.trim().length > 0)
79
+ ?.trim() ?? ticket;
75
80
  const echo = `${ticket.toLowerCase()}:`;
76
81
  if (title.toLowerCase().startsWith(echo))
77
82
  title = title.slice(echo.length).trim() || ticket;
78
83
  return title.length > TITLE_MAX ? title.slice(0, TITLE_MAX).trimEnd() : title;
79
84
  }
85
+ /**
86
+ * The title a run with a plan and no ticket title commits under (the cloud
87
+ * session reads its ticket from a file): the line under the plan's Goal
88
+ * heading, else its first line of prose. Undefined when the plan has neither.
89
+ */
90
+ export function planTitle(planText) {
91
+ const lines = (planText ?? "").split("\n").map((line) => line.trim());
92
+ const prose = (from) => lines.slice(from).find((line) => line && !line.startsWith("#") && !line.startsWith("```"));
93
+ const goal = lines.findIndex((line) => /^#+\s*goal\b/i.test(line));
94
+ const line = (goal >= 0 ? prose(goal + 1) : undefined) ?? prose(0);
95
+ return (line
96
+ ?.replace(/^(?:[-*+]|\d+[.)])\s+/, "")
97
+ .replace(/`/g, "")
98
+ .trim() || undefined);
99
+ }
80
100
  /**
81
101
  * A conventional-commit scope from the run's changed paths: the package name
82
102
  * when every changed path agrees on one (`packages/<name>/…`, `apps/…`,
@@ -125,9 +145,14 @@ export function summarizePlan(planText, ticket) {
125
145
  /**
126
146
  * The trailer's YAGNI-Verify value, derived honestly from the pipeline result:
127
147
  * `passed (<cmds>)` when the final round's verify gate ran (verify-FAILED can't
128
- * reach `clean` by construction), else `skipped (<reason>)`.
148
+ * reach `clean` by construction), else `skipped (<reason>)`. A run that did not
149
+ * stop `clean` (the cloud lane commits every run's work) is `not passed (<stop>)`.
129
150
  */
130
151
  export function verifyTrailerValue(result) {
152
+ if (result.stopReason && result.stopReason !== "clean") {
153
+ const ran = result.verifyCommand ? `; ran ${oneLine(result.verifyCommand)}` : "";
154
+ return `not passed (${result.stopReason}${ran})`;
155
+ }
131
156
  if (result.verifyCommand)
132
157
  return `passed (${oneLine(result.verifyCommand)})`;
133
158
  return `skipped (${oneLine(result.verifyNote ?? "verify did not run")})`;
@@ -204,7 +229,13 @@ export function parsePrUrl(output) {
204
229
  * delivered as the durable /go handoff, landing in permanent scrollback.
205
230
  */
206
231
  function tail(output) {
207
- const t = scrubSecrets(output).trim().split("\n").slice(-3).join(" ").slice(0, NOTE_TAIL_MAX).trim();
232
+ const t = scrubSecrets(output)
233
+ .trim()
234
+ .split("\n")
235
+ .slice(-3)
236
+ .join(" ")
237
+ .slice(0, NOTE_TAIL_MAX)
238
+ .trim();
208
239
  return t;
209
240
  }
210
241
  /**
@@ -311,7 +342,15 @@ export async function runFinish(opts) {
311
342
  notes.push(`Committed locally; push failed: ${tail(push.output) || "git push failed"}.`);
312
343
  return result;
313
344
  }
314
- const pr = await run(["gh", "pr", "create", "--title", subject, "--body", buildPrBody(summary, trailer, opts.remainingFindings, opts.prAttribution)]);
345
+ const pr = await run([
346
+ "gh",
347
+ "pr",
348
+ "create",
349
+ "--title",
350
+ subject,
351
+ "--body",
352
+ buildPrBody(summary, trailer, opts.remainingFindings, opts.prAttribution),
353
+ ]);
315
354
  if (pr.code !== 0) {
316
355
  notes.push(`Committed and pushed ${opts.branch}; PR creation failed: ${tail(pr.output) || "gh failed"}.`);
317
356
  return result;
@@ -327,7 +366,14 @@ export async function runFinish(opts) {
327
366
  // Belt and braces: nothing above should throw, but the invariant (§0.1)
328
367
  // outranks tidiness — a FINISH failure must never fail a clean run.
329
368
  const msg = scrubSecrets(err instanceof Error ? err.message : String(err));
330
- return { committed: false, ...(opts.branch ? { branch: opts.branch } : {}), notes: [...notes, `FINISH failed: ${msg}. The run itself is still clean; the changes are left in ${opts.cwd}.`] };
369
+ return {
370
+ committed: false,
371
+ ...(opts.branch ? { branch: opts.branch } : {}),
372
+ notes: [
373
+ ...notes,
374
+ `FINISH failed: ${msg}. The run itself is still clean; the changes are left in ${opts.cwd}.`,
375
+ ],
376
+ };
331
377
  }
332
378
  }
333
379
  //# sourceMappingURL=finish.js.map
@@ -24,6 +24,17 @@
24
24
  * run ends at the reviewed candidate and never commits, pushes, opens a PR, or
25
25
  * reports one. The mission's own prHandoff owns all of that.
26
26
  *
27
+ * CLOUD (`--cloud`, the one-attempt lane; cloud.ts): the session runs the
28
+ * cloud lane (stages.ts `cloudLane`: plan, implement, review, fix, then verify
29
+ * and finish; one writer, one reviewer, four rounds) on the host's input
30
+ * folder beside the ticket and its env (`YAGNI_ATTEMPT_ID`, `YAGNI_EXECUTION_ID`,
31
+ * `YAGNI_GENERATION`, `YAGNI_MISSION_OUT_DIR`, `YAGNI_GO_STAGE_TIERS`). A fresh
32
+ * attempt plans and stops, writing `plan.md` and `plan.json`; a plan file
33
+ * starts implement; a continue file names its stage. Every execution but a
34
+ * plan commits its work locally on the branch the host checked out, whatever
35
+ * its stop, and never pushes. Its last act is `result.json`, mirrored on the
36
+ * last `pipeline_result` line. Mission mode below is untouched by it.
37
+ *
27
38
  * CONTINUATION (`--continue-file`, mission mode only): the run continues an
28
39
  * earlier attempt whose change is already in the tree as uncommitted edits. The
29
40
  * build baselines the clean base, and an attempt that stopped in review, fix or
@@ -59,13 +70,15 @@
59
70
  * cannot finish, rather than being killed partway through one (see budget.ts). Such a run gets the sandbox lane's
60
71
  * ceilings (`SANDBOX_RUN_BUDGET`), not interactive /go's.
61
72
  */
73
+ import { snapshotWorkspace } from "./workspace.js";
74
+ import type { WorktreeExec } from "./worktree.js";
62
75
  import { type TreeSnapshotDeps } from "./treeSnapshot.js";
63
76
  import { runPipeline as defaultRunPipeline } from "./orchestrator.js";
64
77
  import { makeRunVerify, type VerifyBaselineResult } from "./verify.js";
65
78
  import { type RunBudget } from "./budget.js";
66
79
  import type { Finding, JsonEvent, ModelTier, PipelineStage, StageId, StageTag, StageUsage, StopReason } from "./types.js";
67
80
  /** One-line usage copy, shared by every argument error. */
68
- export declare const HEADLESS_GO_USAGE = "Usage: yagni go --headless --ticket-file <path> [--plan-file <path>] [--memo-file <path>] [--continue-file <path>] [--run-id <id>] [--cwd <path>] [--json]";
81
+ export declare const HEADLESS_GO_USAGE = "Usage: yagni go --headless [--cloud] --ticket-file <path> [--plan-file <path>] [--memo-file <path>] [--continue-file <path>] [--run-id <id>] [--cwd <path>] [--json]";
69
82
  /**
70
83
  * Exit codes. `verified` is deliberately narrow: only a `clean` stop means the
71
84
  * run produced a reviewed candidate, so a round_cap / no_changes / failed run
@@ -80,6 +93,8 @@ export declare const HEADLESS_GO_EXIT: {
80
93
  export interface HeadlessGoArgs {
81
94
  headless: boolean;
82
95
  json: boolean;
96
+ /** The one-attempt lane's session (cloud.ts): its own stage list and outputs. */
97
+ cloud: boolean;
83
98
  ticketFile?: string;
84
99
  planFile?: string;
85
100
  memoFile?: string;
@@ -184,6 +199,10 @@ export interface HeadlessGoDeps {
184
199
  logger?: (event: string, data?: unknown) => void;
185
200
  /** Mission runs' working-tree snapshots (see treeSnapshot.ts); injected in tests. */
186
201
  startTreeSnapshots?: (cwd: string, snapshotDeps: TreeSnapshotDeps) => () => Promise<void>;
202
+ /** A cloud session's git seam: its commit and the reads behind `result.json`. Defaults to FINISH's. */
203
+ exec?: WorktreeExec;
204
+ /** A cloud session's tree reads (HEAD and porcelain status). Defaults to a real `git`. */
205
+ snapshotWorkspace?: typeof snapshotWorkspace;
187
206
  }
188
207
  /**
189
208
  * Parse the argv remainder after `go`. Accepts both `--flag value` and