Repository navigation
refactor(autoland): give each plan step type its own class - #69
Merged
Merged
Conversation
## Summary Phase 1 of the autoland step-abstraction stack. Plan steps were plain dataclasses joined by `PlanStep = Union[LandStep, WorkflowStep, ConfirmStep]`, with `isinstance` chains in every module that handled a plan. This adds an `AutolandStep` base class and moves the per-type behavior for plan syntax, persistence, and replanning progress onto the step classes. - New `stack_pr.autoland.steps` package: `base.py` (the `AutolandStep` ABC, a `PlanParser` context holding the stack, `pr_is_merged`, the next positional index, and the current line number, and the step registry), plus `land.py`, `workflow.py`, and `confirm.py`. The step classes keep their names and fields and are re-exported from `model.py`. `PlanStep` is now an alias of `AutolandStep`. - Each step type declares `kind` (state-file tag), `keyword` (plan syntax), and `label`. `@register_step` adds it to a registry that is looked up by kind or keyword. Registering the same kind or keyword twice raises an error. - Persistence: `to_dict()` / `from_dict()` replace `model._STEP_TYPES` and the type switch in `_deserialize_step`. The state file is byte-for-byte unchanged (`STATE_VERSION` stays 1). - Plan syntax: `parse(args, parser)` and `plan_line(stack)` per step. `parse_plan` and `format_plan_for_editor` now dispatch through the registry. The comment-column rule is a per-step `aligns_comments` flag (false for confirm). A bare `w` still counts as an unrecognized step, via `bare_keyword_ok`. PR-reference/URL resolution moves into `steps/land.py` unchanged. - Progress: `assume_completed()`, `is_checkpoint_done()`, `replan_key(landed_prs)`, `credit_from(old)`, `landed_pr(stack)`, and `marks_landed_prefix` replace `plan._mark_skipped`, `_checkpoint_done`, `_credit`, and the type switch in `_checkpoint_keys`. `landed_prefix_end`, `is_assumed_completed`, and `carry_over_progress` stay in `plan.py` and now call these methods. - `engine.py`, `display.py`, `status.py`, and `native_stack.py` keep their `isinstance` dispatch for now; later phases handle them. `display.py` and `status.py` only get `assert isinstance(step, ConfirmStep)` at their "the remaining type" fallthroughs, because mypy can no longer narrow a base class to the last member of the old union. **Behavior change:** a state file step with an unknown `type` now fails to load with `Unknown plan step type: '<type>'`. Before, it was silently loaded as a `ConfirmStep`, which was a latent bug. `--resume`/`--replan` already report `ValueError`s from loading as "Failed to load state file". ## Test plan - [x] Golden autoland scenarios (`tests/test_autoland_golden.py`) pass with no golden files regenerated, including the checkpoint JSON - [x] New `tests/test_autoland_steps.py`: registry lookup by kind and keyword, duplicate kind/keyword registration error (with no partial registration), unknown state-file step type error, and a round trip of every registered step type through the state file and through plan text (`format_plan_for_editor` -> `parse_plan`) - [x] Full suite passes with `GH_CONFIG_DIR` empty and `GH_TOKEN` unset 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> stack-info: PR: #69, branch: micahjsmith/stack/3
micahjsmith
force-pushed
the
micahjsmith/stack/3
branch
from
October 12, 2026 23:05
d1ba752 to
fd94751
Compare
This was referenced Oct 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked PRs:
Summary
Phase 1 of the autoland step-abstraction stack. Plan steps were plain dataclasses joined by
PlanStep = Union[LandStep, WorkflowStep, ConfirmStep], withisinstancechains in every module that handled a plan. This adds anAutolandStepbase class and moves the per-type behavior for plan syntax, persistence, and replanning progress onto the step classes.stack_pr.autoland.stepspackage:base.py(theAutolandStepABC, aPlanParsercontext holding the stack,pr_is_merged, the next positional index, and the current line number, and the step registry), plusland.py,workflow.py, andconfirm.py. The step classes keep their names and fields and are re-exported frommodel.py.PlanStepis now an alias ofAutolandStep.kind(state-file tag),keyword(plan syntax), andlabel.@register_stepadds it to a registry that is looked up by kind or keyword. Registering the same kind or keyword twice raises an error.to_dict()/from_dict()replacemodel._STEP_TYPESand the type switch in_deserialize_step. The state file is byte-for-byte unchanged (STATE_VERSIONstays 1).parse(args, parser)andplan_line(stack)per step.parse_planandformat_plan_for_editornow dispatch through the registry. The comment-column rule is a per-stepaligns_commentsflag (false for confirm). A barewstill counts as an unrecognized step, viabare_keyword_ok. PR-reference/URL resolution moves intosteps/land.pyunchanged.assume_completed(),is_checkpoint_done(),replan_key(landed_prs),credit_from(old),landed_pr(stack), andmarks_landed_prefixreplaceplan._mark_skipped,_checkpoint_done,_credit, and the type switch in_checkpoint_keys.landed_prefix_end,is_assumed_completed, andcarry_over_progressstay inplan.pyand now call these methods.engine.py,display.py,status.py, andnative_stack.pykeep theirisinstancedispatch for now; later phases handle them.display.pyandstatus.pyonly getassert isinstance(step, ConfirmStep)at their "the remaining type" fallthroughs, because mypy can no longer narrow a base class to the last member of the old union.Behavior change: a state file step with an unknown
typenow fails to load withUnknown plan step type: '<type>'. Before, it was silently loaded as aConfirmStep, which was a latent bug.--resume/--replanalready reportValueErrors from loading as "Failed to load state file".Test plan
tests/test_autoland_golden.py) pass with no golden files regenerated, including the checkpoint JSONtests/test_autoland_steps.py: registry lookup by kind and keyword, duplicate kind/keyword registration error (with no partial registration), unknown state-file step type error, and a round trip of every registered step type through the state file and through plan text (format_plan_for_editor->parse_plan)GH_CONFIG_DIRempty andGH_TOKENunset🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 5.5 (1M context) noreply@anthropic.com