Fold hooks: JSON-RPC summaries for large added folds, concurrent streaming - #1
Merged
Merged
Conversation
Introduce LoadedFile so sources are read on the session thread and the diff itself can later run elsewhere. No behavior change. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Streaming output no longer waits for earlier files: a rayon pool of --jobs workers (default 16) pulls sources through the session serially and diffs them concurrently. The file manifest on the start event already lets clients lay out results in any order. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Folds gain a summary field, always null for now, on the domain model, the wire format, and the terminal frontend schema. No behavior change. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
A [folds.hook] table names a trusted command plus tag, size and timeout filters. Parsed and validated only; nothing invokes it yet. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Streaming spawns the [folds.hook] command once and sends each file's qualifying rhs folds as one NDJSON request; the worker blocks on the reply and fills fold summaries before emitting the file event. Replies are routed by request id so files in flight overlap freely. A failed or timed-out request leaves placeholders in place and adds hook_error to that file. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
examples/hooks/summarize.py answers diffr fold requests concurrently through OpenRouter, defaulting to google/gemini-3.1-flash-lite with reasoning disabled, which answered a three-fold request in under two seconds. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
A wrapper such as uv run can be killed while its child keeps the stdout pipe open, which left diffr waiting forever on the reader thread at exit. Closing stdin first lets the hook finish on EOF; the reader now ends on its own. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
OpenRouter refuses to disable reasoning for Gemini 3.8, so the hook calls the Gemini API with thinking budget zero and a fixed response schema. A three-fold request answers in about two seconds. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Relative command paths now resolve against diffr.toml or the --config file rather than the diffed repository, so one hook config works from any checkout. The repository root is passed as DIFFR_WORKSPACE. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Replace the thread pool and urllib with a single event loop, a shared AsyncClient and a semaphore; replies are written whole between awaits. Three concurrent requests answer in about two seconds. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a08a73-9b4c-7bd1-bb9c-e2efe577c764 Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Dependency-only change ahead of moving hook communication to JSON-RPC. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
The hook is now a server: diffr reserves a port, passes it as DIFFR_HOOK_PORT, waits for it to listen, then calls a typed jsonrpsee client on a small tokio runtime. Rayon workers block on their own call while in-flight requests multiplex on the client. The hand-written framing, id map, reader thread and stdin lock are gone. Hook stdout is discarded so it cannot disturb the event stream; hooks log to stderr. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
One POST route hands each body to jsonrpcserver's async dispatch; the summarize method awaits Gemini on the shared httpx client under a semaphore. No hand-written protocol code remains on either side. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
Sorted imports, literal dicts, an explicit check flag on subprocess.run, and executable bits on the shebang scripts. No behavior change. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: a8ed5d94-5d5f-4d7a-ae57-7c90ef8c7d6b Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
sidkmenon
force-pushed
the
diff-preprocess
branch
from
September 10, 2026 22:12
a8b9274 to
9cd9486
Compare
Both findings predate this branch: one misspelling in the review examples README, and a Git blob hash in a fixture patch that spells a word. AI assistance: implemented with Claude Code. Claude-Session: https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN Agent-Session: 01a089c9-50b5-7593-81bc-8024113a43d4 Agent-Session: c0881bbc-fcf4-466b-b42b-d04811dd045e Agent-Session: 26b74d72-c5f7-46ce-9404-849b5bb0a0ae Agent-Session: 01a08ccf-048f-7790-9e74-4889a953c39f Agent-Session: 118d399f-d820-431e-8b32-8c27fbbc44eb
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.
Summary
Adds a user-configurable fold hook that replaces fold placeholders with richer text, such as model-written pseudocode, before each streaming
fileevent. Along the way, streaming now diffs files on a worker pool and emits them as they finish.--format ndjsondiffs--jobsfiles at once (default 16) on a rayon pool and emits results in completion order. Clients lay out slots from the manifest already onstart. 5x faster on a 1382-file debug-build diff.[folds.hook]table names a trusted command. diffr starts it once per invocation as a JSON-RPC 2.0 server on a loopback port (DIFFR_HOOK_PORT), waits for it to listen, and callssummarizeonce per file with that file's large novel folds on the after side. The worker diffing that file blocks on its own reply; in-flight calls multiplex on a typedjsonrpseeclient over a small tokio runtime. Failures keep the diff and addhook_errorto the file event.summarybeside itsplaceholder; the TUI schema accepts it. No rendering yet.examples/hooks/summarize.py: anaiohttp+jsonrpcserverserver that asks Gemini 3.8 Flash, thinking off, for Python-style pseudocode, concurrently on one asyncio loop with a shared httpx client.DIFFR_WORKSPACEcarries the repo root.Design notes and the full protocol are in
docs/streaming.md.Test plan
cargo test --bin diffr(173 passed), including hook unit tests against a stdlib JSON-RPC server: id routing under 8 concurrent callers, timeout, startup exit, error objects, invalid responsestests/streaming/check.py: hook success, error object, startup exit, unstartable command, config outside the repo with a relative script path,--jobs 1orderingbun testintui/(42 passed)cargo fmt,cargo clippy,ruff formatandruff checkclean on touched filesAI disclosure
Implemented with Claude Code per AI_POLICY.md; every change was read and verified by a human-directed session, and the design was iterated in review.
https://claude.ai/code/session_015ZLcD4fU7YTe4CTcB1Q7oN