ADR-033: Post-merge ADR/plan status sync via merge-status-sync.sh
Status: Accepted
Date: 2026-07-04
Context
A PR can ship the feature an ADR or plan describes while the ADR stays proposed and the plan stays active/draft, because nothing checks status against merge state. ADR-032 was the motivating case: PR #118 merged it, but ADR-032 stayed proposed until a manual audit caught it during an unrelated “what’s left on our plans” question. .context/plans/post-merge-status-sync-2026-07-04.md designed a script to close this gap; this ADR captures its binding decisions.
Decision
- Plan flips auto-apply only when the plan has a single phase; multi-phase plans are always flagged, never auto-flipped. This repo’s own plans are frequently multi-phase and shipped across several PRs, so auto-flipping
status: doneon a PR that only closes one phase would be a silent data-integrity regression. - ADR flips always require human confirmation, regardless of signal strength. Acceptance is a deliberate decision, sometimes with wording changes beyond the status field (see ADR-028, ADR-031) — automating it would make “accepted” mean less.
- Auto-flips are committed via a branch + PR, never directly to
main, mirroringways-of-working.md’s golden rule exactly as a human would follow it by hand. - Merge detection uses
gh pr view --json mergedAt,files,commits,mergeCommitagainst a PR number the user names, not a background poller — a cron-based approach needs write access and persisted “last-checked” state, both bigger asks than the problem justifies today. - Linking heuristic: the PR’s own file list is cross-referenced against each candidate plan/ADR’s own path (
directsignal) and itsrelated:/context:list (frontmattersignal when the linked path is itself another.context//docs/ADR/file,file-touchsignal otherwise). File-touch-only links are always flagged, never auto-applied, since a plan referencing a shared config file doesn’t mean every PR touching that file implements the plan. - Single script,
--dry-run/-nflag — matches the existingskill-auditor aggregate/remediateconvention rather than a separatecheck-*/apply-*pair. - The script is idempotent by re-deriving from live file state — re-running against an already-synced PR reports “nothing to do” with no persisted state needed; an already-open sync PR short-circuits a second apply-mode run.
- Lives as an extension to
adr-capture(scripts/merge-status-sync.sh,references/merge-status-sync.md), not a new top-level skill — it’s the same family of “keep the ADR index honest” work asregenerate-adr-index.sh/check-undocumented-decisions.sh, just triggered by merge state instead of new-decision detection. - CI integration (posting a PR comment listing drifted ADRs/plans) is evaluated, not built — Phase 3 wires a manual step into
ways-of-working.md’s “After merge” section; a required GitHub Actions gate is deferred until the detection logic has been exercised on enough real PRs to trust it. - Implemented in pure POSIX shell (bash/awk/sed), not Python —
.agents/RULES.md’s “Avoid Python/Node.js scripts in skills” rule, adopted during this same implementation, applies here: skill scripts must not assume an interpreter beyond what ships on a stock Unix-like system. This also surfaced two real portability bugs fixed along the way: bash 3.2 (macOS’s stock/bin/bash) losesBASH_REMATCHcapture groups for a[[ =~ ]]pattern used inside a function loaded viasource, and macOS/BSDrealpathhas no-mflag at all (GNU-only) — both replaced with portablecase-glob matching and a hand-rolled path normalizer.
Consequences
- Easier: the “did I forget to flip the ADR” failure mode has a cheap, repeatable command instead of relying on memory —
merge-status-sync.sh --dry-run <n>. - Easier: single-phase plan closures are fully automated end-to-end (branch, commit, push, PR) with no risk of a stray commit to
main. - Harder: ADR acceptance and multi-phase plan confirmation still require a human to act on the flagged report — this reduces the chance of forgetting but doesn’t eliminate the “nobody ran the script” failure mode. Revisit once Phase 3’s CI evaluation lands.