Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,14 @@ Test patterns:
## Releases

Releases are automated via GitHub Actions. When PRs are merged to main, a release is created automatically. Do not create releases manually.

## Decision Log

At the start of a session, read `decision-log.md` in the repo root for past architectural and design decisions.

When making an architectural or design decision, add an entry to `decision-log.md` with:

- **Date** (YYYY-MM-DD)
- **Decision** — what was decided
- **Context** — why it came up, what the problem was
- **Resolution** — what was done and how
17 changes: 17 additions & 0 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,23 @@ func detectSyncIssues(gitClient git.GitClient, stackBranches []stack.StackBranch
continue
}

// Check if parent PR is merged (or parent branch merged via git history)
parentPR := prCache[branch.Parent]
if parentPR != nil && parentPR.State == "MERGED" {
if verbose {
fmt.Printf(" ✗ Parent PR #%d has been merged\n", parentPR.Number)
}
issues = append(issues, fmt.Sprintf(" - Branch '%s': parent '%s' has been merged — run '%s' to reparent", ui.Branch(branch.Name), ui.Branch(branch.Parent), ui.Command("stack sync")))
} else if parentPR == nil && branch.Parent != baseBranch {
// No PR found for parent - check if parent was merged via git history
if merged, err := gitClient.IsAncestor(branch.Parent, "origin/"+baseBranch); err == nil && merged {
if verbose {
fmt.Printf(" ✗ Parent %s appears merged into %s via git history\n", branch.Parent, baseBranch)
}
issues = append(issues, fmt.Sprintf(" - Branch '%s': parent '%s' has been merged — run '%s' to reparent", ui.Branch(branch.Name), ui.Branch(branch.Parent), ui.Command("stack sync")))
}
}

// Check if PR base matches the configured parent (if PR exists)
if pr, exists := prCache[branch.Name]; exists {
if verbose {
Expand Down
32 changes: 32 additions & 0 deletions cmd/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,38 @@ func TestDetectSyncIssues(t *testing.T) {
},
expectedIssues: 1,
},
{
name: "parent PR is merged",
stackBranches: []stack.StackBranch{
{Name: "feature-b", Parent: "feature-a"},
},
prCache: map[string]*github.PRInfo{
"feature-a": {Number: 10, State: "MERGED", Base: "main"},
"feature-b": {Number: 11, State: "OPEN", Base: "feature-a"},
},
setupMocks: func(mockGit *testutil.MockGitClient) {
// PR base still matches parent, but parent is merged — should report merged parent issue
mockGit.On("IsCommitsBehind", "feature-b", "feature-a").Return(false, nil)
mockGit.On("RemoteBranchExists", "feature-b").Return(false)
},
expectedIssues: 1, // merged parent
},
{
name: "parent branch merged via git history (no PR for parent)",
stackBranches: []stack.StackBranch{
{Name: "feature-b", Parent: "feature-a"},
},
prCache: map[string]*github.PRInfo{
"feature-b": {Number: 11, State: "OPEN", Base: "feature-a"},
},
setupMocks: func(mockGit *testutil.MockGitClient) {
// Parent has no PR, but is ancestor of origin/main
mockGit.On("IsAncestor", "feature-a", "origin/main").Return(true, nil)
mockGit.On("IsCommitsBehind", "feature-b", "feature-a").Return(false, nil)
mockGit.On("RemoteBranchExists", "feature-b").Return(false)
},
expectedIssues: 1, // merged parent via git history
},
}

for _, tt := range tests {
Expand Down
11 changes: 11 additions & 0 deletions decision-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Decision Log

Architectural and design decisions for Stackinator.

## 2026-03-06 — Add merged-parent detection to `stack status`

**Decision**: Add merged-parent detection to `stack status`.

**Context**: `stack status` reported "perfectly synced" even when a parent branch's PR had been merged. `stack sync` already had this detection logic (checking PR state + git ancestor fallback) but `status` did not, so users had no warning until they ran sync.

**Resolution**: Mirror `sync.go`'s merged-parent detection (PR state check + git ancestor fallback) in `status.go:detectSyncIssues()`. This lets `status` report when a parent branch has been merged and the child needs re-parenting.