* fix(#2990): gsd-code-fixer worktree attaches to a new branch, not the user-checked-out one
The agent's setup_worktree step ran 'git worktree add "$wt" "$branch"'
where $branch was the user's currently-checked-out branch in the main
repo. Git refuses to check out the same branch in two worktrees by
default, so the call failed before any review fix could be applied.
This is the next-layer failure after #2686 (foreground/background race)
and #2839 (transactional cleanup): the isolation strategy was correct
in design, blocked only by git's same-branch protection.
Fix:
- Create a new branch 'gsd-reviewfix/${padded_phase}-$$' from the
current branch tip and attach the worktree to it via
'git worktree add -b "$reviewfix_branch" "$wt" "$branch"'.
- Cleanup tail is now four steps:
1. 'git -C "$main_repo" merge --ff-only "$reviewfix_branch"'
-- captures the agent's commits on the user's branch. --ff-only
fails loudly on divergence (concurrent commits to $branch); the
temp branch is preserved for manual merge.
2. 'git worktree remove "$wt" --force'.
3. 'git -C "$main_repo" branch -D "$reviewfix_branch"' ONLY if
ff-only succeeded.
4. 'rm -f "$sentinel"' last (preserves #2839 transactional ordering).
- Recovery sentinel JSON now records reviewfix_branch alongside
worktree_path so a re-run after interruption cleans both the orphan
worktree and the orphan temp branch.
Regression test: tests/bug-2990-code-fixer-worktree-branch.test.cjs
parses the agent .md into structured 'git worktree add' invocation
records (skipping occurrences inside markdown inline-code or bash
comments -- those are citations of the OLD pattern, not executable)
and asserts the structural invariants on the new pattern.
Closes#2990
* chore(#2990): add changeset fragment for PR #3001
* chore(#2990): add changeset fragment for PR #3001
* fix(#2990): correct main_repo parsing and ff_status capture (CR feedback)
CodeRabbit on PR #3001 caught two real bugs in the cleanup tail:
1. `awk '/^worktree / { print $2 }'` truncates paths containing
spaces. /path/with spaces/repo becomes /path/with. Replaced with
`sub(/^worktree /, ''); print` which strips the prefix and
preserves the full path.
2. `if ! git merge ...; then ff_status=$?` captures the exit of the
`!` operator (always 1 on failure), not the merge command's exit
code. Restructured to `if cmd; then ff_status=0; else ff_status=$?`
so the else-branch captures the real merge exit code.
Tests still pass: bug-2990 structural assertions on the agent .md
content unchanged.
* fix(#2990): recovery extracts reviewfix_branch and deletes orphan branch (CR)
CodeRabbit on PR #3001 found two issues:
1. (Major) Recovery code only extracted worktree_path from the sentinel.
If a prior run died after `git worktree remove` but before
`git branch -D`, the orphan reviewfix branch survived forever. The
sentinel records reviewfix_branch (line 272) and the docs claim
recovery deletes it, but the code didn't.
Fixed: emit BOTH worktree_path and reviewfix_branch from the parser
(newline-separated), capture each into shell vars, and call
`git branch -D "$prior_branch" 2>/dev/null || true` after worktree
removal but before sentinel deletion.
2. (Quick win) The bug-2990 test used regex .test() against the raw
markdown, which would have been satisfied by prose mentioning the
token. Restructured to:
- parseCleanupGitInvocations() returns ordered records with structured
fields (verb, targetsReviewfixBranch, isMergeFfOnly, isBranchDelete)
- assert exactly-one merge --ff-only AND exactly-one branch -D
- assert merge precedes branch-delete in execution order
- parse the sentinel JSON.stringify call to extract field names and
assert reviewfix_branch is among them
Added 2 new tests for the recovery-block invariant: parses the recovery
node -e block and asserts it extracts parsed.reviewfix_branch alongside
parsed.worktree_path; and asserts the recovery shell calls
`git branch -D "$prior_branch"`.
* test(#2990): add allow-test-rule annotation for product-text parsing (CR follow-up)
The lint-tests CI catch flagged md.match() in the new structural-IR
test suite. The .match() calls extract typed fields (cleanup-tail
git invocation records, sentinel JSON field names, recovery-block
node script content) from agents/gsd-code-fixer.md — which IS the
deployed agent product. Asserting on those typed fields tests the
runtime contract, not source code internals.
source-text-is-the-product is the correct classification per the
existing convention (matches thread-session-management.test.cjs and
the others reclassified in PR #2985's CR follow-up).
* chore(#3001): drop direct CHANGELOG.md edit; release entry now lives in .changeset/
The changeset-fragment workflow (#2975) renders fragments into
CHANGELOG.md at release time. Direct edits to [Unreleased] on
each PR caused merge conflicts on every concurrent PR. This commit
restores CHANGELOG.md to match origin/main; the release entry for
this fix is preserved in the .changeset/*.md fragment(s) on this
branch, which the release workflow consolidates.