mpfaffenberger / mpfaffenberger/code_puppy
Extract a single diff-preview/apply module shared by file_permission_handler and tools/file_modifications; retire renderers.py's dead async twin
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 814
- Forks
- 278
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 76
Description
Two duplication themes where one of the two copies is the one users actually experience.
1. file_permission_handler re-implements file_modifications' edit pipeline as a parallel preview (DRY angle on top of #446)
#446 already documents the behavioral divergence (replace-all vs replace-first, overwrite preview not reading old content). This issue is about the structural cause: the preview and the apply are two independent implementations of the same transformation, so they can never be proven to agree.
Clones (jscpd): plugins/file_permission_handler/register_callbacks.py:142-155 ↔ tools/file_modifications.py:281-294 and :167-179 ↔ :310-322. The plugin's _preview_replace_in_file (register_callbacks.py:131-200) copies the fuzzy-match window logic (_find_best_window, 0.95 score threshold, trailing-newline preservation, difflib.unified_diff with get_diff_context_lines()) from file_modifications.py — importing the helper but cloning the orchestration.
Proposed fix: make the transformation a pure function in one place and have BOTH the preview and the apply call it:
# tools/file_edit_core.py (no I/O, fully unit-testable)
@dataclass
class EditResult:
original: str
modified: str
diff: str # unified diff, canonical formatting
applied: list[...] # per-replacement outcomes
def compute_replacements(original: str, replacements: list[dict]) -> EditResult: ...
def compute_overwrite(original: str | None, new_content: str) -> EditResult: ...
tools/file_modifications.pyappliesEditResult.modifiedto disk.file_permission_handlershowsEditResult.diffas the approval preview.
Then the approved diff is by construction the applied diff — the divergences in #446 become impossible rather than merely fixed, and the fuzzy-match threshold lives in exactly one module.
2. messaging/renderers.py: InteractiveRenderer is a dead async twin of SynchronousInteractiveRenderer
Distinct from #445 (MessageQueue vs MessageBus): this duplication is within the queue system, in one file. InteractiveRenderer.render_message (renderers.py:174-198) and SynchronousInteractiveRenderer._render_message (:286-315) contain the same pause-buffer block (HUMAN_INPUT_REQUEST bypass, is_paused() append, drain + _flush_indicator threshold + _print_message loop); _handle_human_input_request is also implemented twice (:199-205 vs :335-360, and the async copy lacks the prompt_id/response plumbing).
The async copy's own docstring (renderers.py:161-166) says: "This async-based renderer is not currently used in the codebase... the sync renderer is the production path and gets the full treatment." That is a maintained, drifting clone of production logic with zero callers.
Proposed fix: delete InteractiveRenderer (or, if it must stay for a future TUI, reduce it to a thin async wrapper delegating to a shared _PauseBuffer helper object that owns lock + buffer + flush-indicator logic, which SynchronousInteractiveRenderer._render_message and _flush_paused_buffer would also use — those two methods currently duplicate the drain block between themselves as well, :300-315 vs :320-333).
Filed by Zen Reviewer C (code-puppy-60635a) — DRY review round
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read tools/file_modifications.py alongside plugins/file_permission_handler/register_callbacks.py, focusing on the cited replacement, overwrite, and preview paths, then inspect messaging/renderers.py's InteractiveRenderer and SynchronousInteractiveRenderer methods. The work is done when preview and apply use one transformation result and the unused renderer is removed or delegates to shared pause-buffer logic without changing the production path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100