Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/calm-stacks-ready.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kitlangton/stack": minor
---

Add configurable, provider-neutral readiness policies, reconciliation, and readiness-aware undo for stack sync and merge workflows.
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@

## Safety rules

- Linked Git worktrees share `.git/stack/state.json`, `.git/stack/undo.json`, and repo-local `stack.*` configuration. Treat each connected stack as owned by one worktree, scope mutations with a branch from that stack, and never mutate another worktree's stack or run cross-worktree stack mutations concurrently without explicit coordination.
- Bare `stack sync` must stay non-mutating, including scoped and keep-going runs.
- Mutating commands need an explicit mode: `--apply`, or `merge --auto` for code-host auto-merge plus descendant repair.
- Never mutate configured trunk branches like `dev`, `main`, or `master`.
- Before rebasing a branch, create a local backup branch.
- Before repair mutates Git or a hosted change, save an undo checkpoint. Merge child retargets use a pre-merge recovery journal; after the root lands, descendant repair starts from a post-merge baseline that never retargets children back onto the landed branch.
- `stack undo` should restore the last applied mutation from the saved journal.
- `stack undo` should restore the last applied mutation from the saved journal, including hosted-change readiness.

## Current commands

Expand All @@ -24,23 +25,25 @@
- `track` records parentage for an existing branch only when change target branches do not already encode the stack.
- `sync [branch]` previews target-branch inference, stale metadata cleanup, and repairs without mutating branches, requests, or stack metadata using the tree summary output.
- `sync --apply [branch]` applies the common maintenance workflow: remove stale local links, infer clear target-branch stack links, repair branches, retarget requests, refresh links, and show a concise tree summary. With a branch argument, sync only the stack containing that branch.
- `sync [--readiness-mode unmanaged|all-ready|root-ready]` previews the effective readiness policy; `sync --apply` is the explicit conversion and reconciliation path for existing changes.
- `sync` with no branch scopes to the current stack when the current branch is stack-relevant; when off-stack, it keeps the repo-wide behavior.
- `sync --apply --continue-on-failure` / `sync --apply --keep-going` processes independent stacks, reports succeeded and failed stacks, preserves per-stack cleanup output, and exits nonzero if any stack failed.
- `sync` should not auto-track standalone trunk-root requests; infer a trunk-root request only when another open request is based on it.
- `merge` merges the oldest branch in a stack and immediately repairs descendants; when no branch is given, it infers the root from the current branch. It retargets immediate child requests before merge to preserve open work in auto-delete repos.
- `merge --auto` retargets immediate child requests, enables code-host auto-merge, waits for merge, then repairs descendants.
- `merge --auto --through <branch-or-change>` repeats root auto-merge and descendant repair until the target branch or request has landed.
- `merge [--readiness-mode unmanaged|all-ready|root-ready]` applies the effective policy after descendant repair. It must not promote a draft current root immediately before merge; callers should reconcile with `sync --apply` first so readiness-triggered checks can finish.
- `history` explains the most recent applied mutation from the undo journal.
- `undo` restores the last applied mutation.

## Implementation notes

- Persist stack metadata in `.git/stack/state.json`.
- Persist undo state in `.git/stack/undo.json`.
- User preferences live in `git config stack.*` (read at startup in the CLI `live` layer), not in `state.json`. Current keys: `stack.codeHost`, `stack.trunks`, and `stack.blockLink` (default true; set false to render a plain `### Stack` heading without the attribution link).
- User preferences live in `git config stack.*` (read at startup in the CLI `live` layer), not in `state.json`. Current keys: `stack.codeHost`, `stack.trunks`, `stack.blockLink` (default true; set false to render a plain `### Stack` heading without the attribution link), and `stack.readinessMode` (`unmanaged` by default, or `all-ready` / `root-ready`).
- Prefer `Context.Service`-based Effect services and test-first changes.
- Use OpenCode-style service modules for deep seams: export `Interface`, `Service`, adapters like `layer`, `live`, or `memory`, and a namespace self-reexport such as `export * as CodeHost from "./CodeHost.ts"`; consumers import that named namespace directly from the module file.
- Keep local Git behavior behind `Git` and pull/merge-request behavior behind `CodeHost`. Concrete backends live in `services/code-host/GitHub.ts` (via `gh`) and `services/code-host/GitLab.ts` (via `glab`), while their in-memory contract behavior is shared through `services/code-host/Memory.ts`; the CLI picks one backend at startup from `STACK_CODE_HOST`, `git config stack.codeHost`, or an unambiguous `origin` host. Stack orchestration depends on `CodeHost.Service` rather than shelling out to a host CLI directly.
- Keep local Git behavior behind `Git` and pull/merge-request behavior behind `CodeHost`. Readiness is a provider-neutral `CodeHost` capability implemented by both GitHub (via `gh`) and GitLab (via `glab`), not host-specific stack orchestration. Concrete backends live in `services/code-host/GitHub.ts` and `services/code-host/GitLab.ts`, while their in-memory contract behavior is shared through `services/code-host/Memory.ts`; the CLI picks one backend at startup from `STACK_CODE_HOST`, `git config stack.codeHost`, or an unambiguous `origin` host. Stack orchestration depends on `CodeHost.Service` rather than shelling out to a host CLI directly.
- Check the local Effect source tree when available before changing Effect APIs or versions.
- Prefer `effect/Path`, `effect/FileSystem`, and `effect/unstable/process` instead of Node/Bun built-ins in app code.
- Keep logic literal and debuggable over clever abstractions.
Expand Down
82 changes: 74 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ repos that squash-merge and delete branches.
the agent do normal code work with plain `git`, then use `stack` for stack
inspection, repair, merge, and undo workflows.

## Important: Git Worktrees Share Stack State

Linked worktrees use the same common Git directory, so they also share
`.git/stack/state.json`, `.git/stack/undo.json`, and repo-local `stack.*`
configuration. A visible stack is not automatically owned by the current
worktree.

Treat each connected stack as owned by one worktree. Pass a branch from that
owned stack to mutating commands, do not operate on another worktree's stack
without explicit coordination, and do not run stack mutations concurrently
across worktrees. If the current worktree does not have a stack, create an
independent one instead of attaching it to an unrelated visible stack.

## Install

```bash
Expand Down Expand Up @@ -43,24 +56,24 @@ glab auth login # GitLab
4. Preview the stack:

```bash
stack sync
stack sync <owned-stack-branch>
```

5. Apply the safe maintenance workflow:

```bash
stack sync --apply
stack sync --apply <owned-stack-branch>
```

6. Merge from the root when ready:

```bash
stack merge
stack merge --apply
stack merge <owned-stack-root>
stack merge <owned-stack-root> --apply
```

Use `stack merge --auto` when the code host should wait for merge requirements,
then repair descendants automatically after the root lands.
Use `stack merge <owned-stack-root> --auto` when the code host should wait for
merge requirements, then repair descendants automatically after the root lands.

## What It Does

Expand All @@ -70,6 +83,7 @@ then repair descendants automatically after the root lands.
- Records stack intent in `.git/stack/state.json`.
- Repairs descendants after parent branches move or land.
- Retargets PRs/MRs when needed.
- Reconciles PR/MR readiness according to the effective policy.
- Refreshes stack blocks in descriptions.
- Saves `.git/stack/undo.json` before mutations.

Expand Down Expand Up @@ -99,6 +113,54 @@ git config stack.codeHost github # or: gitlab

Use `STACK_CODE_HOST=github|gitlab` for a one-off override.

## Change Readiness

Readiness policy is provider-neutral and works for GitHub PRs and GitLab MRs.
Configure a repository default with:

```bash
git config stack.readinessMode root-ready
```

The supported modes are:

| Mode | Behavior |
| ------------ | ------------------------------------------------------------------------- |
| `unmanaged` | Preserve existing readiness; use the code host's default for new changes. |
| `all-ready` | Make every change in the selected stack ready. |
| `root-ready` | Make each trunk-targeting root ready and every descendant draft. |

`unmanaged` is the default, preserving the behavior of earlier releases. A
command-line override takes precedence over Git config for that invocation; it
does not rewrite the configured default:

```bash
stack sync <owned-stack-branch> --readiness-mode root-ready
stack sync --apply <owned-stack-branch> --readiness-mode root-ready
stack merge <owned-stack-branch> --readiness-mode root-ready
```

An invalid configured value is a startup error even when a flag is present.
Fix it with `git config stack.readinessMode <mode>` or unset it before retrying.

Use `sync` as the explicit conversion and reconciliation path for an existing
stack: preview first, then apply. Readiness changes can start checks or reviews,
so `merge` does not make a draft current root ready immediately before trying to
merge it. Promote and reconcile first with `stack sync --apply`, wait for any
required checks, then run `stack merge`. After a root lands and descendants are
repaired, `merge` applies the effective mode to the remaining stack.

```bash
gh pr checks <root-change> --watch # GitHub
glab ci status --branch <root-branch> --wait # GitLab
```

Applied readiness changes are recorded in the undo journal. `stack undo --apply`
restores the previous readiness along with branch tips, change targets, and
stack metadata. Undo does not unmerge a landed root; after a merge it restores
only the journaled state of the surviving stack. Each applied mutation replaces
the single shared journal, so inspect `stack history` before undoing.

## Trunk Branches

By default, `stack` treats `dev`, `main`, and `master` as trunk branches. Repos
Expand Down Expand Up @@ -133,7 +195,7 @@ Sync preview
Would update PRs: #101, #102

Apply:
stack sync
stack sync --apply <owned-stack-branch>
```

```text
Expand All @@ -156,13 +218,17 @@ stack sync --apply <branch>
# apply only the stack containing branch
stack sync --apply --keep-going
# process independent stacks and report failures
stack sync --readiness-mode <unmanaged|all-ready|root-ready>
# preview an explicit readiness policy
stack doctor # inspect repo, host, metadata, and journal health
stack merge # dry-run the next root merge
stack merge --apply # merge root and repair descendants
stack merge --readiness-mode <unmanaged|all-ready|root-ready>
# override configured readiness for this merge
stack merge --auto # wait for host requirements, then merge and repair
stack merge --auto --through <branch-or-change>
# auto-merge roots through a bounded target
stack history # show the last saved mutation journal
stack undo # preview undo
stack undo --apply # restore branch tips, request targets, and metadata
stack undo --apply # restore tips, targets, readiness, and metadata
```
Loading