Add state attribute and property delegates to Gremlin, address review feedback#1097
Add state attribute and property delegates to Gremlin, address review feedback#1097xbrianh wants to merge 3 commits into
Conversation
- Add State instance attribute to Gremlin class - Initialize state in initialize_with_runtime() and populate before each stage in _collect_stages() - Add property delegates for all fields stages currently access: - gremlin.client → gremlin.state.client - gremlin.artifact_dir → gremlin.state.artifact_dir - gremlin.artifacts → gremlin.state.artifacts - gremlin.cwd → gremlin.state.cwd - gremlin.worktree → gremlin.state.worktree - gremlin.base_ref → gremlin.state.base_ref - gremlin.loop_iteration → gremlin.state.data.loop_iteration - gremlin.attempt → gremlin.state.data.attempt - gremlin.framework_subs() → gremlin.state.framework_subs() - gremlin.done_for() → gremlin.state.done_for() - gremlin.mark_done() → gremlin.state.mark_done() - gremlin.clear_done() → gremlin.state.clear_done() - gremlin.record_bail() → gremlin.state.record_bail() - gremlin.record_stage_progress() → gremlin.state.record_stage_progress() - gremlin.record_state_field() → gremlin.state.record_state_field() - Add unit test verifying delegates are accessible after initialize_with_runtime() Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1. Consolidate 13 identical state-initialization guards into a single _s property accessor, reducing boilerplate by 52 lines. 2. Extract the three-way cwd resolution (worktree → project_root → cwd) into a reusable _resolved_cwd property, eliminating a verbatim duplication between _collect_stages and initialize_with_runtime. 3. Remove self.state overwrite inside _collect_stages loop. Per-stage states are now used only where needed (stage_state.make_runner); the canonical state set in initialize_with_runtime is no longer clobbered by intermediate iterations. 4. Add test_gremlin_state_unchanged_after_run to verify that gremlin.state properties (client, base_ref, repo) correctly reflect canonical values after run() completes, exercising the fixed code path. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a canonical state attribute to Gremlin, along with a _s accessor and a set of delegation properties/methods so callers can access State fields via Gremlin without repeated “state not initialized” guards. Also factors out cwd resolution into _resolved_cwd and adds smoke tests for delegation + state preservation across run().
Changes:
- Introduce
Gremlin.stateplus_saccessor and delegate properties/methods (client,artifacts,cwd,base_ref, progress/done helpers, etc.). - Extract three-way cwd resolution into
_resolved_cwdand reuse it in_collect_stagesandinitialize_with_runtime. - Add smoke tests asserting delegation works after initialization and that
gremlin.stateremains canonical afterrun().
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
gremlins/executor/gremlin.py |
Adds state storage + _s accessor and delegates; refactors cwd/base_ref wiring for stage-state construction and runtime initialization. |
tests/test_gremlin_smoke.py |
Adds smoke tests for state initialization/delegation and ensuring gremlin.state isn’t overwritten by run(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| gremlin = Gremlin.initialize_with_runtime( | ||
| gremlin_id=gremlin_id, | ||
| state_dir=sd, | ||
| project_dir=project_dir, | ||
| pipeline_ref=str(pipeline_yaml), | ||
| project_root=str(project_dir), | ||
| base_ref="main", | ||
| repo="test/repo", | ||
| ) | ||
|
|
||
| # Test that state is initialized after initialize_with_runtime | ||
| assert gremlin.state is not None | ||
|
|
||
| # Test property delegates | ||
| assert isinstance(gremlin.artifact_dir, pathlib.Path) | ||
| assert gremlin.artifact_dir == sd / "artifacts" | ||
| assert gremlin.artifacts is not None | ||
| assert isinstance(gremlin.artifacts, ArtifactRegistry) | ||
| assert gremlin.client is not None | ||
| assert isinstance(gremlin.cwd, str) | ||
| assert gremlin.base_ref == "main" | ||
| assert gremlin.repo == "test/repo" | ||
| assert gremlin.loop_iteration == 1 | ||
| assert gremlin.attempt == "" |
There was a problem hiding this comment.
Fixed: Added cleanup to save/restore working directory and remove worktree after test.
xbrianh
left a comment
There was a problem hiding this comment.
Clean implementation — the _s guard, _resolved_cwd extraction, and the fix to stop overwriting self.state in the stage loop are all solid. One style note on the tests and a minor naming observation below.
| # Test that state is initialized after initialize_with_runtime | ||
| assert gremlin.state is not None | ||
|
|
||
| # Test property delegates |
There was a problem hiding this comment.
These comments narrate what the assertions already say. Drop them — assert gremlin.state is not None and assert isinstance(gremlin.artifacts, ArtifactRegistry) are self-explanatory. Same for # Capture state values before run() (line 196) and # Assert state still reflects canonical values after run() (line 204) in the second test.
There was a problem hiding this comment.
Done: Removed all redundant comments from both tests.
| @property | ||
| def base_ref(self) -> str: | ||
| return self._s.base_ref | ||
|
|
There was a problem hiding this comment.
gremlin.worktree (State delegate, pathlib.Path | None) now shadows the name but reads from a different slot than gremlin.worktree_dir (the raw Gremlin attribute). After initialize_with_runtime they carry the same value, but having both names on the same object is a latent confusion. Worth documenting or unifying — callers reaching for one when they should use the other won't get an error, just a wrong-feeling access pattern.
There was a problem hiding this comment.
Fixed: Renamed worktree_dir to _worktree_dir (internal only) to eliminate shadowing. Public API now uses state delegate (gremlin.worktree) consistently.
1. Add cleanup to test_gremlin_state_delegates_after_initialize: - Save/restore working directory with try/finally block - Clean up worktree directory to prevent state leakage across tests - Use gremlin.worktree instead of gremlin.worktree_dir 2. Remove redundant comments from both delegate tests: - Drop "# Test that state is initialized..." comments - Drop "# Capture state values..." and "# Assert state still..." comments - Let assertions speak for themselves 3. Resolve worktree_dir/worktree shadowing confusion: - Rename worktree_dir to _worktree_dir (internal only) - Update all internal references to use _worktree_dir - Update public API callers to use state delegate (gremlin.worktree) - Remove worktree_dir assertion from test_gremlin_open Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Summary
Adds state attribute and property delegates to the Gremlin class, and addresses review feedback to reduce boilerplate and eliminate state-initialization guards.
Changes
stateattribute to track State object and initialize it ininitialize_with_runtimeclient,artifacts,cwd,worktree,base_ref,loop_iteration,attempt,framework_subs,done_for,mark_done,clear_done,record_bail,record_stage_progress,record_state_field)_sproperty accessor, reducing boilerplate by 52 lines_resolved_cwdproperty, eliminating duplication between_collect_stagesandinitialize_with_runtimeself.stateoverwrite inside_collect_stagesloop; per-stage states are now used only where needed, preserving the canonical state set ininitialize_with_runtimetest_gremlin_state_delegates_after_initializeandtest_gremlin_state_unchanged_after_runto verify property delegation and state preservationTest Coverage
initialize_with_runtimeCloses #1075