Private
Public Access
0
0

unfuck edit workflow.

This commit is contained in:
2026-06-09 13:48:17 -04:00
parent eb8357ec0e
commit 4eba059e89
2 changed files with 59 additions and 38 deletions
+17
View File
@@ -71,9 +71,11 @@ is processed by AI agents, while preserving readability for human review.
## 10. Anti-OOP Conventions ## 10. Anti-OOP Conventions
### Philosophy ### Philosophy
AI agents consistently misinterpret class hierarchies, method resolution, and inheritance. Flat function-call graphs are deterministic and traceable. OOP introduces scoping complexity that compounds with indentation. AI agents consistently misinterpret class hierarchies, method resolution, and inheritance. Flat function-call graphs are deterministic and traceable. OOP introduces scoping complexity that compounds with indentation.
### Hard Rules (Enforced by lint) ### Hard Rules (Enforced by lint)
- **Never write a class for a single method.** Use a function. - **Never write a class for a single method.** Use a function.
- **Never use inheritance for code reuse.** Compose with standalone functions. - **Never use inheritance for code reuse.** Compose with standalone functions.
- **Never use private methods (`_method`).** Module-level functions with clear names suffice. - **Never use private methods (`_method`).** Module-level functions with clear names suffice.
@@ -81,6 +83,7 @@ AI agents consistently misinterpret class hierarchies, method resolution, and in
- **No decorator classes.** Use plain functions with decorators. - **No decorator classes.** Use plain functions with decorators.
### Class Justification Required ### Class Justification Required
Every class definition MUST include a comment explaining WHY it is a class and not a function group or struct: Every class definition MUST include a comment explaining WHY it is a class and not a function group or struct:
```python ```python
@@ -97,13 +100,17 @@ class OperationHelper:
``` ```
### Acceptability Criteria ### Acceptability Criteria
A class is justified ONLY when ALL of: A class is justified ONLY when ALL of:
1. It holds mutable state that must be encapsulated 1. It holds mutable state that must be encapsulated
2. It has 3+ related methods that share state 2. It has 3+ related methods that share state
3. It implements a behavioral interface used polymorphically (not just data grouping) 3. It implements a behavioral interface used polymorphically (not just data grouping)
### Refactoring Existing Classes (Strangler Fig Pattern) ### Refactoring Existing Classes (Strangler Fig Pattern)
When refactoring a class to functions: When refactoring a class to functions:
1. Write test validating current behavior (prevents regression) 1. Write test validating current behavior (prevents regression)
2. Extract one method at a time into module-level functions 2. Extract one method at a time into module-level functions
3. Create wrapper function that delegates to class until migration complete 3. Create wrapper function that delegates to class until migration complete
@@ -111,16 +118,19 @@ When refactoring a class to functions:
5. Commit with `refactor(oop):` prefix 5. Commit with `refactor(oop):` prefix
### Data Structures ### Data Structures
- **Data-only containers:** Use `NamedTuple`, `dataclass(frozen=True)`, or plain `dict` — NOT classes - **Data-only containers:** Use `NamedTuple`, `dataclass(frozen=True)`, or plain `dict` — NOT classes
- **State machines:** Use dict-based transitions, not class + inheritance - **State machines:** Use dict-based transitions, not class + inheritance
- **Configuration:** Plain dict or `TypedDict`, not classes with defaults - **Configuration:** Plain dict or `TypedDict`, not classes with defaults
### Anti-Patterns (Flagged by Ruff PLR rules) ### Anti-Patterns (Flagged by Ruff PLR rules)
- `PLR0912`: Too many branches — extract to functions - `PLR0912`: Too many branches — extract to functions
- `PLR6301`: No public methods — class is a namespace anti-pattern - `PLR6301`: No public methods — class is a namespace anti-pattern
- `PLR0206`: Descriptors in class body — use simple attributes - `PLR0206`: Descriptors in class body — use simple attributes
### Enforcement ### Enforcement
```toml ```toml
[tool.ruff.lint.select] [tool.ruff.lint.select]
select = ["E", "F", "W", "C90", "C4", "PLR0912", "PLR6301", "PLR0206"] select = ["E", "F", "W", "C90", "C4", "PLR0912", "PLR6301", "PLR0206"]
@@ -137,6 +147,7 @@ To prevent `PopID` or `End` leaks in immediate-mode rendering, and to keep code
- **The Context Manager Pattern (Mandatory for complex blocks):** - **The Context Manager Pattern (Mandatory for complex blocks):**
Wrap all `Begin/End` blocks in `imscope` context managers (from `src/imgui_scopes.py`). Wrap all `Begin/End` blocks in `imscope` context managers (from `src/imgui_scopes.py`).
```python ```python
with imscope.window("My Window") as (exp, opened): with imscope.window("My Window") as (exp, opened):
if exp: if exp:
@@ -146,13 +157,17 @@ To prevent `PopID` or `End` leaks in immediate-mode rendering, and to keep code
if exp: if exp:
self._render_tab_content() self._render_tab_content()
``` ```
This adds only 1 space of indentation (project standard) and guarantees the corresponding `End` is called even on early returns or exceptions. **Crucial:** Always check the `exp` (expanded/visible) state before rendering content to avoid ID conflicts and performance overhead. This adds only 1 space of indentation (project standard) and guarantees the corresponding `End` is called even on early returns or exceptions. **Crucial:** Always check the `exp` (expanded/visible) state before rendering content to avoid ID conflicts and performance overhead.
- **The Flat Dispatch Pattern (Recommended for the main loop):** - **The Flat Dispatch Pattern (Recommended for the main loop):**
To avoid nesting multiple window checks, use a dispatch helper that encapsulates the state check and the scope. To avoid nesting multiple window checks, use a dispatch helper that encapsulates the state check and the scope.
```python ```python
self._render_window_if_open("My Window", self._render_my_panel) self._render_window_if_open("My Window", self._render_my_panel)
``` ```
This keeps the main GUI loop as a flat sequence of declarative calls. This keeps the main GUI loop as a flat sequence of declarative calls.
## 12. Structural Dependency Mapping (SDM) ## 12. Structural Dependency Mapping (SDM)
@@ -172,6 +187,7 @@ To minimize token usage and enhance visual scanning for human reviewers, heavily
- **Single-Line Conditionals:** Prefer `if cond: do_this()` over multiline blocks for simple assignments or function calls. **Note:** Function and method definition signatures (`def ...:`) must ALWAYS remain on their own isolated lines and should never be compacted. - **Single-Line Conditionals:** Prefer `if cond: do_this()` over multiline blocks for simple assignments or function calls. **Note:** Function and method definition signatures (`def ...:`) must ALWAYS remain on their own isolated lines and should never be compacted.
- **Semicolon Stacking:** Chain closely related framework calls on a single line using semicolons (e.g., `imgui.same_line(); imgui.text("Label")`). - **Semicolon Stacking:** Chain closely related framework calls on a single line using semicolons (e.g., `imgui.same_line(); imgui.text("Label")`).
- **Alignment:** Align assignments and inline comments vertically when declaring batches of related variables or conditionals. - **Alignment:** Align assignments and inline comments vertically when declaring batches of related variables or conditionals.
```python ```python
if status == 'running': col = (0.0, 1.0, 0.0, 1.0) if status == 'running': col = (0.0, 1.0, 0.0, 1.0)
elif status == 'starting': col = (1.0, 1.0, 0.0, 1.0) elif status == 'starting': col = (1.0, 1.0, 0.0, 1.0)
@@ -185,6 +201,7 @@ For extremely large files that violate the "Anti-OOP" rule by necessity (e.g., `
## 15. Modular Controller Pattern ## 15. Modular Controller Pattern
To prevent "God Object" bloat in core controllers (like `AppController`): To prevent "God Object" bloat in core controllers (like `AppController`):
- **Extract Logic:** Move all state-independent or purely utility logic to module-level functions. - **Extract Logic:** Move all state-independent or purely utility logic to module-level functions.
- **Dependency Injection:** Module-level functions that require class state should accept the instance as their first argument (e.g., `def my_extracted_logic(controller: AppController, ...)`). - **Dependency Injection:** Module-level functions that require class state should accept the instance as their first argument (e.g., `def my_extracted_logic(controller: AppController, ...)`).
- **Handler Maps:** Replace massive `if/elif` blocks (like those in event dispatchers) with dictionaries mapping keys to module-level handler functions. - **Handler Maps:** Replace massive `if/elif` blocks (like those in event dispatchers) with dictionaries mapping keys to module-level handler functions.
+42 -38
View File
@@ -1,16 +1,20 @@
# Manual Slop Edit Tool Workflow # Manual Slop Edit Tool Workflow
## The Problem ## The Problem
The `manual-slop_edit_file` tool requires **exact string matches** (character-for-character). Whitespace differences cause failures. The Python file uses **1-space indentation**. The `manual-slop_edit_file` tool requires **exact string matches** (character-for-character). Whitespace differences cause failures. The Python file uses **1-space indentation**.
## The Rules ## The Rules
### 1. ALWAYS Use Small, Incremental Edits ### 1. ALWAYS Use Small, Incremental Edits
**WRONG:** Replace large blocks (50+ lines) **WRONG:** Replace large blocks (50+ lines)
**RIGHT:** Replace 3-10 lines at a time, verify, repeat **RIGHT:** Replace 3-10 lines at a time, verify, repeat
### 2. Verify Before Editing ### 2. Verify Before Editing
Before ANY edit to a function you haven't touched recently: Before ANY edit to a function you haven't touched recently:
``` ```
1. Run: git checkout -- src/gui_2.py 1. Run: git checkout -- src/gui_2.py
2. Run: py_check_syntax on src/gui_2.py 2. Run: py_check_syntax on src/gui_2.py
@@ -18,11 +22,13 @@ Before ANY edit to a function you haven't touched recently:
``` ```
### 3. Reading Before Editing (CRITICAL) ### 3. Reading Before Editing (CRITICAL)
- Use `get_file_slice` to get the EXACT text including all whitespace - Use `get_file_slice` to get the EXACT text including all whitespace
- Copy text directly from the tool output - do NOT reformat - Copy text directly from the tool output - do NOT reformat
- If using get_definition, verify the text matches before editing - If using get_definition, verify the text matches before editing
### 4. The Edit Tool Parameters (snake_case) ### 4. The Edit Tool Parameters (snake_case)
```python ```python
{ {
"path": "src/gui_2.py", # Required: file path "path": "src/gui_2.py", # Required: file path
@@ -33,6 +39,7 @@ Before ANY edit to a function you haven't touched recently:
``` ```
### 5. 1-Space Indentation in Python ### 5. 1-Space Indentation in Python
- Class methods: ` def` (0 spaces, then 1) - Class methods: ` def` (0 spaces, then 1)
- Method body: ` ` (2 spaces total) - Method body: ` ` (2 spaces total)
- Nested blocks: ` ` (3 spaces total) - Nested blocks: ` ` (3 spaces total)
@@ -41,14 +48,17 @@ Before ANY edit to a function you haven't touched recently:
### 6. The Decorator-Orphan Pitfall (Added 2026-06-07) ### 6. The Decorator-Orphan Pitfall (Added 2026-06-07)
When inserting new methods **before an existing `@property` def**: When inserting new methods **before an existing `@property` def**:
```
```python
@property @property
def perf_profiling_enabled(self) -> bool: def perf_profiling_enabled(self) -> bool:
... ...
``` ```
If you anchor on `def perf_profiling_enabled` and insert before it, the `@property` decorator on the line above is left orphaned on the line right before YOUR new method. Now `@property` decorates your method (which is no longer a property), and the original setter `@perf_profiling_enabled.setter` blows up at import with `'function' object has no attribute 'setter'`. If you anchor on `def perf_profiling_enabled` and insert before it, the `@property` decorator on the line above is left orphaned on the line right before YOUR new method. Now `@property` decorates your method (which is no longer a property), and the original setter `@perf_profiling_enabled.setter` blows up at import with `'function' object has no attribute 'setter'`.
**Fix:** Anchor on a non-decorated landmark, or include the decorator in the replacement: **Fix:** Anchor on a non-decorated landmark, or include the decorator in the replacement:
- `old_string` = ` self._init_actions()\n\n @property\n def perf_profiling_enabled` - `old_string` = ` self._init_actions()\n\n @property\n def perf_profiling_enabled`
- `new_string` = ` self._init_actions()\n\n def your_new(...)\n ...\n\n @property\n def perf_profiling_enabled` - `new_string` = ` self._init_actions()\n\n def your_new(...)\n ...\n\n @property\n def perf_profiling_enabled`
@@ -57,6 +67,7 @@ This keeps the `@property` attached to its original method.
### 7. ast.parse() Is Not Enough (Added 2026-06-07) ### 7. ast.parse() Is Not Enough (Added 2026-06-07)
`py_check_syntax` only confirms `ast.parse()` succeeds. Semantic errors (wrong decorator targets, wrong base class, wrong attribute, missing `self`) are NOT caught. After any multi-line edit, ALWAYS: `py_check_syntax` only confirms `ast.parse()` succeeds. Semantic errors (wrong decorator targets, wrong base class, wrong attribute, missing `self`) are NOT caught. After any multi-line edit, ALWAYS:
1. Import the module: `python -c "from src.app_controller import AppController"` 1. Import the module: `python -c "from src.app_controller import AppController"`
2. Instantiate the class 2. Instantiate the class
3. Call the new method in the way it's expected to be called (`ctrl.foo_ts` for a property, `ctrl.foo_ts()` for a method) 3. Call the new method in the way it's expected to be called (`ctrl.foo_ts` for a property, `ctrl.foo_ts()` for a method)
@@ -67,39 +78,32 @@ This keeps the `@property` attached to its original method.
## Step-by-Step Workflow for gui_2.py ## Step-by-Step Workflow for gui_2.py
### Before ANY edit:
```powershell
git checkout -- src/gui_2.py
```
### Check current state: ### Check current state:
```powershell ```powershell
py_check_syntax path=src/gui_2.py py_check_syntax path=src/gui_2.py
get_file_slice path=src/gui_2.py start_line=X end_line=Y get_file_slice path=src/gui_2.py start_line=X end_line=Y
``` ```
### For each edit: ### For each edit:
1. Make the smallest possible change (3-10 lines) 1. Make the smallest possible change (3-10 lines)
2. Run `py_check_syntax` to verify 2. Run `py_check_syntax` to verify
3. If syntax error, immediately `git checkout -- src/gui_2.py` 3. If syntax error, immediately report to the user to address.
4. Only proceed if syntax is OK 4. Only proceed if syntax is OK
### If edit fails with "old_string not found": ### If edit fails with "old_string not found":
- The text you're trying to replace doesn't EXACTLY match - The text you're trying to replace doesn't EXACTLY match
- Use `get_file_slice` to get the exact text - Use `get_file_slice` to get the exact text
- Copy it character-for-character including whitespace - Copy it character-for-character including whitespace
- Try again with exact match - Try again with exact match
### If syntax error after edit:
```powershell
git checkout -- src/gui_2.py
```
Then try again with smaller edit.
## Alternative: Update Definition Approach ## Alternative: Update Definition Approach
For large function rewrites, use `py_update_definition`: For large function rewrites, use `py_update_definition`:
```
```md
name: function_name name: function_name
path: src/gui_2.py path: src/gui_2.py
new_content: complete new function source new_content: complete new function source
@@ -110,48 +114,48 @@ This replaces the entire function at once using AST detection.
## Context Composition Requirements ## Context Composition Requirements
### Current Broken State ### Current Broken State
Files & Media works. Context Composition needs: Files & Media works. Context Composition needs:
1. Add state tracking at start of function: 1. Add state tracking at start of function:
```python
if not hasattr(self, 'ctx_files_open'): ```python
self.ctx_files_open = True if not hasattr(self, 'ctx_files_open'):
if not hasattr(self, 'ctx_shots_open'): self.ctx_files_open = True
self.ctx_shots_open = True if not hasattr(self, 'ctx_shots_open'):
``` self.ctx_shots_open = True
```
2. Files section with collapsing header and child window: 2. Files section with collapsing header and child window:
```python
if imgui.collapsing_header("Files", self.ctx_files_open): ```python
imgui.begin_child("ctx_files_child", imgui.ImVec2(-1, 200), True) if imgui.collapsing_header("Files", self.ctx_files_open):
# table code here imgui.begin_child("ctx_files_child", imgui.ImVec2(-1, 200), True)
imgui.end_child() # table code here
``` imgui.end_child()
```
3. Screenshots section with collapsing header and child window: 3. Screenshots section with collapsing header and child window:
```python
if imgui.collapsing_header("Screenshots", self.ctx_shots_open): ```python
imgui.begin_child("ctx_shots_child", imgui.ImVec2(-1, 100), True) if imgui.collapsing_header("Screenshots", self.ctx_shots_open):
# screenshot list here imgui.begin_child("ctx_shots_child", imgui.ImVec2(-1, 100), True)
imgui.end_child() # screenshot list here
``` imgui.end_child()
```
4. Fixed presets bar with push_item_width(150) on the combo 4. Fixed presets bar with push_item_width(150) on the combo
5. Remove the batch action bar entirely (Full/Agg/Sig/Def/None/Sel All/Del buttons) 5. Remove the batch action bar entirely (Full/Agg/Sig/Def/None/Sel All/Del buttons)
## Key Files ## Key Files
- `src/gui_2.py` - Main GUI (1-space indentation, CRLF) - `src/gui_2.py` - Main GUI (1-space indentation, CRLF)
- `src/models.py` - Data models including FileItem - `src/models.py` - Data models including FileItem
- Context Composition function: line ~2748 - Context Composition function: line ~2748
## Test Command ## Test Command
```powershell ```powershell
uv run sloppy.py uv run sloppy.py
``` ```
## If Everything Goes Wrong
```powershell
git checkout -- src/gui_2.py
git checkout -- src/models.py
```