@cat-factory/executor-harness 1.37.2 → 1.39.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.js CHANGED
@@ -529,14 +529,19 @@ async function runMultiRepoExplore(job, opts) {
529
529
  { repo: job.repo, cloneBranch: job.branch, ghToken: job.ghToken },
530
530
  ...peers.map((peer) => ({
531
531
  repo: peer.repo,
532
- cloneBranch: peer.repo.baseBranch,
532
+ // A read-only peer clones at its default branch (the bug-investigator) unless the job pins
533
+ // an explicit branch — the merger checks each peer out at its PR branch so the combined diff
534
+ // sees the PR change (`git diff origin/<base>...HEAD`).
535
+ cloneBranch: peer.cloneBranch ?? peer.repo.baseBranch,
533
536
  ghToken: peer.ghToken ?? job.ghToken,
534
537
  })),
535
538
  ].map((leg) => ({ ...leg, dirName: claimDir(leg.repo) }));
536
539
  return withWorkspace('explore-multi', async (root) => {
537
540
  // Clone phase: every repo (read-only) into its sibling dir under the workspace root. No
538
- // work branch, no resume — the investigator only reads — so the legs are independent and
539
- // clone in parallel (wall-clock is the slowest single clone, not the sum).
541
+ // work branch, no resume — the agent only reads — so the legs are independent and clone in
542
+ // parallel (wall-clock is the slowest single clone, not the sum). `full` is honoured per the
543
+ // job (the merger needs full history so `git diff origin/<base>...HEAD` has the merge base;
544
+ // the bug-investigator leaves it shallow).
540
545
  opts.onPhase?.('clone');
541
546
  await Promise.all(legs.map(async (leg) => {
542
547
  const dir = join(root, leg.dirName);
@@ -549,6 +554,7 @@ async function runMultiRepoExplore(job, opts) {
549
554
  repo: { ...leg.repo, baseBranch: leg.cloneBranch },
550
555
  ghToken: leg.ghToken,
551
556
  dir,
557
+ full: job.full,
552
558
  signal: opts.signal,
553
559
  });
554
560
  }));
package/dist/job.js CHANGED
@@ -136,6 +136,10 @@ function parsePeerRepos(value) {
136
136
  // read-only explore fan-out (bug-investigator) — validate it only when present.
137
137
  if (e.newBranch !== undefined)
138
138
  spec.newBranch = str(e.newBranch, `peerRepos[${i}].newBranch`);
139
+ // Read-only explore fan-out: the branch to check the peer out at (the merger's PR branch).
140
+ if (e.cloneBranch !== undefined) {
141
+ spec.cloneBranch = str(e.cloneBranch, `peerRepos[${i}].cloneBranch`);
142
+ }
139
143
  if (typeof e.frameId === 'string' && e.frameId)
140
144
  spec.frameId = e.frameId;
141
145
  if (typeof e.ghToken === 'string' && e.ghToken)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/executor-harness",
3
- "version": "1.37.2",
3
+ "version": "1.39.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",
@@ -26,8 +26,8 @@
26
26
  "hono": "^4.12.27",
27
27
  "typescript": "^6.0.3",
28
28
  "vitest": "^4.1.9",
29
- "@cat-factory/server": "0.101.0",
30
- "@cat-factory/spend": "0.11.15"
29
+ "@cat-factory/server": "0.104.0",
30
+ "@cat-factory/spend": "0.11.21"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsc -p tsconfig.json",
package/src/agent.ts CHANGED
@@ -655,15 +655,20 @@ async function runMultiRepoExplore(job: AgentJob, opts: RunOptions): Promise<Age
655
655
  { repo: job.repo, cloneBranch: job.branch, ghToken: job.ghToken },
656
656
  ...peers.map((peer) => ({
657
657
  repo: peer.repo,
658
- cloneBranch: peer.repo.baseBranch,
658
+ // A read-only peer clones at its default branch (the bug-investigator) unless the job pins
659
+ // an explicit branch — the merger checks each peer out at its PR branch so the combined diff
660
+ // sees the PR change (`git diff origin/<base>...HEAD`).
661
+ cloneBranch: peer.cloneBranch ?? peer.repo.baseBranch,
659
662
  ghToken: peer.ghToken ?? job.ghToken,
660
663
  })),
661
664
  ].map((leg) => ({ ...leg, dirName: claimDir(leg.repo) }))
662
665
 
663
666
  return withWorkspace('explore-multi', async (root) => {
664
667
  // Clone phase: every repo (read-only) into its sibling dir under the workspace root. No
665
- // work branch, no resume — the investigator only reads — so the legs are independent and
666
- // clone in parallel (wall-clock is the slowest single clone, not the sum).
668
+ // work branch, no resume — the agent only reads — so the legs are independent and clone in
669
+ // parallel (wall-clock is the slowest single clone, not the sum). `full` is honoured per the
670
+ // job (the merger needs full history so `git diff origin/<base>...HEAD` has the merge base;
671
+ // the bug-investigator leaves it shallow).
667
672
  opts.onPhase?.('clone')
668
673
  await Promise.all(
669
674
  legs.map(async (leg) => {
@@ -677,6 +682,7 @@ async function runMultiRepoExplore(job: AgentJob, opts: RunOptions): Promise<Age
677
682
  repo: { ...leg.repo, baseBranch: leg.cloneBranch },
678
683
  ghToken: leg.ghToken,
679
684
  dir,
685
+ full: job.full,
680
686
  signal: opts.signal,
681
687
  })
682
688
  }),
package/src/job.ts CHANGED
@@ -78,6 +78,13 @@ export interface PeerRepoSpec {
78
78
  * (the bug-investigator), which only clones the peer to read it and never pushes.
79
79
  */
80
80
  newBranch?: string
81
+ /**
82
+ * The EXISTING branch to check the peer out at for a READ-ONLY explore fan-out (the `merger`
83
+ * scoring the combined diff clones each peer at its PR branch so the diff sees the PR change).
84
+ * Absent ⇒ the peer is cloned at its repo default branch (the bug-investigator). Ignored on the
85
+ * coding fan-out, which creates `newBranch` instead.
86
+ */
87
+ cloneBranch?: string
81
88
  /** Open a PR/MR in this peer when set AND the run changed the peer (skipped for a clean repo). */
82
89
  pr?: PrSpec
83
90
  /** Per-repo GitHub token; defaults to the job's `ghToken` (one installation per workspace today). */
@@ -236,6 +243,10 @@ function parsePeerRepos(value: unknown): PeerRepoSpec[] {
236
243
  // `newBranch` is required for a coding fan-out (it pushes to it) but ABSENT for a
237
244
  // read-only explore fan-out (bug-investigator) — validate it only when present.
238
245
  if (e.newBranch !== undefined) spec.newBranch = str(e.newBranch, `peerRepos[${i}].newBranch`)
246
+ // Read-only explore fan-out: the branch to check the peer out at (the merger's PR branch).
247
+ if (e.cloneBranch !== undefined) {
248
+ spec.cloneBranch = str(e.cloneBranch, `peerRepos[${i}].cloneBranch`)
249
+ }
239
250
  if (typeof e.frameId === 'string' && e.frameId) spec.frameId = e.frameId
240
251
  if (typeof e.ghToken === 'string' && e.ghToken) spec.ghToken = e.ghToken
241
252
  if (typeof e.pr === 'object' && e.pr !== null) {