@cat-factory/executor-harness 1.126.0 → 1.129.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/dist/agent.d.ts CHANGED
@@ -53,6 +53,22 @@ export declare function testSecretEnv(secrets: TestSecretSpec[] | undefined): Re
53
53
  * `process.env` — and a second copy is a second place to forget the redaction.
54
54
  */
55
55
  export declare function secretEnv(secrets: TestSecretSpec[] | undefined): Record<string, string>;
56
+ /**
57
+ * Which refs a REUSED (warm-pool) explore checkout must end up holding: the branch to explore, and
58
+ * the repo's own base branch beside it.
59
+ *
60
+ * Its own function, and named, because the bug it removes is a swap between two branch names both
61
+ * in scope at the call site, and nothing downstream can tell them apart. A read-only reviewer's
62
+ * whole instruction is `git diff origin/<base>...HEAD`, so the base ref has to be as fresh as the
63
+ * branch: passing the explored branch as the base collapses the two refspecs into one, leaves
64
+ * `origin/<base>` at whatever tip the pool dir was first cloned at, and moves the merge base back
65
+ * to that tip. The diff then reports every commit merged into base since as part of the change
66
+ * under review, which is wrong in the direction nothing notices.
67
+ */
68
+ export declare function exploreCheckoutRefs(job: Pick<AgentJob, 'branch' | 'repo'>): {
69
+ branch: string;
70
+ baseBranch: string;
71
+ };
56
72
  /**
57
73
  * Whether a Ralph iteration ({@link AgentJob.validation} set) landed on a MULTI-REPO job (writable
58
74
  * peer repos or read-only reference repos). The post-commit validation command is only wired into
package/dist/agent.js CHANGED
@@ -383,6 +383,21 @@ export function secretEnv(secrets) {
383
383
  registerKnownSecrets(secrets.map((s) => s.value));
384
384
  return Object.fromEntries(secrets.map(({ key, value }) => [key, value]));
385
385
  }
386
+ /**
387
+ * Which refs a REUSED (warm-pool) explore checkout must end up holding: the branch to explore, and
388
+ * the repo's own base branch beside it.
389
+ *
390
+ * Its own function, and named, because the bug it removes is a swap between two branch names both
391
+ * in scope at the call site, and nothing downstream can tell them apart. A read-only reviewer's
392
+ * whole instruction is `git diff origin/<base>...HEAD`, so the base ref has to be as fresh as the
393
+ * branch: passing the explored branch as the base collapses the two refspecs into one, leaves
394
+ * `origin/<base>` at whatever tip the pool dir was first cloned at, and moves the merge base back
395
+ * to that tip. The diff then reports every commit merged into base since as part of the change
396
+ * under review, which is wrong in the direction nothing notices.
397
+ */
398
+ export function exploreCheckoutRefs(job) {
399
+ return { branch: job.branch, baseBranch: job.repo.baseBranch };
400
+ }
386
401
  /**
387
402
  * Read-only exploration: clone `branch`, run the agent making no edits, and return its
388
403
  * prose report — or, when `output.kind==='structured'`, the parsed JSON object as
@@ -408,12 +423,17 @@ async function runExploreMode(job, opts) {
408
423
  let workDir;
409
424
  if (job.persistentCheckout) {
410
425
  logger.info('agent(explore): preparing reused checkout');
426
+ // Both refs, resolved by {@link exploreCheckoutRefs}: which one is the base is the whole
427
+ // decision, so it is made there rather than inline here.
428
+ //
429
+ // `job.full` is deliberately not consulted: the fresh-clone leg inside
430
+ // `prepareExistingCheckout` always clones with full history, so a reused checkout already
431
+ // has the merge base a shallow explore clone would not.
411
432
  await prepareExistingCheckout({
412
433
  dir,
413
434
  repo: job.repo,
414
435
  ghToken: job.ghToken,
415
- branch: job.branch,
416
- baseBranch: job.branch,
436
+ ...exploreCheckoutRefs(job),
417
437
  existing: true,
418
438
  signal: opts.signal,
419
439
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/executor-harness",
3
- "version": "1.126.0",
3
+ "version": "1.129.0",
4
4
  "description": "Container payload: a thin TypeScript wrapper that runs the Pi coding agent against a cloned repo and opens a PR. Runs in the Cloudflare Container (and, in local native mode, as a host process); carries no secrets.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,14 +25,14 @@
25
25
  "access": "public"
26
26
  },
27
27
  "devDependencies": {
28
- "@hono/node-server": "^2.1.0",
28
+ "@hono/node-server": "^2.1.1",
29
29
  "@types/node": "^26.2.0",
30
- "hono": "^4.13.1",
30
+ "hono": "^4.13.3",
31
31
  "typescript": "7.0.2",
32
- "vitest": "^4.1.10",
33
- "@cat-factory/kernel": "0.309.0",
34
- "@cat-factory/server": "0.294.0",
35
- "@cat-factory/spend": "0.16.1"
32
+ "vitest": "^4.1.11",
33
+ "@cat-factory/kernel": "0.316.0",
34
+ "@cat-factory/server": "0.302.0",
35
+ "@cat-factory/spend": "0.16.9"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsc -p tsconfig.json",
package/src/agent.ts CHANGED
@@ -464,6 +464,25 @@ export function secretEnv(secrets: TestSecretSpec[] | undefined): Record<string,
464
464
  return Object.fromEntries(secrets.map(({ key, value }) => [key, value]))
465
465
  }
466
466
 
467
+ /**
468
+ * Which refs a REUSED (warm-pool) explore checkout must end up holding: the branch to explore, and
469
+ * the repo's own base branch beside it.
470
+ *
471
+ * Its own function, and named, because the bug it removes is a swap between two branch names both
472
+ * in scope at the call site, and nothing downstream can tell them apart. A read-only reviewer's
473
+ * whole instruction is `git diff origin/<base>...HEAD`, so the base ref has to be as fresh as the
474
+ * branch: passing the explored branch as the base collapses the two refspecs into one, leaves
475
+ * `origin/<base>` at whatever tip the pool dir was first cloned at, and moves the merge base back
476
+ * to that tip. The diff then reports every commit merged into base since as part of the change
477
+ * under review, which is wrong in the direction nothing notices.
478
+ */
479
+ export function exploreCheckoutRefs(job: Pick<AgentJob, 'branch' | 'repo'>): {
480
+ branch: string
481
+ baseBranch: string
482
+ } {
483
+ return { branch: job.branch, baseBranch: job.repo.baseBranch }
484
+ }
485
+
467
486
  /**
468
487
  * Read-only exploration: clone `branch`, run the agent making no edits, and return its
469
488
  * prose report — or, when `output.kind==='structured'`, the parsed JSON object as
@@ -490,12 +509,17 @@ async function runExploreMode(job: AgentJob, opts: RunOptions): Promise<AgentRes
490
509
  let workDir: string
491
510
  if (job.persistentCheckout) {
492
511
  logger.info('agent(explore): preparing reused checkout')
512
+ // Both refs, resolved by {@link exploreCheckoutRefs}: which one is the base is the whole
513
+ // decision, so it is made there rather than inline here.
514
+ //
515
+ // `job.full` is deliberately not consulted: the fresh-clone leg inside
516
+ // `prepareExistingCheckout` always clones with full history, so a reused checkout already
517
+ // has the merge base a shallow explore clone would not.
493
518
  await prepareExistingCheckout({
494
519
  dir,
495
520
  repo: job.repo,
496
521
  ghToken: job.ghToken,
497
- branch: job.branch,
498
- baseBranch: job.branch,
522
+ ...exploreCheckoutRefs(job),
499
523
  existing: true,
500
524
  signal: opts.signal,
501
525
  })