Proposal (rationale)
Rungs 0.3.1 asks Git for branches merged into a local main. In a release or pull-request checkout
that has only the checked-out ref, all six Rungs gate jobs failed with cannot read git branches; status not reconciled, while the same source passed on main. The existing F-033 fix removed a
shell portability defect but did not cover absence of the local integration ref.
Decision
accepted — 2026-09-05. Make reconciliation correct and actionable in the Git shapes generated CI
actually creates before enabling that CI in Arena Lab.
Plan
Resolve the configured integration branch to one exact ref before asking Git which work branches
are merged or whether a branch tip appears as a merge parent. Resolution is local-only and ordered:
an exact local branch, then the exact origin tracking branch, then exactly one matching tracking
branch from another remote. No match or more than one non-origin match remains unknown rather
than letting Git’s abbreviated-name rules choose a ref.
Requirements
backlog-merged-statusworks when the configured integration branch exists only as a local ref, only asrefs/remotes/origin/<branch>, or only as one other remote-tracking ref.- Local refs take precedence over remote-tracking refs, and
origintakes precedence over other remotes, so the same checkout always selects the same base. - A missing work-item branch remains non-actionable; fallback applies only to the configured integration branch and must not manufacture or infer feature refs.
- Multiple matching non-
originremotes and a genuinely absent integration ref produce an actionable unknown result; the gate must not report green or choose arbitrarily. - Resolution performs no fetch, ref write, checkout or other repository mutation.
Impacts
src/engines2.ts: integration-ref resolution used by merged-branch enumeration and merge-parent inspection.test/core.test.js: Git fixtures for local,origin, unique other-remote and ambiguous/missing ref shapes.- No module schema or generated consumer file changes:
integration_branchremains a branch name and the behavior change is inside the existing engine.
Approach
- Probe exact full ref names with argv-based Git calls. Prefer
refs/heads/<integration_branch>, thenrefs/remotes/origin/<integration_branch>. - If neither exists, enumerate configured remotes, construct each remote’s exact tracking ref, and
accept only one match. This keeps
upstream/release/maindistinct fromupstream/main. Use the resolved full ref consistently for bothgit branch --mergedandlandedWork. - Keep work-item branch enumeration local because item frontmatter records local feature branch names; only the integration/base side needs the remote fallback.
- Return a diagnostic that distinguishes an absent base from ambiguous remote matches. Do not use
rev-parse <short-name>as resolution because its DWIM behavior can select differently as refs are added.
Acceptance criteria / tests
- Existing local-
mainreconciliation behavior remains covered and passing. - With no local
main, a merged work branch is reconciled againstrefs/remotes/origin/mainand the stale pre-review status is reported. - With neither local nor
origin/main, onerefs/remotes/upstream/mainref is used and reports the same stale status. - When local and remote bases disagree, local wins; when
originand another remote disagree,originwins. - A work item whose recorded feature branch ref is absent remains examined without a stale-status finding.
- Two non-
originremote matches or no matching ref returns one actionable unknown finding with zero examined items. - A nested remote branch ending in the same path component is not mistaken for the configured integration branch.
npm testandnpm run rungs -- checkpass.
Out of scope
- Fetching refs, changing checkout depth, configuring CI providers, reconciling deleted feature branch refs, and changing the known fast-forward-merge limitation.
Execution
Plan completed 2026-09-05. Implementation started on
feature/WI-067-ref-only-integration-branch from b78b331 after rebasing onto the completed WI-066
consumer-pin work.
Added an exact-ref resolver in src/engines2.ts. It checks the configured local branch, then the
same branch under origin, then constructs exact tracking refs for configured remotes and accepts
a sole match. Both
merged-branch enumeration and merge-parent inspection now use the resolved full ref. Missing and
ambiguous bases return distinct findings without examining work items. No fetch or ref mutation was
added.
Added two Git-backed tests alongside the existing local-branch fixture. They construct remote refs directly so the scenarios test repository shape without a network or remote server. Implementation matched the plan; no scope was added.
Review
- Existing local behavior: the original landed/nothing-landed fixture passes, and the new
precedence fixture proves a merged local
mainwins over a staleorigin/main. - Origin-only and unique-remote behavior: the remote fallback fixture deletes local
main, first reconciles throughrefs/remotes/origin/main, then through a solerefs/remotes/upstream/main; both report the stalein_progressitem. - Deterministic precedence: with local
mainabsent, staleorigin/mainwins over a mergedupstream/mainand produces no stale-status finding. - Unknown states: two non-
originmatches report the sorted candidate refs withexamined: 0; no matching ref reports the absent local/remote condition withexamined: 0. - Namespace safety: a sole
refs/remotes/upstream/release/maindoes not satisfy an integration branch namedmain. - Missing item refs: after deleting the local feature ref, the remote-fallback fixture still examines the item and reports no stale-status finding.
- Read-only boundary: production code invokes only
show-ref,remote,branch,rev-parseandlog; the ref writes in the tests only construct disposable fixtures. node --test --test-name-pattern="merged-status" test/core.test.js— 3 passed, 0 failed.npm test— build succeeded; 43 passed, 0 failed.npm run rungs -- check— 29 passed, 0 failed, 0 unimplemented, 0 errors. The runner also transparently reported the existing 45 self-test fixtures without builders; they are not counted as passes.