fix(ci,engine): repair test sharding, case-variant ambiguity detection, post-merge CI

Test shards 3 and 4 were silently failing on every open PR because vitest's
CLI parser was treating `--shard X/Y` as positional file filters whenever the
arg arrived after a `--` separator. Removing the `--` in ci-test-shard.mjs
restores per-shard slicing; verified locally that shard 1/4 and 2/4 now run
distinct subsets.

The two consistently-failing engine tests:

1. self-healing in-review-branch-rebind ambiguous case-variant detection:
   dedup keyed on lowercase branch name collapsed two physically distinct
   refs (allowed on Linux ext4) into one candidate, so the "applied" path
   ran instead of "ambiguous-candidates". Dedup now keys on the resolved
   SHA — macOS APFS still collapses (same ref, same SHA), Linux keeps both
   (distinct SHAs) and the ambiguity skip path fires as designed.

2. worktree-acquisition resume-misbinding spy: the production
   verifyResumeBranchNotMisbound returns early when `git merge-base HEAD main`
   fails, which is exactly what happens on shallow checkouts. Bumping the
   test-shards checkout to fetch-depth: 0 makes CI mirror the local git
   state these engine tests rely on.

Also adds `push: branches: [main]` to PR Checks so regressions like this
(which slipped into v0.33.0 with no post-merge run) go red immediately
on landing instead of being discovered on the next PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-25 10:24:21 -07:00
parent 0a6da9f4ce
commit 88c465cfc0
4 changed files with 45 additions and 11 deletions

View File

@@ -2946,16 +2946,26 @@ export class SelfHealingManager {
}
const integrationBase = task.baseBranch || await resolveIntegrationBranch(this.options.rootDir, undefined);
const existingCandidatesByRef = new Map<string, { branch: string; aheadCount: number }>();
// Dedup by resolved SHA, not by lowercase name. On case-insensitive
// filesystems (macOS APFS default) two case-variant refs resolve to the
// same underlying ref → same SHA → collapse to canonical. On
// case-sensitive filesystems (Linux) two case-variants are physically
// distinct refs with distinct SHAs → keep both, so downstream detects
// the ambiguity rather than silently picking one.
const candidateByRefSha = new Map<string, { branch: string; aheadCount: number }>();
const normalizedCandidate = canonicalFusionBranchName(task.id);
for (const branch of candidates) {
let branchSha: string;
try {
await execAsync(`git show-ref --verify --quiet ${shellQuote(`refs/heads/${branch}`)}`, {
const { stdout } = await execAsync(`git rev-parse --verify ${shellQuote(`refs/heads/${branch}`)}`, {
cwd: this.options.rootDir,
timeout: 30_000,
});
branchSha = stdout.trim();
} catch {
continue;
}
if (!branchSha) continue;
let comparisonBase = integrationBase;
try {
@@ -2980,18 +2990,16 @@ export class SelfHealingManager {
timeout: 30_000,
});
const aheadCount = Number.parseInt(aheadCountRaw.stdout.trim(), 10);
const normalizedBranchRef = branch.toLowerCase();
const existingCandidate = existingCandidatesByRef.get(normalizedBranchRef);
const normalizedCandidate = canonicalFusionBranchName(task.id);
if (!existingCandidate || branch === normalizedCandidate) {
existingCandidatesByRef.set(normalizedBranchRef, {
const existing = candidateByRefSha.get(branchSha);
if (!existing || branch === normalizedCandidate) {
candidateByRefSha.set(branchSha, {
branch,
aheadCount: Number.isFinite(aheadCount) ? aheadCount : 0,
});
}
}
const existingCandidates = [...existingCandidatesByRef.values()];
const existingCandidates = [...candidateByRefSha.values()];
if (existingCandidates.length === 0) {
await this.emitBranchRebindAuditEvent({