@smartergpt/lexrunner 1.2.1 → 1.3.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.3.0] - 2026-07-30
10
+
11
+ ### Added
12
+
13
+ - **Windows-to-native-WSL Attempt projection** - Added a read-only containment preflight,
14
+ versioned projection and path-mapping contracts, rollback-safe native provisioning, and
15
+ identity-bound launch, worker, receipt, verification, acceptance, and cleanup behavior.
16
+ - **Shared projection lifecycle** - Added matching CLI
17
+ `attempt projection prepare|status|cleanup|quarantine` operations and MCP lifecycle tools with
18
+ explicit mutation authority, path-private diagnostics, bounded quarantine inspection, and a
19
+ recovery runbook.
20
+ - **Independent merge review gate** - Documented a mandatory-by-default, exact-head review step
21
+ whose verdict is invalidated by candidate changes and whose opt-out requires an explicit warned
22
+ `review_bypassed` receipt.
23
+
24
+ ### Fixed
25
+
26
+ - **Plan validation diagnostics** - Return the individual validation failures instead of only an
27
+ aggregate count, preserving the dogfood evidence needed to repair invalid plans.
28
+ - **Explicit plan references** - Resolve explicitly selected plan artifacts consistently across
29
+ MCP status, ordering, and gate operations.
30
+ - **Projection durability and recovery** - Fail closed across selection revocation/publication,
31
+ staging ownership, quarantine marker persistence, directory sync failures, source refresh, and
32
+ active-worktree cleanup refusal.
33
+
9
34
  ---
10
35
 
11
36
  ## [1.2.1] - 2026-07-21
package/README.mcp.md CHANGED
@@ -113,6 +113,40 @@ This MCP server follows the same architectural pattern as LexBrain and LexMap:
113
113
 
114
114
  ## Available Tools
115
115
 
116
+ ### preflight_attempt_containment
117
+
118
+ Checks whether the current runtime and declared repository/worktree roots can host LexRunner's
119
+ physical directory-identity boundary before an Attempt packet is constructed. This tool is always
120
+ read-only and does not require `ALLOW_MUTATIONS`.
121
+
122
+ It returns `native_ready`, `broker_required`, or `unsupported` with stable reason codes, bounded
123
+ next actions, verification depth for each root, and a privacy-safe binding digest. Native Windows
124
+ and WSL DrvFS/9P inputs recommend a native-WSL projection without weakening the containment
125
+ boundary.
126
+
127
+ ### Native WSL projection lifecycle
128
+
129
+ Four tools share one bounded lifecycle service with the matching
130
+ `lex-pr attempt projection <operation> --input <file|-> --json` commands:
131
+
132
+ - `get_native_wsl_projection_status` reads exact lifecycle state without creating control, lock,
133
+ projection, or SQLite state.
134
+ - `inspect_native_wsl_projection_quarantine` reads privacy-bounded quarantine counts and entry
135
+ digests.
136
+ - `prepare_native_wsl_projection` prepares or exactly reuses the requested committed base.
137
+ - `cleanup_native_wsl_projection` removes an exact idle projection and its matching quarantine
138
+ state; active worktrees are refused.
139
+
140
+ Prepare and cleanup require both `"mutation": {"authorized": true}` in the input and the MCP
141
+ server mutation gate. Status and quarantine inspection remain available when mutations are
142
+ disabled. All results omit source/native paths and expose stable digests, reason codes, bounded
143
+ command evidence, counts, and next actions.
144
+
145
+ On success, pass the returned `selectionDigest` to projected `attempt prepare`. Launch resolves
146
+ the engine-authored selection, emits the machine-verifiable execution path mapping, and preserves
147
+ the request's exact `base_sha` even when the Windows source HEAD or dirty state later changes.
148
+ See the [native Windows-to-WSL projection workflow](docs/workflows/native-wsl-projection.md).
149
+
116
150
  ### mcp_lexrunner_plan_create
117
151
 
118
152
  Creates a plan from configuration files or auto-discovers from GitHub PRs. This is a **convenience wrapper** that combines PR discovery (`pr_list`), plan generation, validation, and file writing into a single operation.
@@ -428,11 +462,21 @@ Executes gates for plan items. Can work with either an internal plan (created vi
428
462
 
429
463
  > **Deprecated alias:** `gates.run`
430
464
 
465
+ All overlapping integration tools use the same plan-reference precedence:
466
+
467
+ 1. `planFile`, when supplied. The explicit reference is authoritative and never falls through.
468
+ 2. `plan.json` in the repository root.
469
+ 3. `<profile>/runner/plan.json` as the backward-compatible `plan_create` fallback.
470
+
471
+ Their results include the same path-independent `planArtifact` identity. Compare its canonical
472
+ `digest` to verify that status, merge ordering, gate execution, and merge preview consumed the
473
+ same frozen plan.
474
+
431
475
  **Parameters:**
432
476
 
433
- - `planFile` (string, optional): Path to external plan.json file. If not provided, uses internal state from the profile directory.
434
- - `onlyItem` (string, optional): Run gates for specific item only
435
- - `onlyGate` (string, optional): Run specific gate only
477
+ - `planFile` (string, optional): Explicit path to an authored plan
478
+ - `onlyItem` (string, optional): Execute gates for this item only
479
+ - `onlyGate` (string, optional): Execute this gate only
436
480
  - `outDir` (string, optional): Output directory for gate results
437
481
 
438
482
  **Returns:**
@@ -451,11 +495,20 @@ Executes gates for plan items. Can work with either an internal plan (created vi
451
495
  ]
452
496
  }
453
497
  ],
454
- "allGreen": true
498
+ "allGreen": true,
499
+ "planArtifact": {
500
+ "contract": "plan-artifact-identity-v1",
501
+ "kind": "execution-plan",
502
+ "schema": "lexrunner.execution-plan",
503
+ "schemaVersion": "1.0.0",
504
+ "digest": "sha256:...",
505
+ "target": "main",
506
+ "itemCount": 1
507
+ }
455
508
  }
456
509
  ```
457
510
 
458
- **Example (using internal plan):**
511
+ **Example (using repository or profile fallback):**
459
512
 
460
513
  ```json
461
514
  {
@@ -481,7 +534,8 @@ Executes gates for plan items. Can work with either an internal plan (created vi
481
534
 
482
535
  **Use Cases:**
483
536
 
484
- - **Internal state**: Run gates on a plan created via `mcp_lexrunner_plan_create` (default behavior)
537
+ - **Authored plan**: Run gates directly from a repository-root or explicitly referenced plan
538
+ - **Profile fallback**: Continue using a plan created via `mcp_lexrunner_plan_create`
485
539
  - **External orchestration**: Run gates on programmatically-created or externally-managed plan files
486
540
  - **Parallel workflows**: Execute gates on multiple independent plans in parallel merge-weave operations
487
541
 
@@ -493,14 +547,29 @@ Applies merge operations with environment-based gating.
493
547
 
494
548
  **Parameters:**
495
549
 
550
+ - `planFile` (string, optional): Explicit path to an authored plan, using the same fallback precedence as gate execution
496
551
  - `dryRun` (boolean, optional): Simulate merge without making changes (default: `true`)
497
552
 
498
553
  **Returns:**
499
554
 
500
555
  ```json
501
556
  {
502
- "allowed": false,
503
- "message": "Mutations not allowed. Set ALLOW_MUTATIONS=true or use dryRun=true."
557
+ "mode": "dry-run",
558
+ "dryRun": true,
559
+ "ok": true,
560
+ "status": "preview",
561
+ "totalItems": 1,
562
+ "levels": [["item1"]],
563
+ "artifactRefs": [],
564
+ "planArtifact": {
565
+ "contract": "plan-artifact-identity-v1",
566
+ "kind": "execution-plan",
567
+ "schema": "lexrunner.execution-plan",
568
+ "schemaVersion": "1.0.0",
569
+ "digest": "sha256:...",
570
+ "target": "main",
571
+ "itemCount": 1
572
+ }
504
573
  }
505
574
  ```
506
575
 
@@ -510,6 +579,7 @@ Applies merge operations with environment-based gating.
510
579
  {
511
580
  "name": "mcp_lexrunner_weave_apply",
512
581
  "arguments": {
582
+ "planFile": "plan.json",
513
583
  "dryRun": true
514
584
  }
515
585
  }
@@ -580,6 +650,7 @@ const externalGatesResult = await client.callTool("mcp_lexrunner_gate_run", {
580
650
 
581
651
  // Check merge eligibility (dry run)
582
652
  const mergeResult = await client.callTool("mcp_lexrunner_weave_apply", {
653
+ planFile: "/tmp/merge-batch/plan.json",
583
654
  dryRun: true,
584
655
  });
585
656
  ```
package/README.md CHANGED
@@ -111,13 +111,14 @@ The checked-in package version is the single source for `lex-pr --version`.
111
111
 
112
112
  <!-- BEGIN GENERATED PACKAGE VERSION -->
113
113
 
114
- Current repository package version: **1.2.1**. npm availability and dist-tags are separate
114
+ Current repository package version: **1.3.0**. npm availability and dist-tags are separate
115
115
  release evidence; inspect the registry rather than inferring publication from source metadata.
116
116
  <!-- END GENERATED PACKAGE VERSION -->
117
117
 
118
- See the [1.2.1 publication repair](docs/releases/1.2.1.md) and underlying
119
- [1.2.0 compatibility decision](docs/releases/1.2.0.md) for the package disposition, semver
120
- rationale, supported assisted behavior, and deferred headless guarantees.
118
+ See the [1.3.0 release notes](docs/releases/1.3.0.md), the
119
+ [1.2.1 publication repair](docs/releases/1.2.1.md), and the underlying
120
+ [1.2.0 compatibility decision](docs/releases/1.2.0.md) for package disposition, semver rationale,
121
+ supported assisted behavior, and deferred guarantees.
121
122
 
122
123
  ## Choose a surface
123
124
 
@@ -125,8 +126,13 @@ rationale, supported assisted behavior, and deferred headless guarantees.
125
126
  `weave merge-order`, and `gate run`.
126
127
  - **Agent/MCP:** use the matching canonical tools and bounded contracts in
127
128
  [`docs/AX.md`](docs/AX.md) and [`README.mcp.md`](README.mcp.md).
128
- - **Assisted agent work:** use `attempt prepare`, `attempt start`, worker attachment/heartbeat,
129
- receipt submission, verification, and acceptance. The foreground host still owns worker launch.
129
+ - **Assisted agent work:** run `attempt preflight` before packet construction. When it reports
130
+ `broker_required`, use `attempt projection status|prepare` to bind the exact committed base into
131
+ native WSL, then continue with `attempt prepare`, `attempt start`, worker
132
+ attachment/heartbeat, receipt submission, verification, and acceptance. The foreground host
133
+ still owns worker launch. Follow the
134
+ [native Windows-to-WSL projection workflow](docs/workflows/native-wsl-projection.md) for recovery
135
+ and cleanup.
130
136
  - **Advanced operators:** read the
131
137
  [orchestration primitives](docs/architecture/orchestration-primitives.md),
132
138
  [headless supervisor boundary](docs/architecture/headless-supervisor.md), and
@@ -156,6 +156,8 @@ declare function gitOperationError(ctx: GitOperationContext): AXError;
156
156
  interface PlanValidationContext {
157
157
  errors: string[];
158
158
  planPath?: string;
159
+ errorCount?: number;
160
+ errorsTruncated?: boolean;
159
161
  }
160
162
  /**
161
163
  * Create an AXError for plan validation failures
@@ -16,12 +16,12 @@ import {
16
16
  hasCapability,
17
17
  parseAutopilotConfig,
18
18
  validateAutopilotConfig
19
- } from "./chunk-6HASN2LZ.js";
19
+ } from "./chunk-WSJEPY7T.js";
20
20
  import "./chunk-4FFGNTAV.js";
21
21
  import "./chunk-6A4IE3TI.js";
22
- import "./chunk-6TB2UCBZ.js";
22
+ import "./chunk-VAGHAQT2.js";
23
23
  import "./chunk-UHFMFO54.js";
24
- import "./chunk-L7YDEIPO.js";
24
+ import "./chunk-26SCN2LJ.js";
25
25
  import "./chunk-TPVORFSN.js";
26
26
  import "./chunk-55VWJYZY.js";
27
27
  import "./chunk-AXOAVRTU.js";
@@ -148,15 +148,23 @@ function gitOperationError(ctx) {
148
148
  }
149
149
  function planValidationError(ctx) {
150
150
  const nextActions = ["Review the plan file for schema violations"];
151
+ const errorCount = ctx.errorCount ?? ctx.errors.length;
151
152
  if (ctx.planPath) {
152
153
  nextActions.push(`Plan file: ${ctx.planPath}`);
153
154
  }
154
- nextActions.push(`Errors: ${ctx.errors.join("; ")}`);
155
+ if (ctx.errors.length > 0) {
156
+ nextActions.push(`Errors: ${ctx.errors.join("; ")}`);
157
+ }
158
+ if (ctx.errorsTruncated) {
159
+ nextActions.push(
160
+ `Only the first ${ctx.errors.length} of ${errorCount} validation errors are included`
161
+ );
162
+ }
155
163
  return createAXError(
156
164
  ErrorCodes.PLAN_VALIDATION_FAILED,
157
- `Plan validation failed with ${ctx.errors.length} error(s)`,
165
+ `Plan validation failed with ${errorCount} error(s)`,
158
166
  nextActions,
159
- { ...ctx }
167
+ { ...ctx, errorCount, errorsTruncated: ctx.errorsTruncated ?? false }
160
168
  );
161
169
  }
162
170
  function toAXError(error, code = ErrorCodes.INTERNAL_ERROR, nextActions) {
@@ -503,6 +511,12 @@ var ErrorCodes = {
503
511
  PLAN_VALIDATION_FAILED: "PLAN_VALIDATION_FAILED",
504
512
  /** Plan file not found */
505
513
  PLAN_NOT_FOUND: "PLAN_NOT_FOUND",
514
+ /** Explicit plan reference is empty, overlong, or otherwise invalid */
515
+ PLAN_REFERENCE_INVALID: "PLAN_REFERENCE_INVALID",
516
+ /** Plan reference exists but cannot be read as a bounded regular file */
517
+ PLAN_UNREADABLE: "PLAN_UNREADABLE",
518
+ /** Plan exceeds the bounded integration artifact limits */
519
+ PLAN_ARTIFACT_LIMIT_EXCEEDED: "PLAN_ARTIFACT_LIMIT_EXCEEDED",
506
520
  /** Cycle detected in plan item dependencies */
507
521
  PLAN_CYCLE_DETECTED: "PLAN_CYCLE_DETECTED",
508
522
  /** Referenced dependency not found in plan */
@@ -2,7 +2,7 @@ import {
2
2
  AXErrorException,
3
3
  cycleDetectedError,
4
4
  unknownDependencyError
5
- } from "./chunk-L7YDEIPO.js";
5
+ } from "./chunk-26SCN2LJ.js";
6
6
  import {
7
7
  METRICS,
8
8
  metrics
@@ -8,14 +8,14 @@ import {
8
8
  import {
9
9
  MemoryMonitor,
10
10
  computeMergeOrder
11
- } from "./chunk-6TB2UCBZ.js";
11
+ } from "./chunk-VAGHAQT2.js";
12
12
  import {
13
13
  canonicalJSONStringify
14
14
  } from "./chunk-UHFMFO54.js";
15
15
  import {
16
16
  securitySarifParseError,
17
17
  securityScanFailedError
18
- } from "./chunk-L7YDEIPO.js";
18
+ } from "./chunk-26SCN2LJ.js";
19
19
  import {
20
20
  emitGateFrame2 as emitGateFrame
21
21
  } from "./chunk-TPVORFSN.js";
@@ -3204,7 +3204,7 @@ function runTaskkill(pid) {
3204
3204
  }
3205
3205
 
3206
3206
  // src/gates.ts
3207
- async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName, skipValidation = false, repoRoot, turnCostTracker) {
3207
+ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName, skipValidation = false, repoRoot, turnCostTracker, suppressStdout = false) {
3208
3208
  if (!skipValidation && gate.input) {
3209
3209
  try {
3210
3210
  validateGateInput(gate.name, gate.input);
@@ -3229,9 +3229,11 @@ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName,
3229
3229
  for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt++) {
3230
3230
  if (attempt > 1 && retryConfig.backoffSeconds > 0) {
3231
3231
  const delayMs = retryConfig.backoffSeconds * 1e3;
3232
- console.log(
3233
- `\u23F3 Retrying gate '${gate.name}' (attempt ${attempt}/${retryConfig.maxAttempts}) after ${retryConfig.backoffSeconds}s delay...`
3234
- );
3232
+ if (!suppressStdout) {
3233
+ console.log(
3234
+ `\u23F3 Retrying gate '${gate.name}' (attempt ${attempt}/${retryConfig.maxAttempts}) after ${retryConfig.backoffSeconds}s delay...`
3235
+ );
3236
+ }
3235
3237
  await new Promise((resolve5) => setTimeout(resolve5, delayMs));
3236
3238
  }
3237
3239
  const result = await executeGateAttempt(gate, artifactDir, attempt, timeoutMs, repoRoot);
@@ -3258,7 +3260,14 @@ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName,
3258
3260
  console.error(formatErrorForUser(classified));
3259
3261
  attemptRecords.push(attemptRecord);
3260
3262
  if (itemName && attemptRecords.length > 1) {
3261
- await writeFlakeReport(itemName, gate.name, attemptRecords, totalDuration, artifactDir);
3263
+ await writeFlakeReport(
3264
+ itemName,
3265
+ gate.name,
3266
+ attemptRecords,
3267
+ totalDuration,
3268
+ artifactDir,
3269
+ suppressStdout
3270
+ );
3262
3271
  }
3263
3272
  return result;
3264
3273
  } else if (classified.type === "transient" /* Transient */ && attempt < retryConfig.maxAttempts) {
@@ -3274,9 +3283,18 @@ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName,
3274
3283
  attemptRecords.push(attemptRecord);
3275
3284
  if (result.status === "pass") {
3276
3285
  if (attempt > 1) {
3277
- console.log(`\u2705 Gate '${gate.name}' succeeded on attempt ${attempt}`);
3286
+ if (!suppressStdout) {
3287
+ console.log(`\u2705 Gate '${gate.name}' succeeded on attempt ${attempt}`);
3288
+ }
3278
3289
  if (itemName) {
3279
- await writeFlakeReport(itemName, gate.name, attemptRecords, totalDuration, artifactDir);
3290
+ await writeFlakeReport(
3291
+ itemName,
3292
+ gate.name,
3293
+ attemptRecords,
3294
+ totalDuration,
3295
+ artifactDir,
3296
+ suppressStdout
3297
+ );
3280
3298
  }
3281
3299
  }
3282
3300
  return result;
@@ -3285,7 +3303,14 @@ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName,
3285
3303
  if (attempt > 1) {
3286
3304
  console.error(`\u274C Gate '${gate.name}' failed after ${attempt} attempts`);
3287
3305
  if (itemName) {
3288
- await writeFlakeReport(itemName, gate.name, attemptRecords, totalDuration, artifactDir);
3306
+ await writeFlakeReport(
3307
+ itemName,
3308
+ gate.name,
3309
+ attemptRecords,
3310
+ totalDuration,
3311
+ artifactDir,
3312
+ suppressStdout
3313
+ );
3289
3314
  }
3290
3315
  }
3291
3316
  return result;
@@ -3294,7 +3319,7 @@ async function executeGate(gate, policy, artifactDir, timeoutMs = 3e4, itemName,
3294
3319
  }
3295
3320
  return lastResult;
3296
3321
  }
3297
- async function writeFlakeReport(itemName, gateName, attempts, totalDuration, artifactDir) {
3322
+ async function writeFlakeReport(itemName, gateName, attempts, totalDuration, artifactDir, suppressStdout = false) {
3298
3323
  if (attempts.length <= 1) {
3299
3324
  return;
3300
3325
  }
@@ -3316,7 +3341,9 @@ async function writeFlakeReport(itemName, gateName, attempts, totalDuration, art
3316
3341
  const reportPath = path9.join(flakeReportDir, `${itemName}-${gateName}.json`);
3317
3342
  const reportContent = canonicalJSONStringify(flakeReport);
3318
3343
  fs9.writeFileSync(reportPath, reportContent, "utf-8");
3319
- console.log(`\u{1F4CA} Flake report written: ${reportPath}`);
3344
+ if (!suppressStdout) {
3345
+ console.log(`\u{1F4CA} Flake report written: ${reportPath}`);
3346
+ }
3320
3347
  }
3321
3348
  async function executeGateAttempt(gate, artifactDir, attempt, timeoutMs, repoRoot) {
3322
3349
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -3349,7 +3376,7 @@ async function executeGateAttempt(gate, artifactDir, attempt, timeoutMs, repoRoo
3349
3376
  }
3350
3377
  async function executeLocalGate(gate, artifactDir, attempt, startedAt, startTime, timeoutMs, repoRoot) {
3351
3378
  try {
3352
- const { getCommandValidator } = await import("./commandValidator-VHBB2H3K.js");
3379
+ const { getCommandValidator } = await import("./commandValidator-LVEMJTZP.js");
3353
3380
  const validator = getCommandValidator();
3354
3381
  validator.validate(gate.run);
3355
3382
  } catch (validationError) {
@@ -3575,29 +3602,31 @@ ${violations.map((v) => ` - ${v}`).join("\n")}`;
3575
3602
  lastAttempt: startedAt
3576
3603
  };
3577
3604
  }
3578
- async function emitGateExecutionFrame(gateName, itemName, result, runId) {
3605
+ async function emitGateExecutionFrame(gateName, itemName, result, runId, emitReceipt = true) {
3579
3606
  if (result.status === "blocked" || result.status === "skipped" || result.status === "retrying") {
3580
3607
  return void 0;
3581
3608
  }
3582
3609
  const passed = result.status === "pass";
3583
3610
  const duration = result.duration || 0;
3584
3611
  const outcome = passed ? "success" : "failure";
3585
- emitGateReceipt(
3586
- gateName,
3587
- itemName,
3588
- passed,
3589
- duration,
3590
- runId,
3591
- { log: true, json: true },
3592
- // Include detailed context for failure receipts
3593
- !passed ? {
3594
- error: result.stderr,
3595
- exitCode: result.exitCode,
3596
- artifacts: result.artifacts,
3597
- failureKind: result.failureKind,
3598
- descendantsReaped: result.timeoutCleanup?.descendantsReaped
3599
- } : void 0
3600
- );
3612
+ if (emitReceipt) {
3613
+ emitGateReceipt(
3614
+ gateName,
3615
+ itemName,
3616
+ passed,
3617
+ duration,
3618
+ runId,
3619
+ { log: true, json: true },
3620
+ // Include detailed context for failure receipts
3621
+ !passed ? {
3622
+ error: result.stderr,
3623
+ exitCode: result.exitCode,
3624
+ artifacts: result.artifacts,
3625
+ failureKind: result.failureKind,
3626
+ descendantsReaped: result.timeoutCleanup?.descendantsReaped
3627
+ } : void 0
3628
+ );
3629
+ }
3601
3630
  if (!runId) {
3602
3631
  return void 0;
3603
3632
  }
@@ -3622,6 +3651,9 @@ async function executeItemGates(item, policy, executionState, artifactDir, timeo
3622
3651
  fs9.mkdirSync(itemArtifactDir, { recursive: true });
3623
3652
  }
3624
3653
  for (const gate of item.gates) {
3654
+ if (options?.onlyGate && gate.name !== options.onlyGate) {
3655
+ continue;
3656
+ }
3625
3657
  if (shouldBlockGate(gate, policy)) {
3626
3658
  const blockedResult = {
3627
3659
  gate: gate.name,
@@ -3652,7 +3684,13 @@ async function executeItemGates(item, policy, executionState, artifactDir, timeo
3652
3684
  options.baseDir
3653
3685
  );
3654
3686
  }
3655
- await emitGateExecutionFrame(gate.name, item.name, result2, options?.runId);
3687
+ await emitGateExecutionFrame(
3688
+ gate.name,
3689
+ item.name,
3690
+ result2,
3691
+ options?.runId,
3692
+ options?.emitReceipt !== false
3693
+ );
3656
3694
  continue;
3657
3695
  }
3658
3696
  const result = await executeGate(
@@ -3663,7 +3701,8 @@ async function executeItemGates(item, policy, executionState, artifactDir, timeo
3663
3701
  item.name,
3664
3702
  skipValidation,
3665
3703
  repoRoot,
3666
- options?.turnCostTracker
3704
+ options?.turnCostTracker,
3705
+ options?.suppressStdout
3667
3706
  );
3668
3707
  results.push(result);
3669
3708
  executionState.updateGateResult(item.name, result);
@@ -3681,7 +3720,13 @@ async function executeItemGates(item, policy, executionState, artifactDir, timeo
3681
3720
  );
3682
3721
  await promptCounterExampleIfEnabled(result, options);
3683
3722
  }
3684
- await emitGateExecutionFrame(gate.name, item.name, result, options?.runId);
3723
+ await emitGateExecutionFrame(
3724
+ gate.name,
3725
+ item.name,
3726
+ result,
3727
+ options?.runId,
3728
+ options?.emitReceipt !== false
3729
+ );
3685
3730
  }
3686
3731
  return results;
3687
3732
  }
@@ -3699,7 +3744,9 @@ async function executeGatesWithPolicy(plan, executionState, artifactDir, timeout
3699
3744
  try {
3700
3745
  const hostilityScore = runEnvironmentQualityCheck({ cwd: workingDir });
3701
3746
  const adjustment = calculateHostilityAdjustedTimeout(timeoutMs, hostilityScore);
3702
- logTimeoutAdjustment(adjustment, "all_gates");
3747
+ if (!options?.suppressStdout) {
3748
+ logTimeoutAdjustment(adjustment, "all_gates");
3749
+ }
3703
3750
  effectiveTimeoutMs = adjustment.adjustedTimeoutMs;
3704
3751
  } catch {
3705
3752
  }
@@ -3717,7 +3764,9 @@ async function executeGatesWithPolicy(plan, executionState, artifactDir, timeout
3717
3764
  if (!fs9.existsSync(artifactDir)) {
3718
3765
  fs9.mkdirSync(artifactDir, { recursive: true });
3719
3766
  }
3720
- const executionOrder = buildExecutionOrder(plan);
3767
+ const executionOrder = buildExecutionOrder(plan).filter(
3768
+ (itemName) => !options?.onlyItem || itemName === options.onlyItem
3769
+ );
3721
3770
  const maxWorkers = policy.maxWorkers;
3722
3771
  const pendingNodes = [...executionOrder];
3723
3772
  const executing = /* @__PURE__ */ new Set();